Timber - Version 0.10.2

Version Description

  • added more aliases for easier coding (post.thumbnail instead of post.get_thumbnail, etc.)
  • Garbage removal
Download this release

Release Info

Developer jarednova
Plugin Icon 128x128 Timber
Version 0.10.2
Comparing to
See all releases

Version 0.10.2

Files changed (514) hide show
  1. LICENSE.txt +7 -0
  2. README.md +37 -0
  3. Twig/.editorconfig +18 -0
  4. Twig/.gitignore +2 -0
  5. Twig/.travis.yml +14 -0
  6. Twig/AUTHORS +9 -0
  7. Twig/CHANGELOG +605 -0
  8. Twig/LICENSE +31 -0
  9. Twig/README.markdown +17 -0
  10. Twig/composer.json +31 -0
  11. Twig/doc/advanced.rst +776 -0
  12. Twig/doc/advanced_legacy.rst +887 -0
  13. Twig/doc/api.rst +585 -0
  14. Twig/doc/coding_standards.rst +101 -0
  15. Twig/doc/deprecated.rst +98 -0
  16. Twig/doc/filters/abs.rst +18 -0
  17. Twig/doc/filters/capitalize.rst +11 -0
  18. Twig/doc/filters/convert_encoding.rst +28 -0
  19. Twig/doc/filters/date.rst +88 -0
  20. Twig/doc/filters/date_modify.rst +23 -0
  21. Twig/doc/filters/default.rst +33 -0
  22. Twig/doc/filters/escape.rst +93 -0
  23. Twig/doc/filters/format.rst +16 -0
  24. Twig/doc/filters/index.rst +33 -0
  25. Twig/doc/filters/join.rst +23 -0
  26. Twig/doc/filters/json_encode.rst +19 -0
  27. Twig/doc/filters/keys.rst +11 -0
  28. Twig/doc/filters/length.rst +12 -0
  29. Twig/doc/filters/lower.rst +10 -0
  30. Twig/doc/filters/merge.rst +41 -0
  31. Twig/doc/filters/nl2br.rst +22 -0
  32. Twig/doc/filters/number_format.rst +45 -0
  33. Twig/doc/filters/raw.rst +12 -0
  34. Twig/doc/filters/replace.rst +19 -0
  35. Twig/doc/filters/reverse.rst +23 -0
  36. Twig/doc/filters/slice.rst +70 -0
  37. Twig/doc/filters/sort.rst +17 -0
  38. Twig/doc/filters/split.rst +53 -0
  39. Twig/doc/filters/striptags.rst +15 -0
  40. Twig/doc/filters/title.rst +11 -0
  41. Twig/doc/filters/trim.rst +29 -0
  42. Twig/doc/filters/upper.rst +10 -0
  43. Twig/doc/filters/url_encode.rst +14 -0
  44. Twig/doc/functions/attribute.rst +18 -0
  45. Twig/doc/functions/block.rst +15 -0
  46. Twig/doc/functions/constant.rst +18 -0
  47. Twig/doc/functions/cycle.rst +25 -0
  48. Twig/doc/functions/date.rst +52 -0
  49. Twig/doc/functions/dump.rst +69 -0
  50. Twig/doc/functions/include.rst +80 -0
  51. Twig/doc/functions/index.rst +17 -0
  52. Twig/doc/functions/parent.rst +20 -0
  53. Twig/doc/functions/random.rst +29 -0
  54. Twig/doc/functions/range.rst +45 -0
  55. Twig/doc/functions/template_from_string.rst +32 -0
  56. Twig/doc/index.rst +18 -0
  57. Twig/doc/internals.rst +140 -0
  58. Twig/doc/intro.rst +164 -0
  59. Twig/doc/recipes.rst +474 -0
  60. Twig/doc/tags/autoescape.rst +71 -0
  61. Twig/doc/tags/block.rst +11 -0
  62. Twig/doc/tags/do.rst +12 -0
  63. Twig/doc/tags/embed.rst +178 -0
  64. Twig/doc/tags/extends.rst +268 -0
  65. Twig/doc/tags/filter.rst +21 -0
  66. Twig/doc/tags/flush.rst +17 -0
  67. Twig/doc/tags/for.rst +157 -0
  68. Twig/doc/tags/from.rst +8 -0
  69. Twig/doc/tags/if.rst +43 -0
  70. Twig/doc/tags/import.rst +57 -0
  71. Twig/doc/tags/include.rst +86 -0
  72. Twig/doc/tags/index.rst +24 -0
  73. Twig/doc/tags/macro.rst +83 -0
  74. Twig/doc/tags/sandbox.rst +30 -0
  75. Twig/doc/tags/set.rst +32 -0
  76. Twig/doc/tags/spaceless.rst +37 -0
  77. Twig/doc/tags/use.rst +123 -0
  78. Twig/doc/tags/verbatim.rst +24 -0
  79. Twig/doc/templates.rst +837 -0
  80. Twig/doc/tests/constant.rst +11 -0
  81. Twig/doc/tests/defined.rst +30 -0
  82. Twig/doc/tests/divisibleby.rst +10 -0
  83. Twig/doc/tests/empty.rst +11 -0
  84. Twig/doc/tests/even.rst +10 -0
  85. Twig/doc/tests/index.rst +15 -0
  86. Twig/doc/tests/iterable.rst +19 -0
  87. Twig/doc/tests/null.rst +12 -0
  88. Twig/doc/tests/odd.rst +10 -0
  89. Twig/doc/tests/sameas.rst +11 -0
  90. Twig/ext/twig/.gitignore +30 -0
  91. Twig/ext/twig/LICENSE +22 -0
  92. Twig/ext/twig/config.m4 +8 -0
  93. Twig/ext/twig/config.w32 +8 -0
  94. Twig/ext/twig/php_twig.h +31 -0
  95. Twig/ext/twig/twig.c +1051 -0
  96. Twig/lib/Twig/Autoloader.php +44 -0
  97. Twig/lib/Twig/Compiler.php +268 -0
  98. Twig/lib/Twig/CompilerInterface.php +36 -0
  99. Twig/lib/Twig/Environment.php +1209 -0
  100. Twig/lib/Twig/Error.php +233 -0
  101. Twig/lib/Twig/Error/Loader.php +32 -0
  102. Twig/lib/Twig/Error/Runtime.php +21 -0
  103. Twig/lib/Twig/Error/Syntax.php +21 -0
  104. Twig/lib/Twig/ExistsLoaderInterface.php +29 -0
  105. Twig/lib/Twig/ExpressionParser.php +601 -0
  106. Twig/lib/Twig/Extension.php +93 -0
  107. Twig/lib/Twig/Extension/Core.php +1276 -0
  108. Twig/lib/Twig/Extension/Debug.php +70 -0
  109. Twig/lib/Twig/Extension/Escaper.php +107 -0
  110. Twig/lib/Twig/Extension/Optimizer.php +35 -0
  111. Twig/lib/Twig/Extension/Sandbox.php +112 -0
  112. Twig/lib/Twig/Extension/Staging.php +114 -0
  113. Twig/lib/Twig/Extension/StringLoader.php +64 -0
  114. Twig/lib/Twig/ExtensionInterface.php +84 -0
  115. Twig/lib/Twig/Filter.php +84 -0
  116. Twig/lib/Twig/Filter/Function.php +38 -0
  117. Twig/lib/Twig/Filter/Method.php +40 -0
  118. Twig/lib/Twig/Filter/Node.php +40 -0
  119. Twig/lib/Twig/FilterCallableInterface.php +24 -0
  120. Twig/lib/Twig/FilterInterface.php +43 -0
  121. Twig/lib/Twig/Function.php +72 -0
  122. Twig/lib/Twig/Function/Function.php +39 -0
  123. Twig/lib/Twig/Function/Method.php +41 -0
  124. Twig/lib/Twig/Function/Node.php +40 -0
  125. Twig/lib/Twig/FunctionCallableInterface.php +24 -0
  126. Twig/lib/Twig/FunctionInterface.php +40 -0
  127. Twig/lib/Twig/Lexer.php +406 -0
  128. Twig/lib/Twig/LexerInterface.php +30 -0
  129. Twig/lib/Twig/Loader/Array.php +99 -0
  130. Twig/lib/Twig/Loader/Chain.php +136 -0
  131. Twig/lib/Twig/Loader/Filesystem.php +221 -0
  132. Twig/lib/Twig/Loader/String.php +60 -0
  133. Twig/lib/Twig/LoaderInterface.php +53 -0
  134. Twig/lib/Twig/Markup.php +38 -0
  135. Twig/lib/Twig/Node.php +227 -0
  136. Twig/lib/Twig/Node/AutoEscape.php +40 -0
  137. Twig/lib/Twig/Node/Block.php +45 -0
  138. Twig/lib/Twig/Node/BlockReference.php +38 -0
  139. Twig/lib/Twig/Node/Body.php +20 -0
  140. Twig/lib/Twig/Node/Do.php +39 -0
  141. Twig/lib/Twig/Node/Embed.php +39 -0
  142. Twig/lib/Twig/Node/Expression.php +21 -0
  143. Twig/lib/Twig/Node/Expression/Array.php +86 -0
  144. Twig/lib/Twig/Node/Expression/AssignName.php +28 -0
  145. Twig/lib/Twig/Node/Expression/Binary.php +40 -0
  146. Twig/lib/Twig/Node/Expression/Binary/Add.php +18 -0
  147. Twig/lib/Twig/Node/Expression/Binary/And.php +18 -0
  148. Twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php +18 -0
  149. Twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php +18 -0
  150. Twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php +18 -0
  151. Twig/lib/Twig/Node/Expression/Binary/Concat.php +18 -0
  152. Twig/lib/Twig/Node/Expression/Binary/Div.php +18 -0
  153. Twig/lib/Twig/Node/Expression/Binary/Equal.php +17 -0
  154. Twig/lib/Twig/Node/Expression/Binary/FloorDiv.php +29 -0
  155. Twig/lib/Twig/Node/Expression/Binary/Greater.php +17 -0
  156. Twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php +17 -0
  157. Twig/lib/Twig/Node/Expression/Binary/In.php +33 -0
  158. Twig/lib/Twig/Node/Expression/Binary/Less.php +17 -0
  159. Twig/lib/Twig/Node/Expression/Binary/LessEqual.php +17 -0
  160. Twig/lib/Twig/Node/Expression/Binary/Mod.php +18 -0
  161. Twig/lib/Twig/Node/Expression/Binary/Mul.php +18 -0
  162. Twig/lib/Twig/Node/Expression/Binary/NotEqual.php +17 -0
  163. Twig/lib/Twig/Node/Expression/Binary/NotIn.php +33 -0
  164. Twig/lib/Twig/Node/Expression/Binary/Or.php +18 -0
  165. Twig/lib/Twig/Node/Expression/Binary/Power.php +33 -0
  166. Twig/lib/Twig/Node/Expression/Binary/Range.php +33 -0
  167. Twig/lib/Twig/Node/Expression/Binary/Sub.php +18 -0
  168. Twig/lib/Twig/Node/Expression/BlockReference.php +52 -0
  169. Twig/lib/Twig/Node/Expression/Call.php +171 -0
  170. Twig/lib/Twig/Node/Expression/Conditional.php +31 -0
  171. Twig/lib/Twig/Node/Expression/Constant.php +23 -0
  172. Twig/lib/Twig/Node/Expression/ExtensionReference.php +34 -0
  173. Twig/lib/Twig/Node/Expression/Filter.php +36 -0
  174. Twig/lib/Twig/Node/Expression/Filter/Default.php +44 -0
  175. Twig/lib/Twig/Node/Expression/Function.php +35 -0
  176. Twig/lib/Twig/Node/Expression/GetAttr.php +53 -0
  177. Twig/lib/Twig/Node/Expression/MethodCall.php +41 -0
  178. Twig/lib/Twig/Node/Expression/Name.php +88 -0
  179. Twig/lib/Twig/Node/Expression/Parent.php +48 -0
  180. Twig/lib/Twig/Node/Expression/TempName.php +26 -0
  181. Twig/lib/Twig/Node/Expression/Test.php +32 -0
  182. Twig/lib/Twig/Node/Expression/Test/Constant.php +36 -0
  183. Twig/lib/Twig/Node/Expression/Test/Defined.php +55 -0
  184. Twig/lib/Twig/Node/Expression/Test/Divisibleby.php +34 -0
  185. Twig/lib/Twig/Node/Expression/Test/Even.php +33 -0
  186. Twig/lib/Twig/Node/Expression/Test/Null.php +32 -0
  187. Twig/lib/Twig/Node/Expression/Test/Odd.php +33 -0
  188. Twig/lib/Twig/Node/Expression/Test/Sameas.php +30 -0
  189. Twig/lib/Twig/Node/Expression/Unary.php +30 -0
  190. Twig/lib/Twig/Node/Expression/Unary/Neg.php +18 -0
  191. Twig/lib/Twig/Node/Expression/Unary/Not.php +18 -0
  192. Twig/lib/Twig/Node/Expression/Unary/Pos.php +18 -0
  193. Twig/lib/Twig/Node/Flush.php +37 -0
  194. Twig/lib/Twig/Node/For.php +113 -0
  195. Twig/lib/Twig/Node/ForLoop.php +56 -0
  196. Twig/lib/Twig/Node/If.php +67 -0
  197. Twig/lib/Twig/Node/Import.php +51 -0
  198. Twig/lib/Twig/Node/Include.php +100 -0
  199. Twig/lib/Twig/Node/Macro.php +97 -0
  200. Twig/lib/Twig/Node/Module.php +372 -0
  201. Twig/lib/Twig/Node/Print.php +40 -0
  202. Twig/lib/Twig/Node/Sandbox.php +48 -0
  203. Twig/lib/Twig/Node/SandboxedModule.php +61 -0
  204. Twig/lib/Twig/Node/SandboxedPrint.php +60 -0
  205. Twig/lib/Twig/Node/Set.php +102 -0
  206. Twig/lib/Twig/Node/SetTemp.php +35 -0
  207. Twig/lib/Twig/Node/Spaceless.php +41 -0
  208. Twig/lib/Twig/Node/Text.php +40 -0
  209. Twig/lib/Twig/NodeInterface.php +31 -0
  210. Twig/lib/Twig/NodeOutputInterface.php +20 -0
  211. Twig/lib/Twig/NodeTraverser.php +89 -0
  212. Twig/lib/Twig/NodeVisitor/Escaper.php +168 -0
  213. Twig/lib/Twig/NodeVisitor/Optimizer.php +247 -0
  214. Twig/lib/Twig/NodeVisitor/SafeAnalysis.php +133 -0
  215. Twig/lib/Twig/NodeVisitor/Sandbox.php +93 -0
  216. Twig/lib/Twig/NodeVisitorInterface.php +48 -0
  217. Twig/lib/Twig/Parser.php +395 -0
  218. Twig/lib/Twig/ParserInterface.php +29 -0
  219. Twig/lib/Twig/Sandbox/SecurityError.php +20 -0
  220. Twig/lib/Twig/Sandbox/SecurityPolicy.php +120 -0
  221. Twig/lib/Twig/Sandbox/SecurityPolicyInterface.php +25 -0
  222. Twig/lib/Twig/SimpleFilter.php +97 -0
  223. Twig/lib/Twig/SimpleFunction.php +85 -0
  224. Twig/lib/Twig/SimpleTest.php +47 -0
  225. Twig/lib/Twig/Template.php +454 -0
  226. Twig/lib/Twig/TemplateInterface.php +48 -0
  227. Twig/lib/Twig/Test.php +35 -0
  228. Twig/lib/Twig/Test/Function.php +36 -0
  229. Twig/lib/Twig/Test/IntegrationTestCase.php +155 -0
  230. Twig/lib/Twig/Test/Method.php +38 -0
  231. Twig/lib/Twig/Test/Node.php +38 -0
  232. Twig/lib/Twig/Test/NodeTestCase.php +58 -0
  233. Twig/lib/Twig/TestCallableInterface.php +22 -0
  234. Twig/lib/Twig/TestInterface.php +27 -0
  235. Twig/lib/Twig/Token.php +219 -0
  236. Twig/lib/Twig/TokenParser.php +34 -0
  237. Twig/lib/Twig/TokenParser/AutoEscape.php +89 -0
  238. Twig/lib/Twig/TokenParser/Block.php +83 -0
  239. Twig/lib/Twig/TokenParser/Do.php +42 -0
  240. Twig/lib/Twig/TokenParser/Embed.php +66 -0
  241. Twig/lib/Twig/TokenParser/Extends.php +54 -0
  242. Twig/lib/Twig/TokenParser/Filter.php +61 -0
  243. Twig/lib/Twig/TokenParser/Flush.php +42 -0
  244. Twig/lib/Twig/TokenParser/For.php +136 -0
  245. Twig/lib/Twig/TokenParser/From.php +74 -0
  246. Twig/lib/Twig/TokenParser/If.php +94 -0
  247. Twig/lib/Twig/TokenParser/Import.php +49 -0
  248. Twig/lib/Twig/TokenParser/Include.php +80 -0
  249. Twig/lib/Twig/TokenParser/Macro.php +70 -0
  250. Twig/lib/Twig/TokenParser/Sandbox.php +68 -0
  251. Twig/lib/Twig/TokenParser/Set.php +84 -0
  252. Twig/lib/Twig/TokenParser/Spaceless.php +59 -0
  253. Twig/lib/Twig/TokenParser/Use.php +84 -0
  254. Twig/lib/Twig/TokenParserBroker.php +139 -0
  255. Twig/lib/Twig/TokenParserBrokerInterface.php +46 -0
  256. Twig/lib/Twig/TokenParserInterface.php +42 -0
  257. Twig/lib/Twig/TokenStream.php +145 -0
  258. Twig/phpunit.xml.dist +25 -0
  259. Twig/test/Twig/Tests/AutoloaderTest.php +21 -0
  260. Twig/test/Twig/Tests/CompilerTest.php +33 -0
  261. Twig/test/Twig/Tests/EnvironmentTest.php +272 -0
  262. Twig/test/Twig/Tests/ErrorTest.php +159 -0
  263. Twig/test/Twig/Tests/ExpressionParserTest.php +332 -0
  264. Twig/test/Twig/Tests/Extension/CoreTest.php +115 -0
  265. Twig/test/Twig/Tests/Extension/SandboxTest.php +212 -0
  266. Twig/test/Twig/Tests/FileCachingTest.php +70 -0
  267. Twig/test/Twig/Tests/Fixtures/errors/base.html +1 -0
  268. Twig/test/Twig/Tests/Fixtures/errors/index.html +7 -0
  269. Twig/test/Twig/Tests/Fixtures/exceptions/unclosed_tag.test +20 -0
  270. Twig/test/Twig/Tests/Fixtures/expressions/array.test +61 -0
  271. Twig/test/Twig/Tests/Fixtures/expressions/array_call.test +14 -0
  272. Twig/test/Twig/Tests/Fixtures/expressions/binary.test +46 -0
  273. Twig/test/Twig/Tests/Fixtures/expressions/bitwise.test +14 -0
  274. Twig/test/Twig/Tests/Fixtures/expressions/comparison.test +14 -0
  275. Twig/test/Twig/Tests/Fixtures/expressions/dotdot.test +20 -0
  276. Twig/test/Twig/Tests/Fixtures/expressions/grouping.test +8 -0
  277. Twig/test/Twig/Tests/Fixtures/expressions/literals.test +22 -0
  278. Twig/test/Twig/Tests/Fixtures/expressions/magic_call.test +27 -0
  279. Twig/test/Twig/Tests/Fixtures/expressions/method_call.test +28 -0
  280. Twig/test/Twig/Tests/Fixtures/expressions/postfix.test +22 -0
  281. Twig/test/Twig/Tests/Fixtures/expressions/strings.test +10 -0
  282. Twig/test/Twig/Tests/Fixtures/expressions/ternary_operator.test +18 -0
  283. Twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_noelse.test +10 -0
  284. Twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_nothen.test +10 -0
  285. Twig/test/Twig/Tests/Fixtures/expressions/unary.test +12 -0
  286. Twig/test/Twig/Tests/Fixtures/expressions/unary_precedence.test +14 -0
  287. Twig/test/Twig/Tests/Fixtures/filters/abs.test +30 -0
  288. Twig/test/Twig/Tests/Fixtures/filters/convert_encoding.test +10 -0
  289. Twig/test/Twig/Tests/Fixtures/filters/date.test +61 -0
  290. Twig/test/Twig/Tests/Fixtures/filters/date_default_format.test +14 -0
  291. Twig/test/Twig/Tests/Fixtures/filters/date_default_format_interval.test +16 -0
  292. Twig/test/Twig/Tests/Fixtures/filters/date_interval.test +19 -0
  293. Twig/test/Twig/Tests/Fixtures/filters/date_modify.test +14 -0
  294. Twig/test/Twig/Tests/Fixtures/filters/date_namedargs.test +15 -0
  295. Twig/test/Twig/Tests/Fixtures/filters/default.test +150 -0
  296. Twig/test/Twig/Tests/Fixtures/filters/dynamic_filter.test +10 -0
  297. Twig/test/Twig/Tests/Fixtures/filters/escape.test +8 -0
  298. Twig/test/Twig/Tests/Fixtures/filters/escape_non_supported_charset.test +8 -0
  299. Twig/test/Twig/Tests/Fixtures/filters/force_escape.test +18 -0
  300. Twig/test/Twig/Tests/Fixtures/filters/format.test +8 -0
  301. Twig/test/Twig/Tests/Fixtures/filters/join.test +12 -0
  302. Twig/test/Twig/Tests/Fixtures/filters/json_encode.test +12 -0
  303. Twig/test/Twig/Tests/Fixtures/filters/length.test +14 -0
  304. Twig/test/Twig/Tests/Fixtures/filters/length_utf8.test +12 -0
  305. Twig/test/Twig/Tests/Fixtures/filters/merge.test +16 -0
  306. Twig/test/Twig/Tests/Fixtures/filters/nl2br.test +14 -0
  307. Twig/test/Twig/Tests/Fixtures/filters/number_format.test +18 -0
  308. Twig/test/Twig/Tests/Fixtures/filters/number_format_default.test +21 -0
  309. Twig/test/Twig/Tests/Fixtures/filters/replace.test +8 -0
  310. Twig/test/Twig/Tests/Fixtures/filters/reverse.test +18 -0
  311. Twig/test/Twig/Tests/Fixtures/filters/slice.test +42 -0
  312. Twig/test/Twig/Tests/Fixtures/filters/sort.test +10 -0
  313. Twig/test/Twig/Tests/Fixtures/filters/special_chars.test +8 -0
  314. Twig/test/Twig/Tests/Fixtures/filters/split.test +18 -0
  315. Twig/test/Twig/Tests/Fixtures/filters/trim.test +12 -0
  316. Twig/test/Twig/Tests/Fixtures/functions/attribute.test +12 -0
  317. Twig/test/Twig/Tests/Fixtures/functions/block.test +12 -0
  318. Twig/test/Twig/Tests/Fixtures/functions/constant.test +10 -0
  319. Twig/test/Twig/Tests/Fixtures/functions/cycle.test +16 -0
  320. Twig/test/Twig/Tests/Fixtures/functions/date.test +25 -0
  321. Twig/test/Twig/Tests/Fixtures/functions/date_namedargs.test +11 -0
  322. Twig/test/Twig/Tests/Fixtures/functions/dump.test +16 -0
  323. Twig/test/Twig/Tests/Fixtures/functions/dump_array.test +19 -0
  324. Twig/test/Twig/Tests/Fixtures/functions/dynamic_function.test +10 -0
  325. Twig/test/Twig/Tests/Fixtures/functions/include/basic.test +17 -0
  326. Twig/test/Twig/Tests/Fixtures/functions/include/expression.test +17 -0
  327. Twig/test/Twig/Tests/Fixtures/functions/include/ignore_missing.test +10 -0
  328. Twig/test/Twig/Tests/Fixtures/functions/include/missing.test +8 -0
  329. Twig/test/Twig/Tests/Fixtures/functions/include/missing_nested.test +16 -0
  330. Twig/test/Twig/Tests/Fixtures/functions/include/sandbox.test +10 -0
  331. Twig/test/Twig/Tests/Fixtures/functions/include/template_instance.test +10 -0
  332. Twig/test/Twig/Tests/Fixtures/functions/include/templates_as_array.test +12 -0
  333. Twig/test/Twig/Tests/Fixtures/functions/include/with_context.test +16 -0
  334. Twig/test/Twig/Tests/Fixtures/functions/include/with_variables.test +12 -0
  335. Twig/test/Twig/Tests/Fixtures/functions/range.test +8 -0
  336. Twig/test/Twig/Tests/Fixtures/functions/special_chars.test +8 -0
  337. Twig/test/Twig/Tests/Fixtures/functions/template_from_string.test +11 -0
  338. Twig/test/Twig/Tests/Fixtures/macros/default_values.test +16 -0
  339. Twig/test/Twig/Tests/Fixtures/macros/nested_calls.test +18 -0
  340. Twig/test/Twig/Tests/Fixtures/macros/reserved_variables.test +14 -0
  341. Twig/test/Twig/Tests/Fixtures/macros/simple.test +22 -0
  342. Twig/test/Twig/Tests/Fixtures/macros/with_filters.test +14 -0
  343. Twig/test/Twig/Tests/Fixtures/regression/empty_token.test +8 -0
  344. Twig/test/Twig/Tests/Fixtures/regression/simple_xml_element.test +17 -0
  345. Twig/test/Twig/Tests/Fixtures/regression/strings_like_numbers.test +8 -0
  346. Twig/test/Twig/Tests/Fixtures/tags/autoescape/basic.test +26 -0
  347. Twig/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test +12 -0
  348. Twig/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test +10 -0
  349. Twig/test/Twig/Tests/Fixtures/tags/autoescape/functions.test +83 -0
  350. Twig/test/Twig/Tests/Fixtures/tags/autoescape/literal.test +45 -0
  351. Twig/test/Twig/Tests/Fixtures/tags/autoescape/nested.test +26 -0
  352. Twig/test/Twig/Tests/Fixtures/tags/autoescape/objects.test +26 -0
  353. Twig/test/Twig/Tests/Fixtures/tags/autoescape/raw.test +10 -0
  354. Twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test +17 -0
  355. Twig/test/Twig/Tests/Fixtures/tags/autoescape/type.test +69 -0
  356. Twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test +131 -0
  357. Twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test +23 -0
  358. Twig/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test +68 -0
  359. Twig/test/Twig/Tests/Fixtures/tags/autoescape/with_preserves_safety_filters.test +50 -0
  360. Twig/test/Twig/Tests/Fixtures/tags/block/basic.test +11 -0
  361. Twig/test/Twig/Tests/Fixtures/tags/block/block_unique_name.test +11 -0
  362. Twig/test/Twig/Tests/Fixtures/tags/block/special_chars.test +10 -0
  363. Twig/test/Twig/Tests/Fixtures/tags/embed/basic.test +35 -0
  364. Twig/test/Twig/Tests/Fixtures/tags/embed/multiple.test +50 -0
  365. Twig/test/Twig/Tests/Fixtures/tags/embed/nested.test +42 -0
  366. Twig/test/Twig/Tests/Fixtures/tags/embed/with_extends.test +57 -0
  367. Twig/test/Twig/Tests/Fixtures/tags/filter/basic.test +10 -0
  368. Twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.test +8 -0
  369. Twig/test/Twig/Tests/Fixtures/tags/filter/multiple.test +10 -0
  370. Twig/test/Twig/Tests/Fixtures/tags/filter/nested.test +16 -0
  371. Twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test +13 -0
  372. Twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test +29 -0
  373. Twig/test/Twig/Tests/Fixtures/tags/for/condition.test +14 -0
  374. Twig/test/Twig/Tests/Fixtures/tags/for/context.test +18 -0
  375. Twig/test/Twig/Tests/Fixtures/tags/for/else.test +23 -0
  376. Twig/test/Twig/Tests/Fixtures/tags/for/inner_variables.test +17 -0
  377. Twig/test/Twig/Tests/Fixtures/tags/for/keys.test +11 -0
  378. Twig/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test +11 -0
  379. Twig/test/Twig/Tests/Fixtures/tags/for/loop_context.test +19 -0
  380. Twig/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test +10 -0
  381. Twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test +10 -0
  382. Twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test +9 -0
  383. Twig/test/Twig/Tests/Fixtures/tags/for/nested_else.test +17 -0
  384. Twig/test/Twig/Tests/Fixtures/tags/for/objects.test +43 -0
  385. Twig/test/Twig/Tests/Fixtures/tags/for/objects_countable.test +47 -0
  386. Twig/test/Twig/Tests/Fixtures/tags/for/recursive.test +18 -0
  387. Twig/test/Twig/Tests/Fixtures/tags/for/values.test +11 -0
  388. Twig/test/Twig/Tests/Fixtures/tags/from.test +14 -0
  389. Twig/test/Twig/Tests/Fixtures/tags/if/basic.test +22 -0
  390. Twig/test/Twig/Tests/Fixtures/tags/if/expression.test +22 -0
  391. Twig/test/Twig/Tests/Fixtures/tags/include/basic.test +16 -0
  392. Twig/test/Twig/Tests/Fixtures/tags/include/expression.test +16 -0
  393. Twig/test/Twig/Tests/Fixtures/tags/include/ignore_missing.test +10 -0
  394. Twig/test/Twig/Tests/Fixtures/tags/include/missing.test +8 -0
  395. Twig/test/Twig/Tests/Fixtures/tags/include/missing_nested.test +16 -0
  396. Twig/test/Twig/Tests/Fixtures/tags/include/only.test +16 -0
  397. Twig/test/Twig/Tests/Fixtures/tags/include/template_instance.test +10 -0
  398. Twig/test/Twig/Tests/Fixtures/tags/include/templates_as_array.test +12 -0
  399. Twig/test/Twig/Tests/Fixtures/tags/include/with_variables.test +12 -0
  400. Twig/test/Twig/Tests/Fixtures/tags/inheritance/basic.test +14 -0
  401. Twig/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test +14 -0
  402. Twig/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test +14 -0
  403. Twig/test/Twig/Tests/Fixtures/tags/inheritance/empty.test +10 -0
  404. Twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array.test +12 -0
  405. Twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test +12 -0
  406. Twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks.test +22 -0
  407. Twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks_parent_only.test +15 -0
  408. Twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test +16 -0
  409. Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent.test +12 -0
  410. Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_change.test +16 -0
  411. Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_in_a_block.test +8 -0
  412. Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test +20 -0
  413. Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test +28 -0
  414. Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends.test +8 -0
  415. Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends_but_traits.test +14 -0
  416. Twig/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test +14 -0
  417. Twig/test/Twig/Tests/Fixtures/tags/inheritance/use.test +44 -0
  418. Twig/test/Twig/Tests/Fixtures/tags/macro/basic.test +17 -0
  419. Twig/test/Twig/Tests/Fixtures/tags/macro/endmacro_name.test +16 -0
  420. Twig/test/Twig/Tests/Fixtures/tags/macro/external.test +17 -0
  421. Twig/test/Twig/Tests/Fixtures/tags/macro/from.test +18 -0
  422. Twig/test/Twig/Tests/Fixtures/tags/macro/global.test +14 -0
  423. Twig/test/Twig/Tests/Fixtures/tags/macro/self_import.test +17 -0
  424. Twig/test/Twig/Tests/Fixtures/tags/macro/special_chars.test +14 -0
  425. Twig/test/Twig/Tests/Fixtures/tags/raw/basic.test +10 -0
  426. Twig/test/Twig/Tests/Fixtures/tags/raw/mixed_usage_with_raw.test +10 -0
  427. Twig/test/Twig/Tests/Fixtures/tags/raw/whitespace_control.test +56 -0
  428. Twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid1.test +11 -0
  429. Twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid2.test +14 -0
  430. Twig/test/Twig/Tests/Fixtures/tags/sandbox/simple.test +22 -0
  431. Twig/test/Twig/Tests/Fixtures/tags/set/basic.test +20 -0
  432. Twig/test/Twig/Tests/Fixtures/tags/set/capture-empty.test +9 -0
  433. Twig/test/Twig/Tests/Fixtures/tags/set/capture.test +10 -0
  434. Twig/test/Twig/Tests/Fixtures/tags/set/expression.test +12 -0
  435. Twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test +12 -0
  436. Twig/test/Twig/Tests/Fixtures/tags/special_chars.test +8 -0
  437. Twig/test/Twig/Tests/Fixtures/tags/trim_block.test +74 -0
  438. Twig/test/Twig/Tests/Fixtures/tags/use/aliases.test +12 -0
  439. Twig/test/Twig/Tests/Fixtures/tags/use/basic.test +12 -0
  440. Twig/test/Twig/Tests/Fixtures/tags/use/deep.test +22 -0
  441. Twig/test/Twig/Tests/Fixtures/tags/use/deep_empty.test +10 -0
  442. Twig/test/Twig/Tests/Fixtures/tags/use/multiple.test +21 -0
  443. Twig/test/Twig/Tests/Fixtures/tags/use/multiple_aliases.test +23 -0
  444. Twig/test/Twig/Tests/Fixtures/tags/verbatim/basic.test +10 -0
  445. Twig/test/Twig/Tests/Fixtures/tags/verbatim/mixed_usage_with_raw.test +10 -0
  446. Twig/test/Twig/Tests/Fixtures/tags/verbatim/whitespace_control.test +56 -0
  447. Twig/test/Twig/Tests/Fixtures/tests/array.test +24 -0
  448. Twig/test/Twig/Tests/Fixtures/tests/constant.test +12 -0
  449. Twig/test/Twig/Tests/Fixtures/tests/defined.test +108 -0
  450. Twig/test/Twig/Tests/Fixtures/tests/empty.test +45 -0
  451. Twig/test/Twig/Tests/Fixtures/tests/even.test +14 -0
  452. Twig/test/Twig/Tests/Fixtures/tests/in.test +48 -0
  453. Twig/test/Twig/Tests/Fixtures/tests/in_with_objects.test +19 -0
  454. Twig/test/Twig/Tests/Fixtures/tests/iterable.test +19 -0
  455. Twig/test/Twig/Tests/Fixtures/tests/odd.test +10 -0
  456. Twig/test/Twig/Tests/IntegrationTest.php +217 -0
  457. Twig/test/Twig/Tests/LexerTest.php +249 -0
  458. Twig/test/Twig/Tests/Loader/ArrayTest.php +97 -0
  459. Twig/test/Twig/Tests/Loader/ChainTest.php +63 -0
  460. Twig/test/Twig/Tests/Loader/FilesystemTest.php +90 -0
  461. Twig/test/Twig/Tests/Loader/Fixtures/named/index.html +1 -0
  462. Twig/test/Twig/Tests/Loader/Fixtures/named_bis/index.html +1 -0
  463. Twig/test/Twig/Tests/Loader/Fixtures/named_final/index.html +1 -0
  464. Twig/test/Twig/Tests/Loader/Fixtures/named_ter/index.html +1 -0
  465. Twig/test/Twig/Tests/Loader/Fixtures/normal/index.html +1 -0
  466. Twig/test/Twig/Tests/Loader/Fixtures/normal_bis/index.html +1 -0
  467. Twig/test/Twig/Tests/Loader/Fixtures/normal_final/index.html +1 -0
  468. Twig/test/Twig/Tests/Loader/Fixtures/normal_ter/index.html +1 -0
  469. Twig/test/Twig/Tests/NativeExtensionTest.php +29 -0
  470. Twig/test/Twig/Tests/Node/AutoEscapeTest.php +44 -0
  471. Twig/test/Twig/Tests/Node/BlockReferenceTest.php +43 -0
  472. Twig/test/Twig/Tests/Node/BlockTest.php +51 -0
  473. Twig/test/Twig/Tests/Node/DoTest.php +44 -0
  474. Twig/test/Twig/Tests/Node/Expression/ArrayTest.php +49 -0
  475. Twig/test/Twig/Tests/Node/Expression/AssignNameTest.php +41 -0
  476. Twig/test/Twig/Tests/Node/Expression/Binary/AddTest.php +47 -0
  477. Twig/test/Twig/Tests/Node/Expression/Binary/AndTest.php +47 -0
  478. Twig/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php +47 -0
  479. Twig/test/Twig/Tests/Node/Expression/Binary/DivTest.php +47 -0
  480. Twig/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php +47 -0
  481. Twig/test/Twig/Tests/Node/Expression/Binary/ModTest.php +47 -0
  482. Twig/test/Twig/Tests/Node/Expression/Binary/MulTest.php +47 -0
  483. Twig/test/Twig/Tests/Node/Expression/Binary/OrTest.php +47 -0
  484. Twig/test/Twig/Tests/Node/Expression/Binary/SubTest.php +47 -0
  485. Twig/test/Twig/Tests/Node/Expression/ConditionalTest.php +50 -0
  486. Twig/test/Twig/Tests/Node/Expression/ConstantTest.php +42 -0
  487. Twig/test/Twig/Tests/Node/Expression/FilterTest.php +133 -0
  488. Twig/test/Twig/Tests/Node/Expression/FunctionTest.php +99 -0
  489. Twig/test/Twig/Tests/Node/Expression/GetAttrTest.php +62 -0
  490. Twig/test/Twig/Tests/Node/Expression/NameTest.php +49 -0
  491. Twig/test/Twig/Tests/Node/Expression/PHP53/FilterInclude.php +6 -0
  492. Twig/test/Twig/Tests/Node/Expression/PHP53/FunctionInclude.php +6 -0
  493. Twig/test/Twig/Tests/Node/Expression/PHP53/TestInclude.php +6 -0
  494. Twig/test/Twig/Tests/Node/Expression/ParentTest.php +40 -0
  495. Twig/test/Twig/Tests/Node/Expression/TestTest.php +68 -0
  496. Twig/test/Twig/Tests/Node/Expression/Unary/NegTest.php +44 -0
  497. Twig/test/Twig/Tests/Node/Expression/Unary/NotTest.php +44 -0
  498. Twig/test/Twig/Tests/Node/Expression/Unary/PosTest.php +44 -0
  499. Twig/test/Twig/Tests/Node/ForTest.php +203 -0
  500. Twig/test/Twig/Tests/Node/IfTest.php +100 -0
  501. Twig/test/Twig/Tests/Node/ImportTest.php +52 -0
  502. Twig/test/Twig/Tests/Node/IncludeTest.php +96 -0
  503. Twig/test/Twig/Tests/Node/MacroTest.php +73 -0
  504. Twig/test/Twig/Tests/Node/ModuleTest.php +196 -0
  505. Twig/test/Twig/Tests/Node/PrintTest.php +41 -0
  506. Twig/test/Twig/Tests/Node/SandboxTest.php +56 -0
  507. Twig/test/Twig/Tests/Node/SandboxedModuleTest.php +173 -0
  508. Twig/test/Twig/Tests/Node/SandboxedPrintTest.php +45 -0
  509. Twig/test/Twig/Tests/Node/SetTest.php +81 -0
  510. Twig/test/Twig/Tests/Node/SpacelessTest.php +49 -0
  511. Twig/test/Twig/Tests/Node/TextTest.php +40 -0
  512. Twig/test/Twig/Tests/NodeVisitor/OptimizerTest.php +114 -0
  513. Twig/test/Twig/Tests/ParserTest.php +180 -0
  514. Twig/test/Twig/Tests/TemplateTest.php +395 -0
LICENSE.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ Copyright (c) 2012-2013 Jared Novack
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
README.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div style="text-align:center">
2
+ <a href="http://jarednova.github.com/timber"><img src="https://github.com/jarednova/timber/blob/master/images/logo/timber-badge-large.jpg?raw=true" style="display:block; margin:auto;"/></a>
3
+ <div>
4
+ By Jared Novack (<a href="http://twitter.com/jarednova">@JaredNova</a>) and <a href="http://upstatement.com">Upstatement</a> (<a href="http://twitter.com/upstatement">@Upstatement</a>)</div>
5
+ </div>
6
+
7
+ ### Because WordPress is awesome, but the_loop isn't
8
+ Timber helps you create fully-customized WordPress themes faster with more sustainable code. With Timber, you write your HTML using the [Twig Template Engine](http://twig.sensiolabs.org/) separate from your PHP files.
9
+
10
+ This cleans-up your theme code so, for example, your php file can focus on being the data, while your twig/html file can focus 100% on the HTML and display.
11
+
12
+ Once Timber is installed and activated in your plugin directory, it gives any WordPress theme the ability to take advantage of the power of Twig and other Timber features.
13
+
14
+ ### Looking for docs?
15
+ * [Timber Documentation](https://github.com/jarednova/timber/wiki/)
16
+ * [Twig Reference](http://twig.sensiolabs.org/documentation)
17
+ * **[Video Tutorials](https://github.com/jarednova/timber/wiki/Video-Tutorials)**
18
+ * [Overview / Getting Started Guide](https://github.com/jarednova/timber/wiki/getting-started)
19
+
20
+ #### What does it look like?
21
+ Nothing. Timber is meant for you to build a theme on. Like the [Starkers](https://github.com/viewportindustries/starkers) or [Boilerplate theme](https://github.com/zencoder/html5-boilerplate-for-wordpress) it comes style-free, because you're the style expert. Instead, Timber handles the logic you need to make a kick-ass looking site.
22
+
23
+ #### Who is it good for?
24
+ Timber is great for any WordPress developer who cares about writing good, maintainable code. It helps teams of designers and developers working together. At [Upstatement](http://upstatement.com) we made Timber because not everyone knows the ins-and-outs of the_loop(), WordPress codex and PHP (nor should they). With Timber your best WordPress dev can focus on building the .php files with requests from WordPress and pass the data into .twig files. Once there, designers can easily mark-up data and build out a site's look-and-feel.
25
+
26
+ #### Should I use it?
27
+ Well, it's **free**! And it's GPL-licensed, so use in personal or commerical work. Just don't re-sell it.
28
+
29
+ #### Upgrade Notes
30
+ Twig is no longer a submodule, just a part of the repo. If you have trouble pulling, just delete the `Twig` folder. Then you should be good.
31
+ In May 2013 there was a major rewrite of Timber. Trust me, it's worth it. But if you're looking for the old [Parent Theme Timber](https://github.com/jarednova/timber/tree/theme) you can still find it on this [branch](https://github.com/jarednova/timber/tree/theme).
32
+
33
+
34
+ [![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/jarednova/timber/trend.png)](https://bitdeli.com/free "Bitdeli Badge")
35
+
36
+
37
+
Twig/.editorconfig ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ; top-most EditorConfig file
2
+ root = true
3
+
4
+ ; Unix-style newlines
5
+ [*]
6
+ end_of_line = LF
7
+
8
+ [*.php]
9
+ indent_style = space
10
+ indent_size = 4
11
+
12
+ [*.test]
13
+ indent_style = space
14
+ indent_size = 4
15
+
16
+ [*.rst]
17
+ indent_style = space
18
+ indent_size = 4
Twig/.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
1
+ /ext/twig/autom4te.cache/
2
+
Twig/.travis.yml ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ language: php
2
+
3
+ php:
4
+ - 5.2
5
+ - 5.3
6
+ - 5.4
7
+
8
+ env:
9
+ - TWIG_EXT=no
10
+ - TWIG_EXT=yes
11
+
12
+ before_script:
13
+ - if [ "$TWIG_EXT" == "yes" ]; then sh -c "cd ext/twig && phpize && ./configure --enable-twig && make && sudo make install"; fi
14
+ - if [ "$TWIG_EXT" == "yes" ]; then echo "extension=twig.so" >> `php --ini | grep "Loaded Configuration" | sed -e "s|.*:\s*||"`; fi
Twig/AUTHORS ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ Twig is written and maintained by the Twig Team:
2
+
3
+ Lead Developer:
4
+
5
+ - Fabien Potencier <fabien.potencier@symfony-project.org>
6
+
7
+ Project Founder:
8
+
9
+ - Armin Ronacher <armin.ronacher@active-4.com>
Twig/CHANGELOG ADDED
@@ -0,0 +1,605 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * 1.12.2 (2013-XX-XX)
2
+
3
+ * n/a
4
+
5
+ * 1.12.1 (2013-01-15)
6
+
7
+ * added support for object instances as the second argument of the constant function
8
+ * relaxed globals management to avoid a BC break
9
+ * added support for {{ some_string[:2] }}
10
+
11
+ * 1.12.0 (2013-01-08)
12
+
13
+ * added verbatim as an alias for the raw tag to avoid confusion with the raw filter
14
+ * fixed registration of tests and functions as anonymous functions
15
+ * fixed globals management
16
+
17
+ * 1.12.0-RC1 (2012-12-29)
18
+
19
+ * added an include function (does the same as the include tag but in a more flexible way)
20
+ * added the ability to use any PHP callable to define filters, functions, and tests
21
+ * added a syntax error when using a loop variable that is not defined
22
+ * added the ability to set default values for macro arguments
23
+ * added support for named arguments for filters, tests, and functions
24
+ * moved filters/functions/tests syntax errors to the parser
25
+ * added support for extended ternary operator syntaxes
26
+
27
+ * 1.11.1 (2012-11-11)
28
+
29
+ * fixed debug info line numbering (was off by 2)
30
+ * fixed escaping when calling a macro inside another one (regression introduced in 1.9.1)
31
+ * optimized variable access on PHP 5.4
32
+ * fixed a crash of the C extension when an exception was thrown from a macro called without being imported (using _self.XXX)
33
+
34
+ * 1.11.0 (2012-11-07)
35
+
36
+ * fixed macro compilation when a variable name is a PHP reserved keyword
37
+ * changed the date filter behavior to always apply the default timezone, except if false is passed as the timezone
38
+ * fixed bitwise operator precedences
39
+ * added the template_from_string function
40
+ * fixed default timezone usage for the date function
41
+ * optimized the way Twig exceptions are managed (to make them faster)
42
+ * added Twig_ExistsLoaderInterface (implementing this interface in your loader make the chain loader much faster)
43
+
44
+ * 1.10.3 (2012-10-19)
45
+
46
+ * fixed wrong template location in some error messages
47
+ * reverted a BC break introduced in 1.10.2
48
+ * added a split filter
49
+
50
+ * 1.10.2 (2012-10-15)
51
+
52
+ * fixed macro calls on PHP 5.4
53
+
54
+ * 1.10.1 (2012-10-15)
55
+
56
+ * made a speed optimization to macro calls when imported via the "import" tag
57
+ * fixed C extension compilation on Windows
58
+ * fixed a segfault in the C extension when using DateTime objects
59
+
60
+ * 1.10.0 (2012-09-28)
61
+
62
+ * extracted functional tests framework to make it reusable for third-party extensions
63
+ * added namespaced templates support in Twig_Loader_Filesystem
64
+ * added Twig_Loader_Filesystem::prependPath()
65
+ * fixed an error when a token parser pass a closure as a test to the subparse() method
66
+
67
+ * 1.9.2 (2012-08-25)
68
+
69
+ * fixed the in operator for objects that contain circular references
70
+ * fixed the C extension when accessing a public property of an object implementing the \ArrayAccess interface
71
+
72
+ * 1.9.1 (2012-07-22)
73
+
74
+ * optimized macro calls when auto-escaping is on
75
+ * fixed wrong parent class for Twig_Function_Node
76
+ * made Twig_Loader_Chain more explicit about problems
77
+
78
+ * 1.9.0 (2012-07-13)
79
+
80
+ * made the parsing independent of the template loaders
81
+ * fixed exception trace when an error occurs when rendering a child template
82
+ * added escaping strategies for CSS, URL, and HTML attributes
83
+ * fixed nested embed tag calls
84
+ * added the date_modify filter
85
+
86
+ * 1.8.3 (2012-06-17)
87
+
88
+ * fixed paths in the filesystem loader when passing a path that ends with a slash or a backslash
89
+ * fixed escaping when a project defines a function named html or js
90
+ * fixed chmod mode to apply the umask correctly
91
+
92
+ * 1.8.2 (2012-05-30)
93
+
94
+ * added the abs filter
95
+ * fixed a regression when using a number in template attributes
96
+ * fixed compiler when mbstring.func_overload is set to 2
97
+ * fixed DateTimeZone support in date filter
98
+
99
+ * 1.8.1 (2012-05-17)
100
+
101
+ * fixed a regression when dealing with SimpleXMLElement instances in templates
102
+ * fixed "is_safe" value for the "dump" function when "html_errors" is not defined in php.ini
103
+ * switched to use mbstring whenever possible instead of iconv (you might need to update your encoding as mbstring and iconv encoding names sometimes differ)
104
+
105
+ * 1.8.0 (2012-05-08)
106
+
107
+ * enforced interface when adding tests, filters, functions, and node visitors from extensions
108
+ * fixed a side-effect of the date filter where the timezone might be changed
109
+ * simplified usage of the autoescape tag; the only (optional) argument is now the escaping strategy or false (with a BC layer)
110
+ * added a way to dynamically change the auto-escaping strategy according to the template "filename"
111
+ * changed the autoescape option to also accept a supported escaping strategy (for BC, true is equivalent to html)
112
+ * added an embed tag
113
+
114
+ * 1.7.0 (2012-04-24)
115
+
116
+ * fixed a PHP warning when using CIFS
117
+ * fixed template line number in some exceptions
118
+ * added an iterable test
119
+ * added an error when defining two blocks with the same name in a template
120
+ * added the preserves_safety option for filters
121
+ * fixed a PHP notice when trying to access a key on a non-object/array variable
122
+ * enhanced error reporting when the template file is an instance of SplFileInfo
123
+ * added Twig_Environment::mergeGlobals()
124
+ * added compilation checks to avoid misuses of the sandbox tag
125
+ * fixed filesystem loader freshness logic for high traffic websites
126
+ * fixed random function when charset is null
127
+
128
+ * 1.6.5 (2012-04-11)
129
+
130
+ * fixed a regression when a template only extends another one without defining any blocks
131
+
132
+ * 1.6.4 (2012-04-02)
133
+
134
+ * fixed PHP notice in Twig_Error::guessTemplateLine() introduced in 1.6.3
135
+ * fixed performance when compiling large files
136
+ * optimized parent template creation when the template does not use dynamic inheritance
137
+
138
+ * 1.6.3 (2012-03-22)
139
+
140
+ * fixed usage of Z_ADDREF_P for PHP 5.2 in the C extension
141
+ * fixed compilation of numeric values used in templates when using a locale where the decimal separator is not a dot
142
+ * made the strategy used to guess the real template file name and line number in exception messages much faster and more accurate
143
+
144
+ * 1.6.2 (2012-03-18)
145
+
146
+ * fixed sandbox mode when used with inheritance
147
+ * added preserveKeys support for the slice filter
148
+ * fixed the date filter when a DateTime instance is passed with a specific timezone
149
+ * added a trim filter
150
+
151
+ * 1.6.1 (2012-02-29)
152
+
153
+ * fixed Twig C extension
154
+ * removed the creation of Twig_Markup instances when not needed
155
+ * added a way to set the default global timezone for dates
156
+ * fixed the slice filter on strings when the length is not specified
157
+ * fixed the creation of the cache directory in case of a race condition
158
+
159
+ * 1.6.0 (2012-02-04)
160
+
161
+ * fixed raw blocks when used with the whitespace trim option
162
+ * made a speed optimization to macro calls when imported via the "from" tag
163
+ * fixed globals, parsers, visitors, filters, tests, and functions management in Twig_Environment when a new one or new extension is added
164
+ * fixed the attribute function when passing arguments
165
+ * added slice notation support for the [] operator (syntactic sugar for the slice operator)
166
+ * added a slice filter
167
+ * added string support for the reverse filter
168
+ * fixed the empty test and the length filter for Twig_Markup instances
169
+ * added a date function to ease date comparison
170
+ * fixed unary operators precedence
171
+ * added recursive parsing support in the parser
172
+ * added string and integer handling for the random function
173
+
174
+ * 1.5.1 (2012-01-05)
175
+
176
+ * fixed a regression when parsing strings
177
+
178
+ * 1.5.0 (2012-01-04)
179
+
180
+ * added Traversable objects support for the join filter
181
+
182
+ * 1.5.0-RC2 (2011-12-30)
183
+
184
+ * added a way to set the default global date interval format
185
+ * fixed the date filter for DateInterval instances (setTimezone() does not exist for them)
186
+ * refactored Twig_Template::display() to ease its extension
187
+ * added a number_format filter
188
+
189
+ * 1.5.0-RC1 (2011-12-26)
190
+
191
+ * removed the need to quote hash keys
192
+ * allowed hash keys to be any expression
193
+ * added a do tag
194
+ * added a flush tag
195
+ * added support for dynamically named filters and functions
196
+ * added a dump function to help debugging templates
197
+ * added a nl2br filter
198
+ * added a random function
199
+ * added a way to change the default format for the date filter
200
+ * fixed the lexer when an operator ending with a letter ends a line
201
+ * added string interpolation support
202
+ * enhanced exceptions for unknown filters, functions, tests, and tags
203
+
204
+ * 1.4.0 (2011-12-07)
205
+
206
+ * fixed lexer when using big numbers (> PHP_INT_MAX)
207
+ * added missing preserveKeys argument to the reverse filter
208
+ * fixed macros containing filter tag calls
209
+
210
+ * 1.4.0-RC2 (2011-11-27)
211
+
212
+ * removed usage of Reflection in Twig_Template::getAttribute()
213
+ * added a C extension that can optionally replace Twig_Template::getAttribute()
214
+ * added negative timestamp support to the date filter
215
+
216
+ * 1.4.0-RC1 (2011-11-20)
217
+
218
+ * optimized variable access when using PHP 5.4
219
+ * changed the precedence of the .. operator to be more consistent with languages that implements such a feature like Ruby
220
+ * added an Exception to Twig_Loader_Array::isFresh() method when the template does not exist to be consistent with other loaders
221
+ * added Twig_Function_Node to allow more complex functions to have their own Node class
222
+ * added Twig_Filter_Node to allow more complex filters to have their own Node class
223
+ * added Twig_Test_Node to allow more complex tests to have their own Node class
224
+ * added a better error message when a template is empty but contain a BOM
225
+ * fixed "in" operator for empty strings
226
+ * fixed the "defined" test and the "default" filter (now works with more than one call (foo.bar.foo) and for both values of the strict_variables option)
227
+ * changed the way extensions are loaded (addFilter/addFunction/addGlobal/addTest/addNodeVisitor/addTokenParser/addExtension can now be called in any order)
228
+ * added Twig_Environment::display()
229
+ * made the escape filter smarter when the encoding is not supported by PHP
230
+ * added a convert_encoding filter
231
+ * moved all node manipulations outside the compile() Node method
232
+ * made several speed optimizations
233
+
234
+ * 1.3.0 (2011-10-08)
235
+
236
+ no changes
237
+
238
+ * 1.3.0-RC1 (2011-10-04)
239
+
240
+ * added an optimization for the parent() function
241
+ * added cache reloading when auto_reload is true and an extension has been modified
242
+ * added the possibility to force the escaping of a string already marked as safe (instance of Twig_Markup)
243
+ * allowed empty templates to be used as traits
244
+ * added traits support for the "parent" function
245
+
246
+ * 1.2.0 (2011-09-13)
247
+
248
+ no changes
249
+
250
+ * 1.2.0-RC1 (2011-09-10)
251
+
252
+ * enhanced the exception when a tag remains unclosed
253
+ * added support for empty Countable objects for the "empty" test
254
+ * fixed algorithm that determines if a template using inheritance is valid (no output between block definitions)
255
+ * added better support for encoding problems when escaping a string (available as of PHP 5.4)
256
+ * added a way to ignore a missing template when using the "include" tag ({% include "foo" ignore missing %})
257
+ * added support for an array of templates to the "include" and "extends" tags ({% include ['foo', 'bar'] %})
258
+ * added support for bitwise operators in expressions
259
+ * added the "attribute" function to allow getting dynamic attributes on variables
260
+ * added Twig_Loader_Chain
261
+ * added Twig_Loader_Array::setTemplate()
262
+ * added an optimization for the set tag when used to capture a large chunk of static text
263
+ * changed name regex to match PHP one "[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*" (works for blocks, tags, functions, filters, and macros)
264
+ * removed the possibility to use the "extends" tag from a block
265
+ * added "if" modifier support to "for" loops
266
+
267
+ * 1.1.2 (2011-07-30)
268
+
269
+ * fixed json_encode filter on PHP 5.2
270
+ * fixed regression introduced in 1.1.1 ({{ block(foo|lower) }})
271
+ * fixed inheritance when using conditional parents
272
+ * fixed compilation of templates when the body of a child template is not empty
273
+ * fixed output when a macro throws an exception
274
+ * fixed a parsing problem when a large chunk of text is enclosed in a comment tag
275
+ * added PHPDoc for all Token parsers and Core extension functions
276
+
277
+ * 1.1.1 (2011-07-17)
278
+
279
+ * added a performance optimization in the Optimizer (also helps to lower the number of nested level calls)
280
+ * made some performance improvement for some edge cases
281
+
282
+ * 1.1.0 (2011-06-28)
283
+
284
+ * fixed json_encode filter
285
+
286
+ * 1.1.0-RC3 (2011-06-24)
287
+
288
+ * fixed method case-sensitivity when using the sandbox mode
289
+ * added timezone support for the date filter
290
+ * fixed possible security problems with NUL bytes
291
+
292
+ * 1.1.0-RC2 (2011-06-16)
293
+
294
+ * added an exception when the template passed to "use" is not a string
295
+ * made 'a.b is defined' not throw an exception if a is not defined (in strict mode)
296
+ * added {% line \d+ %} directive
297
+
298
+ * 1.1.0-RC1 (2011-05-28)
299
+
300
+ Flush your cache after upgrading.
301
+
302
+ * fixed date filter when using a timestamp
303
+ * fixed the defined test for some cases
304
+ * fixed a parsing problem when a large chunk of text is enclosed in a raw tag
305
+ * added support for horizontal reuse of template blocks (see docs for more information)
306
+ * added whitespace control modifier to all tags (see docs for more information)
307
+ * added null as an alias for none (the null test is also an alias for the none test now)
308
+ * made TRUE, FALSE, NONE equivalent to their lowercase counterparts
309
+ * wrapped all compilation and runtime exceptions with Twig_Error_Runtime and added logic to guess the template name and line
310
+ * moved display() method to Twig_Template (generated templates should now use doDisplay() instead)
311
+
312
+ * 1.0.0 (2011-03-27)
313
+
314
+ * fixed output when using mbstring
315
+ * fixed duplicate call of methods when using the sandbox
316
+ * made the charset configurable for the escape filter
317
+
318
+ * 1.0.0-RC2 (2011-02-21)
319
+
320
+ * changed the way {% set %} works when capturing (the content is now marked as safe)
321
+ * added support for macro name in the endmacro tag
322
+ * make Twig_Error compatible with PHP 5.3.0 >
323
+ * fixed an infinite loop on some Windows configurations
324
+ * fixed the "length" filter for numbers
325
+ * fixed Template::getAttribute() as properties in PHP are case sensitive
326
+ * removed coupling between Twig_Node and Twig_Template
327
+ * fixed the ternary operator precedence rule
328
+
329
+ * 1.0.0-RC1 (2011-01-09)
330
+
331
+ Backward incompatibilities:
332
+
333
+ * the "items" filter, which has been deprecated for quite a long time now, has been removed
334
+ * the "range" filter has been converted to a function: 0|range(10) -> range(0, 10)
335
+ * the "constant" filter has been converted to a function: {{ some_date|date('DATE_W3C'|constant) }} -> {{ some_date|date(constant('DATE_W3C')) }}
336
+ * the "cycle" filter has been converted to a function: {{ ['odd', 'even']|cycle(i) }} -> {{ cycle(['odd', 'even'], i) }}
337
+ * the "for" tag does not support "joined by" anymore
338
+ * the "autoescape" first argument is now "true"/"false" (instead of "on"/"off")
339
+ * the "parent" tag has been replaced by a "parent" function ({{ parent() }} instead of {% parent %})
340
+ * the "display" tag has been replaced by a "block" function ({{ block('title') }} instead of {% display title %})
341
+ * removed the grammar and simple token parser (moved to the Twig Extensions repository)
342
+
343
+ Changes:
344
+
345
+ * added "needs_context" option for filters and functions (the context is then passed as a first argument)
346
+ * added global variables support
347
+ * made macros return their value instead of echoing directly (fixes calling a macro in sandbox mode)
348
+ * added the "from" tag to import macros as functions
349
+ * added support for functions (a function is just syntactic sugar for a getAttribute() call)
350
+ * made macros callable when sandbox mode is enabled
351
+ * added an exception when a macro uses a reserved name
352
+ * the "default" filter now uses the "empty" test instead of just checking for null
353
+ * added the "empty" test
354
+
355
+ * 0.9.10 (2010-12-16)
356
+
357
+ Backward incompatibilities:
358
+
359
+ * The Escaper extension is enabled by default, which means that all displayed
360
+ variables are now automatically escaped. You can revert to the previous
361
+ behavior by removing the extension via $env->removeExtension('escaper')
362
+ or just set the 'autoescape' option to 'false'.
363
+ * removed the "without loop" attribute for the "for" tag (not needed anymore
364
+ as the Optimizer take care of that for most cases)
365
+ * arrays and hashes have now a different syntax
366
+ * arrays keep the same syntax with square brackets: [1, 2]
367
+ * hashes now use curly braces (["a": "b"] should now be written as {"a": "b"})
368
+ * support for "arrays with keys" and "hashes without keys" is not supported anymore ([1, "foo": "bar"] or {"foo": "bar", 1})
369
+ * the i18n extension is now part of the Twig Extensions repository
370
+
371
+ Changes:
372
+
373
+ * added the merge filter
374
+ * removed 'is_escaper' option for filters (a left over from the previous version) -- you must use 'is_safe' now instead
375
+ * fixed usage of operators as method names (like is, in, and not)
376
+ * changed the order of execution for node visitors
377
+ * fixed default() filter behavior when used with strict_variables set to on
378
+ * fixed filesystem loader compatibility with PHAR files
379
+ * enhanced error messages when an unexpected token is parsed in an expression
380
+ * fixed filename not being added to syntax error messages
381
+ * added the autoescape option to enable/disable autoescaping
382
+ * removed the newline after a comment (mimicks PHP behavior)
383
+ * added a syntax error exception when parent block is used on a template that does not extend another one
384
+ * made the Escaper extension enabled by default
385
+ * fixed sandbox extension when used with auto output escaping
386
+ * fixed escaper when wrapping a Twig_Node_Print (the original class must be preserved)
387
+ * added an Optimizer extension (enabled by default; optimizes "for" loops and "raw" filters)
388
+ * added priority to node visitors
389
+
390
+ * 0.9.9 (2010-11-28)
391
+
392
+ Backward incompatibilities:
393
+ * the self special variable has been renamed to _self
394
+ * the odd and even filters are now tests:
395
+ {{ foo|odd }} must now be written {{ foo is odd }}
396
+ * the "safe" filter has been renamed to "raw"
397
+ * in Node classes,
398
+ sub-nodes are now accessed via getNode() (instead of property access)
399
+ attributes via getAttribute() (instead of array access)
400
+ * the urlencode filter had been renamed to url_encode
401
+ * the include tag now merges the passed variables with the current context by default
402
+ (the old behavior is still possible by adding the "only" keyword)
403
+ * moved Exceptions to Twig_Error_* (Twig_SyntaxError/Twig_RuntimeError are now Twig_Error_Syntax/Twig_Error_Runtime)
404
+ * removed support for {{ 1 < i < 3 }} (use {{ i > 1 and i < 3 }} instead)
405
+ * the "in" filter has been removed ({{ a|in(b) }} should now be written {{ a in b }})
406
+
407
+ Changes:
408
+ * added file and line to Twig_Error_Runtime exceptions thrown from Twig_Template
409
+ * changed trans tag to accept any variable for the plural count
410
+ * fixed sandbox mode (__toString() method check was not enforced if called implicitly from complex statements)
411
+ * added the ** (power) operator
412
+ * changed the algorithm used for parsing expressions
413
+ * added the spaceless tag
414
+ * removed trim_blocks option
415
+ * added support for is*() methods for attributes (foo.bar now looks for foo->getBar() or foo->isBar())
416
+ * changed all exceptions to extend Twig_Error
417
+ * fixed unary expressions ({{ not(1 or 0) }})
418
+ * fixed child templates (with an extend tag) that uses one or more imports
419
+ * added support for {{ 1 not in [2, 3] }} (more readable than the current {{ not (1 in [2, 3]) }})
420
+ * escaping has been rewritten
421
+ * the implementation of template inheritance has been rewritten
422
+ (blocks can now be called individually and still work with inheritance)
423
+ * fixed error handling for if tag when a syntax error occurs within a subparse process
424
+ * added a way to implement custom logic for resolving token parsers given a tag name
425
+ * fixed js escaper to be stricter (now uses a whilelist-based js escaper)
426
+ * added the following filers: "constant", "trans", "replace", "json_encode"
427
+ * added a "constant" test
428
+ * fixed objects with __toString() not being autoescaped
429
+ * fixed subscript expressions when calling __call() (methods now keep the case)
430
+ * added "test" feature (accessible via the "is" operator)
431
+ * removed the debug tag (should be done in an extension)
432
+ * fixed trans tag when no vars are used in plural form
433
+ * fixed race condition when writing template cache
434
+ * added the special _charset variable to reference the current charset
435
+ * added the special _context variable to reference the current context
436
+ * renamed self to _self (to avoid conflict)
437
+ * fixed Twig_Template::getAttribute() for protected properties
438
+
439
+ * 0.9.8 (2010-06-28)
440
+
441
+ Backward incompatibilities:
442
+ * the trans tag plural count is now attached to the plural tag:
443
+ old: `{% trans count %}...{% plural %}...{% endtrans %}`
444
+ new: `{% trans %}...{% plural count %}...{% endtrans %}`
445
+
446
+ * added a way to translate strings coming from a variable ({% trans var %})
447
+ * fixed trans tag when used with the Escaper extension
448
+ * fixed default cache umask
449
+ * removed Twig_Template instances from the debug tag output
450
+ * fixed objects with __isset() defined
451
+ * fixed set tag when used with a capture
452
+ * fixed type hinting for Twig_Environment::addFilter() method
453
+
454
+ * 0.9.7 (2010-06-12)
455
+
456
+ Backward incompatibilities:
457
+ * changed 'as' to '=' for the set tag ({% set title as "Title" %} must now be {% set title = "Title" %})
458
+ * removed the sandboxed attribute of the include tag (use the new sandbox tag instead)
459
+ * refactored the Node system (if you have custom nodes, you will have to update them to use the new API)
460
+
461
+ * added self as a special variable that refers to the current template (useful for importing macros from the current template)
462
+ * added Twig_Template instance support to the include tag
463
+ * added support for dynamic and conditional inheritance ({% extends some_var %} and {% extends standalone ? "minimum" : "base" %})
464
+ * added a grammar sub-framework to ease the creation of custom tags
465
+ * fixed the for tag for large arrays (some loop variables are now only available for arrays and objects that implement the Countable interface)
466
+ * removed the Twig_Resource::resolveMissingFilter() method
467
+ * fixed the filter tag which did not apply filtering to included files
468
+ * added a bunch of unit tests
469
+ * added a bunch of phpdoc
470
+ * added a sandbox tag in the sandbox extension
471
+ * changed the date filter to support any date format supported by DateTime
472
+ * added strict_variable setting to throw an exception when an invalid variable is used in a template (disabled by default)
473
+ * added the lexer, parser, and compiler as arguments to the Twig_Environment constructor
474
+ * changed the cache option to only accepts an explicit path to a cache directory or false
475
+ * added a way to add token parsers, filters, and visitors without creating an extension
476
+ * added three interfaces: Twig_NodeInterface, Twig_TokenParserInterface, and Twig_FilterInterface
477
+ * changed the generated code to match the new coding standards
478
+ * fixed sandbox mode (__toString() method check was not enforced if called implicitly from a simple statement like {{ article }})
479
+ * added an exception when a child template has a non-empty body (as it is always ignored when rendering)
480
+
481
+ * 0.9.6 (2010-05-12)
482
+
483
+ * fixed variables defined outside a loop and for which the value changes in a for loop
484
+ * fixed the test suite for PHP 5.2 and older versions of PHPUnit
485
+ * added support for __call() in expression resolution
486
+ * fixed node visiting for macros (macros are now visited by visitors as any other node)
487
+ * fixed nested block definitions with a parent call (rarely useful but nonetheless supported now)
488
+ * added the cycle filter
489
+ * fixed the Lexer when mbstring.func_overload is used with an mbstring.internal_encoding different from ASCII
490
+ * added a long-syntax for the set tag ({% set foo %}...{% endset %})
491
+ * unit tests are now powered by PHPUnit
492
+ * added support for gettext via the `i18n` extension
493
+ * fixed twig_capitalize_string_filter() and fixed twig_length_filter() when used with UTF-8 values
494
+ * added a more useful exception if an if tag is not closed properly
495
+ * added support for escaping strategy in the autoescape tag
496
+ * fixed lexer when a template has a big chunk of text between/in a block
497
+
498
+ * 0.9.5 (2010-01-20)
499
+
500
+ As for any new release, don't forget to remove all cached templates after
501
+ upgrading.
502
+
503
+ If you have defined custom filters, you MUST upgrade them for this release. To
504
+ upgrade, replace "array" with "new Twig_Filter_Function", and replace the
505
+ environment constant by the "needs_environment" option:
506
+
507
+ // before
508
+ 'even' => array('twig_is_even_filter', false),
509
+ 'escape' => array('twig_escape_filter', true),
510
+
511
+ // after
512
+ 'even' => new Twig_Filter_Function('twig_is_even_filter'),
513
+ 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true)),
514
+
515
+ If you have created NodeTransformer classes, you will need to upgrade them to
516
+ the new interface (please note that the interface is not yet considered
517
+ stable).
518
+
519
+ * fixed list nodes that did not extend the Twig_NodeListInterface
520
+ * added the "without loop" option to the for tag (it disables the generation of the loop variable)
521
+ * refactored node transformers to node visitors
522
+ * fixed automatic-escaping for blocks
523
+ * added a way to specify variables to pass to an included template
524
+ * changed the automatic-escaping rules to be more sensible and more configurable in custom filters (the documentation lists all the rules)
525
+ * improved the filter system to allow object methods to be used as filters
526
+ * changed the Array and String loaders to actually make use of the cache mechanism
527
+ * included the default filter function definitions in the extension class files directly (Core, Escaper)
528
+ * added the // operator (like the floor() PHP function)
529
+ * added the .. operator (as a syntactic sugar for the range filter when the step is 1)
530
+ * added the in operator (as a syntactic sugar for the in filter)
531
+ * added the following filters in the Core extension: in, range
532
+ * added support for arrays (same behavior as in PHP, a mix between lists and dictionaries, arrays and hashes)
533
+ * enhanced some error messages to provide better feedback in case of parsing errors
534
+
535
+ * 0.9.4 (2009-12-02)
536
+
537
+ If you have custom loaders, you MUST upgrade them for this release: The
538
+ Twig_Loader base class has been removed, and the Twig_LoaderInterface has also
539
+ been changed (see the source code for more information or the documentation).
540
+
541
+ * added support for DateTime instances for the date filter
542
+ * fixed loop.last when the array only has one item
543
+ * made it possible to insert newlines in tag and variable blocks
544
+ * fixed a bug when a literal '\n' were present in a template text
545
+ * fixed bug when the filename of a template contains */
546
+ * refactored loaders
547
+
548
+ * 0.9.3 (2009-11-11)
549
+
550
+ This release is NOT backward compatible with the previous releases.
551
+
552
+ The loaders do not take the cache and autoReload arguments anymore. Instead,
553
+ the Twig_Environment class has two new options: cache and auto_reload.
554
+ Upgrading your code means changing this kind of code:
555
+
556
+ $loader = new Twig_Loader_Filesystem('/path/to/templates', '/path/to/compilation_cache', true);
557
+ $twig = new Twig_Environment($loader);
558
+
559
+ to something like this:
560
+
561
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
562
+ $twig = new Twig_Environment($loader, array(
563
+ 'cache' => '/path/to/compilation_cache',
564
+ 'auto_reload' => true,
565
+ ));
566
+
567
+ * deprecated the "items" filter as it is not needed anymore
568
+ * made cache and auto_reload options of Twig_Environment instead of arguments of Twig_Loader
569
+ * optimized template loading speed
570
+ * removed output when an error occurs in a template and render() is used
571
+ * made major speed improvements for loops (up to 300% on even the smallest loops)
572
+ * added properties as part of the sandbox mode
573
+ * added public properties support (obj.item can now be the item property on the obj object)
574
+ * extended set tag to support expression as value ({% set foo as 'foo' ~ 'bar' %} )
575
+ * fixed bug when \ was used in HTML
576
+
577
+ * 0.9.2 (2009-10-29)
578
+
579
+ * made some speed optimizations
580
+ * changed the cache extension to .php
581
+ * added a js escaping strategy
582
+ * added support for short block tag
583
+ * changed the filter tag to allow chained filters
584
+ * made lexer more flexible as you can now change the default delimiters
585
+ * added set tag
586
+ * changed default directory permission when cache dir does not exist (more secure)
587
+ * added macro support
588
+ * changed filters first optional argument to be a Twig_Environment instance instead of a Twig_Template instance
589
+ * made Twig_Autoloader::autoload() a static method
590
+ * avoid writing template file if an error occurs
591
+ * added $ escaping when outputting raw strings
592
+ * enhanced some error messages to ease debugging
593
+ * fixed empty cache files when the template contains an error
594
+
595
+ * 0.9.1 (2009-10-14)
596
+
597
+ * fixed a bug in PHP 5.2.6
598
+ * fixed numbers with one than one decimal
599
+ * added support for method calls with arguments ({{ foo.bar('a', 43) }})
600
+ * made small speed optimizations
601
+ * made minor tweaks to allow better extensibility and flexibility
602
+
603
+ * 0.9.0 (2009-10-12)
604
+
605
+ * Initial release
Twig/LICENSE ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2009-2013 by the Twig Team, see AUTHORS for more details.
2
+
3
+ Some rights reserved.
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are
7
+ met:
8
+
9
+ * Redistributions of source code must retain the above copyright
10
+ notice, this list of conditions and the following disclaimer.
11
+
12
+ * Redistributions in binary form must reproduce the above
13
+ copyright notice, this list of conditions and the following
14
+ disclaimer in the documentation and/or other materials provided
15
+ with the distribution.
16
+
17
+ * The names of the contributors may not be used to endorse or
18
+ promote products derived from this software without specific
19
+ prior written permission.
20
+
21
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25
+ OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Twig/README.markdown ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Twig, the flexible, fast, and secure template language for PHP
2
+ ==============================================================
3
+
4
+ [![Build Status](https://secure.travis-ci.org/fabpot/Twig.png?branch=master)](http://travis-ci.org/fabpot/Twig)
5
+
6
+ Twig is a template language for PHP, released under the new BSD license (code
7
+ and documentation).
8
+
9
+ Twig uses a syntax similar to the Django and Jinja template languages which
10
+ inspired the Twig runtime environment.
11
+
12
+ More Information
13
+ ----------------
14
+
15
+ Read the [documentation][1] for more information.
16
+
17
+ [1]: http://twig.sensiolabs.org/documentation
Twig/composer.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "twig/twig",
3
+ "type": "library",
4
+ "description": "Twig, the flexible, fast, and secure template language for PHP",
5
+ "keywords": ["templating"],
6
+ "homepage": "http://twig.sensiolabs.org",
7
+ "license": "BSD-3",
8
+ "authors": [
9
+ {
10
+ "name": "Fabien Potencier",
11
+ "email": "fabien@symfony.com"
12
+ },
13
+ {
14
+ "name": "Armin Ronacher",
15
+ "email": "armin.ronacher@active-4.com"
16
+ }
17
+ ],
18
+ "require": {
19
+ "php": ">=5.2.4"
20
+ },
21
+ "autoload": {
22
+ "psr-0" : {
23
+ "Twig_" : "lib/"
24
+ }
25
+ },
26
+ "extra": {
27
+ "branch-alias": {
28
+ "dev-master": "1.12-dev"
29
+ }
30
+ }
31
+ }
Twig/doc/advanced.rst ADDED
@@ -0,0 +1,776 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Extending Twig
2
+ ==============
3
+
4
+ .. caution::
5
+
6
+ This section describes how to extend Twig as of **Twig 1.12**. If you are
7
+ using an older version, read the :doc:`legacy<advanced_legacy>` chapter
8
+ instead.
9
+
10
+ Twig can be extended in many ways; you can add extra tags, filters, tests,
11
+ operators, global variables, and functions. You can even extend the parser
12
+ itself with node visitors.
13
+
14
+ .. note::
15
+
16
+ The first section of this chapter describes how to extend Twig easily. If
17
+ you want to reuse your changes in different projects or if you want to
18
+ share them with others, you should then create an extension as described
19
+ in the following section.
20
+
21
+ .. caution::
22
+
23
+ When extending Twig without creating an extension, Twig won't be able to
24
+ recompile your templates when the PHP code is updated. To see your changes
25
+ in real-time, either disable template caching or package your code into an
26
+ extension (see the next section of this chapter).
27
+
28
+ Before extending Twig, you must understand the differences between all the
29
+ different possible extension points and when to use them.
30
+
31
+ First, remember that Twig has two main language constructs:
32
+
33
+ * ``{{ }}``: used to print the result of an expression evaluation;
34
+
35
+ * ``{% %}``: used to execute statements.
36
+
37
+ To understand why Twig exposes so many extension points, let's see how to
38
+ implement a *Lorem ipsum* generator (it needs to know the number of words to
39
+ generate).
40
+
41
+ You can use a ``lipsum`` *tag*:
42
+
43
+ .. code-block:: jinja
44
+
45
+ {% lipsum 40 %}
46
+
47
+ That works, but using a tag for ``lipsum`` is not a good idea for at least
48
+ three main reasons:
49
+
50
+ * ``lipsum`` is not a language construct;
51
+ * The tag outputs something;
52
+ * The tag is not flexible as you cannot use it in an expression:
53
+
54
+ .. code-block:: jinja
55
+
56
+ {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
57
+
58
+ In fact, you rarely need to create tags; and that's good news because tags are
59
+ the most complex extension point of Twig.
60
+
61
+ Now, let's use a ``lipsum`` *filter*:
62
+
63
+ .. code-block:: jinja
64
+
65
+ {{ 40|lipsum }}
66
+
67
+ Again, it works, but it looks weird. A filter transforms the passed value to
68
+ something else but here we use the value to indicate the number of words to
69
+ generate (so, ``40`` is an argument of the filter, not the value we want to
70
+ transform).
71
+
72
+ Next, let's use a ``lipsum`` *function*:
73
+
74
+ .. code-block:: jinja
75
+
76
+ {{ lipsum(40) }}
77
+
78
+ Here we go. For this specific example, the creation of a function is the
79
+ extension point to use. And you can use it anywhere an expression is accepted:
80
+
81
+ .. code-block:: jinja
82
+
83
+ {{ 'some text' ~ lipsum(40) ~ 'some more text' }}
84
+
85
+ {% set lipsum = lipsum(40) %}
86
+
87
+ Last but not the least, you can also use a *global* object with a method able
88
+ to generate lorem ipsum text:
89
+
90
+ .. code-block:: jinja
91
+
92
+ {{ text.lipsum(40) }}
93
+
94
+ As a rule of thumb, use functions for frequently used features and global
95
+ objects for everything else.
96
+
97
+ Keep in mind the following when you want to extend Twig:
98
+
99
+ ========== ========================== ========== =========================
100
+ What? Implementation difficulty? How often? When?
101
+ ========== ========================== ========== =========================
102
+ *macro* trivial frequent Content generation
103
+ *global* trivial frequent Helper object
104
+ *function* trivial frequent Content generation
105
+ *filter* trivial frequent Value transformation
106
+ *tag* complex rare DSL language construct
107
+ *test* trivial rare Boolean decision
108
+ *operator* trivial rare Values transformation
109
+ ========== ========================== ========== =========================
110
+
111
+ Globals
112
+ -------
113
+
114
+ A global variable is like any other template variable, except that it's
115
+ available in all templates and macros::
116
+
117
+ $twig = new Twig_Environment($loader);
118
+ $twig->addGlobal('text', new Text());
119
+
120
+ You can then use the ``text`` variable anywhere in a template:
121
+
122
+ .. code-block:: jinja
123
+
124
+ {{ text.lipsum(40) }}
125
+
126
+ Filters
127
+ -------
128
+
129
+ Creating a filter is as simple as associating a name with a PHP callable::
130
+
131
+ // an anonymous function
132
+ $filter = new Twig_SimpleFilter('rot13', function ($string) {
133
+ return str_rot13($string);
134
+ });
135
+
136
+ // or a simple PHP function
137
+ $filter = new Twig_SimpleFilter('rot13', 'str_rot13');
138
+
139
+ // or a class method
140
+ $filter = new Twig_SimpleFilter('rot13', array('SomeClass', 'rot13Filter'));
141
+
142
+ The first argument passed to the ``Twig_SimpleFilter`` constructor is the name
143
+ of the filter you will use in templates and the second one is the PHP callable
144
+ to associate with it.
145
+
146
+ Then, add the filter to your Twig environment::
147
+
148
+ $twig = new Twig_Environment($loader);
149
+ $twig->addFilter($filter);
150
+
151
+ And here is how to use it in a template:
152
+
153
+ .. code-block:: jinja
154
+
155
+ {{ 'Twig'|rot13 }}
156
+
157
+ {# will output Gjvt #}
158
+
159
+ When called by Twig, the PHP callable receives the left side of the filter
160
+ (before the pipe ``|``) as the first argument and the extra arguments passed
161
+ to the filter (within parentheses ``()``) as extra arguments.
162
+
163
+ For instance, the following code:
164
+
165
+ .. code-block:: jinja
166
+
167
+ {{ 'TWIG'|lower }}
168
+ {{ now|date('d/m/Y') }}
169
+
170
+ is compiled to something like the following::
171
+
172
+ <?php echo strtolower('TWIG') ?>
173
+ <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
174
+
175
+ The ``Twig_SimpleFilter`` class takes an array of options as its last
176
+ argument::
177
+
178
+ $filter = new Twig_SimpleFilter('rot13', 'str_rot13', $options);
179
+
180
+ Environment aware Filters
181
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
182
+
183
+ If you want to access the current environment instance in your filter, set the
184
+ ``needs_environment`` option to ``true``; Twig will pass the current
185
+ environment as the first argument to the filter call::
186
+
187
+ $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $string) {
188
+ // get the current charset for instance
189
+ $charset = $env->getCharset();
190
+
191
+ return str_rot13($string);
192
+ }, array('needs_environment' => true));
193
+
194
+ Context aware Filters
195
+ ~~~~~~~~~~~~~~~~~~~~~
196
+
197
+ If you want to access the current context in your filter, set the
198
+ ``needs_context`` option to ``true``; Twig will pass the current context as
199
+ the first argument to the filter call (or the second one if
200
+ ``needs_environment`` is also set to ``true``)::
201
+
202
+ $filter = new Twig_SimpleFilter('rot13', function ($context, $string) {
203
+ // ...
204
+ }, array('needs_context' => true));
205
+
206
+ $filter = new Twig_SimpleFilter('rot13', function (Twig_Environment $env, $context, $string) {
207
+ // ...
208
+ }, array('needs_context' => true, 'needs_environment' => true));
209
+
210
+ Automatic Escaping
211
+ ~~~~~~~~~~~~~~~~~~
212
+
213
+ If automatic escaping is enabled, the output of the filter may be escaped
214
+ before printing. If your filter acts as an escaper (or explicitly outputs html
215
+ or JavaScript code), you will want the raw output to be printed. In such a
216
+ case, set the ``is_safe`` option::
217
+
218
+ $filter = new Twig_SimpleFilter('nl2br', 'nl2br', array('is_safe' => array('html')));
219
+
220
+ Some filters may need to work on input that is already escaped or safe, for
221
+ example when adding (safe) html tags to originally unsafe output. In such a
222
+ case, set the ``pre_escape`` option to escape the input data before it is run
223
+ through your filter::
224
+
225
+ $filter = new Twig_SimpleFilter('somefilter', 'somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
226
+
227
+ Dynamic Filters
228
+ ~~~~~~~~~~~~~~~
229
+
230
+ A filter name containing the special ``*`` character is a dynamic filter as
231
+ the ``*`` can be any string::
232
+
233
+ $filter = new Twig_SimpleFilter('*_path', function ($name, $arguments) {
234
+ // ...
235
+ });
236
+
237
+ The following filters will be matched by the above defined dynamic filter:
238
+
239
+ * ``product_path``
240
+ * ``category_path``
241
+
242
+ A dynamic filter can define more than one dynamic parts::
243
+
244
+ $filter = new Twig_SimpleFilter('*_path', function ($name, $suffix, $arguments) {
245
+ // ...
246
+ });
247
+
248
+ The filter will receive all dynamic part values before the normal filters
249
+ arguments, but after the environment and the context. For instance, a call to
250
+ ``'foo'|a_path_b()`` will result in the following arguments to be passed to
251
+ the filter: ``('a', 'b', 'foo')``.
252
+
253
+ Functions
254
+ ---------
255
+
256
+ Functions are defined in the exact same way as filters, but you need to create
257
+ an instance of ``Twig_SimpleFunction``::
258
+
259
+ $twig = new Twig_Environment($loader);
260
+ $function = new Twig_SimpleFunction('function_name', function () {
261
+ // ...
262
+ });
263
+ $twig->addFunction($function);
264
+
265
+ Functions support the same features as filters, except for the ``pre_escape``
266
+ and ``preserves_safety`` options.
267
+
268
+ Tests
269
+ -----
270
+
271
+ Tests are defined in the exact same way as filters and functions, but you need
272
+ to create an instance of ``Twig_SimpleTest``::
273
+
274
+ $twig = new Twig_Environment($loader);
275
+ $test = new Twig_SimpleTest('test_name', function () {
276
+ // ...
277
+ });
278
+ $twig->addTest($test);
279
+
280
+ Tests do not support any options.
281
+
282
+ Tags
283
+ ----
284
+
285
+ One of the most exciting feature of a template engine like Twig is the
286
+ possibility to define new language constructs. This is also the most complex
287
+ feature as you need to understand how Twig's internals work.
288
+
289
+ Let's create a simple ``set`` tag that allows the definition of simple
290
+ variables from within a template. The tag can be used like follows:
291
+
292
+ .. code-block:: jinja
293
+
294
+ {% set name = "value" %}
295
+
296
+ {{ name }}
297
+
298
+ {# should output value #}
299
+
300
+ .. note::
301
+
302
+ The ``set`` tag is part of the Core extension and as such is always
303
+ available. The built-in version is slightly more powerful and supports
304
+ multiple assignments by default (cf. the template designers chapter for
305
+ more information).
306
+
307
+ Three steps are needed to define a new tag:
308
+
309
+ * Defining a Token Parser class (responsible for parsing the template code);
310
+
311
+ * Defining a Node class (responsible for converting the parsed code to PHP);
312
+
313
+ * Registering the tag.
314
+
315
+ Registering a new tag
316
+ ~~~~~~~~~~~~~~~~~~~~~
317
+
318
+ Adding a tag is as simple as calling the ``addTokenParser`` method on the
319
+ ``Twig_Environment`` instance::
320
+
321
+ $twig = new Twig_Environment($loader);
322
+ $twig->addTokenParser(new Project_Set_TokenParser());
323
+
324
+ Defining a Token Parser
325
+ ~~~~~~~~~~~~~~~~~~~~~~~
326
+
327
+ Now, let's see the actual code of this class::
328
+
329
+ class Project_Set_TokenParser extends Twig_TokenParser
330
+ {
331
+ public function parse(Twig_Token $token)
332
+ {
333
+ $lineno = $token->getLine();
334
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
335
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, '=');
336
+ $value = $this->parser->getExpressionParser()->parseExpression();
337
+
338
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
339
+
340
+ return new Project_Set_Node($name, $value, $lineno, $this->getTag());
341
+ }
342
+
343
+ public function getTag()
344
+ {
345
+ return 'set';
346
+ }
347
+ }
348
+
349
+ The ``getTag()`` method must return the tag we want to parse, here ``set``.
350
+
351
+ The ``parse()`` method is invoked whenever the parser encounters a ``set``
352
+ tag. It should return a ``Twig_Node`` instance that represents the node (the
353
+ ``Project_Set_Node`` calls creating is explained in the next section).
354
+
355
+ The parsing process is simplified thanks to a bunch of methods you can call
356
+ from the token stream (``$this->parser->getStream()``):
357
+
358
+ * ``getCurrent()``: Gets the current token in the stream.
359
+
360
+ * ``next()``: Moves to the next token in the stream, *but returns the old one*.
361
+
362
+ * ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
363
+ the current token is of a particular type or value (or both). The value may be an
364
+ array of several possible values.
365
+
366
+ * ``expect($type[, $value[, $message]])``: If the current token isn't of the given
367
+ type/value a syntax error is thrown. Otherwise, if the type and value are correct,
368
+ the token is returned and the stream moves to the next token.
369
+
370
+ * ``look()``: Looks a the next token without consuming it.
371
+
372
+ Parsing expressions is done by calling the ``parseExpression()`` like we did for
373
+ the ``set`` tag.
374
+
375
+ .. tip::
376
+
377
+ Reading the existing ``TokenParser`` classes is the best way to learn all
378
+ the nitty-gritty details of the parsing process.
379
+
380
+ Defining a Node
381
+ ~~~~~~~~~~~~~~~
382
+
383
+ The ``Project_Set_Node`` class itself is rather simple::
384
+
385
+ class Project_Set_Node extends Twig_Node
386
+ {
387
+ public function __construct($name, Twig_Node_Expression $value, $lineno, $tag = null)
388
+ {
389
+ parent::__construct(array('value' => $value), array('name' => $name), $lineno, $tag);
390
+ }
391
+
392
+ public function compile(Twig_Compiler $compiler)
393
+ {
394
+ $compiler
395
+ ->addDebugInfo($this)
396
+ ->write('$context[\''.$this->getAttribute('name').'\'] = ')
397
+ ->subcompile($this->getNode('value'))
398
+ ->raw(";\n")
399
+ ;
400
+ }
401
+ }
402
+
403
+ The compiler implements a fluid interface and provides methods that helps the
404
+ developer generate beautiful and readable PHP code:
405
+
406
+ * ``subcompile()``: Compiles a node.
407
+
408
+ * ``raw()``: Writes the given string as is.
409
+
410
+ * ``write()``: Writes the given string by adding indentation at the beginning
411
+ of each line.
412
+
413
+ * ``string()``: Writes a quoted string.
414
+
415
+ * ``repr()``: Writes a PHP representation of a given value (see
416
+ ``Twig_Node_For`` for a usage example).
417
+
418
+ * ``addDebugInfo()``: Adds the line of the original template file related to
419
+ the current node as a comment.
420
+
421
+ * ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a
422
+ usage example).
423
+
424
+ * ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
425
+ usage example).
426
+
427
+ .. _creating_extensions:
428
+
429
+ Creating an Extension
430
+ ---------------------
431
+
432
+ The main motivation for writing an extension is to move often used code into a
433
+ reusable class like adding support for internationalization. An extension can
434
+ define tags, filters, tests, operators, global variables, functions, and node
435
+ visitors.
436
+
437
+ Creating an extension also makes for a better separation of code that is
438
+ executed at compilation time and code needed at runtime. As such, it makes
439
+ your code faster.
440
+
441
+ Most of the time, it is useful to create a single extension for your project,
442
+ to host all the specific tags and filters you want to add to Twig.
443
+
444
+ .. tip::
445
+
446
+ When packaging your code into an extension, Twig is smart enough to
447
+ recompile your templates whenever you make a change to it (when
448
+ ``auto_reload`` is enabled).
449
+
450
+ .. note::
451
+
452
+ Before writing your own extensions, have a look at the Twig official
453
+ extension repository: http://github.com/fabpot/Twig-extensions.
454
+
455
+ An extension is a class that implements the following interface::
456
+
457
+ interface Twig_ExtensionInterface
458
+ {
459
+ /**
460
+ * Initializes the runtime environment.
461
+ *
462
+ * This is where you can load some file that contains filter functions for instance.
463
+ *
464
+ * @param Twig_Environment $environment The current Twig_Environment instance
465
+ */
466
+ function initRuntime(Twig_Environment $environment);
467
+
468
+ /**
469
+ * Returns the token parser instances to add to the existing list.
470
+ *
471
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
472
+ */
473
+ function getTokenParsers();
474
+
475
+ /**
476
+ * Returns the node visitor instances to add to the existing list.
477
+ *
478
+ * @return array An array of Twig_NodeVisitorInterface instances
479
+ */
480
+ function getNodeVisitors();
481
+
482
+ /**
483
+ * Returns a list of filters to add to the existing list.
484
+ *
485
+ * @return array An array of filters
486
+ */
487
+ function getFilters();
488
+
489
+ /**
490
+ * Returns a list of tests to add to the existing list.
491
+ *
492
+ * @return array An array of tests
493
+ */
494
+ function getTests();
495
+
496
+ /**
497
+ * Returns a list of functions to add to the existing list.
498
+ *
499
+ * @return array An array of functions
500
+ */
501
+ function getFunctions();
502
+
503
+ /**
504
+ * Returns a list of operators to add to the existing list.
505
+ *
506
+ * @return array An array of operators
507
+ */
508
+ function getOperators();
509
+
510
+ /**
511
+ * Returns a list of global variables to add to the existing list.
512
+ *
513
+ * @return array An array of global variables
514
+ */
515
+ function getGlobals();
516
+
517
+ /**
518
+ * Returns the name of the extension.
519
+ *
520
+ * @return string The extension name
521
+ */
522
+ function getName();
523
+ }
524
+
525
+ To keep your extension class clean and lean, it can inherit from the built-in
526
+ ``Twig_Extension`` class instead of implementing the whole interface. That
527
+ way, you just need to implement the ``getName()`` method as the
528
+ ``Twig_Extension`` provides empty implementations for all other methods.
529
+
530
+ The ``getName()`` method must return a unique identifier for your extension.
531
+
532
+ Now, with this information in mind, let's create the most basic extension
533
+ possible::
534
+
535
+ class Project_Twig_Extension extends Twig_Extension
536
+ {
537
+ public function getName()
538
+ {
539
+ return 'project';
540
+ }
541
+ }
542
+
543
+ .. note::
544
+
545
+ Of course, this extension does nothing for now. We will customize it in
546
+ the next sections.
547
+
548
+ Twig does not care where you save your extension on the filesystem, as all
549
+ extensions must be registered explicitly to be available in your templates.
550
+
551
+ You can register an extension by using the ``addExtension()`` method on your
552
+ main ``Environment`` object::
553
+
554
+ $twig = new Twig_Environment($loader);
555
+ $twig->addExtension(new Project_Twig_Extension());
556
+
557
+ Of course, you need to first load the extension file by either using
558
+ ``require_once()`` or by using an autoloader (see `spl_autoload_register()`_).
559
+
560
+ .. tip::
561
+
562
+ The bundled extensions are great examples of how extensions work.
563
+
564
+ Globals
565
+ ~~~~~~~
566
+
567
+ Global variables can be registered in an extension via the ``getGlobals()``
568
+ method::
569
+
570
+ class Project_Twig_Extension extends Twig_Extension
571
+ {
572
+ public function getGlobals()
573
+ {
574
+ return array(
575
+ 'text' => new Text(),
576
+ );
577
+ }
578
+
579
+ // ...
580
+ }
581
+
582
+ Functions
583
+ ~~~~~~~~~
584
+
585
+ Functions can be registered in an extension via the ``getFunctions()``
586
+ method::
587
+
588
+ class Project_Twig_Extension extends Twig_Extension
589
+ {
590
+ public function getFunctions()
591
+ {
592
+ return array(
593
+ new Twig_SimpleFunction('lipsum', 'generate_lipsum'),
594
+ );
595
+ }
596
+
597
+ // ...
598
+ }
599
+
600
+ Filters
601
+ ~~~~~~~
602
+
603
+ To add a filter to an extension, you need to override the ``getFilters()``
604
+ method. This method must return an array of filters to add to the Twig
605
+ environment::
606
+
607
+ class Project_Twig_Extension extends Twig_Extension
608
+ {
609
+ public function getFilters()
610
+ {
611
+ return array(
612
+ new Twig_SimpleFilter('rot13', 'str_rot13'),
613
+ );
614
+ }
615
+
616
+ // ...
617
+ }
618
+
619
+ Tags
620
+ ~~~~
621
+
622
+ Adding a tag in an extension can be done by overriding the
623
+ ``getTokenParsers()`` method. This method must return an array of tags to add
624
+ to the Twig environment::
625
+
626
+ class Project_Twig_Extension extends Twig_Extension
627
+ {
628
+ public function getTokenParsers()
629
+ {
630
+ return array(new Project_Set_TokenParser());
631
+ }
632
+
633
+ // ...
634
+ }
635
+
636
+ In the above code, we have added a single new tag, defined by the
637
+ ``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
638
+ responsible for parsing the tag and compiling it to PHP.
639
+
640
+ Operators
641
+ ~~~~~~~~~
642
+
643
+ The ``getOperators()`` methods allows to add new operators. Here is how to add
644
+ ``!``, ``||``, and ``&&`` operators::
645
+
646
+ class Project_Twig_Extension extends Twig_Extension
647
+ {
648
+ public function getOperators()
649
+ {
650
+ return array(
651
+ array(
652
+ '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
653
+ ),
654
+ array(
655
+ '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
656
+ '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
657
+ ),
658
+ );
659
+ }
660
+
661
+ // ...
662
+ }
663
+
664
+ Tests
665
+ ~~~~~
666
+
667
+ The ``getTests()`` methods allows to add new test functions::
668
+
669
+ class Project_Twig_Extension extends Twig_Extension
670
+ {
671
+ public function getTests()
672
+ {
673
+ return array(
674
+ new Twig_SimpleTest('even', 'twig_test_even'),
675
+ );
676
+ }
677
+
678
+ // ...
679
+ }
680
+
681
+ Overloading
682
+ -----------
683
+
684
+ To overload an already defined filter, test, operator, global variable, or
685
+ function, define it again **as late as possible**::
686
+
687
+ $twig = new Twig_Environment($loader);
688
+ $twig->addFilter(new Twig_SimpleFilter('date', function ($timestamp, $format = 'F j, Y H:i') {
689
+ // do something different from the built-in date filter
690
+ }));
691
+
692
+ Here, we have overloaded the built-in ``date`` filter with a custom one.
693
+
694
+ That also works with an extension::
695
+
696
+ class MyCoreExtension extends Twig_Extension
697
+ {
698
+ public function getFilters()
699
+ {
700
+ return array(
701
+ new Twig_SimpleFilter('date', array($this, 'dateFilter')),
702
+ );
703
+ }
704
+
705
+ public function dateFilter($timestamp, $format = 'F j, Y H:i')
706
+ {
707
+ // do something different from the built-in date filter
708
+ }
709
+
710
+ public function getName()
711
+ {
712
+ return 'project';
713
+ }
714
+ }
715
+
716
+ $twig = new Twig_Environment($loader);
717
+ $twig->addExtension(new MyCoreExtension());
718
+
719
+ .. caution::
720
+
721
+ Note that overloading the built-in Twig elements is not recommended as it
722
+ might be confusing.
723
+
724
+ Testing an Extension
725
+ --------------------
726
+
727
+ Functional Tests
728
+ ~~~~~~~~~~~~~~~~
729
+
730
+ You can create functional tests for extensions simply by creating the
731
+ following file structure in your test directory::
732
+
733
+ Fixtures/
734
+ filters/
735
+ foo.test
736
+ bar.test
737
+ functions/
738
+ foo.test
739
+ bar.test
740
+ tags/
741
+ foo.test
742
+ bar.test
743
+ IntegrationTest.php
744
+
745
+ The ``IntegrationTest.php`` file should look like this::
746
+
747
+ class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
748
+ {
749
+ public function getExtensions()
750
+ {
751
+ return array(
752
+ new Project_Twig_Extension1(),
753
+ new Project_Twig_Extension2(),
754
+ );
755
+ }
756
+
757
+ public function getFixturesDir()
758
+ {
759
+ return dirname(__FILE__).'/Fixtures/';
760
+ }
761
+ }
762
+
763
+ Fixtures examples can be found within the Twig repository
764
+ `tests/Twig/Fixtures`_ directory.
765
+
766
+ Node Tests
767
+ ~~~~~~~~~~
768
+
769
+ Testing the node visitors can be complex, so extend your test cases from
770
+ ``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository
771
+ `tests/Twig/Node`_ directory.
772
+
773
+ .. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register
774
+ .. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
775
+ .. _`tests/Twig/Fixtures`: https://github.com/fabpot/Twig/tree/master/test/Twig/Tests/Fixtures
776
+ .. _`tests/Twig/Node`: https://github.com/fabpot/Twig/tree/master/test/Twig/Tests/Node
Twig/doc/advanced_legacy.rst ADDED
@@ -0,0 +1,887 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Extending Twig
2
+ ==============
3
+
4
+ .. caution::
5
+
6
+ This section describes how to extends Twig for versions **older than
7
+ 1.12**. If you are using a newer version, read the :doc:`newer<advanced>`
8
+ chapter instead.
9
+
10
+ Twig can be extended in many ways; you can add extra tags, filters, tests,
11
+ operators, global variables, and functions. You can even extend the parser
12
+ itself with node visitors.
13
+
14
+ .. note::
15
+
16
+ The first section of this chapter describes how to extend Twig easily. If
17
+ you want to reuse your changes in different projects or if you want to
18
+ share them with others, you should then create an extension as described
19
+ in the following section.
20
+
21
+ .. caution::
22
+
23
+ When extending Twig by calling methods on the Twig environment instance,
24
+ Twig won't be able to recompile your templates when the PHP code is
25
+ updated. To see your changes in real-time, either disable template caching
26
+ or package your code into an extension (see the next section of this
27
+ chapter).
28
+
29
+ Before extending Twig, you must understand the differences between all the
30
+ different possible extension points and when to use them.
31
+
32
+ First, remember that Twig has two main language constructs:
33
+
34
+ * ``{{ }}``: used to print the result of an expression evaluation;
35
+
36
+ * ``{% %}``: used to execute statements.
37
+
38
+ To understand why Twig exposes so many extension points, let's see how to
39
+ implement a *Lorem ipsum* generator (it needs to know the number of words to
40
+ generate).
41
+
42
+ You can use a ``lipsum`` *tag*:
43
+
44
+ .. code-block:: jinja
45
+
46
+ {% lipsum 40 %}
47
+
48
+ That works, but using a tag for ``lipsum`` is not a good idea for at least
49
+ three main reasons:
50
+
51
+ * ``lipsum`` is not a language construct;
52
+ * The tag outputs something;
53
+ * The tag is not flexible as you cannot use it in an expression:
54
+
55
+ .. code-block:: jinja
56
+
57
+ {{ 'some text' ~ {% lipsum 40 %} ~ 'some more text' }}
58
+
59
+ In fact, you rarely need to create tags; and that's good news because tags are
60
+ the most complex extension point of Twig.
61
+
62
+ Now, let's use a ``lipsum`` *filter*:
63
+
64
+ .. code-block:: jinja
65
+
66
+ {{ 40|lipsum }}
67
+
68
+ Again, it works, but it looks weird. A filter transforms the passed value to
69
+ something else but here we use the value to indicate the number of words to
70
+ generate (so, ``40`` is an argument of the filter, not the value we want to
71
+ transform).
72
+
73
+ Next, let's use a ``lipsum`` *function*:
74
+
75
+ .. code-block:: jinja
76
+
77
+ {{ lipsum(40) }}
78
+
79
+ Here we go. For this specific example, the creation of a function is the
80
+ extension point to use. And you can use it anywhere an expression is accepted:
81
+
82
+ .. code-block:: jinja
83
+
84
+ {{ 'some text' ~ ipsum(40) ~ 'some more text' }}
85
+
86
+ {% set ipsum = ipsum(40) %}
87
+
88
+ Last but not the least, you can also use a *global* object with a method able
89
+ to generate lorem ipsum text:
90
+
91
+ .. code-block:: jinja
92
+
93
+ {{ text.lipsum(40) }}
94
+
95
+ As a rule of thumb, use functions for frequently used features and global
96
+ objects for everything else.
97
+
98
+ Keep in mind the following when you want to extend Twig:
99
+
100
+ ========== ========================== ========== =========================
101
+ What? Implementation difficulty? How often? When?
102
+ ========== ========================== ========== =========================
103
+ *macro* trivial frequent Content generation
104
+ *global* trivial frequent Helper object
105
+ *function* trivial frequent Content generation
106
+ *filter* trivial frequent Value transformation
107
+ *tag* complex rare DSL language construct
108
+ *test* trivial rare Boolean decision
109
+ *operator* trivial rare Values transformation
110
+ ========== ========================== ========== =========================
111
+
112
+ Globals
113
+ -------
114
+
115
+ A global variable is like any other template variable, except that it's
116
+ available in all templates and macros::
117
+
118
+ $twig = new Twig_Environment($loader);
119
+ $twig->addGlobal('text', new Text());
120
+
121
+ You can then use the ``text`` variable anywhere in a template:
122
+
123
+ .. code-block:: jinja
124
+
125
+ {{ text.lipsum(40) }}
126
+
127
+ Filters
128
+ -------
129
+
130
+ A filter is a regular PHP function or an object method that takes the left
131
+ side of the filter (before the pipe ``|``) as first argument and the extra
132
+ arguments passed to the filter (within parentheses ``()``) as extra arguments.
133
+
134
+ Defining a filter is as easy as associating the filter name with a PHP
135
+ callable. For instance, let's say you have the following code in a template:
136
+
137
+ .. code-block:: jinja
138
+
139
+ {{ 'TWIG'|lower }}
140
+
141
+ When compiling this template to PHP, Twig looks for the PHP callable
142
+ associated with the ``lower`` filter. The ``lower`` filter is a built-in Twig
143
+ filter, and it is simply mapped to the PHP ``strtolower()`` function. After
144
+ compilation, the generated PHP code is roughly equivalent to:
145
+
146
+ .. code-block:: html+php
147
+
148
+ <?php echo strtolower('TWIG') ?>
149
+
150
+ As you can see, the ``'TWIG'`` string is passed as a first argument to the PHP
151
+ function.
152
+
153
+ A filter can also take extra arguments like in the following example:
154
+
155
+ .. code-block:: jinja
156
+
157
+ {{ now|date('d/m/Y') }}
158
+
159
+ In this case, the extra arguments are passed to the function after the main
160
+ argument, and the compiled code is equivalent to:
161
+
162
+ .. code-block:: html+php
163
+
164
+ <?php echo twig_date_format_filter($now, 'd/m/Y') ?>
165
+
166
+ Let's see how to create a new filter.
167
+
168
+ In this section, we will create a ``rot13`` filter, which should return the
169
+ `rot13`_ transformation of a string. Here is an example of its usage and the
170
+ expected output:
171
+
172
+ .. code-block:: jinja
173
+
174
+ {{ "Twig"|rot13 }}
175
+
176
+ {# should displays Gjvt #}
177
+
178
+ Adding a filter is as simple as calling the ``addFilter()`` method on the
179
+ ``Twig_Environment`` instance::
180
+
181
+ $twig = new Twig_Environment($loader);
182
+ $twig->addFilter('rot13', new Twig_Filter_Function('str_rot13'));
183
+
184
+ The second argument of ``addFilter()`` is an instance of ``Twig_Filter``.
185
+ Here, we use ``Twig_Filter_Function`` as the filter is a PHP function. The
186
+ first argument passed to the ``Twig_Filter_Function`` constructor is the name
187
+ of the PHP function to call, here ``str_rot13``, a native PHP function.
188
+
189
+ Let's say I now want to be able to add a prefix before the converted string:
190
+
191
+ .. code-block:: jinja
192
+
193
+ {{ "Twig"|rot13('prefix_') }}
194
+
195
+ {# should displays prefix_Gjvt #}
196
+
197
+ As the PHP ``str_rot13()`` function does not support this requirement, let's
198
+ create a new PHP function::
199
+
200
+ function project_compute_rot13($string, $prefix = '')
201
+ {
202
+ return $prefix.str_rot13($string);
203
+ }
204
+
205
+ As you can see, the ``prefix`` argument of the filter is passed as an extra
206
+ argument to the ``project_compute_rot13()`` function.
207
+
208
+ Adding this filter is as easy as before::
209
+
210
+ $twig->addFilter('rot13', new Twig_Filter_Function('project_compute_rot13'));
211
+
212
+ For better encapsulation, a filter can also be defined as a static method of a
213
+ class. The ``Twig_Filter_Function`` class can also be used to register such
214
+ static methods as filters::
215
+
216
+ $twig->addFilter('rot13', new Twig_Filter_Function('SomeClass::rot13Filter'));
217
+
218
+ .. tip::
219
+
220
+ In an extension, you can also define a filter as a static method of the
221
+ extension class.
222
+
223
+ Environment aware Filters
224
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
225
+
226
+ The ``Twig_Filter`` classes take options as their last argument. For instance,
227
+ if you want access to the current environment instance in your filter, set the
228
+ ``needs_environment`` option to ``true``::
229
+
230
+ $filter = new Twig_Filter_Function('str_rot13', array('needs_environment' => true));
231
+
232
+ Twig will then pass the current environment as the first argument to the
233
+ filter call::
234
+
235
+ function twig_compute_rot13(Twig_Environment $env, $string)
236
+ {
237
+ // get the current charset for instance
238
+ $charset = $env->getCharset();
239
+
240
+ return str_rot13($string);
241
+ }
242
+
243
+ Automatic Escaping
244
+ ~~~~~~~~~~~~~~~~~~
245
+
246
+ If automatic escaping is enabled, the output of the filter may be escaped
247
+ before printing. If your filter acts as an escaper (or explicitly outputs html
248
+ or javascript code), you will want the raw output to be printed. In such a
249
+ case, set the ``is_safe`` option::
250
+
251
+ $filter = new Twig_Filter_Function('nl2br', array('is_safe' => array('html')));
252
+
253
+ Some filters may need to work on input that is already escaped or safe, for
254
+ example when adding (safe) html tags to originally unsafe output. In such a
255
+ case, set the ``pre_escape`` option to escape the input data before it is run
256
+ through your filter::
257
+
258
+ $filter = new Twig_Filter_Function('somefilter', array('pre_escape' => 'html', 'is_safe' => array('html')));
259
+
260
+ Dynamic Filters
261
+ ~~~~~~~~~~~~~~~
262
+
263
+ .. versionadded:: 1.5
264
+ Dynamic filters support was added in Twig 1.5.
265
+
266
+ A filter name containing the special ``*`` character is a dynamic filter as
267
+ the ``*`` can be any string::
268
+
269
+ $twig->addFilter('*_path', new Twig_Filter_Function('twig_path'));
270
+
271
+ function twig_path($name, $arguments)
272
+ {
273
+ // ...
274
+ }
275
+
276
+ The following filters will be matched by the above defined dynamic filter:
277
+
278
+ * ``product_path``
279
+ * ``category_path``
280
+
281
+ A dynamic filter can define more than one dynamic parts::
282
+
283
+ $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
284
+
285
+ function twig_path($name, $suffix, $arguments)
286
+ {
287
+ // ...
288
+ }
289
+
290
+ The filter will receive all dynamic part values before the normal filters
291
+ arguments. For instance, a call to ``'foo'|a_path_b()`` will result in the
292
+ following PHP call: ``twig_path('a', 'b', 'foo')``.
293
+
294
+ Functions
295
+ ---------
296
+
297
+ A function is a regular PHP function or an object method that can be called from
298
+ templates.
299
+
300
+ .. code-block:: jinja
301
+
302
+ {{ constant("DATE_W3C") }}
303
+
304
+ When compiling this template to PHP, Twig looks for the PHP callable
305
+ associated with the ``constant`` function. The ``constant`` function is a built-in Twig
306
+ function, and it is simply mapped to the PHP ``constant()`` function. After
307
+ compilation, the generated PHP code is roughly equivalent to:
308
+
309
+ .. code-block:: html+php
310
+
311
+ <?php echo constant('DATE_W3C') ?>
312
+
313
+ Adding a function is similar to adding a filter. This can be done by calling the
314
+ ``addFunction()`` method on the ``Twig_Environment`` instance::
315
+
316
+ $twig = new Twig_Environment($loader);
317
+ $twig->addFunction('functionName', new Twig_Function_Function('someFunction'));
318
+
319
+ You can also expose extension methods as functions in your templates::
320
+
321
+ // $this is an object that implements Twig_ExtensionInterface.
322
+ $twig = new Twig_Environment($loader);
323
+ $twig->addFunction('otherFunction', new Twig_Function_Method($this, 'someMethod'));
324
+
325
+ Functions also support ``needs_environment`` and ``is_safe`` parameters.
326
+
327
+ Dynamic Functions
328
+ ~~~~~~~~~~~~~~~~~
329
+
330
+ .. versionadded:: 1.5
331
+ Dynamic functions support was added in Twig 1.5.
332
+
333
+ A function name containing the special ``*`` character is a dynamic function
334
+ as the ``*`` can be any string::
335
+
336
+ $twig->addFunction('*_path', new Twig_Function_Function('twig_path'));
337
+
338
+ function twig_path($name, $arguments)
339
+ {
340
+ // ...
341
+ }
342
+
343
+ The following functions will be matched by the above defined dynamic function:
344
+
345
+ * ``product_path``
346
+ * ``category_path``
347
+
348
+ A dynamic function can define more than one dynamic parts::
349
+
350
+ $twig->addFilter('*_path_*', new Twig_Filter_Function('twig_path'));
351
+
352
+ function twig_path($name, $suffix, $arguments)
353
+ {
354
+ // ...
355
+ }
356
+
357
+ The function will receive all dynamic part values before the normal functions
358
+ arguments. For instance, a call to ``a_path_b('foo')`` will result in the
359
+ following PHP call: ``twig_path('a', 'b', 'foo')``.
360
+
361
+ Tags
362
+ ----
363
+
364
+ One of the most exciting feature of a template engine like Twig is the
365
+ possibility to define new language constructs. This is also the most complex
366
+ feature as you need to understand how Twig's internals work.
367
+
368
+ Let's create a simple ``set`` tag that allows the definition of simple
369
+ variables from within a template. The tag can be used like follows:
370
+
371
+ .. code-block:: jinja
372
+
373
+ {% set name = "value" %}
374
+
375
+ {{ name }}
376
+
377
+ {# should output value #}
378
+
379
+ .. note::
380
+
381
+ The ``set`` tag is part of the Core extension and as such is always
382
+ available. The built-in version is slightly more powerful and supports
383
+ multiple assignments by default (cf. the template designers chapter for
384
+ more information).
385
+
386
+ Three steps are needed to define a new tag:
387
+
388
+ * Defining a Token Parser class (responsible for parsing the template code);
389
+
390
+ * Defining a Node class (responsible for converting the parsed code to PHP);
391
+
392
+ * Registering the tag.
393
+
394
+ Registering a new tag
395
+ ~~~~~~~~~~~~~~~~~~~~~
396
+
397
+ Adding a tag is as simple as calling the ``addTokenParser`` method on the
398
+ ``Twig_Environment`` instance::
399
+
400
+ $twig = new Twig_Environment($loader);
401
+ $twig->addTokenParser(new Project_Set_TokenParser());
402
+
403
+ Defining a Token Parser
404
+ ~~~~~~~~~~~~~~~~~~~~~~~
405
+
406
+ Now, let's see the actual code of this class::
407
+
408
+ class Project_Set_TokenParser extends Twig_TokenParser
409
+ {
410
+ public function parse(Twig_Token $token)
411
+ {
412
+ $lineno = $token->getLine();
413
+ $name = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue();
414
+ $this->parser->getStream()->expect(Twig_Token::OPERATOR_TYPE, '=');
415
+ $value = $this->parser->getExpressionParser()->parseExpression();
416
+
417
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
418
+
419
+ return new Project_Set_Node($name, $value, $lineno, $this->getTag());
420
+ }
421
+
422
+ public function getTag()
423
+ {
424
+ return 'set';
425
+ }
426
+ }
427
+
428
+ The ``getTag()`` method must return the tag we want to parse, here ``set``.
429
+
430
+ The ``parse()`` method is invoked whenever the parser encounters a ``set``
431
+ tag. It should return a ``Twig_Node`` instance that represents the node (the
432
+ ``Project_Set_Node`` calls creating is explained in the next section).
433
+
434
+ The parsing process is simplified thanks to a bunch of methods you can call
435
+ from the token stream (``$this->parser->getStream()``):
436
+
437
+ * ``getCurrent()``: Gets the current token in the stream.
438
+
439
+ * ``next()``: Moves to the next token in the stream, *but returns the old one*.
440
+
441
+ * ``test($type)``, ``test($value)`` or ``test($type, $value)``: Determines whether
442
+ the current token is of a particular type or value (or both). The value may be an
443
+ array of several possible values.
444
+
445
+ * ``expect($type[, $value[, $message]])``: If the current token isn't of the given
446
+ type/value a syntax error is thrown. Otherwise, if the type and value are correct,
447
+ the token is returned and the stream moves to the next token.
448
+
449
+ * ``look()``: Looks a the next token without consuming it.
450
+
451
+ Parsing expressions is done by calling the ``parseExpression()`` like we did for
452
+ the ``set`` tag.
453
+
454
+ .. tip::
455
+
456
+ Reading the existing ``TokenParser`` classes is the best way to learn all
457
+ the nitty-gritty details of the parsing process.
458
+
459
+ Defining a Node
460
+ ~~~~~~~~~~~~~~~
461
+
462
+ The ``Project_Set_Node`` class itself is rather simple::
463
+
464
+ class Project_Set_Node extends Twig_Node
465
+ {
466
+ public function __construct($name, Twig_Node_Expression $value, $lineno, $tag = null)
467
+ {
468
+ parent::__construct(array('value' => $value), array('name' => $name), $lineno, $tag);
469
+ }
470
+
471
+ public function compile(Twig_Compiler $compiler)
472
+ {
473
+ $compiler
474
+ ->addDebugInfo($this)
475
+ ->write('$context[\''.$this->getAttribute('name').'\'] = ')
476
+ ->subcompile($this->getNode('value'))
477
+ ->raw(";\n")
478
+ ;
479
+ }
480
+ }
481
+
482
+ The compiler implements a fluid interface and provides methods that helps the
483
+ developer generate beautiful and readable PHP code:
484
+
485
+ * ``subcompile()``: Compiles a node.
486
+
487
+ * ``raw()``: Writes the given string as is.
488
+
489
+ * ``write()``: Writes the given string by adding indentation at the beginning
490
+ of each line.
491
+
492
+ * ``string()``: Writes a quoted string.
493
+
494
+ * ``repr()``: Writes a PHP representation of a given value (see
495
+ ``Twig_Node_For`` for a usage example).
496
+
497
+ * ``addDebugInfo()``: Adds the line of the original template file related to
498
+ the current node as a comment.
499
+
500
+ * ``indent()``: Indents the generated code (see ``Twig_Node_Block`` for a
501
+ usage example).
502
+
503
+ * ``outdent()``: Outdents the generated code (see ``Twig_Node_Block`` for a
504
+ usage example).
505
+
506
+ .. _creating_extensions:
507
+
508
+ Creating an Extension
509
+ ---------------------
510
+
511
+ The main motivation for writing an extension is to move often used code into a
512
+ reusable class like adding support for internationalization. An extension can
513
+ define tags, filters, tests, operators, global variables, functions, and node
514
+ visitors.
515
+
516
+ Creating an extension also makes for a better separation of code that is
517
+ executed at compilation time and code needed at runtime. As such, it makes
518
+ your code faster.
519
+
520
+ Most of the time, it is useful to create a single extension for your project,
521
+ to host all the specific tags and filters you want to add to Twig.
522
+
523
+ .. tip::
524
+
525
+ When packaging your code into an extension, Twig is smart enough to
526
+ recompile your templates whenever you make a change to it (when the
527
+ ``auto_reload`` is enabled).
528
+
529
+ .. note::
530
+
531
+ Before writing your own extensions, have a look at the Twig official
532
+ extension repository: http://github.com/fabpot/Twig-extensions.
533
+
534
+ An extension is a class that implements the following interface::
535
+
536
+ interface Twig_ExtensionInterface
537
+ {
538
+ /**
539
+ * Initializes the runtime environment.
540
+ *
541
+ * This is where you can load some file that contains filter functions for instance.
542
+ *
543
+ * @param Twig_Environment $environment The current Twig_Environment instance
544
+ */
545
+ function initRuntime(Twig_Environment $environment);
546
+
547
+ /**
548
+ * Returns the token parser instances to add to the existing list.
549
+ *
550
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
551
+ */
552
+ function getTokenParsers();
553
+
554
+ /**
555
+ * Returns the node visitor instances to add to the existing list.
556
+ *
557
+ * @return array An array of Twig_NodeVisitorInterface instances
558
+ */
559
+ function getNodeVisitors();
560
+
561
+ /**
562
+ * Returns a list of filters to add to the existing list.
563
+ *
564
+ * @return array An array of filters
565
+ */
566
+ function getFilters();
567
+
568
+ /**
569
+ * Returns a list of tests to add to the existing list.
570
+ *
571
+ * @return array An array of tests
572
+ */
573
+ function getTests();
574
+
575
+ /**
576
+ * Returns a list of functions to add to the existing list.
577
+ *
578
+ * @return array An array of functions
579
+ */
580
+ function getFunctions();
581
+
582
+ /**
583
+ * Returns a list of operators to add to the existing list.
584
+ *
585
+ * @return array An array of operators
586
+ */
587
+ function getOperators();
588
+
589
+ /**
590
+ * Returns a list of global variables to add to the existing list.
591
+ *
592
+ * @return array An array of global variables
593
+ */
594
+ function getGlobals();
595
+
596
+ /**
597
+ * Returns the name of the extension.
598
+ *
599
+ * @return string The extension name
600
+ */
601
+ function getName();
602
+ }
603
+
604
+ To keep your extension class clean and lean, it can inherit from the built-in
605
+ ``Twig_Extension`` class instead of implementing the whole interface. That
606
+ way, you just need to implement the ``getName()`` method as the
607
+ ``Twig_Extension`` provides empty implementations for all other methods.
608
+
609
+ The ``getName()`` method must return a unique identifier for your extension.
610
+
611
+ Now, with this information in mind, let's create the most basic extension
612
+ possible::
613
+
614
+ class Project_Twig_Extension extends Twig_Extension
615
+ {
616
+ public function getName()
617
+ {
618
+ return 'project';
619
+ }
620
+ }
621
+
622
+ .. note::
623
+
624
+ Of course, this extension does nothing for now. We will customize it in
625
+ the next sections.
626
+
627
+ Twig does not care where you save your extension on the filesystem, as all
628
+ extensions must be registered explicitly to be available in your templates.
629
+
630
+ You can register an extension by using the ``addExtension()`` method on your
631
+ main ``Environment`` object::
632
+
633
+ $twig = new Twig_Environment($loader);
634
+ $twig->addExtension(new Project_Twig_Extension());
635
+
636
+ Of course, you need to first load the extension file by either using
637
+ ``require_once()`` or by using an autoloader (see `spl_autoload_register()`_).
638
+
639
+ .. tip::
640
+
641
+ The bundled extensions are great examples of how extensions work.
642
+
643
+ Globals
644
+ ~~~~~~~
645
+
646
+ Global variables can be registered in an extension via the ``getGlobals()``
647
+ method::
648
+
649
+ class Project_Twig_Extension extends Twig_Extension
650
+ {
651
+ public function getGlobals()
652
+ {
653
+ return array(
654
+ 'text' => new Text(),
655
+ );
656
+ }
657
+
658
+ // ...
659
+ }
660
+
661
+ Functions
662
+ ~~~~~~~~~
663
+
664
+ Functions can be registered in an extension via the ``getFunctions()``
665
+ method::
666
+
667
+ class Project_Twig_Extension extends Twig_Extension
668
+ {
669
+ public function getFunctions()
670
+ {
671
+ return array(
672
+ 'lipsum' => new Twig_Function_Function('generate_lipsum'),
673
+ );
674
+ }
675
+
676
+ // ...
677
+ }
678
+
679
+ Filters
680
+ ~~~~~~~
681
+
682
+ To add a filter to an extension, you need to override the ``getFilters()``
683
+ method. This method must return an array of filters to add to the Twig
684
+ environment::
685
+
686
+ class Project_Twig_Extension extends Twig_Extension
687
+ {
688
+ public function getFilters()
689
+ {
690
+ return array(
691
+ 'rot13' => new Twig_Filter_Function('str_rot13'),
692
+ );
693
+ }
694
+
695
+ // ...
696
+ }
697
+
698
+ As you can see in the above code, the ``getFilters()`` method returns an array
699
+ where keys are the name of the filters (``rot13``) and the values the
700
+ definition of the filter (``new Twig_Filter_Function('str_rot13')``).
701
+
702
+ As seen in the previous chapter, you can also define filters as static methods
703
+ on the extension class::
704
+
705
+ $twig->addFilter('rot13', new Twig_Filter_Function('Project_Twig_Extension::rot13Filter'));
706
+
707
+ You can also use ``Twig_Filter_Method`` instead of ``Twig_Filter_Function``
708
+ when defining a filter to use a method::
709
+
710
+ class Project_Twig_Extension extends Twig_Extension
711
+ {
712
+ public function getFilters()
713
+ {
714
+ return array(
715
+ 'rot13' => new Twig_Filter_Method($this, 'rot13Filter'),
716
+ );
717
+ }
718
+
719
+ public function rot13Filter($string)
720
+ {
721
+ return str_rot13($string);
722
+ }
723
+
724
+ // ...
725
+ }
726
+
727
+ The first argument of the ``Twig_Filter_Method`` constructor is always
728
+ ``$this``, the current extension object. The second one is the name of the
729
+ method to call.
730
+
731
+ Using methods for filters is a great way to package your filter without
732
+ polluting the global namespace. This also gives the developer more flexibility
733
+ at the cost of a small overhead.
734
+
735
+ Overriding default Filters
736
+ ..........................
737
+
738
+ If some default core filters do not suit your needs, you can easily override
739
+ them by creating your own extension. Just use the same names as the one you
740
+ want to override::
741
+
742
+ class MyCoreExtension extends Twig_Extension
743
+ {
744
+ public function getFilters()
745
+ {
746
+ return array(
747
+ 'date' => new Twig_Filter_Method($this, 'dateFilter'),
748
+ // ...
749
+ );
750
+ }
751
+
752
+ public function dateFilter($timestamp, $format = 'F j, Y H:i')
753
+ {
754
+ return '...'.twig_date_format_filter($timestamp, $format);
755
+ }
756
+
757
+ public function getName()
758
+ {
759
+ return 'project';
760
+ }
761
+ }
762
+
763
+ Here, we override the ``date`` filter with a custom one. Using this extension
764
+ is as simple as registering the ``MyCoreExtension`` extension by calling the
765
+ ``addExtension()`` method on the environment instance::
766
+
767
+ $twig = new Twig_Environment($loader);
768
+ $twig->addExtension(new MyCoreExtension());
769
+
770
+ Tags
771
+ ~~~~
772
+
773
+ Adding a tag in an extension can be done by overriding the
774
+ ``getTokenParsers()`` method. This method must return an array of tags to add
775
+ to the Twig environment::
776
+
777
+ class Project_Twig_Extension extends Twig_Extension
778
+ {
779
+ public function getTokenParsers()
780
+ {
781
+ return array(new Project_Set_TokenParser());
782
+ }
783
+
784
+ // ...
785
+ }
786
+
787
+ In the above code, we have added a single new tag, defined by the
788
+ ``Project_Set_TokenParser`` class. The ``Project_Set_TokenParser`` class is
789
+ responsible for parsing the tag and compiling it to PHP.
790
+
791
+ Operators
792
+ ~~~~~~~~~
793
+
794
+ The ``getOperators()`` methods allows to add new operators. Here is how to add
795
+ ``!``, ``||``, and ``&&`` operators::
796
+
797
+ class Project_Twig_Extension extends Twig_Extension
798
+ {
799
+ public function getOperators()
800
+ {
801
+ return array(
802
+ array(
803
+ '!' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
804
+ ),
805
+ array(
806
+ '||' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
807
+ '&&' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
808
+ ),
809
+ );
810
+ }
811
+
812
+ // ...
813
+ }
814
+
815
+ Tests
816
+ ~~~~~
817
+
818
+ The ``getTests()`` methods allows to add new test functions::
819
+
820
+ class Project_Twig_Extension extends Twig_Extension
821
+ {
822
+ public function getTests()
823
+ {
824
+ return array(
825
+ 'even' => new Twig_Test_Function('twig_test_even'),
826
+ );
827
+ }
828
+
829
+ // ...
830
+ }
831
+
832
+ Testing an Extension
833
+ --------------------
834
+
835
+ .. versionadded:: 1.10
836
+ Support for functional tests was added in Twig 1.10.
837
+
838
+ Functional Tests
839
+ ~~~~~~~~~~~~~~~~
840
+
841
+ You can create functional tests for extensions simply by creating the
842
+ following file structure in your test directory::
843
+
844
+ Fixtures/
845
+ filters/
846
+ foo.test
847
+ bar.test
848
+ functions/
849
+ foo.test
850
+ bar.test
851
+ tags/
852
+ foo.test
853
+ bar.test
854
+ IntegrationTest.php
855
+
856
+ The ``IntegrationTest.php`` file should look like this::
857
+
858
+ class Project_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
859
+ {
860
+ public function getExtensions()
861
+ {
862
+ return array(
863
+ new Project_Twig_Extension1(),
864
+ new Project_Twig_Extension2(),
865
+ );
866
+ }
867
+
868
+ public function getFixturesDir()
869
+ {
870
+ return dirname(__FILE__).'/Fixtures/';
871
+ }
872
+ }
873
+
874
+ Fixtures examples can be found within the Twig repository
875
+ `tests/Twig/Fixtures`_ directory.
876
+
877
+ Node Tests
878
+ ~~~~~~~~~~
879
+
880
+ Testing the node visitors can be complex, so extend your test cases from
881
+ ``Twig_Test_NodeTestCase``. Examples can be found in the Twig repository
882
+ `tests/Twig/Node`_ directory.
883
+
884
+ .. _`spl_autoload_register()`: http://www.php.net/spl_autoload_register
885
+ .. _`rot13`: http://www.php.net/manual/en/function.str-rot13.php
886
+ .. _`tests/Twig/Fixtures`: https://github.com/fabpot/Twig/tree/master/test/Twig/Tests/Fixtures
887
+ .. _`tests/Twig/Node`: https://github.com/fabpot/Twig/tree/master/test/Twig/Tests/Node
Twig/doc/api.rst ADDED
@@ -0,0 +1,585 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Twig for Developers
2
+ ===================
3
+
4
+ This chapter describes the API to Twig and not the template language. It will
5
+ be most useful as reference to those implementing the template interface to
6
+ the application and not those who are creating Twig templates.
7
+
8
+ Basics
9
+ ------
10
+
11
+ Twig uses a central object called the **environment** (of class
12
+ ``Twig_Environment``). Instances of this class are used to store the
13
+ configuration and extensions, and are used to load templates from the file
14
+ system or other locations.
15
+
16
+ Most applications will create one ``Twig_Environment`` object on application
17
+ initialization and use that to load templates. In some cases it's however
18
+ useful to have multiple environments side by side, if different configurations
19
+ are in use.
20
+
21
+ The simplest way to configure Twig to load templates for your application
22
+ looks roughly like this::
23
+
24
+ require_once '/path/to/lib/Twig/Autoloader.php';
25
+ Twig_Autoloader::register();
26
+
27
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
28
+ $twig = new Twig_Environment($loader, array(
29
+ 'cache' => '/path/to/compilation_cache',
30
+ ));
31
+
32
+ This will create a template environment with the default settings and a loader
33
+ that looks up the templates in the ``/path/to/templates/`` folder. Different
34
+ loaders are available and you can also write your own if you want to load
35
+ templates from a database or other resources.
36
+
37
+ .. note::
38
+
39
+ Notice that the second argument of the environment is an array of options.
40
+ The ``cache`` option is a compilation cache directory, where Twig caches
41
+ the compiled templates to avoid the parsing phase for sub-sequent
42
+ requests. It is very different from the cache you might want to add for
43
+ the evaluated templates. For such a need, you can use any available PHP
44
+ cache library.
45
+
46
+ To load a template from this environment you just have to call the
47
+ ``loadTemplate()`` method which then returns a ``Twig_Template`` instance::
48
+
49
+ $template = $twig->loadTemplate('index.html');
50
+
51
+ To render the template with some variables, call the ``render()`` method::
52
+
53
+ echo $template->render(array('the' => 'variables', 'go' => 'here'));
54
+
55
+ .. note::
56
+
57
+ The ``display()`` method is a shortcut to output the template directly.
58
+
59
+ You can also load and render the template in one fell swoop::
60
+
61
+ echo $twig->render('index.html', array('the' => 'variables', 'go' => 'here'));
62
+
63
+ .. _environment_options:
64
+
65
+ Environment Options
66
+ -------------------
67
+
68
+ When creating a new ``Twig_Environment`` instance, you can pass an array of
69
+ options as the constructor second argument::
70
+
71
+ $twig = new Twig_Environment($loader, array('debug' => true));
72
+
73
+ The following options are available:
74
+
75
+ * ``debug``: When set to ``true``, the generated templates have a
76
+ ``__toString()`` method that you can use to display the generated nodes
77
+ (default to ``false``).
78
+
79
+ * ``charset``: The charset used by the templates (default to ``utf-8``).
80
+
81
+ * ``base_template_class``: The base template class to use for generated
82
+ templates (default to ``Twig_Template``).
83
+
84
+ * ``cache``: An absolute path where to store the compiled templates, or
85
+ ``false`` to disable caching (which is the default).
86
+
87
+ * ``auto_reload``: When developing with Twig, it's useful to recompile the
88
+ template whenever the source code changes. If you don't provide a value for
89
+ the ``auto_reload`` option, it will be determined automatically based on the
90
+ ``debug`` value.
91
+
92
+ * ``strict_variables``: If set to ``false``, Twig will silently ignore invalid
93
+ variables (variables and or attributes/methods that do not exist) and
94
+ replace them with a ``null`` value. When set to ``true``, Twig throws an
95
+ exception instead (default to ``false``).
96
+
97
+ * ``autoescape``: If set to ``true``, auto-escaping will be enabled by default
98
+ for all templates (default to ``true``). As of Twig 1.8, you can set the
99
+ escaping strategy to use (``html``, ``js``, ``false`` to disable).
100
+ As of Twig 1.9, you can set the escaping strategy to use (``css``, ``url``,
101
+ ``html_attr``, or a PHP callback that takes the template "filename" and must
102
+ return the escaping strategy to use -- the callback cannot be a function name
103
+ to avoid collision with built-in escaping strategies).
104
+
105
+ * ``optimizations``: A flag that indicates which optimizations to apply
106
+ (default to ``-1`` -- all optimizations are enabled; set it to ``0`` to
107
+ disable).
108
+
109
+ Loaders
110
+ -------
111
+
112
+ Loaders are responsible for loading templates from a resource such as the file
113
+ system.
114
+
115
+ Compilation Cache
116
+ ~~~~~~~~~~~~~~~~~
117
+
118
+ All template loaders can cache the compiled templates on the filesystem for
119
+ future reuse. It speeds up Twig a lot as templates are only compiled once; and
120
+ the performance boost is even larger if you use a PHP accelerator such as APC.
121
+ See the ``cache`` and ``auto_reload`` options of ``Twig_Environment`` above
122
+ for more information.
123
+
124
+ Built-in Loaders
125
+ ~~~~~~~~~~~~~~~~
126
+
127
+ Here is a list of the built-in loaders Twig provides:
128
+
129
+ ``Twig_Loader_Filesystem``
130
+ ..........................
131
+
132
+ .. versionadded:: 1.10
133
+ The ``prependPath()`` and support for namespaces were added in Twig 1.10.
134
+
135
+ ``Twig_Loader_Filesystem`` loads templates from the file system. This loader
136
+ can find templates in folders on the file system and is the preferred way to
137
+ load them::
138
+
139
+ $loader = new Twig_Loader_Filesystem($templateDir);
140
+
141
+ It can also look for templates in an array of directories::
142
+
143
+ $loader = new Twig_Loader_Filesystem(array($templateDir1, $templateDir2));
144
+
145
+ With such a configuration, Twig will first look for templates in
146
+ ``$templateDir1`` and if they do not exist, it will fallback to look for them
147
+ in the ``$templateDir2``.
148
+
149
+ You can add or prepend paths via the ``addPath()`` and ``prependPath()``
150
+ methods::
151
+
152
+ $loader->addPath($templateDir3);
153
+ $loader->prependPath($templateDir4);
154
+
155
+ The filesystem loader also supports namespaced templates. This allows to group
156
+ your templates under different namespaces which have their own template paths.
157
+
158
+ When using the ``setPaths()``, ``addPath()``, and ``prependPath()`` methods,
159
+ specify the namespace as the second argument (when not specified, these
160
+ methods act on the "main" namespace)::
161
+
162
+ $loader->addPath($templateDir, 'admin');
163
+
164
+ Namespaced templates can be accessed via the special
165
+ ``@namespace_name/template_path`` notation::
166
+
167
+ $twig->render('@admin/index.html', array());
168
+
169
+ ``Twig_Loader_String``
170
+ ......................
171
+
172
+ ``Twig_Loader_String`` loads templates from strings. It's a dummy loader as
173
+ the template reference is the template source code::
174
+
175
+ $loader = new Twig_Loader_String();
176
+ $twig = new Twig_Environment($loader);
177
+
178
+ echo $twig->render('Hello {{ name }}!', array('name' => 'Fabien'));
179
+
180
+ This loader should only be used for unit testing as it has severe limitations:
181
+ several tags, like ``extends`` or ``include`` do not make sense to use as the
182
+ reference to the template is the template source code itself.
183
+
184
+ ``Twig_Loader_Array``
185
+ .....................
186
+
187
+ ``Twig_Loader_Array`` loads a template from a PHP array. It's passed an array
188
+ of strings bound to template names::
189
+
190
+ $loader = new Twig_Loader_Array(array(
191
+ 'index.html' => 'Hello {{ name }}!',
192
+ ));
193
+ $twig = new Twig_Environment($loader);
194
+
195
+ echo $twig->render('index.html', array('name' => 'Fabien'));
196
+
197
+ This loader is very useful for unit testing. It can also be used for small
198
+ projects where storing all templates in a single PHP file might make sense.
199
+
200
+ .. tip::
201
+
202
+ When using the ``Array`` or ``String`` loaders with a cache mechanism, you
203
+ should know that a new cache key is generated each time a template content
204
+ "changes" (the cache key being the source code of the template). If you
205
+ don't want to see your cache grows out of control, you need to take care
206
+ of clearing the old cache file by yourself.
207
+
208
+ ``Twig_Loader_Chain``
209
+ .....................
210
+
211
+ ``Twig_Loader_Chain`` delegates the loading of templates to other loaders::
212
+
213
+ $loader1 = new Twig_Loader_Array(array(
214
+ 'base.html' => '{% block content %}{% endblock %}',
215
+ ));
216
+ $loader2 = new Twig_Loader_Array(array(
217
+ 'index.html' => '{% extends "base.twig" %}{% block content %}Hello {{ name }}{% endblock %}',
218
+ 'base.html' => 'Will never be loaded',
219
+ ));
220
+
221
+ $loader = new Twig_Loader_Chain(array($loader1, $loader2));
222
+
223
+ $twig = new Twig_Environment($loader);
224
+
225
+ When looking for a template, Twig will try each loader in turn and it will
226
+ return as soon as the template is found. When rendering the ``index.html``
227
+ template from the above example, Twig will load it with ``$loader2`` but the
228
+ ``base.html`` template will be loaded from ``$loader1``.
229
+
230
+ ``Twig_Loader_Chain`` accepts any loader that implements
231
+ ``Twig_LoaderInterface``.
232
+
233
+ .. note::
234
+
235
+ You can also add loaders via the ``addLoader()`` method.
236
+
237
+ Create your own Loader
238
+ ~~~~~~~~~~~~~~~~~~~~~~
239
+
240
+ All loaders implement the ``Twig_LoaderInterface``::
241
+
242
+ interface Twig_LoaderInterface
243
+ {
244
+ /**
245
+ * Gets the source code of a template, given its name.
246
+ *
247
+ * @param string $name string The name of the template to load
248
+ *
249
+ * @return string The template source code
250
+ */
251
+ function getSource($name);
252
+
253
+ /**
254
+ * Gets the cache key to use for the cache for a given template name.
255
+ *
256
+ * @param string $name string The name of the template to load
257
+ *
258
+ * @return string The cache key
259
+ */
260
+ function getCacheKey($name);
261
+
262
+ /**
263
+ * Returns true if the template is still fresh.
264
+ *
265
+ * @param string $name The template name
266
+ * @param timestamp $time The last modification time of the cached template
267
+ */
268
+ function isFresh($name, $time);
269
+ }
270
+
271
+ As an example, here is how the built-in ``Twig_Loader_String`` reads::
272
+
273
+ class Twig_Loader_String implements Twig_LoaderInterface
274
+ {
275
+ public function getSource($name)
276
+ {
277
+ return $name;
278
+ }
279
+
280
+ public function getCacheKey($name)
281
+ {
282
+ return $name;
283
+ }
284
+
285
+ public function isFresh($name, $time)
286
+ {
287
+ return false;
288
+ }
289
+ }
290
+
291
+ The ``isFresh()`` method must return ``true`` if the current cached template
292
+ is still fresh, given the last modification time, or ``false`` otherwise.
293
+
294
+ .. tip::
295
+
296
+ As of Twig 1.11.0, you can also implement ``Twig_ExistsLoaderInterface``
297
+ to make your loader faster when used with the chain loader.
298
+
299
+ Using Extensions
300
+ ----------------
301
+
302
+ Twig extensions are packages that add new features to Twig. Using an
303
+ extension is as simple as using the ``addExtension()`` method::
304
+
305
+ $twig->addExtension(new Twig_Extension_Sandbox());
306
+
307
+ Twig comes bundled with the following extensions:
308
+
309
+ * *Twig_Extension_Core*: Defines all the core features of Twig.
310
+
311
+ * *Twig_Extension_Escaper*: Adds automatic output-escaping and the possibility
312
+ to escape/unescape blocks of code.
313
+
314
+ * *Twig_Extension_Sandbox*: Adds a sandbox mode to the default Twig
315
+ environment, making it safe to evaluated untrusted code.
316
+
317
+ * *Twig_Extension_Optimizer*: Optimizers the node tree before compilation.
318
+
319
+ The core, escaper, and optimizer extensions do not need to be added to the
320
+ Twig environment, as they are registered by default.
321
+
322
+ Built-in Extensions
323
+ -------------------
324
+
325
+ This section describes the features added by the built-in extensions.
326
+
327
+ .. tip::
328
+
329
+ Read the chapter about extending Twig to learn how to create your own
330
+ extensions.
331
+
332
+ Core Extension
333
+ ~~~~~~~~~~~~~~
334
+
335
+ The ``core`` extension defines all the core features of Twig:
336
+
337
+ * Tags:
338
+
339
+ * ``for``
340
+ * ``if``
341
+ * ``extends``
342
+ * ``include``
343
+ * ``block``
344
+ * ``filter``
345
+ * ``macro``
346
+ * ``import``
347
+ * ``from``
348
+ * ``set``
349
+ * ``spaceless``
350
+ * ``autoescape``
351
+ * ``do``
352
+ * ``embed``
353
+ * ``flush``
354
+ * ``verbatim``
355
+ * ``sandbox``
356
+ * ``use``
357
+
358
+ * Filters:
359
+
360
+ * ``date``
361
+ * ``format``
362
+ * ``replace``
363
+ * ``url_encode``
364
+ * ``json_encode``
365
+ * ``title``
366
+ * ``capitalize``
367
+ * ``upper``
368
+ * ``lower``
369
+ * ``striptags``
370
+ * ``join``
371
+ * ``reverse``
372
+ * ``length``
373
+ * ``sort``
374
+ * ``merge``
375
+ * ``default``
376
+ * ``keys``
377
+ * ``escape``
378
+ * ``e``
379
+ * ``abs``
380
+ * ``convert_encoding``
381
+ * ``date_modify``
382
+ * ``nl2br``
383
+ * ``number_format``
384
+ * ``raw``
385
+ * ``slice``
386
+ * ``trim``
387
+
388
+ * Functions:
389
+
390
+ * ``range``
391
+ * ``constant``
392
+ * ``cycle``
393
+ * ``parent``
394
+ * ``block``
395
+ * ``attribute``
396
+ * ``date``
397
+ * ``dump``
398
+ * ``random``
399
+
400
+ * Tests:
401
+
402
+ * ``even``
403
+ * ``odd``
404
+ * ``defined``
405
+ * ``sameas``
406
+ * ``null``
407
+ * ``divisibleby``
408
+ * ``constant``
409
+ * ``empty``
410
+ * ``iterable``
411
+
412
+ Escaper Extension
413
+ ~~~~~~~~~~~~~~~~~
414
+
415
+ The ``escaper`` extension adds automatic output escaping to Twig. It defines a
416
+ tag, ``autoescape``, and a filter, ``raw``.
417
+
418
+ When creating the escaper extension, you can switch on or off the global
419
+ output escaping strategy::
420
+
421
+ $escaper = new Twig_Extension_Escaper(true);
422
+ $twig->addExtension($escaper);
423
+
424
+ If set to ``true``, all variables in templates are escaped (using the ``html``
425
+ escaping strategy), except those using the ``raw`` filter:
426
+
427
+ .. code-block:: jinja
428
+
429
+ {{ article.to_html|raw }}
430
+
431
+ You can also change the escaping mode locally by using the ``autoescape`` tag
432
+ (see the :doc:`autoescape<tags/autoescape>` doc for the syntax used before
433
+ Twig 1.8):
434
+
435
+ .. code-block:: jinja
436
+
437
+ {% autoescape 'html' %}
438
+ {{ var }}
439
+ {{ var|raw }} {# var won't be escaped #}
440
+ {{ var|escape }} {# var won't be double-escaped #}
441
+ {% endautoescape %}
442
+
443
+ .. warning::
444
+
445
+ The ``autoescape`` tag has no effect on included files.
446
+
447
+ The escaping rules are implemented as follows:
448
+
449
+ * Literals (integers, booleans, arrays, ...) used in the template directly as
450
+ variables or filter arguments are never automatically escaped:
451
+
452
+ .. code-block:: jinja
453
+
454
+ {{ "Twig<br />" }} {# won't be escaped #}
455
+
456
+ {% set text = "Twig<br />" %}
457
+ {{ text }} {# will be escaped #}
458
+
459
+ * Expressions which the result is always a literal or a variable marked safe
460
+ are never automatically escaped:
461
+
462
+ .. code-block:: jinja
463
+
464
+ {{ foo ? "Twig<br />" : "<br />Twig" }} {# won't be escaped #}
465
+
466
+ {% set text = "Twig<br />" %}
467
+ {{ foo ? text : "<br />Twig" }} {# will be escaped #}
468
+
469
+ {% set text = "Twig<br />" %}
470
+ {{ foo ? text|raw : "<br />Twig" }} {# won't be escaped #}
471
+
472
+ {% set text = "Twig<br />" %}
473
+ {{ foo ? text|escape : "<br />Twig" }} {# the result of the expression won't be escaped #}
474
+
475
+ * Escaping is applied before printing, after any other filter is applied:
476
+
477
+ .. code-block:: jinja
478
+
479
+ {{ var|upper }} {# is equivalent to {{ var|upper|escape }} #}
480
+
481
+ * The `raw` filter should only be used at the end of the filter chain:
482
+
483
+ .. code-block:: jinja
484
+
485
+ {{ var|raw|upper }} {# will be escaped #}
486
+
487
+ {{ var|upper|raw }} {# won't be escaped #}
488
+
489
+ * Automatic escaping is not applied if the last filter in the chain is marked
490
+ safe for the current context (e.g. ``html`` or ``js``). ``escaper`` and
491
+ ``escaper('html')`` are marked safe for html, ``escaper('js')`` is marked
492
+ safe for javascript, ``raw`` is marked safe for everything.
493
+
494
+ .. code-block:: jinja
495
+
496
+ {% autoescape 'js' %}
497
+ {{ var|escape('html') }} {# will be escaped for html and javascript #}
498
+ {{ var }} {# will be escaped for javascript #}
499
+ {{ var|escape('js') }} {# won't be double-escaped #}
500
+ {% endautoescape %}
501
+
502
+ .. note::
503
+
504
+ Note that autoescaping has some limitations as escaping is applied on
505
+ expressions after evaluation. For instance, when working with
506
+ concatenation, ``{{ foo|raw ~ bar }}`` won't give the expected result as
507
+ escaping is applied on the result of the concatenation, not on the
508
+ individual variables (so, the ``raw`` filter won't have any effect here).
509
+
510
+ Sandbox Extension
511
+ ~~~~~~~~~~~~~~~~~
512
+
513
+ The ``sandbox`` extension can be used to evaluate untrusted code. Access to
514
+ unsafe attributes and methods is prohibited. The sandbox security is managed
515
+ by a policy instance. By default, Twig comes with one policy class:
516
+ ``Twig_Sandbox_SecurityPolicy``. This class allows you to white-list some
517
+ tags, filters, properties, and methods::
518
+
519
+ $tags = array('if');
520
+ $filters = array('upper');
521
+ $methods = array(
522
+ 'Article' => array('getTitle', 'getBody'),
523
+ );
524
+ $properties = array(
525
+ 'Article' => array('title', 'body'),
526
+ );
527
+ $functions = array('range');
528
+ $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
529
+
530
+ With the previous configuration, the security policy will only allow usage of
531
+ the ``if`` tag, and the ``upper`` filter. Moreover, the templates will only be
532
+ able to call the ``getTitle()`` and ``getBody()`` methods on ``Article``
533
+ objects, and the ``title`` and ``body`` public properties. Everything else
534
+ won't be allowed and will generate a ``Twig_Sandbox_SecurityError`` exception.
535
+
536
+ The policy object is the first argument of the sandbox constructor::
537
+
538
+ $sandbox = new Twig_Extension_Sandbox($policy);
539
+ $twig->addExtension($sandbox);
540
+
541
+ By default, the sandbox mode is disabled and should be enabled when including
542
+ untrusted template code by using the ``sandbox`` tag:
543
+
544
+ .. code-block:: jinja
545
+
546
+ {% sandbox %}
547
+ {% include 'user.html' %}
548
+ {% endsandbox %}
549
+
550
+ You can sandbox all templates by passing ``true`` as the second argument of
551
+ the extension constructor::
552
+
553
+ $sandbox = new Twig_Extension_Sandbox($policy, true);
554
+
555
+ Optimizer Extension
556
+ ~~~~~~~~~~~~~~~~~~~
557
+
558
+ The ``optimizer`` extension optimizes the node tree before compilation::
559
+
560
+ $twig->addExtension(new Twig_Extension_Optimizer());
561
+
562
+ By default, all optimizations are turned on. You can select the ones you want
563
+ to enable by passing them to the constructor::
564
+
565
+ $optimizer = new Twig_Extension_Optimizer(Twig_NodeVisitor_Optimizer::OPTIMIZE_FOR);
566
+
567
+ $twig->addExtension($optimizer);
568
+
569
+ Exceptions
570
+ ----------
571
+
572
+ Twig can throw exceptions:
573
+
574
+ * ``Twig_Error``: The base exception for all errors.
575
+
576
+ * ``Twig_Error_Syntax``: Thrown to tell the user that there is a problem with
577
+ the template syntax.
578
+
579
+ * ``Twig_Error_Runtime``: Thrown when an error occurs at runtime (when a filter
580
+ does not exist for instance).
581
+
582
+ * ``Twig_Error_Loader``: Thrown when an error occurs during template loading.
583
+
584
+ * ``Twig_Sandbox_SecurityError``: Thrown when an unallowed tag, filter, or
585
+ method is called in a sandboxed template.
Twig/doc/coding_standards.rst ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Coding Standards
2
+ ================
3
+
4
+ When writing Twig templates, we recommend you to follow these official coding
5
+ standards:
6
+
7
+ * Put one (and only one) space after the start of a delimiter (``{{``, ``{%``,
8
+ and ``{#``) and before the end of a delimiter (``}}``, ``%}``, and ``#}``):
9
+
10
+ .. code-block:: jinja
11
+
12
+ {{ foo }}
13
+ {# comment #}
14
+ {% if foo %}{% endif %}
15
+
16
+ When using the whitespace control character, do not put any spaces between
17
+ it and the delimiter:
18
+
19
+ .. code-block:: jinja
20
+
21
+ {{- foo -}}
22
+ {#- comment -#}
23
+ {%- if foo -%}{%- endif -%}
24
+
25
+ * Put one (and only one) space before and after the following operators:
26
+ comparison operators (``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``), math
27
+ operators (``+``, ``-``, ``/``, ``*``, ``%``, ``//``, ``**``), logic
28
+ operators (``not``, ``and``, ``or``), ``~``, ``is``, ``in``, and the ternary
29
+ operator (``?:``):
30
+
31
+ .. code-block:: jinja
32
+
33
+ {{ 1 + 2 }}
34
+ {{ foo ~ bar }}
35
+ {{ true ? true : false }}
36
+
37
+ * Put one (and only one) space after the ``:`` sign in hashes and ``,`` in
38
+ arrays and hashes:
39
+
40
+ .. code-block:: jinja
41
+
42
+ {{ [1, 2, 3] }}
43
+ {{ {'foo': 'bar'} }}
44
+
45
+ * Do not put any spaces after an opening parenthesis and before a closing
46
+ parenthesis in expressions:
47
+
48
+ .. code-block:: jinja
49
+
50
+ {{ 1 + (2 * 3) }}
51
+
52
+ * Do not put any spaces before and after string delimiters:
53
+
54
+ .. code-block:: jinja
55
+
56
+ {{ 'foo' }}
57
+ {{ "foo" }}
58
+
59
+ * Do not put any spaces before and after the following operators: ``|``,
60
+ ``.``, ``..``, ``[]``:
61
+
62
+ .. code-block:: jinja
63
+
64
+ {{ foo|upper|lower }}
65
+ {{ user.name }}
66
+ {{ user[name] }}
67
+ {% for i in 1..12 %}{% endfor %}
68
+
69
+ * Do not put any spaces before and after the parenthesis used for filter and
70
+ function calls:
71
+
72
+ .. code-block:: jinja
73
+
74
+ {{ foo|default('foo') }}
75
+ {{ range(1..10) }}
76
+
77
+ * Do not put any spaces before and after the opening and the closing of arrays
78
+ and hashes:
79
+
80
+ .. code-block:: jinja
81
+
82
+ {{ [1, 2, 3] }}
83
+ {{ {'foo': 'bar'} }}
84
+
85
+ * Use lower cased and underscored variable names:
86
+
87
+ .. code-block:: jinja
88
+
89
+ {% set foo = 'foo' %}
90
+ {% set foo_bar = 'foo' %}
91
+
92
+ * Indent your code inside tags (use the same indentation as the one used for
93
+ the main language of the file):
94
+
95
+ .. code-block:: jinja
96
+
97
+ {% block foo %}
98
+ {% if true %}
99
+ true
100
+ {% endif %}
101
+ {% endblock %}
Twig/doc/deprecated.rst ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Deprecated Features
2
+ ===================
3
+
4
+ This document lists all deprecated features in Twig. Deprecated features are
5
+ kept for backward compatibility and removed in the next major release (a
6
+ feature that was deprecated in Twig 1.x is removed in Twig 2.0).
7
+
8
+ Token Parsers
9
+ -------------
10
+
11
+ * As of Twig 1.x, the token parser broker sub-system is deprecated. The
12
+ following class and interface will be removed in 2.0:
13
+
14
+ * ``Twig_TokenParserBrokerInterface``
15
+ * ``Twig_TokenParserBroker``
16
+
17
+ Extensions
18
+ ----------
19
+
20
+ * As of Twig 1.x, the ability to remove an extension is deprecated and the
21
+ ``Twig_Environment::removeExtension()`` method will be removed in 2.0.
22
+
23
+ PEAR
24
+ ----
25
+
26
+ PEAR support will be discontinued in Twig 2.0, and no PEAR packages will be
27
+ provided. Use Composer instead.
28
+
29
+ Filters
30
+ -------
31
+
32
+ * As of Twig 1.x, use ``Twig_SimpleFilter`` to add a filter. The following
33
+ classes and interfaces will be removed in 2.0:
34
+
35
+ * ``Twig_FilterInterface``
36
+ * ``Twig_FilterCallableInterface``
37
+ * ``Twig_Filter``
38
+ * ``Twig_Filter_Function``
39
+ * ``Twig_Filter_Method``
40
+ * ``Twig_Filter_Node``
41
+
42
+ * As of Twig 2.x, the ``Twig_SimpleFilter`` class is deprecated and will be
43
+ removed in Twig 3.x (use ``Twig_Filter`` instead). In Twig 2.x,
44
+ ``Twig_SimpleFilter`` is just an alias for ``Twig_Filter``.
45
+
46
+ Functions
47
+ ---------
48
+
49
+ * As of Twig 1.x, use ``Twig_SimpleFunction`` to add a function. The following
50
+ classes and interfaces will be removed in 2.0:
51
+
52
+ * ``Twig_FunctionInterface``
53
+ * ``Twig_FunctionCallableInterface``
54
+ * ``Twig_Function``
55
+ * ``Twig_Function_Function``
56
+ * ``Twig_Function_Method``
57
+ * ``Twig_Function_Node``
58
+
59
+ * As of Twig 2.x, the ``Twig_SimpleFunction`` class is deprecated and will be
60
+ removed in Twig 3.x (use ``Twig_Function`` instead). In Twig 2.x,
61
+ ``Twig_SimpleFunction`` is just an alias for ``Twig_Function``.
62
+
63
+ Tests
64
+ -----
65
+
66
+ * As of Twig 1.x, use ``Twig_SimpleTest`` to add a test. The following classes
67
+ and interfaces will be removed in 2.0:
68
+
69
+ * ``Twig_TestInterface``
70
+ * ``Twig_TestCallableInterface``
71
+ * ``Twig_Test``
72
+ * ``Twig_Test_Function``
73
+ * ``Twig_Test_Method``
74
+ * ``Twig_Test_Node``
75
+
76
+ * As of Twig 2.x, the ``Twig_SimpleTest`` class is deprecated and will be
77
+ removed in Twig 3.x (use ``Twig_Test`` instead). In Twig 2.x,
78
+ ``Twig_SimpleTest`` is just an alias for ``Twig_Test``.
79
+
80
+ Interfaces
81
+ ----------
82
+
83
+ * As of Twig 2.x, the following interfaces are deprecated and empty (they will
84
+ be removed in Twig 3.0):
85
+
86
+ * ``Twig_CompilerInterface`` (use ``Twig_Compiler`` instead)
87
+ * ``Twig_LexerInterface`` (use ``Twig_Lexer`` instead)
88
+ * ``Twig_NodeInterface`` (use ``Twig_Node`` instead)
89
+ * ``Twig_ParserInterface`` (use ``Twig_Parser`` instead)
90
+ * ``Twig_ExistsLoaderInterface`` (merged with ``Twig_LoaderInterface``)
91
+ * ``Twig_TemplateInterface`` (use ``Twig_Template`` instead)
92
+
93
+ Globals
94
+ -------
95
+
96
+ * As of Twig 2.x, the ability to register a global variable after the runtime
97
+ or the extensions have been initialized is not possible anymore (but
98
+ changing the value of an already registered global is possible).
Twig/doc/filters/abs.rst ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``abs``
2
+ =======
3
+
4
+ The ``abs`` filter returns the absolute value.
5
+
6
+ .. code-block:: jinja
7
+
8
+ {# number = -5 #}
9
+
10
+ {{ number|abs }}
11
+
12
+ {# outputs 5 #}
13
+
14
+ .. note::
15
+
16
+ Internally, Twig uses the PHP `abs`_ function.
17
+
18
+ .. _`abs`: http://php.net/abs
Twig/doc/filters/capitalize.rst ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ``capitalize``
2
+ ==============
3
+
4
+ The ``capitalize`` filter capitalizes a value. The first character will be
5
+ uppercase, all others lowercase:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {{ 'my first car'|capitalize }}
10
+
11
+ {# outputs 'My first car' #}
Twig/doc/filters/convert_encoding.rst ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``convert_encoding``
2
+ ====================
3
+
4
+ .. versionadded:: 1.4
5
+ The ``convert_encoding`` filter was added in Twig 1.4.
6
+
7
+ The ``convert_encoding`` filter converts a string from one encoding to
8
+ another. The first argument is the expected output charset and the second one
9
+ is the input charset:
10
+
11
+ .. code-block:: jinja
12
+
13
+ {{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
14
+
15
+ .. note::
16
+
17
+ This filter relies on the `iconv`_ or `mbstring`_ extension, so one of
18
+ them must be installed. In case both are installed, `mbstring`_ is used by
19
+ default (Twig before 1.8.1 uses `iconv`_ by default).
20
+
21
+ Arguments
22
+ ---------
23
+
24
+ * ``from``: The input charset
25
+ * ``to``: The output charset
26
+
27
+ .. _`iconv`: http://php.net/iconv
28
+ .. _`mbstring`: http://php.net/mbstring
Twig/doc/filters/date.rst ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``date``
2
+ ========
3
+
4
+ .. versionadded:: 1.1
5
+ The timezone support has been added in Twig 1.1.
6
+
7
+ .. versionadded:: 1.5
8
+ The default date format support has been added in Twig 1.5.
9
+
10
+ .. versionadded:: 1.6.1
11
+ The default timezone support has been added in Twig 1.6.1.
12
+
13
+ .. versionadded:: 1.11.0
14
+ The introduction of the false value for the timezone was introduced in Twig 1.11.0
15
+
16
+ The ``date`` filter formats a date to a given format:
17
+
18
+ .. code-block:: jinja
19
+
20
+ {{ post.published_at|date("m/d/Y") }}
21
+
22
+ The ``date`` filter accepts strings (it must be in a format supported by the
23
+ `strtotime`_ function), `DateTime`_ instances, or `DateInterval`_ instances. For
24
+ instance, to display the current date, filter the word "now":
25
+
26
+ .. code-block:: jinja
27
+
28
+ {{ "now"|date("m/d/Y") }}
29
+
30
+ To escape words and characters in the date format use ``\\`` in front of each
31
+ character:
32
+
33
+ .. code-block:: jinja
34
+
35
+ {{ post.published_at|date("F jS \\a\\t g:ia") }}
36
+
37
+ If the value passed to the ``date`` filter is ``null``, it will return the
38
+ current date by default. If an empty string is desired instead of the current
39
+ date, use a ternary operator:
40
+
41
+ .. code-block:: jinja
42
+
43
+ {{ post.published_at is empty ? "" : post.published_at|date("m/d/Y") }}
44
+
45
+ If no format is provided, Twig will use the default one: ``F j, Y H:i``. This
46
+ default can be easily changed by calling the ``setDateFormat()`` method on the
47
+ ``core`` extension instance. The first argument is the default format for
48
+ dates and the second one is the default format for date intervals:
49
+
50
+ .. code-block:: php
51
+
52
+ $twig = new Twig_Environment($loader);
53
+ $twig->getExtension('core')->setDateFormat('d/m/Y', '%d days');
54
+
55
+ Timezone
56
+ --------
57
+
58
+ By default, the date is displayed by applying the default timezone (the one
59
+ specified in php.ini or declared in Twig -- see below), but you can override
60
+ it by explicitly specifying a timezone:
61
+
62
+ .. code-block:: jinja
63
+
64
+ {{ post.published_at|date("m/d/Y", "Europe/Paris") }}
65
+
66
+ If the date is already a DateTime object, and if you want to keep its current
67
+ timezone, pass ``false`` as the timezone value:
68
+
69
+ .. code-block:: jinja
70
+
71
+ {{ post.published_at|date("m/d/Y", false) }}
72
+
73
+ The default timezone can also be set globally by calling ``setTimezone()``:
74
+
75
+ .. code-block:: php
76
+
77
+ $twig = new Twig_Environment($loader);
78
+ $twig->getExtension('core')->setTimezone('Europe/Paris');
79
+
80
+ Arguments
81
+ ---------
82
+
83
+ * ``format``: The date format
84
+ * ``timezone``: The date timezone
85
+
86
+ .. _`strtotime`: http://www.php.net/strtotime
87
+ .. _`DateTime`: http://www.php.net/DateTime
88
+ .. _`DateInterval`: http://www.php.net/DateInterval
Twig/doc/filters/date_modify.rst ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``date_modify``
2
+ ===============
3
+
4
+ .. versionadded:: 1.9.0
5
+ The date_modify filter has been added in Twig 1.9.0.
6
+
7
+ The ``date_modify`` filter modifies a date with a given modifier string:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {{ post.published_at|date_modify("+1 day")|date("m/d/Y") }}
12
+
13
+ The ``date_modify`` filter accepts strings (it must be in a format supported
14
+ by the `strtotime`_ function) or `DateTime`_ instances. You can easily combine
15
+ it with the :doc:`date<date>` filter for formatting.
16
+
17
+ Arguments
18
+ ---------
19
+
20
+ * ``modifier``: The modifier
21
+
22
+ .. _`strtotime`: http://www.php.net/strtotime
23
+ .. _`DateTime`: http://www.php.net/DateTime
Twig/doc/filters/default.rst ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``default``
2
+ ===========
3
+
4
+ The ``default`` filter returns the passed default value if the value is
5
+ undefined or empty, otherwise the value of the variable:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {{ var|default('var is not defined') }}
10
+
11
+ {{ var.foo|default('foo item on var is not defined') }}
12
+
13
+ {{ var['foo']|default('foo item on var is not defined') }}
14
+
15
+ {{ ''|default('passed var is empty') }}
16
+
17
+ When using the ``default`` filter on an expression that uses variables in some
18
+ method calls, be sure to use the ``default`` filter whenever a variable can be
19
+ undefined:
20
+
21
+ .. code-block:: jinja
22
+
23
+ {{ var.method(foo|default('foo'))|default('foo') }}
24
+
25
+ .. note::
26
+
27
+ Read the documentation for the :doc:`defined<../tests/defined>` and
28
+ :doc:`empty<../tests/empty>` tests to learn more about their semantics.
29
+
30
+ Arguments
31
+ ---------
32
+
33
+ * ``default``: The default value
Twig/doc/filters/escape.rst ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``escape``
2
+ ==========
3
+
4
+ .. versionadded:: 1.9.0
5
+ The ``css``, ``url``, and ``html_attr`` strategies were added in Twig
6
+ 1.9.0.
7
+
8
+ The ``escape`` filter escapes a string for safe insertion into the final
9
+ output. It supports different escaping strategies depending on the template
10
+ context.
11
+
12
+ By default, it uses the HTML escaping strategy:
13
+
14
+ .. code-block:: jinja
15
+
16
+ {{ user.username|escape }}
17
+
18
+ For convenience, the ``e`` filter is defined as an alias:
19
+
20
+ .. code-block:: jinja
21
+
22
+ {{ user.username|e }}
23
+
24
+ The ``escape`` filter can also be used in other contexts than HTML thanks to
25
+ an optional argument which defines the escaping strategy to use:
26
+
27
+ .. code-block:: jinja
28
+
29
+ {{ user.username|e }}
30
+ {# is equivalent to #}
31
+ {{ user.username|e('html') }}
32
+
33
+ And here is how to escape variables included in JavaScript code:
34
+
35
+ .. code-block:: jinja
36
+
37
+ {{ user.username|escape('js') }}
38
+ {{ user.username|e('js') }}
39
+
40
+ The ``escape`` filter supports the following escaping strategies:
41
+
42
+ * ``html``: escapes a string for the **HTML body** context.
43
+
44
+ * ``js``: escapes a string for the **JavaScript context**.
45
+
46
+ * ``css``: escapes a string for the **CSS context**. CSS escaping can be
47
+ applied to any string being inserted into CSS and escapes everything except
48
+ alphanumerics.
49
+
50
+ * ``url``: escapes a string for the **URI or parameter contexts**. This should
51
+ not be used to escape an entire URI; only a subcomponent being inserted.
52
+
53
+ * ``html_attr``: escapes a string for the **HTML attribute** context.
54
+
55
+ .. note::
56
+
57
+ Internally, ``escape`` uses the PHP native `htmlspecialchars`_ function
58
+ for the HTML escaping strategy.
59
+
60
+ .. caution::
61
+
62
+ When using automatic escaping, Twig tries to not double-escape a variable
63
+ when the automatic escaping strategy is the same as the one applied by the
64
+ escape filter; but that does not work when using a variable as the
65
+ escaping strategy:
66
+
67
+ .. code-block:: jinja
68
+
69
+ {% set strategy = 'html' %}
70
+
71
+ {% autoescape 'html' %}
72
+ {{ var|escape('html') }} {# won't be double-escaped #}
73
+ {{ var|escape(strategy) }} {# will be double-escaped #}
74
+ {% endautoescape %}
75
+
76
+ When using a variable as the escaping strategy, you should disable
77
+ automatic escaping:
78
+
79
+ .. code-block:: jinja
80
+
81
+ {% set strategy = 'html' %}
82
+
83
+ {% autoescape 'html' %}
84
+ {{ var|escape(strategy)|raw }} {# won't be double-escaped #}
85
+ {% endautoescape %}
86
+
87
+ Arguments
88
+ ---------
89
+
90
+ * ``strategy``: The escaping strategy
91
+ * ``charset``: The string charset
92
+
93
+ .. _`htmlspecialchars`: http://php.net/htmlspecialchars
Twig/doc/filters/format.rst ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``format``
2
+ ==========
3
+
4
+ The ``format`` filter formats a given string by replacing the placeholders
5
+ (placeholders follows the `printf`_ notation):
6
+
7
+ .. code-block:: jinja
8
+
9
+ {{ "I like %s and %s."|format(foo, "bar") }}
10
+
11
+ {# returns I like foo and bar
12
+ if the foo parameter equals to the foo string. #}
13
+
14
+ .. _`printf`: http://www.php.net/printf
15
+
16
+ .. seealso:: :doc:`replace<replace>`
Twig/doc/filters/index.rst ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Filters
2
+ =======
3
+
4
+ .. toctree::
5
+ :maxdepth: 1
6
+
7
+ date
8
+ date_modify
9
+ format
10
+ replace
11
+ number_format
12
+ url_encode
13
+ json_encode
14
+ convert_encoding
15
+ title
16
+ capitalize
17
+ nl2br
18
+ upper
19
+ lower
20
+ striptags
21
+ join
22
+ split
23
+ reverse
24
+ abs
25
+ length
26
+ sort
27
+ default
28
+ keys
29
+ escape
30
+ raw
31
+ merge
32
+ slice
33
+ trim
Twig/doc/filters/join.rst ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``join``
2
+ ========
3
+
4
+ The ``join`` filter returns a string which is the concatenation of the items
5
+ of a sequence:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {{ [1, 2, 3]|join }}
10
+ {# returns 123 #}
11
+
12
+ The separator between elements is an empty string per default, but you can
13
+ define it with the optional first parameter:
14
+
15
+ .. code-block:: jinja
16
+
17
+ {{ [1, 2, 3]|join('|') }}
18
+ {# returns 1|2|3 #}
19
+
20
+ Arguments
21
+ ---------
22
+
23
+ * ``glue``: The separator
Twig/doc/filters/json_encode.rst ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``json_encode``
2
+ ===============
3
+
4
+ The ``json_encode`` filter returns the JSON representation of a string:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {{ data|json_encode() }}
9
+
10
+ .. note::
11
+
12
+ Internally, Twig uses the PHP `json_encode`_ function.
13
+
14
+ Arguments
15
+ ---------
16
+
17
+ * ``options``: The options
18
+
19
+ .. _`json_encode`: http://php.net/json_encode
Twig/doc/filters/keys.rst ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ``keys``
2
+ ========
3
+
4
+ The ``keys`` filter returns the keys of an array. It is useful when you want to
5
+ iterate over the keys of an array:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% for key in array|keys %}
10
+ ...
11
+ {% endfor %}
Twig/doc/filters/length.rst ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``length``
2
+ ==========
3
+
4
+ The ``length`` filters returns the number of items of a sequence or mapping, or
5
+ the length of a string:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% if users|length > 10 %}
10
+ ...
11
+ {% endif %}
12
+
Twig/doc/filters/lower.rst ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ ``lower``
2
+ =========
3
+
4
+ The ``lower`` filter converts a value to lowercase:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {{ 'WELCOME'|lower }}
9
+
10
+ {# outputs 'welcome' #}
Twig/doc/filters/merge.rst ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``merge``
2
+ =========
3
+
4
+ The ``merge`` filter merges an array with the another array:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {% set values = [1, 2] %}
9
+
10
+ {% set values = values|merge(['apple', 'orange']) %}
11
+
12
+ {# values now contains [1, 2, 'apple', 'orange'] #}
13
+
14
+ New values are added at the end of the existing ones.
15
+
16
+ The ``merge`` filter also works on hashes:
17
+
18
+ .. code-block:: jinja
19
+
20
+ {% set items = { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'unknown' } %}
21
+
22
+ {% set items = items|merge({ 'peugeot': 'car', 'renault': 'car' }) %}
23
+
24
+ {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'renault': 'car' } #}
25
+
26
+ For hashes, the merging process occurs on the keys: if the key does not
27
+ already exist, it is added but if the key already exists, its value is
28
+ overridden.
29
+
30
+ .. tip::
31
+
32
+ If you want to ensure that some values are defined in an array (by given
33
+ default values), reverse the two elements in the call:
34
+
35
+ .. code-block:: jinja
36
+
37
+ {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
38
+
39
+ {% set items = { 'apple': 'unknown' }|merge(items) %}
40
+
41
+ {# items now contains { 'apple': 'fruit', 'orange': 'fruit' } #}
Twig/doc/filters/nl2br.rst ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``nl2br``
2
+ =========
3
+
4
+ .. versionadded:: 1.5
5
+ The nl2br filter was added in Twig 1.5.
6
+
7
+ The ``nl2br`` filter inserts HTML line breaks before all newlines in a string:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {{ "I like Twig.\nYou will like it too."|nl2br }}
12
+ {# outputs
13
+
14
+ I like Twig.<br />
15
+ You will like it too.
16
+
17
+ #}
18
+
19
+ .. note::
20
+
21
+ The ``nl2br`` filter pre-escapes the input before applying the
22
+ transformation.
Twig/doc/filters/number_format.rst ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``number_format``
2
+ =================
3
+
4
+ .. versionadded:: 1.5
5
+ The number_format filter was added in Twig 1.5
6
+
7
+ The ``number_format`` filter formats numbers. It is a wrapper around PHP's
8
+ `number_format`_ function:
9
+
10
+ .. code-block:: jinja
11
+
12
+ {{ 200.35|number_format }}
13
+
14
+ You can control the number of decimal places, decimal point, and thousands
15
+ separator using the additional arguments:
16
+
17
+ .. code-block:: jinja
18
+
19
+ {{ 9800.333|number_format(2, '.', ',') }}
20
+
21
+ If no formatting options are provided then Twig will use the default formatting
22
+ options of:
23
+
24
+ - 0 decimal places.
25
+ - ``.`` as the decimal point.
26
+ - ``,`` as the thousands separator.
27
+
28
+ These defaults can be easily changed through the core extension:
29
+
30
+ .. code-block:: php
31
+
32
+ $twig = new Twig_Environment($loader);
33
+ $twig->getExtension('core')->setNumberFormat(3, '.', ',');
34
+
35
+ The defaults set for ``number_format`` can be over-ridden upon each call using the
36
+ additional parameters.
37
+
38
+ Arguments
39
+ ---------
40
+
41
+ * ``decimal``: The number of decimal points to display
42
+ * ``decimal_point``: The character(s) to use for the decimal point
43
+ * ``decimal_sep``: The character(s) to use for the thousands separator
44
+
45
+ .. _`number_format`: http://php.net/number_format
Twig/doc/filters/raw.rst ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``raw``
2
+ =======
3
+
4
+ The ``raw`` filter marks the value as being "safe", which means that in an
5
+ environment with automatic escaping enabled this variable will not be escaped
6
+ if ``raw`` is the last filter applied to it:
7
+
8
+ .. code-block:: jinja
9
+
10
+ {% autoescape true %}
11
+ {{ var|raw }} {# var won't be escaped #}
12
+ {% endautoescape %}
Twig/doc/filters/replace.rst ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``replace``
2
+ ===========
3
+
4
+ The ``replace`` filter formats a given string by replacing the placeholders
5
+ (placeholders are free-form):
6
+
7
+ .. code-block:: jinja
8
+
9
+ {{ "I like %this% and %that%."|replace({'%this%': foo, '%that%': "bar"}) }}
10
+
11
+ {# returns I like foo and bar
12
+ if the foo parameter equals to the foo string. #}
13
+
14
+ Arguments
15
+ ---------
16
+
17
+ * ``replace_pairs``: The placeholder values
18
+
19
+ .. seealso:: :doc:`format<format>`
Twig/doc/filters/reverse.rst ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``reverse``
2
+ ===========
3
+
4
+ .. versionadded:: 1.6
5
+ Support for strings has been added in Twig 1.6.
6
+
7
+ The ``reverse`` filter reverses a sequence, a mapping, or a string:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {% for use in users|reverse %}
12
+ ...
13
+ {% endfor %}
14
+
15
+ {{ '1234'|reverse }}
16
+
17
+ {# outputs 4321 #}
18
+
19
+ .. note::
20
+
21
+ It also works with objects implementing the `Traversable`_ interface.
22
+
23
+ .. _`Traversable`: http://php.net/Traversable
Twig/doc/filters/slice.rst ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``slice``
2
+ ===========
3
+
4
+ .. versionadded:: 1.6
5
+ The slice filter was added in Twig 1.6.
6
+
7
+ The ``slice`` filter extracts a slice of a sequence, a mapping, or a string:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {% for i in [1, 2, 3, 4]|slice(1, 2) %}
12
+ {# will iterate over 2 and 3 #}
13
+ {% endfor %}
14
+
15
+ {{ '1234'|slice(1, 2) }}
16
+
17
+ {# outputs 23 #}
18
+
19
+ You can use any valid expression for both the start and the length:
20
+
21
+ .. code-block:: jinja
22
+
23
+ {% for i in [1, 2, 3, 4]|slice(start, length) %}
24
+ {# ... #}
25
+ {% endfor %}
26
+
27
+ As syntactic sugar, you can also use the ``[]`` notation:
28
+
29
+ .. code-block:: jinja
30
+
31
+ {% for i in [1, 2, 3, 4][start:length] %}
32
+ {# ... #}
33
+ {% endfor %}
34
+
35
+ {{ '1234'[1:2] }}
36
+
37
+ {# you can omit the first argument -- which is the same as 0 #}
38
+ {{ '1234'[:2] }} {# will display "12" #}
39
+
40
+ {# you can omit the last argument -- which will select everything till the end #}
41
+ {{ '1234'[2:] }} {# will display "34 #}
42
+
43
+ The ``slice`` filter works as the `array_slice`_ PHP function for arrays and
44
+ `substr`_ for strings.
45
+
46
+ If the start is non-negative, the sequence will start at that start in the
47
+ variable. If start is negative, the sequence will start that far from the end
48
+ of the variable.
49
+
50
+ If length is given and is positive, then the sequence will have up to that
51
+ many elements in it. If the variable is shorter than the length, then only the
52
+ available variable elements will be present. If length is given and is
53
+ negative then the sequence will stop that many elements from the end of the
54
+ variable. If it is omitted, then the sequence will have everything from offset
55
+ up until the end of the variable.
56
+
57
+ .. note::
58
+
59
+ It also works with objects implementing the `Traversable`_ interface.
60
+
61
+ Arguments
62
+ ---------
63
+
64
+ * ``start``: The start of the slice
65
+ * ``length``: The size of the slice
66
+ * ``preserve_keys``: Whether to preserve key or not (when the input is an array)
67
+
68
+ .. _`Traversable`: http://php.net/manual/en/class.traversable.php
69
+ .. _`array_slice`: http://php.net/array_slice
70
+ .. _`substr`: http://php.net/substr
Twig/doc/filters/sort.rst ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``sort``
2
+ ========
3
+
4
+ The ``sort`` filter sorts an array:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {% for use in users|sort %}
9
+ ...
10
+ {% endfor %}
11
+
12
+ .. note::
13
+
14
+ Internally, Twig uses the PHP `asort`_ function to maintain index
15
+ association.
16
+
17
+ .. _`asort`: http://php.net/asort
Twig/doc/filters/split.rst ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``split``
2
+ =========
3
+
4
+ .. versionadded:: 1.10.3
5
+ The split filter was added in Twig 1.10.3.
6
+
7
+ The ``split`` filter splits a string by the given delimiter and returns a list
8
+ of strings:
9
+
10
+ .. code-block:: jinja
11
+
12
+ {{ "one,two,three"|split(',') }}
13
+ {# returns ['one', 'two', 'three'] #}
14
+
15
+ You can also pass a ``limit`` argument:
16
+
17
+ * If ``limit`` is positive, the returned array will contain a maximum of
18
+ limit elements with the last element containing the rest of string;
19
+
20
+ * If ``limit`` is negative, all components except the last -limit are
21
+ returned;
22
+
23
+ * If ``limit`` is zero, then this is treated as 1.
24
+
25
+ .. code-block:: jinja
26
+
27
+ {{ "one,two,three,four,five"|split(',', 3) }}
28
+ {# returns ['one', 'two', 'three,four,five'] #}
29
+
30
+ If the ``delimiter`` is an empty string, then value will be split by equal
31
+ chunks. Length is set by the ``limit`` argument (one character by default).
32
+
33
+ .. code-block:: jinja
34
+
35
+ {{ "123"|split('') }}
36
+ {# returns ['1', '2', '3'] #}
37
+
38
+ {{ "aabbcc"|split('', 2) }}
39
+ {# returns ['aa', 'bb', 'cc'] #}
40
+
41
+ .. note::
42
+
43
+ Internally, Twig uses the PHP `explode`_ or `str_split`_ (if delimiter is
44
+ empty) functions for string splitting.
45
+
46
+ Arguments
47
+ ---------
48
+
49
+ * ``delimiter``: The delimiter
50
+ * ``limit``: The limit argument
51
+
52
+ .. _`explode`: http://php.net/explode
53
+ .. _`str_split`: http://php.net/str_split
Twig/doc/filters/striptags.rst ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``striptags``
2
+ =============
3
+
4
+ The ``striptags`` filter strips SGML/XML tags and replace adjacent whitespace
5
+ by one space:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% some_html|striptags %}
10
+
11
+ .. note::
12
+
13
+ Internally, Twig uses the PHP `strip_tags`_ function.
14
+
15
+ .. _`strip_tags`: http://php.net/strip_tags
Twig/doc/filters/title.rst ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ``title``
2
+ =========
3
+
4
+ The ``title`` filter returns a titlecased version of the value. Words will
5
+ start with uppercase letters, all remaining characters are lowercase:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {{ 'my first car'|title }}
10
+
11
+ {# outputs 'My First Car' #}
Twig/doc/filters/trim.rst ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``trim``
2
+ ========
3
+
4
+ .. versionadded:: 1.6.2
5
+ The trim filter was added in Twig 1.6.2.
6
+
7
+ The ``trim`` filter strips whitespace (or other characters) from the beginning
8
+ and end of a string:
9
+
10
+ .. code-block:: jinja
11
+
12
+ {{ ' I like Twig. '|trim }}
13
+
14
+ {# outputs 'I like Twig.' #}
15
+
16
+ {{ ' I like Twig.'|trim('.') }}
17
+
18
+ {# outputs ' I like Twig' #}
19
+
20
+ .. note::
21
+
22
+ Internally, Twig uses the PHP `trim`_ function.
23
+
24
+ Arguments
25
+ ---------
26
+
27
+ * ``character_mask``: The characters to strip
28
+
29
+ .. _`trim`: http://php.net/trim
Twig/doc/filters/upper.rst ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ ``upper``
2
+ =========
3
+
4
+ The ``upper`` filter converts a value to uppercase:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {{ 'welcome'|upper }}
9
+
10
+ {# outputs 'WELCOME' #}
Twig/doc/filters/url_encode.rst ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``url_encode``
2
+ ==============
3
+
4
+ The ``url_encode`` filter URL encodes a given string:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {{ data|url_encode() }}
9
+
10
+ .. note::
11
+
12
+ Internally, Twig uses the PHP `urlencode`_ function.
13
+
14
+ .. _`urlencode`: http://php.net/urlencode
Twig/doc/functions/attribute.rst ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``attribute``
2
+ =============
3
+
4
+ .. versionadded:: 1.2
5
+ The ``attribute`` function was added in Twig 1.2.
6
+
7
+ ``attribute`` can be used to access a "dynamic" attribute of a variable:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {{ attribute(object, method) }}
12
+ {{ attribute(object, method, arguments) }}
13
+ {{ attribute(array, item) }}
14
+
15
+ .. note::
16
+
17
+ The resolution algorithm is the same as the one used for the ``.``
18
+ notation, except that the item can be any valid expression.
Twig/doc/functions/block.rst ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``block``
2
+ =========
3
+
4
+ When a template uses inheritance and if you want to print a block multiple
5
+ times, use the ``block`` function:
6
+
7
+ .. code-block:: jinja
8
+
9
+ <title>{% block title %}{% endblock %}</title>
10
+
11
+ <h1>{{ block('title') }}</h1>
12
+
13
+ {% block body %}{% endblock %}
14
+
15
+ .. seealso:: :doc:`extends<../tags/extends>`, :doc:`parent<../functions/parent>`
Twig/doc/functions/constant.rst ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``constant``
2
+ ============
3
+
4
+ .. versionadded: 1.12.1
5
+ constant now accepts object instances as the second argument.
6
+
7
+ ``constant`` returns the constant value for a given string:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {{ some_date|date(constant('DATE_W3C')) }}
12
+ {{ constant('Namespace\\Classname::CONSTANT_NAME') }}
13
+
14
+ As of 1.12.1 you can read constants from object instances as well:
15
+
16
+ .. code-block:: jinja
17
+
18
+ {{ constant('RSS', date) }}
Twig/doc/functions/cycle.rst ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``cycle``
2
+ =========
3
+
4
+ The ``cycle`` function cycles on an array of values:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {% for i in 0..10 %}
9
+ {{ cycle(['odd', 'even'], i) }}
10
+ {% endfor %}
11
+
12
+ The array can contain any number of values:
13
+
14
+ .. code-block:: jinja
15
+
16
+ {% set fruits = ['apple', 'orange', 'citrus'] %}
17
+
18
+ {% for i in 0..10 %}
19
+ {{ cycle(fruits, i) }}
20
+ {% endfor %}
21
+
22
+ Arguments
23
+ ---------
24
+
25
+ * ``position``: The cycle position
Twig/doc/functions/date.rst ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``date``
2
+ ========
3
+
4
+ .. versionadded:: 1.6
5
+ The date function has been added in Twig 1.6.
6
+
7
+ .. versionadded:: 1.6.1
8
+ The default timezone support has been added in Twig 1.6.1.
9
+
10
+ Converts an argument to a date to allow date comparison:
11
+
12
+ .. code-block:: jinja
13
+
14
+ {% if date(user.created_at) < date('+2days') %}
15
+ {# do something #}
16
+ {% endif %}
17
+
18
+ The argument must be in a format supported by the `date`_ function.
19
+
20
+ You can pass a timezone as the second argument:
21
+
22
+ .. code-block:: jinja
23
+
24
+ {% if date(user.created_at) < date('+2days', 'Europe/Paris') %}
25
+ {# do something #}
26
+ {% endif %}
27
+
28
+ If no argument is passed, the function returns the current date:
29
+
30
+ .. code-block:: jinja
31
+
32
+ {% if date(user.created_at) < date() %}
33
+ {# always! #}
34
+ {% endif %}
35
+
36
+ .. note::
37
+
38
+ You can set the default timezone globally by calling ``setTimezone()`` on
39
+ the ``core`` extension instance:
40
+
41
+ .. code-block:: php
42
+
43
+ $twig = new Twig_Environment($loader);
44
+ $twig->getExtension('core')->setTimezone('Europe/Paris');
45
+
46
+ Arguments
47
+ ---------
48
+
49
+ * ``date``: The date
50
+ * ``timezone``: The timezone
51
+
52
+ .. _`date`: http://www.php.net/date
Twig/doc/functions/dump.rst ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``dump``
2
+ ========
3
+
4
+ .. versionadded:: 1.5
5
+ The dump function was added in Twig 1.5.
6
+
7
+ The ``dump`` function dumps information about a template variable. This is
8
+ mostly useful to debug a template that does not behave as expected by
9
+ introspecting its variables:
10
+
11
+ .. code-block:: jinja
12
+
13
+ {{ dump(user) }}
14
+
15
+ .. note::
16
+
17
+ The ``dump`` function is not available by default. You must add the
18
+ ``Twig_Extension_Debug`` extension explicitly when creating your Twig
19
+ environment::
20
+
21
+ $twig = new Twig_Environment($loader, array(
22
+ 'debug' => true,
23
+ // ...
24
+ ));
25
+ $twig->addExtension(new Twig_Extension_Debug());
26
+
27
+ Even when enabled, the ``dump`` function won't display anything if the
28
+ ``debug`` option on the environment is not enabled (to avoid leaking debug
29
+ information on a production server).
30
+
31
+ In an HTML context, wrap the output with a ``pre`` tag to make it easier to
32
+ read:
33
+
34
+ .. code-block:: jinja
35
+
36
+ <pre>
37
+ {{ dump(user) }}
38
+ </pre>
39
+
40
+ .. tip::
41
+
42
+ Using a ``pre`` tag is not needed when `XDebug`_ is enabled and
43
+ ``html_errors`` is ``on``; as a bonus, the output is also nicer with
44
+ XDebug enabled.
45
+
46
+ You can debug several variables by passing them as additional arguments:
47
+
48
+ .. code-block:: jinja
49
+
50
+ {{ dump(user, categories) }}
51
+
52
+ If you don't pass any value, all variables from the current context are
53
+ dumped:
54
+
55
+ .. code-block:: jinja
56
+
57
+ {{ dump() }}
58
+
59
+ .. note::
60
+
61
+ Internally, Twig uses the PHP `var_dump`_ function.
62
+
63
+ Arguments
64
+ ---------
65
+
66
+ * ``context``: The context to dump
67
+
68
+ .. _`XDebug`: http://xdebug.org/docs/display
69
+ .. _`var_dump`: http://php.net/var_dump
Twig/doc/functions/include.rst ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``include``
2
+ ===========
3
+
4
+ .. versionadded:: 1.12
5
+ The include function was added in Twig 1.12.
6
+
7
+ The ``include`` function returns the rendered content of a template:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {{ include('template.html') }}
12
+ {{ include(some_var) }}
13
+
14
+ Included templates have access to the variables of the active context.
15
+
16
+ If you are using the filesystem loader, the templates are looked for in the
17
+ paths defined by it.
18
+
19
+ The context is passed by default to the template but you can also pass
20
+ additional variables:
21
+
22
+ .. code-block:: jinja
23
+
24
+ {# template.html will have access to the variables from the current context and the additional ones provided #}
25
+ {{ include('template.html', {foo: 'bar'}) }}
26
+
27
+ You can disable access to the context by setting ``with_context`` to
28
+ ``false``:
29
+
30
+ .. code-block:: jinja
31
+
32
+ {# only the foo variable will be accessible #}
33
+ {{ include('template.html', {foo: 'bar'}, with_context = false) }}
34
+
35
+ .. code-block:: jinja
36
+
37
+ {# no variables will be accessible #}
38
+ {{ include('template.html', with_context = false) }}
39
+
40
+ And if the expression evaluates to a ``Twig_Template`` object, Twig will use it
41
+ directly::
42
+
43
+ // {{ include(template) }}
44
+
45
+ $template = $twig->loadTemplate('some_template.twig');
46
+
47
+ $twig->loadTemplate('template.twig')->display(array('template' => $template));
48
+
49
+ When you set the ``ignore_missing`` flag, Twig will return an empty string if
50
+ the template does not exist:
51
+
52
+ .. code-block:: jinja
53
+
54
+ {{ include('sidebar.html', ignore_missing = true) }}
55
+
56
+ You can also provide a list of templates that are checked for existence before
57
+ inclusion. The first template that exists will be rendered:
58
+
59
+ .. code-block:: jinja
60
+
61
+ {{ include(['page_detailed.html', 'page.html']) }}
62
+
63
+ If ``ignore_missing`` is set, it will fall back to rendering nothing if none
64
+ of the templates exist, otherwise it will throw an exception.
65
+
66
+ When including a template created by an end user, you should consider
67
+ sandboxing it:
68
+
69
+ .. code-block:: jinja
70
+
71
+ {{ include('page.html', sandboxed = true) }}
72
+
73
+ Arguments
74
+ ---------
75
+
76
+ * ``template``: The template to render
77
+ * ``variables``: The variables to pass to the template
78
+ * ``with_context``: Whether to pass the current context variables or not
79
+ * ``ignore_missing``: Whether to ignore missing templates or not
80
+ * ``sandboxed``: Whether to sandbox the template or not
Twig/doc/functions/index.rst ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Functions
2
+ =========
3
+
4
+ .. toctree::
5
+ :maxdepth: 1
6
+
7
+ range
8
+ cycle
9
+ constant
10
+ random
11
+ attribute
12
+ block
13
+ parent
14
+ dump
15
+ date
16
+ template_from_string
17
+ include
Twig/doc/functions/parent.rst ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``parent``
2
+ ==========
3
+
4
+ When a template uses inheritance, it's possible to render the contents of the
5
+ parent block when overriding a block by using the ``parent`` function:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% extends "base.html" %}
10
+
11
+ {% block sidebar %}
12
+ <h3>Table Of Contents</h3>
13
+ ...
14
+ {{ parent() }}
15
+ {% endblock %}
16
+
17
+ The ``parent()`` call will return the content of the ``sidebar`` block as
18
+ defined in the ``base.html`` template.
19
+
20
+ .. seealso:: :doc:`extends<../tags/extends>`, :doc:`block<../functions/block>`, :doc:`block<../tags/block>`
Twig/doc/functions/random.rst ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``random``
2
+ ==========
3
+
4
+ .. versionadded:: 1.5
5
+ The random function was added in Twig 1.5.
6
+
7
+ .. versionadded:: 1.6
8
+ String and integer handling was added in Twig 1.6.
9
+
10
+ The ``random`` function returns a random value depending on the supplied
11
+ parameter type:
12
+
13
+ * a random item from a sequence;
14
+ * a random character from a string;
15
+ * a random integer between 0 and the integer parameter (inclusive).
16
+
17
+ .. code-block:: jinja
18
+
19
+ {{ random(['apple', 'orange', 'citrus']) }} {# example output: orange #}
20
+ {{ random('ABC') }} {# example output: C #}
21
+ {{ random() }} {# example output: 15386094 (works as native PHP `mt_rand`_ function) #}
22
+ {{ random(5) }} {# example output: 3 #}
23
+
24
+ Arguments
25
+ ---------
26
+
27
+ * ``values``: The values
28
+
29
+ .. _`mt_rand`: http://php.net/mt_rand
Twig/doc/functions/range.rst ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``range``
2
+ =========
3
+
4
+ Returns a list containing an arithmetic progression of integers:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {% for i in range(0, 3) %}
9
+ {{ i }},
10
+ {% endfor %}
11
+
12
+ {# returns 0, 1, 2, 3 #}
13
+
14
+ When step is given (as the third parameter), it specifies the increment (or
15
+ decrement):
16
+
17
+ .. code-block:: jinja
18
+
19
+ {% for i in range(0, 6, 2) %}
20
+ {{ i }},
21
+ {% endfor %}
22
+
23
+ {# returns 0, 2, 4, 6 #}
24
+
25
+ The Twig built-in ``..`` operator is just syntactic sugar for the ``range``
26
+ function (with a step of 1):
27
+
28
+ .. code-block:: jinja
29
+
30
+ {% for i in 0..3 %}
31
+ {{ i }},
32
+ {% endfor %}
33
+
34
+ .. tip::
35
+
36
+ The ``range`` function works as the native PHP `range`_ function.
37
+
38
+ Arguments
39
+ ---------
40
+
41
+ * ``low``: The first value of the sequence.
42
+ * ``high``: The highest possible value of the sequence.
43
+ * ``step``: The increment between elements of the sequence.
44
+
45
+ .. _`range`: http://php.net/range
Twig/doc/functions/template_from_string.rst ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``template_from_string``
2
+ ========================
3
+
4
+ .. versionadded:: 1.11
5
+ The template_from_string function was added in Twig 1.11.
6
+
7
+ The ``template_from_string`` function loads a template from a string:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {% include template_from_string("Hello {{ name }}") %}
12
+ {% include template_from_string(page.template) %}
13
+
14
+ .. note::
15
+
16
+ The ``template_from_string`` function is not available by default. You
17
+ must add the ``Twig_Extension_StringLoader`` extension explicitly when
18
+ creating your Twig environment::
19
+
20
+ $twig = new Twig_Environment(...);
21
+ $twig->addExtension(new Twig_Extension_StringLoader());
22
+
23
+ .. note::
24
+
25
+ Even if you will probably always use the ``template_from_string`` function
26
+ with the ``include`` tag, you can use it with any tag or function that
27
+ takes a template as an argument (like the ``embed`` or ``extends`` tags).
28
+
29
+ Arguments
30
+ ---------
31
+
32
+ * ``template``: The template
Twig/doc/index.rst ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Twig
2
+ ====
3
+
4
+ .. toctree::
5
+ :maxdepth: 2
6
+
7
+ intro
8
+ templates
9
+ api
10
+ advanced
11
+ internals
12
+ recipes
13
+ coding_standards
14
+ tags/index
15
+ filters/index
16
+ functions/index
17
+ tests/index
18
+ deprecated
Twig/doc/internals.rst ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Twig Internals
2
+ ==============
3
+
4
+ Twig is very extensible and you can easily hack it. Keep in mind that you
5
+ should probably try to create an extension before hacking the core, as most
6
+ features and enhancements can be done with extensions. This chapter is also
7
+ useful for people who want to understand how Twig works under the hood.
8
+
9
+ How Twig works?
10
+ ---------------
11
+
12
+ The rendering of a Twig template can be summarized into four key steps:
13
+
14
+ * **Load** the template: If the template is already compiled, load it and go
15
+ to the *evaluation* step, otherwise:
16
+
17
+ * First, the **lexer** tokenizes the template source code into small pieces
18
+ for easier processing;
19
+ * Then, the **parser** converts the token stream into a meaningful tree
20
+ of nodes (the Abstract Syntax Tree);
21
+ * Eventually, the *compiler* transforms the AST into PHP code;
22
+
23
+ * **Evaluate** the template: It basically means calling the ``display()``
24
+ method of the compiled template and passing it the context.
25
+
26
+ The Lexer
27
+ ---------
28
+
29
+ The lexer tokenizes a template source code into a token stream (each token is
30
+ an instance of ``Twig_Token``, and the stream is an instance of
31
+ ``Twig_TokenStream``). The default lexer recognizes 13 different token types:
32
+
33
+ * ``Twig_Token::BLOCK_START_TYPE``, ``Twig_Token::BLOCK_END_TYPE``: Delimiters for blocks (``{% %}``)
34
+ * ``Twig_Token::VAR_START_TYPE``, ``Twig_Token::VAR_END_TYPE``: Delimiters for variables (``{{ }}``)
35
+ * ``Twig_Token::TEXT_TYPE``: A text outside an expression;
36
+ * ``Twig_Token::NAME_TYPE``: A name in an expression;
37
+ * ``Twig_Token::NUMBER_TYPE``: A number in an expression;
38
+ * ``Twig_Token::STRING_TYPE``: A string in an expression;
39
+ * ``Twig_Token::OPERATOR_TYPE``: An operator;
40
+ * ``Twig_Token::PUNCTUATION_TYPE``: A punctuation sign;
41
+ * ``Twig_Token::INTERPOLATION_START_TYPE``, ``Twig_Token::INTERPOLATION_END_TYPE`` (as of Twig 1.5): Delimiters for string interpolation;
42
+ * ``Twig_Token::EOF_TYPE``: Ends of template.
43
+
44
+ You can manually convert a source code into a token stream by calling the
45
+ ``tokenize()`` of an environment::
46
+
47
+ $stream = $twig->tokenize($source, $identifier);
48
+
49
+ As the stream has a ``__toString()`` method, you can have a textual
50
+ representation of it by echoing the object::
51
+
52
+ echo $stream."\n";
53
+
54
+ Here is the output for the ``Hello {{ name }}`` template:
55
+
56
+ .. code-block:: text
57
+
58
+ TEXT_TYPE(Hello )
59
+ VAR_START_TYPE()
60
+ NAME_TYPE(name)
61
+ VAR_END_TYPE()
62
+ EOF_TYPE()
63
+
64
+ .. note::
65
+
66
+ You can change the default lexer use by Twig (``Twig_Lexer``) by calling
67
+ the ``setLexer()`` method::
68
+
69
+ $twig->setLexer($lexer);
70
+
71
+ The Parser
72
+ ----------
73
+
74
+ The parser converts the token stream into an AST (Abstract Syntax Tree), or a
75
+ node tree (an instance of ``Twig_Node_Module``). The core extension defines
76
+ the basic nodes like: ``for``, ``if``, ... and the expression nodes.
77
+
78
+ You can manually convert a token stream into a node tree by calling the
79
+ ``parse()`` method of an environment::
80
+
81
+ $nodes = $twig->parse($stream);
82
+
83
+ Echoing the node object gives you a nice representation of the tree::
84
+
85
+ echo $nodes."\n";
86
+
87
+ Here is the output for the ``Hello {{ name }}`` template:
88
+
89
+ .. code-block:: text
90
+
91
+ Twig_Node_Module(
92
+ Twig_Node_Text(Hello )
93
+ Twig_Node_Print(
94
+ Twig_Node_Expression_Name(name)
95
+ )
96
+ )
97
+
98
+ .. note::
99
+
100
+ The default parser (``Twig_TokenParser``) can be also changed by calling the
101
+ ``setParser()`` method::
102
+
103
+ $twig->setParser($parser);
104
+
105
+ The Compiler
106
+ ------------
107
+
108
+ The last step is done by the compiler. It takes a node tree as an input and
109
+ generates PHP code usable for runtime execution of the template.
110
+
111
+ You can call the compiler by hand with the ``compile()`` method of an
112
+ environment::
113
+
114
+ $php = $twig->compile($nodes);
115
+
116
+ The ``compile()`` method returns the PHP source code representing the node.
117
+
118
+ The generated template for a ``Hello {{ name }}`` template reads as follows
119
+ (the actual output can differ depending on the version of Twig you are
120
+ using)::
121
+
122
+ /* Hello {{ name }} */
123
+ class __TwigTemplate_1121b6f109fe93ebe8c6e22e3712bceb extends Twig_Template
124
+ {
125
+ protected function doDisplay(array $context, array $blocks = array())
126
+ {
127
+ // line 1
128
+ echo "Hello ";
129
+ echo twig_escape_filter($this->env, $this->getContext($context, "name"), "ndex", null, true);
130
+ }
131
+
132
+ // some more code
133
+ }
134
+
135
+ .. note::
136
+
137
+ As for the lexer and the parser, the default compiler (``Twig_Compiler``) can
138
+ be changed by calling the ``setCompiler()`` method::
139
+
140
+ $twig->setCompiler($compiler);
Twig/doc/intro.rst ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Introduction
2
+ ============
3
+
4
+ This is the documentation for Twig, the flexible, fast, and secure template
5
+ engine for PHP.
6
+
7
+ If you have any exposure to other text-based template languages, such as
8
+ Smarty, Django, or Jinja, you should feel right at home with Twig. It's both
9
+ designer and developer friendly by sticking to PHP's principles and adding
10
+ functionality useful for templating environments.
11
+
12
+ The key-features are...
13
+
14
+ * *Fast*: Twig compiles templates down to plain optimized PHP code. The
15
+ overhead compared to regular PHP code was reduced to the very minimum.
16
+
17
+ * *Secure*: Twig has a sandbox mode to evaluate untrusted template code. This
18
+ allows Twig to be used as a template language for applications where users
19
+ may modify the template design.
20
+
21
+ * *Flexible*: Twig is powered by a flexible lexer and parser. This allows the
22
+ developer to define its own custom tags and filters, and create its own DSL.
23
+
24
+ Prerequisites
25
+ -------------
26
+
27
+ Twig needs at least **PHP 5.2.4** to run.
28
+
29
+ Installation
30
+ ------------
31
+
32
+ You have multiple ways to install Twig.
33
+
34
+ Installing via Composer (recommended)
35
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36
+
37
+ 1. Install composer in your project:
38
+
39
+ .. code-block:: bash
40
+
41
+ curl -s http://getcomposer.org/installer | php
42
+
43
+ 2. Create a ``composer.json`` file in your project root:
44
+
45
+ .. code-block:: javascript
46
+
47
+ {
48
+ "require": {
49
+ "twig/twig": "1.*"
50
+ }
51
+ }
52
+
53
+ 3. Install via composer
54
+
55
+ .. code-block:: bash
56
+
57
+ php composer.phar install
58
+
59
+ .. note::
60
+ If you want to learn more about Composer, the ``composer.json`` file syntax
61
+ and its usage, you can read the `online documentation`_.
62
+
63
+ Installing from the tarball release
64
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
65
+
66
+ 1. Download the most recent tarball from the `download page`_
67
+ 2. Unpack the tarball
68
+ 3. Move the files somewhere in your project
69
+
70
+ Installing the development version
71
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
72
+
73
+ 1. Install Git
74
+ 2. ``git clone git://github.com/fabpot/Twig.git``
75
+
76
+ Installing the PEAR package
77
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~
78
+
79
+ 1. Install PEAR
80
+ 2. ``pear channel-discover pear.twig-project.org``
81
+ 3. ``pear install twig/Twig`` (or ``pear install twig/Twig-beta``)
82
+
83
+
84
+ Installing the C extension
85
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
86
+
87
+ .. versionadded:: 1.4
88
+ The C extension was added in Twig 1.4.
89
+
90
+ Twig comes with a C extension that enhances the performance of the Twig
91
+ runtime engine. You can install it like any other PHP extension:
92
+
93
+ .. code-block:: bash
94
+
95
+ $ cd ext/twig
96
+ $ phpize
97
+ $ ./configure
98
+ $ make
99
+ $ make install
100
+
101
+ Finally, enable the extension in your ``php.ini`` configuration file:
102
+
103
+ .. code-block:: ini
104
+
105
+ extension=twig.so
106
+
107
+ And from now on, Twig will automatically compile your templates to take
108
+ advantage of the C extension. Note that this extension does not replace the
109
+ PHP code but only provides an optimized version of the
110
+ ``Twig_Template::getAttribute()`` method.
111
+
112
+ .. tip::
113
+
114
+ On Windows, you can also simply download and install a `pre-build DLL`_.
115
+
116
+ Basic API Usage
117
+ ---------------
118
+
119
+ This section gives you a brief introduction to the PHP API for Twig.
120
+
121
+ The first step to use Twig is to register its autoloader::
122
+
123
+ require_once '/path/to/lib/Twig/Autoloader.php';
124
+ Twig_Autoloader::register();
125
+
126
+ Replace the ``/path/to/lib/`` path with the path you used for Twig
127
+ installation.
128
+
129
+ If you have installed Twig via Composer you can take advantage of Composer's
130
+ autoload mechanism by replacing the previous snippet for::
131
+
132
+ require_once '/path/to/vendor/autoload.php'
133
+
134
+ .. note::
135
+
136
+ Twig follows the PEAR convention names for its classes, which means you
137
+ can easily integrate Twig classes loading in your own autoloader.
138
+
139
+ .. code-block:: php
140
+
141
+ $loader = new Twig_Loader_String();
142
+ $twig = new Twig_Environment($loader);
143
+
144
+ echo $twig->render('Hello {{ name }}!', array('name' => 'Fabien'));
145
+
146
+ Twig uses a loader (``Twig_Loader_String``) to locate templates, and an
147
+ environment (``Twig_Environment``) to store the configuration.
148
+
149
+ The ``render()`` method loads the template passed as a first argument and
150
+ renders it with the variables passed as a second argument.
151
+
152
+ As templates are generally stored on the filesystem, Twig also comes with a
153
+ filesystem loader::
154
+
155
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
156
+ $twig = new Twig_Environment($loader, array(
157
+ 'cache' => '/path/to/compilation_cache',
158
+ ));
159
+
160
+ echo $twig->render('index.html', array('name' => 'Fabien'));
161
+
162
+ .. _`download page`: https://github.com/fabpot/Twig/tags
163
+ .. _`online documentation`: http://getcomposer.org/doc
164
+ .. _`pre-build DLL`: https://github.com/stealth35/stealth35.github.com/downloads
Twig/doc/recipes.rst ADDED
@@ -0,0 +1,474 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Recipes
2
+ =======
3
+
4
+ Making a Layout conditional
5
+ ---------------------------
6
+
7
+ Working with Ajax means that the same content is sometimes displayed as is,
8
+ and sometimes decorated with a layout. As Twig layout template names can be
9
+ any valid expression, you can pass a variable that evaluates to ``true`` when
10
+ the request is made via Ajax and choose the layout accordingly:
11
+
12
+ .. code-block:: jinja
13
+
14
+ {% extends request.ajax ? "base_ajax.html" : "base.html" %}
15
+
16
+ {% block content %}
17
+ This is the content to be displayed.
18
+ {% endblock %}
19
+
20
+ Making an Include dynamic
21
+ -------------------------
22
+
23
+ When including a template, its name does not need to be a string. For
24
+ instance, the name can depend on the value of a variable:
25
+
26
+ .. code-block:: jinja
27
+
28
+ {% include var ~ '_foo.html' %}
29
+
30
+ If ``var`` evaluates to ``index``, the ``index_foo.html`` template will be
31
+ rendered.
32
+
33
+ As a matter of fact, the template name can be any valid expression, such as
34
+ the following:
35
+
36
+ .. code-block:: jinja
37
+
38
+ {% include var|default('index') ~ '_foo.html' %}
39
+
40
+ Overriding a Template that also extends itself
41
+ ----------------------------------------------
42
+
43
+ A template can be customized in two different ways:
44
+
45
+ * *Inheritance*: A template *extends* a parent template and overrides some
46
+ blocks;
47
+
48
+ * *Replacement*: If you use the filesystem loader, Twig loads the first
49
+ template it finds in a list of configured directories; a template found in a
50
+ directory *replaces* another one from a directory further in the list.
51
+
52
+ But how do you combine both: *replace* a template that also extends itself
53
+ (aka a template in a directory further in the list)?
54
+
55
+ Let's say that your templates are loaded from both ``.../templates/mysite``
56
+ and ``.../templates/default`` in this order. The ``page.twig`` template,
57
+ stored in ``.../templates/default`` reads as follows:
58
+
59
+ .. code-block:: jinja
60
+
61
+ {# page.twig #}
62
+ {% extends "layout.twig" %}
63
+
64
+ {% block content %}
65
+ {% endblock %}
66
+
67
+ You can replace this template by putting a file with the same name in
68
+ ``.../templates/mysite``. And if you want to extend the original template, you
69
+ might be tempted to write the following:
70
+
71
+ .. code-block:: jinja
72
+
73
+ {# page.twig in .../templates/mysite #}
74
+ {% extends "page.twig" %} {# from .../templates/default #}
75
+
76
+ Of course, this will not work as Twig will always load the template from
77
+ ``.../templates/mysite``.
78
+
79
+ It turns out it is possible to get this to work, by adding a directory right
80
+ at the end of your template directories, which is the parent of all of the
81
+ other directories: ``.../templates`` in our case. This has the effect of
82
+ making every template file within our system uniquely addressable. Most of the
83
+ time you will use the "normal" paths, but in the special case of wanting to
84
+ extend a template with an overriding version of itself we can reference its
85
+ parent's full, unambiguous template path in the extends tag:
86
+
87
+ .. code-block:: jinja
88
+
89
+ {# page.twig in .../templates/mysite #}
90
+ {% extends "default/page.twig" %} {# from .../templates #}
91
+
92
+ .. note::
93
+
94
+ This recipe was inspired by the following Django wiki page:
95
+ http://code.djangoproject.com/wiki/ExtendingTemplates
96
+
97
+ Customizing the Syntax
98
+ ----------------------
99
+
100
+ Twig allows some syntax customization for the block delimiters. It's not
101
+ recommended to use this feature as templates will be tied with your custom
102
+ syntax. But for specific projects, it can make sense to change the defaults.
103
+
104
+ To change the block delimiters, you need to create your own lexer object::
105
+
106
+ $twig = new Twig_Environment();
107
+
108
+ $lexer = new Twig_Lexer($twig, array(
109
+ 'tag_comment' => array('{#', '#}'),
110
+ 'tag_block' => array('{%', '%}'),
111
+ 'tag_variable' => array('{{', '}}'),
112
+ ));
113
+ $twig->setLexer($lexer);
114
+
115
+ Here are some configuration example that simulates some other template engines
116
+ syntax::
117
+
118
+ // Ruby erb syntax
119
+ $lexer = new Twig_Lexer($twig, array(
120
+ 'tag_comment' => array('<%#', '%>'),
121
+ 'tag_block' => array('<%', '%>'),
122
+ 'tag_variable' => array('<%=', '%>'),
123
+ ));
124
+
125
+ // SGML Comment Syntax
126
+ $lexer = new Twig_Lexer($twig, array(
127
+ 'tag_comment' => array('<!--#', '-->'),
128
+ 'tag_block' => array('<!--', '-->'),
129
+ 'tag_variable' => array('${', '}'),
130
+ ));
131
+
132
+ // Smarty like
133
+ $lexer = new Twig_Lexer($twig, array(
134
+ 'tag_comment' => array('{*', '*}'),
135
+ 'tag_block' => array('{', '}'),
136
+ 'tag_variable' => array('{$', '}'),
137
+ ));
138
+
139
+ Using dynamic Object Properties
140
+ -------------------------------
141
+
142
+ When Twig encounters a variable like ``article.title``, it tries to find a
143
+ ``title`` public property in the ``article`` object.
144
+
145
+ It also works if the property does not exist but is rather defined dynamically
146
+ thanks to the magic ``__get()`` method; you just need to also implement the
147
+ ``__isset()`` magic method like shown in the following snippet of code::
148
+
149
+ class Article
150
+ {
151
+ public function __get($name)
152
+ {
153
+ if ('title' == $name) {
154
+ return 'The title';
155
+ }
156
+
157
+ // throw some kind of error
158
+ }
159
+
160
+ public function __isset($name)
161
+ {
162
+ if ('title' == $name) {
163
+ return true;
164
+ }
165
+
166
+ return false;
167
+ }
168
+ }
169
+
170
+ Accessing the parent Context in Nested Loops
171
+ --------------------------------------------
172
+
173
+ Sometimes, when using nested loops, you need to access the parent context. The
174
+ parent context is always accessible via the ``loop.parent`` variable. For
175
+ instance, if you have the following template data::
176
+
177
+ $data = array(
178
+ 'topics' => array(
179
+ 'topic1' => array('Message 1 of topic 1', 'Message 2 of topic 1'),
180
+ 'topic2' => array('Message 1 of topic 2', 'Message 2 of topic 2'),
181
+ ),
182
+ );
183
+
184
+ And the following template to display all messages in all topics:
185
+
186
+ .. code-block:: jinja
187
+
188
+ {% for topic, messages in topics %}
189
+ * {{ loop.index }}: {{ topic }}
190
+ {% for message in messages %}
191
+ - {{ loop.parent.loop.index }}.{{ loop.index }}: {{ message }}
192
+ {% endfor %}
193
+ {% endfor %}
194
+
195
+ The output will be similar to:
196
+
197
+ .. code-block:: text
198
+
199
+ * 1: topic1
200
+ - 1.1: The message 1 of topic 1
201
+ - 1.2: The message 2 of topic 1
202
+ * 2: topic2
203
+ - 2.1: The message 1 of topic 2
204
+ - 2.2: The message 2 of topic 2
205
+
206
+ In the inner loop, the ``loop.parent`` variable is used to access the outer
207
+ context. So, the index of the current ``topic`` defined in the outer for loop
208
+ is accessible via the ``loop.parent.loop.index`` variable.
209
+
210
+ Defining undefined Functions and Filters on the Fly
211
+ ---------------------------------------------------
212
+
213
+ When a function (or a filter) is not defined, Twig defaults to throw a
214
+ ``Twig_Error_Syntax`` exception. However, it can also call a `callback`_ (any
215
+ valid PHP callable) which should return a function (or a filter).
216
+
217
+ For filters, register callbacks with ``registerUndefinedFilterCallback()``.
218
+ For functions, use ``registerUndefinedFunctionCallback()``::
219
+
220
+ // auto-register all native PHP functions as Twig functions
221
+ // don't try this at home as it's not secure at all!
222
+ $twig->registerUndefinedFunctionCallback(function ($name) {
223
+ if (function_exists($name)) {
224
+ return new Twig_Function_Function($name);
225
+ }
226
+
227
+ return false;
228
+ });
229
+
230
+ If the callable is not able to return a valid function (or filter), it must
231
+ return ``false``.
232
+
233
+ If you register more than one callback, Twig will call them in turn until one
234
+ does not return ``false``.
235
+
236
+ .. tip::
237
+
238
+ As the resolution of functions and filters is done during compilation,
239
+ there is no overhead when registering these callbacks.
240
+
241
+ Validating the Template Syntax
242
+ ------------------------------
243
+
244
+ When template code is providing by a third-party (through a web interface for
245
+ instance), it might be interesting to validate the template syntax before
246
+ saving it. If the template code is stored in a `$template` variable, here is
247
+ how you can do it::
248
+
249
+ try {
250
+ $twig->parse($twig->tokenize($template));
251
+
252
+ // the $template is valid
253
+ } catch (Twig_Error_Syntax $e) {
254
+ // $template contains one or more syntax errors
255
+ }
256
+
257
+ If you iterate over a set of files, you can pass the filename to the
258
+ ``tokenize()`` method to get the filename in the exception message::
259
+
260
+ foreach ($files as $file) {
261
+ try {
262
+ $twig->parse($twig->tokenize($template, $file));
263
+
264
+ // the $template is valid
265
+ } catch (Twig_Error_Syntax $e) {
266
+ // $template contains one or more syntax errors
267
+ }
268
+ }
269
+
270
+ .. note::
271
+
272
+ This method won't catch any sandbox policy violations because the policy
273
+ is enforced during template rendering (as Twig needs the context for some
274
+ checks like allowed methods on objects).
275
+
276
+ Refreshing modified Templates when APC is enabled and apc.stat = 0
277
+ ------------------------------------------------------------------
278
+
279
+ When using APC with ``apc.stat`` set to ``0`` and Twig cache enabled, clearing
280
+ the template cache won't update the APC cache. To get around this, one can
281
+ extend ``Twig_Environment`` and force the update of the APC cache when Twig
282
+ rewrites the cache::
283
+
284
+ class Twig_Environment_APC extends Twig_Environment
285
+ {
286
+ protected function writeCacheFile($file, $content)
287
+ {
288
+ parent::writeCacheFile($file, $content);
289
+
290
+ // Compile cached file into bytecode cache
291
+ apc_compile_file($file);
292
+ }
293
+ }
294
+
295
+ Reusing a stateful Node Visitor
296
+ -------------------------------
297
+
298
+ When attaching a visitor to a ``Twig_Environment`` instance, Twig uses it to
299
+ visit *all* templates it compiles. If you need to keep some state information
300
+ around, you probably want to reset it when visiting a new template.
301
+
302
+ This can be easily achieved with the following code::
303
+
304
+ protected $someTemplateState = array();
305
+
306
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
307
+ {
308
+ if ($node instanceof Twig_Node_Module) {
309
+ // reset the state as we are entering a new template
310
+ $this->someTemplateState = array();
311
+ }
312
+
313
+ // ...
314
+
315
+ return $node;
316
+ }
317
+
318
+ Using the Template name to set the default Escaping Strategy
319
+ ------------------------------------------------------------
320
+
321
+ .. versionadded:: 1.8
322
+ This recipe requires Twig 1.8 or later.
323
+
324
+ The ``autoescape`` option determines the default escaping strategy to use when
325
+ no escaping is applied on a variable. When Twig is used to mostly generate
326
+ HTML files, you can set it to ``html`` and explicitly change it to ``js`` when
327
+ you have some dynamic JavaScript files thanks to the ``autoescape`` tag:
328
+
329
+ .. code-block:: jinja
330
+
331
+ {% autoescape 'js' %}
332
+ ... some JS ...
333
+ {% endautoescape %}
334
+
335
+ But if you have many HTML and JS files, and if your template names follow some
336
+ conventions, you can instead determine the default escaping strategy to use
337
+ based on the template name. Let's say that your template names always ends
338
+ with ``.html`` for HTML files, ``.js`` for JavaScript ones, and ``.css`` for
339
+ stylesheets, here is how you can configure Twig::
340
+
341
+ class TwigEscapingGuesser
342
+ {
343
+ function guess($filename)
344
+ {
345
+ // get the format
346
+ $format = substr($filename, strrpos($filename, '.') + 1);
347
+
348
+ switch ($format) {
349
+ case 'js':
350
+ return 'js';
351
+ case 'css':
352
+ return 'css';
353
+ case 'html':
354
+ default:
355
+ return 'html';
356
+ }
357
+ }
358
+ }
359
+
360
+ $loader = new Twig_Loader_Filesystem('/path/to/templates');
361
+ $twig = new Twig_Environment($loader, array(
362
+ 'autoescape' => array(new TwigEscapingGuesser(), 'guess'),
363
+ ));
364
+
365
+ This dynamic strategy does not incur any overhead at runtime as auto-escaping
366
+ is done at compilation time.
367
+
368
+ Using a Database to store Templates
369
+ -----------------------------------
370
+
371
+ If you are developing a CMS, templates are usually stored in a database. This
372
+ recipe gives you a simple PDO template loader you can use as a starting point
373
+ for your own.
374
+
375
+ First, let's create a temporary in-memory SQLite3 database to work with::
376
+
377
+ $dbh = new PDO('sqlite::memory:');
378
+ $dbh->exec('CREATE TABLE templates (name STRING, source STRING, last_modified INTEGER)');
379
+ $base = '{% block content %}{% endblock %}';
380
+ $index = '
381
+ {% extends "base.twig" %}
382
+ {% block content %}Hello {{ name }}{% endblock %}
383
+ ';
384
+ $now = time();
385
+ $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('base.twig', '$base', $now)");
386
+ $dbh->exec("INSERT INTO templates (name, source, last_modified) VALUES ('index.twig', '$index', $now)");
387
+
388
+ We have created a simple ``templates`` table that hosts two templates:
389
+ ``base.twig`` and ``index.twig``.
390
+
391
+ Now, let's define a loader able to use this database::
392
+
393
+ class DatabaseTwigLoader implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
394
+ {
395
+ protected $dbh;
396
+
397
+ public function __construct(PDO $dbh)
398
+ {
399
+ $this->dbh = $dbh;
400
+ }
401
+
402
+ public function getSource($name)
403
+ {
404
+ if (false === $source = $this->getValue('source', $name)) {
405
+ throw new Twig_Error_Loader(sprintf('Template "%s" does not exist.', $name));
406
+ }
407
+
408
+ return $source;
409
+ }
410
+
411
+ // Twig_ExistsLoaderInterface as of Twig 1.11
412
+ public function exists($name)
413
+ {
414
+ return $name === $this->getValue('name', $name);
415
+ }
416
+
417
+ public function getCacheKey($name)
418
+ {
419
+ return $name;
420
+ }
421
+
422
+ public function isFresh($name, $time)
423
+ {
424
+ if (false === $lastModified = $this->getValue('last_modified', $name)) {
425
+ return false;
426
+ }
427
+
428
+ return $lastModified <= $time;
429
+ }
430
+
431
+ protected function getValue($column, $name)
432
+ {
433
+ $sth = $this->dbh->prepare('SELECT '.$column.' FROM templates WHERE name = :name');
434
+ $sth->execute(array(':name' => (string) $name));
435
+
436
+ return $sth->fetchColumn();
437
+ }
438
+ }
439
+
440
+ Finally, here is an example on how you can use it::
441
+
442
+ $loader = new DatabaseTwigLoader($dbh);
443
+ $twig = new Twig_Environment($loader);
444
+
445
+ echo $twig->render('index.twig', array('name' => 'Fabien'));
446
+
447
+ Using different Template Sources
448
+ --------------------------------
449
+
450
+ This recipe is the continuation of the previous one. Even if you store the
451
+ contributed templates in a database, you might want to keep the original/base
452
+ templates on the filesystem. When templates can be loaded from different
453
+ sources, you need to use the ``Twig_Loader_Chain`` loader.
454
+
455
+ As you can see in the previous recipe, we reference the template in the exact
456
+ same way as we would have done it with a regular filesystem loader. This is
457
+ the key to be able to mix and match templates coming from the database, the
458
+ filesystem, or any other loader for that matter: the template name should be a
459
+ logical name, and not the path from the filesystem::
460
+
461
+ $loader1 = new DatabaseTwigLoader($dbh);
462
+ $loader2 = new Twig_Loader_Array(array(
463
+ 'base.twig' => '{% block content %}{% endblock %}',
464
+ ));
465
+ $loader = new Twig_Loader_Chain(array($loader1, $loader2));
466
+
467
+ $twig = new Twig_Environment($loader);
468
+
469
+ echo $twig->render('index.twig', array('name' => 'Fabien'));
470
+
471
+ Now that the ``base.twig`` templates is defined in an array loader, you can
472
+ remove it from the database, and everything else will still work as before.
473
+
474
+ .. _callback: http://www.php.net/manual/en/function.is-callable.php
Twig/doc/tags/autoescape.rst ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``autoescape``
2
+ ==============
3
+
4
+ Whether automatic escaping is enabled or not, you can mark a section of a
5
+ template to be escaped or not by using the ``autoescape`` tag:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {# The following syntax works as of Twig 1.8 -- see the note below for previous versions #}
10
+
11
+ {% autoescape %}
12
+ Everything will be automatically escaped in this block
13
+ using the HTML strategy
14
+ {% endautoescape %}
15
+
16
+ {% autoescape 'html' %}
17
+ Everything will be automatically escaped in this block
18
+ using the HTML strategy
19
+ {% endautoescape %}
20
+
21
+ {% autoescape 'js' %}
22
+ Everything will be automatically escaped in this block
23
+ using the js escaping strategy
24
+ {% endautoescape %}
25
+
26
+ {% autoescape false %}
27
+ Everything will be outputted as is in this block
28
+ {% endautoescape %}
29
+
30
+ .. note::
31
+
32
+ Before Twig 1.8, the syntax was different:
33
+
34
+ .. code-block:: jinja
35
+
36
+ {% autoescape true %}
37
+ Everything will be automatically escaped in this block
38
+ using the HTML strategy
39
+ {% endautoescape %}
40
+
41
+ {% autoescape false %}
42
+ Everything will be outputted as is in this block
43
+ {% endautoescape %}
44
+
45
+ {% autoescape true js %}
46
+ Everything will be automatically escaped in this block
47
+ using the js escaping strategy
48
+ {% endautoescape %}
49
+
50
+ When automatic escaping is enabled everything is escaped by default except for
51
+ values explicitly marked as safe. Those can be marked in the template by using
52
+ the :doc:`raw<../filters/raw>` filter:
53
+
54
+ .. code-block:: jinja
55
+
56
+ {% autoescape %}
57
+ {{ safe_value|raw }}
58
+ {% endautoescape %}
59
+
60
+ Functions returning template data (like :doc:`macros<macro>` and
61
+ :doc:`parent<../functions/parent>`) always return safe markup.
62
+
63
+ .. note::
64
+
65
+ Twig is smart enough to not escape an already escaped value by the
66
+ :doc:`escape<../filters/escape>` filter.
67
+
68
+ .. note::
69
+
70
+ The chapter :doc:`Twig for Developers<../api>` gives more information
71
+ about when and how automatic escaping is applied.
Twig/doc/tags/block.rst ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ``block``
2
+ =========
3
+
4
+ Blocks are used for inheritance and act as placeholders and replacements at
5
+ the same time. They are documented in detail in the documentation for the
6
+ :doc:`extends<../tags/extends>` tag.
7
+
8
+ Block names should consist of alphanumeric characters, and underscores. Dashes
9
+ are not permitted.
10
+
11
+ .. seealso:: :doc:`block<../functions/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`, :doc:`extends<../tags/extends>`
Twig/doc/tags/do.rst ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``do``
2
+ ======
3
+
4
+ .. versionadded:: 1.5
5
+ The do tag was added in Twig 1.5.
6
+
7
+ The ``do`` tag works exactly like the regular variable expression (``{{ ...
8
+ }}``) just that it doesn't print anything:
9
+
10
+ .. code-block:: jinja
11
+
12
+ {% do 1 + 2 %}
Twig/doc/tags/embed.rst ADDED
@@ -0,0 +1,178 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``embed``
2
+ =========
3
+
4
+ .. versionadded:: 1.8
5
+ The ``embed`` tag was added in Twig 1.8.
6
+
7
+ The ``embed`` tag combines the behaviour of :doc:`include<include>` and
8
+ :doc:`extends<extends>`.
9
+ It allows you to include another template's contents, just like ``include``
10
+ does. But it also allows you to override any block defined inside the
11
+ included template, like when extending a template.
12
+
13
+ Think of an embedded template as a "micro layout skeleton".
14
+
15
+ .. code-block:: jinja
16
+
17
+ {% embed "teasers_skeleton.twig" %}
18
+ {# These blocks are defined in "teasers_skeleton.twig" #}
19
+ {# and we override them right here: #}
20
+ {% block left_teaser %}
21
+ Some content for the left teaser box
22
+ {% endblock %}
23
+ {% block right_teaser %}
24
+ Some content for the right teaser box
25
+ {% endblock %}
26
+ {% endembed %}
27
+
28
+ The ``embed`` tag takes the idea of template inheritance to the level of
29
+ content fragments. While template inheritance allows for "document skeletons",
30
+ which are filled with life by child templates, the ``embed`` tag allows you to
31
+ create "skeletons" for smaller units of content and re-use and fill them
32
+ anywhere you like.
33
+
34
+ Since the use case may not be obvious, let's look at a simplified example.
35
+ Imagine a base template shared by multiple HTML pages, defining a single block
36
+ named "content":
37
+
38
+ .. code-block:: text
39
+
40
+ ┌─── page layout ─────────────────────┐
41
+ │ │
42
+ │ ┌── block "content" ──┐ │
43
+ │ │ │ │
44
+ │ │ │ │
45
+ │ │ (child template to │ │
46
+ │ │ put content here) │ │
47
+ │ │ │ │
48
+ │ │ │ │
49
+ │ └─────────────────────┘ │
50
+ │ │
51
+ └─────────────────────────────────────┘
52
+
53
+ Some pages ("foo" and "bar") share the same content structure -
54
+ two vertically stacked boxes:
55
+
56
+ .. code-block:: text
57
+
58
+ ┌─── page layout ─────────────────────┐
59
+ │ │
60
+ │ ┌── block "content" ──┐ │
61
+ │ │ ┌─ block "top" ───┐ │ │
62
+ │ │ │ │ │ │
63
+ │ │ └─────────────────┘ │ │
64
+ │ │ ┌─ block "bottom" ┐ │ │
65
+ │ │ │ │ │ │
66
+ │ │ └─────────────────┘ │ │
67
+ │ └─────────────────────┘ │
68
+ │ │
69
+ └─────────────────────────────────────┘
70
+
71
+ While other pages ("boom" and "baz") share a different content structure -
72
+ two boxes side by side:
73
+
74
+ .. code-block:: text
75
+
76
+ ┌─── page layout ─────────────────────┐
77
+ │ │
78
+ │ ┌── block "content" ──┐ │
79
+ │ │ │ │
80
+ │ │ ┌ block ┐ ┌ block ┐ │ │
81
+ │ │ │"left" │ │"right"│ │ │
82
+ │ │ │ │ │ │ │ │
83
+ │ │ │ │ │ │ │ │
84
+ │ │ └───────┘ └───────┘ │ │
85
+ │ └─────────────────────┘ │
86
+ │ │
87
+ └─────────────────────────────────────┘
88
+
89
+ Without the ``embed`` tag, you have two ways to design your templates:
90
+
91
+ * Create two "intermediate" base templates that extend the master layout
92
+ template: one with vertically stacked boxes to be used by the "foo" and
93
+ "bar" pages and another one with side-by-side boxes for the "boom" and
94
+ "baz" pages.
95
+
96
+ * Embed the markup for the top/bottom and left/right boxes into each page
97
+ template directly.
98
+
99
+ These two solutions do not scale well because they each have a major drawback:
100
+
101
+ * The first solution may indeed work for this simplified example. But imagine
102
+ we add a sidebar, which may again contain different, recurring structures
103
+ of content. Now we would need to create intermediate base templates for
104
+ all occurring combinations of content structure and sidebar structure...
105
+ and so on.
106
+
107
+ * The second solution involves duplication of common code with all its negative
108
+ consequences: any change involves finding and editing all affected copies
109
+ of the structure, correctness has to be verified for each copy, copies may
110
+ go out of sync by careless modifications etc.
111
+
112
+ In such a situation, the ``embed`` tag comes in handy. The common layout
113
+ code can live in a single base template, and the two different content structures,
114
+ let's call them "micro layouts" go into separate templates which are embedded
115
+ as necessary:
116
+
117
+ Page template ``foo.twig``:
118
+
119
+ .. code-block:: jinja
120
+
121
+ {% extends "layout_skeleton.twig" %}
122
+
123
+ {% block content %}
124
+ {% embed "vertical_boxes_skeleton.twig" %}
125
+ {% block top %}
126
+ Some content for the top box
127
+ {% endblock %}
128
+
129
+ {% block bottom %}
130
+ Some content for the bottom box
131
+ {% endblock %}
132
+ {% endembed %}
133
+ {% endblock %}
134
+
135
+ And here is the code for ``vertical_boxes_skeleton.twig``:
136
+
137
+ .. code-block:: html+jinja
138
+
139
+ <div class="top_box">
140
+ {% block top %}
141
+ Top box default content
142
+ {% endblock %}
143
+ </div>
144
+
145
+ <div class="bottom_box">
146
+ {% block bottom %}
147
+ Bottom box default content
148
+ {% endblock %}
149
+ </div>
150
+
151
+ The goal of the ``vertical_boxes_skeleton.twig`` template being to factor
152
+ out the HTML markup for the boxes.
153
+
154
+ The ``embed`` tag takes the exact same arguments as the ``include`` tag:
155
+
156
+ .. code-block:: jinja
157
+
158
+ {% embed "base" with {'foo': 'bar'} %}
159
+ ...
160
+ {% endembed %}
161
+
162
+ {% embed "base" with {'foo': 'bar'} only %}
163
+ ...
164
+ {% endembed %}
165
+
166
+ {% embed "base" ignore missing %}
167
+ ...
168
+ {% endembed %}
169
+
170
+ .. warning::
171
+
172
+ As embedded templates do not have "names", auto-escaping strategies based
173
+ on the template "filename" won't work as expected if you change the
174
+ context (for instance, if you embed a CSS/JavaScript template into an HTML
175
+ one). In that case, explicitly set the default auto-escaping strategy with
176
+ the ``autoescape`` tag.
177
+
178
+ .. seealso:: :doc:`include<../tags/include>`
Twig/doc/tags/extends.rst ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``extends``
2
+ ===========
3
+
4
+ The ``extends`` tag can be used to extend a template from another one.
5
+
6
+ .. note::
7
+
8
+ Like PHP, Twig does not support multiple inheritance. So you can only have
9
+ one extends tag called per rendering. However, Twig supports horizontal
10
+ :doc:`reuse<use>`.
11
+
12
+ Let's define a base template, ``base.html``, which defines a simple HTML
13
+ skeleton document:
14
+
15
+ .. code-block:: html+jinja
16
+
17
+ <!DOCTYPE html>
18
+ <html>
19
+ <head>
20
+ {% block head %}
21
+ <link rel="stylesheet" href="style.css" />
22
+ <title>{% block title %}{% endblock %} - My Webpage</title>
23
+ {% endblock %}
24
+ </head>
25
+ <body>
26
+ <div id="content">{% block content %}{% endblock %}</div>
27
+ <div id="footer">
28
+ {% block footer %}
29
+ &copy; Copyright 2011 by <a href="http://domain.invalid/">you</a>.
30
+ {% endblock %}
31
+ </div>
32
+ </body>
33
+ </html>
34
+
35
+ In this example, the :doc:`block<block>` tags define four blocks that child
36
+ templates can fill in.
37
+
38
+ All the ``block`` tag does is to tell the template engine that a child
39
+ template may override those portions of the template.
40
+
41
+ Child Template
42
+ --------------
43
+
44
+ A child template might look like this:
45
+
46
+ .. code-block:: jinja
47
+
48
+ {% extends "base.html" %}
49
+
50
+ {% block title %}Index{% endblock %}
51
+ {% block head %}
52
+ {{ parent() }}
53
+ <style type="text/css">
54
+ .important { color: #336699; }
55
+ </style>
56
+ {% endblock %}
57
+ {% block content %}
58
+ <h1>Index</h1>
59
+ <p class="important">
60
+ Welcome on my awesome homepage.
61
+ </p>
62
+ {% endblock %}
63
+
64
+ The ``extends`` tag is the key here. It tells the template engine that this
65
+ template "extends" another template. When the template system evaluates this
66
+ template, first it locates the parent. The extends tag should be the first tag
67
+ in the template.
68
+
69
+ Note that since the child template doesn't define the ``footer`` block, the
70
+ value from the parent template is used instead.
71
+
72
+ You can't define multiple ``block`` tags with the same name in the same
73
+ template. This limitation exists because a block tag works in "both"
74
+ directions. That is, a block tag doesn't just provide a hole to fill - it also
75
+ defines the content that fills the hole in the *parent*. If there were two
76
+ similarly-named ``block`` tags in a template, that template's parent wouldn't
77
+ know which one of the blocks' content to use.
78
+
79
+ If you want to print a block multiple times you can however use the
80
+ ``block`` function:
81
+
82
+ .. code-block:: jinja
83
+
84
+ <title>{% block title %}{% endblock %}</title>
85
+ <h1>{{ block('title') }}</h1>
86
+ {% block body %}{% endblock %}
87
+
88
+ Parent Blocks
89
+ -------------
90
+
91
+ It's possible to render the contents of the parent block by using the
92
+ :doc:`parent<../functions/parent>` function. This gives back the results of
93
+ the parent block:
94
+
95
+ .. code-block:: jinja
96
+
97
+ {% block sidebar %}
98
+ <h3>Table Of Contents</h3>
99
+ ...
100
+ {{ parent() }}
101
+ {% endblock %}
102
+
103
+ Named Block End-Tags
104
+ --------------------
105
+
106
+ Twig allows you to put the name of the block after the end tag for better
107
+ readability:
108
+
109
+ .. code-block:: jinja
110
+
111
+ {% block sidebar %}
112
+ {% block inner_sidebar %}
113
+ ...
114
+ {% endblock inner_sidebar %}
115
+ {% endblock sidebar %}
116
+
117
+ Of course, the name after the ``endblock`` word must match the block name.
118
+
119
+ Block Nesting and Scope
120
+ -----------------------
121
+
122
+ Blocks can be nested for more complex layouts. Per default, blocks have access
123
+ to variables from outer scopes:
124
+
125
+ .. code-block:: jinja
126
+
127
+ {% for item in seq %}
128
+ <li>{% block loop_item %}{{ item }}{% endblock %}</li>
129
+ {% endfor %}
130
+
131
+ Block Shortcuts
132
+ ---------------
133
+
134
+ For blocks with few content, it's possible to use a shortcut syntax. The
135
+ following constructs do the same:
136
+
137
+ .. code-block:: jinja
138
+
139
+ {% block title %}
140
+ {{ page_title|title }}
141
+ {% endblock %}
142
+
143
+ .. code-block:: jinja
144
+
145
+ {% block title page_title|title %}
146
+
147
+ Dynamic Inheritance
148
+ -------------------
149
+
150
+ Twig supports dynamic inheritance by using a variable as the base template:
151
+
152
+ .. code-block:: jinja
153
+
154
+ {% extends some_var %}
155
+
156
+ If the variable evaluates to a ``Twig_Template`` object, Twig will use it as
157
+ the parent template::
158
+
159
+ // {% extends layout %}
160
+
161
+ $layout = $twig->loadTemplate('some_layout_template.twig');
162
+
163
+ $twig->display('template.twig', array('layout' => $layout));
164
+
165
+ .. versionadded:: 1.2
166
+ The possibility to pass an array of templates has been added in Twig 1.2.
167
+
168
+ You can also provide a list of templates that are checked for existence. The
169
+ first template that exists will be used as a parent:
170
+
171
+ .. code-block:: jinja
172
+
173
+ {% extends ['layout.html', 'base_layout.html'] %}
174
+
175
+ Conditional Inheritance
176
+ -----------------------
177
+
178
+ As the template name for the parent can be any valid Twig expression, it's
179
+ possible to make the inheritance mechanism conditional:
180
+
181
+ .. code-block:: jinja
182
+
183
+ {% extends standalone ? "minimum.html" : "base.html" %}
184
+
185
+ In this example, the template will extend the "minimum.html" layout template
186
+ if the ``standalone`` variable evaluates to ``true``, and "base.html"
187
+ otherwise.
188
+
189
+ How blocks work?
190
+ ----------------
191
+
192
+ A block provides a way to change how a certain part of a template is rendered
193
+ but it does not interfere in any way with the logic around it.
194
+
195
+ Let's take the following example to illustrate how a block work and more
196
+ importantly, how it does not work:
197
+
198
+ .. code-block:: jinja
199
+
200
+ {# base.twig #}
201
+
202
+ {% for post in posts %}
203
+ {% block post %}
204
+ <h1>{{ post.title }}</h1>
205
+ <p>{{ post.body }}</p>
206
+ {% endblock %}
207
+ {% endfor %}
208
+
209
+ If you render this template, the result would be exactly the same with or
210
+ without the ``block`` tag. The ``block`` inside the ``for`` loop is just a way
211
+ to make it overridable by a child template:
212
+
213
+ .. code-block:: jinja
214
+
215
+ {# child.twig #}
216
+
217
+ {% extends "base.twig" %}
218
+
219
+ {% block post %}
220
+ <article>
221
+ <header>{{ post.title }}</header>
222
+ <section>{{ post.text }}</section>
223
+ </article>
224
+ {% endblock %}
225
+
226
+ Now, when rendering the child template, the loop is going to use the block
227
+ defined in the child template instead of the one defined in the base one; the
228
+ executed template is then equivalent to the following one:
229
+
230
+ .. code-block:: jinja
231
+
232
+ {% for post in posts %}
233
+ <article>
234
+ <header>{{ post.title }}</header>
235
+ <section>{{ post.text }}</section>
236
+ </article>
237
+ {% endfor %}
238
+
239
+ Let's take another example: a block included within an ``if`` statement:
240
+
241
+ .. code-block:: jinja
242
+
243
+ {% if posts is empty %}
244
+ {% block head %}
245
+ {{ parent() }}
246
+
247
+ <meta name="robots" content="noindex, follow">
248
+ {% endblock head %}
249
+ {% endif %}
250
+
251
+ Contrary to what you might think, this template does not define a block
252
+ conditionally; it just makes overridable by a child template the output of
253
+ what will be rendered when the condition is ``true``.
254
+
255
+ If you want the output to be displayed conditionally, use the following
256
+ instead:
257
+
258
+ .. code-block:: jinja
259
+
260
+ {% block head %}
261
+ {{ parent() }}
262
+
263
+ {% if posts is empty %}
264
+ <meta name="robots" content="noindex, follow">
265
+ {% endif %}
266
+ {% endblock head %}
267
+
268
+ .. seealso:: :doc:`block<../functions/block>`, :doc:`block<../tags/block>`, :doc:`parent<../functions/parent>`, :doc:`use<../tags/use>`
Twig/doc/tags/filter.rst ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``filter``
2
+ ==========
3
+
4
+ Filter sections allow you to apply regular Twig filters on a block of template
5
+ data. Just wrap the code in the special ``filter`` section:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% filter upper %}
10
+ This text becomes uppercase
11
+ {% endfilter %}
12
+
13
+ You can also chain filters:
14
+
15
+ .. code-block:: jinja
16
+
17
+ {% filter lower|escape %}
18
+ <strong>SOME TEXT</strong>
19
+ {% endfilter %}
20
+
21
+ {# outputs "&lt;strong&gt;some text&lt;/strong&gt;" #}
Twig/doc/tags/flush.rst ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``flush``
2
+ =========
3
+
4
+ .. versionadded:: 1.5
5
+ The flush tag was added in Twig 1.5.
6
+
7
+ The ``flush`` tag tells Twig to flush the output buffer:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {% flush %}
12
+
13
+ .. note::
14
+
15
+ Internally, Twig uses the PHP `flush`_ function.
16
+
17
+ .. _`flush`: http://php.net/flush
Twig/doc/tags/for.rst ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``for``
2
+ =======
3
+
4
+ Loop over each item in a sequence. For example, to display a list of users
5
+ provided in a variable called ``users``:
6
+
7
+ .. code-block:: jinja
8
+
9
+ <h1>Members</h1>
10
+ <ul>
11
+ {% for user in users %}
12
+ <li>{{ user.username|e }}</li>
13
+ {% endfor %}
14
+ </ul>
15
+
16
+ .. note::
17
+
18
+ A sequence can be either an array or an object implementing the
19
+ ``Traversable`` interface.
20
+
21
+ If you do need to iterate over a sequence of numbers, you can use the ``..``
22
+ operator:
23
+
24
+ .. code-block:: jinja
25
+
26
+ {% for i in 0..10 %}
27
+ * {{ i }}
28
+ {% endfor %}
29
+
30
+ The above snippet of code would print all numbers from 0 to 10.
31
+
32
+ It can be also useful with letters:
33
+
34
+ .. code-block:: jinja
35
+
36
+ {% for letter in 'a'..'z' %}
37
+ * {{ letter }}
38
+ {% endfor %}
39
+
40
+ The ``..`` operator can take any expression at both sides:
41
+
42
+ .. code-block:: jinja
43
+
44
+ {% for letter in 'a'|upper..'z'|upper %}
45
+ * {{ letter }}
46
+ {% endfor %}
47
+
48
+ .. tip:
49
+
50
+ If you need a step different from 1, you can use the ``range`` function
51
+ instead.
52
+
53
+ The `loop` variable
54
+ -------------------
55
+
56
+ Inside of a ``for`` loop block you can access some special variables:
57
+
58
+ ===================== =============================================================
59
+ Variable Description
60
+ ===================== =============================================================
61
+ ``loop.index`` The current iteration of the loop. (1 indexed)
62
+ ``loop.index0`` The current iteration of the loop. (0 indexed)
63
+ ``loop.revindex`` The number of iterations from the end of the loop (1 indexed)
64
+ ``loop.revindex0`` The number of iterations from the end of the loop (0 indexed)
65
+ ``loop.first`` True if first iteration
66
+ ``loop.last`` True if last iteration
67
+ ``loop.length`` The number of items in the sequence
68
+ ``loop.parent`` The parent context
69
+ ===================== =============================================================
70
+
71
+ .. code-block:: jinja
72
+
73
+ {% for user in users %}
74
+ {{ loop.index }} - {{ user.username }}
75
+ {% endfor %}
76
+
77
+ .. note::
78
+
79
+ The ``loop.length``, ``loop.revindex``, ``loop.revindex0``, and
80
+ ``loop.last`` variables are only available for PHP arrays, or objects that
81
+ implement the ``Countable`` interface. They are also not available when
82
+ looping with a condition.
83
+
84
+ .. versionadded:: 1.2
85
+ The ``if`` modifier support has been added in Twig 1.2.
86
+
87
+ Adding a condition
88
+ ------------------
89
+
90
+ Unlike in PHP, it's not possible to ``break`` or ``continue`` in a loop. You
91
+ can however filter the sequence during iteration which allows you to skip
92
+ items. The following example skips all the users which are not active:
93
+
94
+ .. code-block:: jinja
95
+
96
+ <ul>
97
+ {% for user in users if user.active %}
98
+ <li>{{ user.username|e }}</li>
99
+ {% endfor %}
100
+ </ul>
101
+
102
+ The advantage is that the special loop variable will count correctly thus not
103
+ counting the users not iterated over. Keep in mind that properties like
104
+ ``loop.last`` will not be defined when using loop conditions.
105
+
106
+ .. note::
107
+
108
+ Using the ``loop`` variable within the condition is not recommended as it
109
+ will probably not be doing what you expect it to. For instance, adding a
110
+ condition like ``loop.index > 4`` won't work as the index is only
111
+ incremented when the condition is true (so the condition will never
112
+ match).
113
+
114
+ The `else` Clause
115
+ -----------------
116
+
117
+ If no iteration took place because the sequence was empty, you can render a
118
+ replacement block by using ``else``:
119
+
120
+ .. code-block:: jinja
121
+
122
+ <ul>
123
+ {% for user in users %}
124
+ <li>{{ user.username|e }}</li>
125
+ {% else %}
126
+ <li><em>no user found</em></li>
127
+ {% endfor %}
128
+ </ul>
129
+
130
+ Iterating over Keys
131
+ -------------------
132
+
133
+ By default, a loop iterates over the values of the sequence. You can iterate
134
+ on keys by using the ``keys`` filter:
135
+
136
+ .. code-block:: jinja
137
+
138
+ <h1>Members</h1>
139
+ <ul>
140
+ {% for key in users|keys %}
141
+ <li>{{ key }}</li>
142
+ {% endfor %}
143
+ </ul>
144
+
145
+ Iterating over Keys and Values
146
+ ------------------------------
147
+
148
+ You can also access both keys and values:
149
+
150
+ .. code-block:: jinja
151
+
152
+ <h1>Members</h1>
153
+ <ul>
154
+ {% for key, user in users %}
155
+ <li>{{ key }}: {{ user.username|e }}</li>
156
+ {% endfor %}
157
+ </ul>
Twig/doc/tags/from.rst ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ ``from``
2
+ ========
3
+
4
+ The ``from`` tags import :doc:`macro<../tags/macro>` names into the current
5
+ namespace. The tag is documented in detail in the documentation for the
6
+ :doc:`import<../tags/import>` tag.
7
+
8
+ .. seealso:: :doc:`macro<../tags/macro>`, :doc:`import<../tags/import>`
Twig/doc/tags/if.rst ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``if``
2
+ ======
3
+
4
+ The ``if`` statement in Twig is comparable with the if statements of PHP.
5
+
6
+ In the simplest form you can use it to test if an expression evaluates to
7
+ ``true``:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {% if online == false %}
12
+ <p>Our website is in maintenance mode. Please, come back later.</p>
13
+ {% endif %}
14
+
15
+ You can also test if an array is not empty:
16
+
17
+ .. code-block:: jinja
18
+
19
+ {% if users %}
20
+ <ul>
21
+ {% for user in users %}
22
+ <li>{{ user.username|e }}</li>
23
+ {% endfor %}
24
+ </ul>
25
+ {% endif %}
26
+
27
+ .. note::
28
+
29
+ If you want to test if the variable is defined, use ``if users is
30
+ defined`` instead.
31
+
32
+ For multiple branches ``elseif`` and ``else`` can be used like in PHP. You can use
33
+ more complex ``expressions`` there too:
34
+
35
+ .. code-block:: jinja
36
+
37
+ {% if kenny.sick %}
38
+ Kenny is sick.
39
+ {% elseif kenny.dead %}
40
+ You killed Kenny! You bastard!!!
41
+ {% else %}
42
+ Kenny looks okay --- so far
43
+ {% endif %}
Twig/doc/tags/import.rst ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``import``
2
+ ==========
3
+
4
+ Twig supports putting often used code into :doc:`macros<../tags/macro>`. These
5
+ macros can go into different templates and get imported from there.
6
+
7
+ There are two ways to import templates. You can import the complete template
8
+ into a variable or request specific macros from it.
9
+
10
+ Imagine we have a helper module that renders forms (called ``forms.html``):
11
+
12
+ .. code-block:: jinja
13
+
14
+ {% macro input(name, value, type, size) %}
15
+ <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
16
+ {% endmacro %}
17
+
18
+ {% macro textarea(name, value, rows) %}
19
+ <textarea name="{{ name }}" rows="{{ rows|default(10) }}" cols="{{ cols|default(40) }}">{{ value|e }}</textarea>
20
+ {% endmacro %}
21
+
22
+ The easiest and most flexible is importing the whole module into a variable.
23
+ That way you can access the attributes:
24
+
25
+ .. code-block:: jinja
26
+
27
+ {% import 'forms.html' as forms %}
28
+
29
+ <dl>
30
+ <dt>Username</dt>
31
+ <dd>{{ forms.input('username') }}</dd>
32
+ <dt>Password</dt>
33
+ <dd>{{ forms.input('password', null, 'password') }}</dd>
34
+ </dl>
35
+ <p>{{ forms.textarea('comment') }}</p>
36
+
37
+ Alternatively you can import names from the template into the current
38
+ namespace:
39
+
40
+ .. code-block:: jinja
41
+
42
+ {% from 'forms.html' import input as input_field, textarea %}
43
+
44
+ <dl>
45
+ <dt>Username</dt>
46
+ <dd>{{ input_field('username') }}</dd>
47
+ <dt>Password</dt>
48
+ <dd>{{ input_field('password', '', 'password') }}</dd>
49
+ </dl>
50
+ <p>{{ textarea('comment') }}</p>
51
+
52
+ .. tip::
53
+
54
+ To import macros from the current file, use the special ``_self`` variable
55
+ for the source.
56
+
57
+ .. seealso:: :doc:`macro<../tags/macro>`, :doc:`from<../tags/from>`
Twig/doc/tags/include.rst ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``include``
2
+ ===========
3
+
4
+ The ``include`` statement includes a template and return the rendered content
5
+ of that file into the current namespace:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% include 'header.html' %}
10
+ Body
11
+ {% include 'footer.html' %}
12
+
13
+ Included templates have access to the variables of the active context.
14
+
15
+ If you are using the filesystem loader, the templates are looked for in the
16
+ paths defined by it.
17
+
18
+ You can add additional variables by passing them after the ``with`` keyword:
19
+
20
+ .. code-block:: jinja
21
+
22
+ {# template.html will have access to the variables from the current context and the additional ones provided #}
23
+ {% include 'template.html' with {'foo': 'bar'} %}
24
+
25
+ {% set vars = {'foo': 'bar'} %}
26
+ {% include 'template.html' with vars %}
27
+
28
+ You can disable access to the context by appending the ``only`` keyword:
29
+
30
+ .. code-block:: jinja
31
+
32
+ {# only the foo variable will be accessible #}
33
+ {% include 'template.html' with {'foo': 'bar'} only %}
34
+
35
+ .. code-block:: jinja
36
+
37
+ {# no variables will be accessible #}
38
+ {% include 'template.html' only %}
39
+
40
+ .. tip::
41
+
42
+ When including a template created by an end user, you should consider
43
+ sandboxing it. More information in the :doc:`Twig for Developers<../api>`
44
+ chapter and in the :doc:`sandbox<../tags/sandbox>` tag documentation.
45
+
46
+ The template name can be any valid Twig expression:
47
+
48
+ .. code-block:: jinja
49
+
50
+ {% include some_var %}
51
+ {% include ajax ? 'ajax.html' : 'not_ajax.html' %}
52
+
53
+ And if the expression evaluates to a ``Twig_Template`` object, Twig will use it
54
+ directly::
55
+
56
+ // {% include template %}
57
+
58
+ $template = $twig->loadTemplate('some_template.twig');
59
+
60
+ $twig->loadTemplate('template.twig')->display(array('template' => $template));
61
+
62
+ .. versionadded:: 1.2
63
+ The ``ignore missing`` feature has been added in Twig 1.2.
64
+
65
+ You can mark an include with ``ignore missing`` in which case Twig will ignore
66
+ the statement if the template to be ignored does not exist. It has to be
67
+ placed just after the template name. Here some valid examples:
68
+
69
+ .. code-block:: jinja
70
+
71
+ {% include 'sidebar.html' ignore missing %}
72
+ {% include 'sidebar.html' ignore missing with {'foo': 'bar'} %}
73
+ {% include 'sidebar.html' ignore missing only %}
74
+
75
+ .. versionadded:: 1.2
76
+ The possibility to pass an array of templates has been added in Twig 1.2.
77
+
78
+ You can also provide a list of templates that are checked for existence before
79
+ inclusion. The first template that exists will be included:
80
+
81
+ .. code-block:: jinja
82
+
83
+ {% include ['page_detailed.html', 'page.html'] %}
84
+
85
+ If ``ignore missing`` is given, it will fall back to rendering nothing if none
86
+ of the templates exist, otherwise it will throw an exception.
Twig/doc/tags/index.rst ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Tags
2
+ ====
3
+
4
+ .. toctree::
5
+ :maxdepth: 1
6
+
7
+ for
8
+ if
9
+ macro
10
+ filter
11
+ set
12
+ extends
13
+ block
14
+ include
15
+ import
16
+ from
17
+ use
18
+ spaceless
19
+ autoescape
20
+ verbatim
21
+ flush
22
+ do
23
+ sandbox
24
+ embed
Twig/doc/tags/macro.rst ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``macro``
2
+ =========
3
+
4
+ Macros are comparable with functions in regular programming languages. They
5
+ are useful to put often used HTML idioms into reusable elements to not repeat
6
+ yourself.
7
+
8
+ Here is a small example of a macro that renders a form element:
9
+
10
+ .. code-block:: jinja
11
+
12
+ {% macro input(name, value, type, size) %}
13
+ <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
14
+ {% endmacro %}
15
+
16
+ Macros differs from native PHP functions in a few ways:
17
+
18
+ * Default argument values are defined by using the ``default`` filter in the
19
+ macro body;
20
+
21
+ * Arguments of a macro are always optional.
22
+
23
+ But as PHP functions, macros don't have access to the current template
24
+ variables.
25
+
26
+ .. tip::
27
+
28
+ You can pass the whole context as an argument by using the special
29
+ ``_context`` variable.
30
+
31
+ Macros can be defined in any template, and need to be "imported" before being
32
+ used (see the documentation for the :doc:`import<../tags/import>` tag for more
33
+ information):
34
+
35
+ .. code-block:: jinja
36
+
37
+ {% import "forms.html" as forms %}
38
+
39
+ The above ``import`` call imports the "forms.html" file (which can contain only
40
+ macros, or a template and some macros), and import the functions as items of
41
+ the ``forms`` variable.
42
+
43
+ The macro can then be called at will:
44
+
45
+ .. code-block:: jinja
46
+
47
+ <p>{{ forms.input('username') }}</p>
48
+ <p>{{ forms.input('password', null, 'password') }}</p>
49
+
50
+ If macros are defined and used in the same template, you can use the
51
+ special ``_self`` variable to import them:
52
+
53
+ .. code-block:: jinja
54
+
55
+ {% import _self as forms %}
56
+
57
+ <p>{{ forms.input('username') }}</p>
58
+
59
+ .. warning::
60
+
61
+ When you define a macro in the template where you are going to use it, you
62
+ might be tempted to call the macro directly via ``_self.input()`` instead
63
+ of importing it; even if seems to work, this is just a side-effect of the
64
+ current implementation and it won't work anymore in Twig 2.x.
65
+
66
+ When you want to use a macro in another macro from the same file, you need to
67
+ import it locally:
68
+
69
+ .. code-block:: jinja
70
+
71
+ {% macro input(name, value, type, size) %}
72
+ <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
73
+ {% endmacro %}
74
+
75
+ {% macro wrapped_input(name, value, type, size) %}
76
+ {% import _self as forms %}
77
+
78
+ <div class="field">
79
+ {{ forms.input(name, value, type, size) }}
80
+ </div>
81
+ {% endmacro %}
82
+
83
+ .. seealso:: :doc:`from<../tags/from>`, :doc:`import<../tags/import>`
Twig/doc/tags/sandbox.rst ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``sandbox``
2
+ ===========
3
+
4
+ The ``sandbox`` tag can be used to enable the sandboxing mode for an included
5
+ template, when sandboxing is not enabled globally for the Twig environment:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% sandbox %}
10
+ {% include 'user.html' %}
11
+ {% endsandbox %}
12
+
13
+ .. warning::
14
+
15
+ The ``sandbox`` tag is only available when the sandbox extension is
16
+ enabled (see the :doc:`Twig for Developers<../api>` chapter).
17
+
18
+ .. note::
19
+
20
+ The ``sandbox`` tag can only be used to sandbox an include tag and it
21
+ cannot be used to sandbox a section of a template. The following example
22
+ won't work:
23
+
24
+ .. code-block:: jinja
25
+
26
+ {% sandbox %}
27
+ {% for i in 1..2 %}
28
+ {{ i }}
29
+ {% endfor %}
30
+ {% endsandbox %}
Twig/doc/tags/set.rst ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``set``
2
+ =======
3
+
4
+ Inside code blocks you can also assign values to variables. Assignments use
5
+ the ``set`` tag and can have multiple targets:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% set foo = 'foo' %}
10
+
11
+ {% set foo = [1, 2] %}
12
+
13
+ {% set foo = {'foo': 'bar'} %}
14
+
15
+ {% set foo = 'foo' ~ 'bar' %}
16
+
17
+ {% set foo, bar = 'foo', 'bar' %}
18
+
19
+ The ``set`` tag can also be used to 'capture' chunks of text:
20
+
21
+ .. code-block:: jinja
22
+
23
+ {% set foo %}
24
+ <div id="pagination">
25
+ ...
26
+ </div>
27
+ {% endset %}
28
+
29
+ .. caution::
30
+
31
+ If you enable automatic output escaping, Twig will only consider the
32
+ content to be safe when capturing chunks of text.
Twig/doc/tags/spaceless.rst ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``spaceless``
2
+ =============
3
+
4
+ Use the ``spaceless`` tag to remove whitespace *between HTML tags*, not
5
+ whitespace within HTML tags or whitespace in plain text:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% spaceless %}
10
+ <div>
11
+ <strong>foo</strong>
12
+ </div>
13
+ {% endspaceless %}
14
+
15
+ {# output will be <div><strong>foo</strong></div> #}
16
+
17
+ This tag is not meant to "optimize" the size of the generated HTML content but
18
+ merely to avoid extra whitespace between HTML tags to avoid browser rendering
19
+ quirks under some circumstances.
20
+
21
+ .. tip::
22
+
23
+ If you want to optimize the size of the generated HTML content, gzip
24
+ compress the output instead.
25
+
26
+ .. tip::
27
+
28
+ If you want to create a tag that actually removes all extra whitespace in
29
+ an HTML string, be warned that this is not as easy as it seems to be
30
+ (think of ``textarea`` or ``pre`` tags for instance). Using a third-party
31
+ library like Tidy is probably a better idea.
32
+
33
+ .. tip::
34
+
35
+ For more information on whitespace control, read the
36
+ :doc:`dedicated<../templates>` section of the documentation and learn how
37
+ you can also use the whitespace control modifier on your tags.
Twig/doc/tags/use.rst ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``use``
2
+ =======
3
+
4
+ .. versionadded:: 1.1
5
+ Horizontal reuse was added in Twig 1.1.
6
+
7
+ .. note::
8
+
9
+ Horizontal reuse is an advanced Twig feature that is hardly ever needed in
10
+ regular templates. It is mainly used by projects that need to make
11
+ template blocks reusable without using inheritance.
12
+
13
+ Template inheritance is one of the most powerful Twig's feature but it is
14
+ limited to single inheritance; a template can only extend one other template.
15
+ This limitation makes template inheritance simple to understand and easy to
16
+ debug:
17
+
18
+ .. code-block:: jinja
19
+
20
+ {% extends "base.html" %}
21
+
22
+ {% block title %}{% endblock %}
23
+ {% block content %}{% endblock %}
24
+
25
+ Horizontal reuse is a way to achieve the same goal as multiple inheritance,
26
+ but without the associated complexity:
27
+
28
+ .. code-block:: jinja
29
+
30
+ {% extends "base.html" %}
31
+
32
+ {% use "blocks.html" %}
33
+
34
+ {% block title %}{% endblock %}
35
+ {% block content %}{% endblock %}
36
+
37
+ The ``use`` statement tells Twig to import the blocks defined in
38
+ ```blocks.html`` into the current template (it's like macros, but for blocks):
39
+
40
+ .. code-block:: jinja
41
+
42
+ # blocks.html
43
+ {% block sidebar %}{% endblock %}
44
+
45
+ In this example, the ``use`` statement imports the ``sidebar`` block into the
46
+ main template. The code is mostly equivalent to the following one (the
47
+ imported blocks are not outputted automatically):
48
+
49
+ .. code-block:: jinja
50
+
51
+ {% extends "base.html" %}
52
+
53
+ {% block sidebar %}{% endblock %}
54
+ {% block title %}{% endblock %}
55
+ {% block content %}{% endblock %}
56
+
57
+ .. note::
58
+
59
+ The ``use`` tag only imports a template if it does not extend another
60
+ template, if it does not define macros, and if the body is empty. But it
61
+ can *use* other templates.
62
+
63
+ .. note::
64
+
65
+ Because ``use`` statements are resolved independently of the context
66
+ passed to the template, the template reference cannot be an expression.
67
+
68
+ The main template can also override any imported block. If the template
69
+ already defines the ``sidebar`` block, then the one defined in ``blocks.html``
70
+ is ignored. To avoid name conflicts, you can rename imported blocks:
71
+
72
+ .. code-block:: jinja
73
+
74
+ {% extends "base.html" %}
75
+
76
+ {% use "blocks.html" with sidebar as base_sidebar %}
77
+
78
+ {% block sidebar %}{% endblock %}
79
+ {% block title %}{% endblock %}
80
+ {% block content %}{% endblock %}
81
+
82
+ .. versionadded:: 1.3
83
+ The ``parent()`` support was added in Twig 1.3.
84
+
85
+ The ``parent()`` function automatically determines the correct inheritance
86
+ tree, so it can be used when overriding a block defined in an imported
87
+ template:
88
+
89
+ .. code-block:: jinja
90
+
91
+ {% extends "base.html" %}
92
+
93
+ {% use "blocks.html" %}
94
+
95
+ {% block sidebar %}
96
+ {{ parent() }}
97
+ {% endblock %}
98
+
99
+ {% block title %}{% endblock %}
100
+ {% block content %}{% endblock %}
101
+
102
+ In this example, ``parent()`` will correctly call the ``sidebar`` block from
103
+ the ``blocks.html`` template.
104
+
105
+ .. tip::
106
+
107
+ In Twig 1.2, renaming allows you to simulate inheritance by calling the
108
+ "parent" block:
109
+
110
+ .. code-block:: jinja
111
+
112
+ {% extends "base.html" %}
113
+
114
+ {% use "blocks.html" with sidebar as parent_sidebar %}
115
+
116
+ {% block sidebar %}
117
+ {{ block('parent_sidebar') }}
118
+ {% endblock %}
119
+
120
+ .. note::
121
+
122
+ You can use as many ``use`` statements as you want in any given template.
123
+ If two imported templates define the same block, the latest one wins.
Twig/doc/tags/verbatim.rst ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``verbatim``
2
+ ============
3
+
4
+ .. versionadded:: 1.12
5
+ The ``verbatim`` tag was added in Twig 1.12 (it was named ``raw`` before).
6
+
7
+ The ``verbatim`` tag marks sections as being raw text that should not be
8
+ parsed. For example to put Twig syntax as example into a template you can use
9
+ this snippet:
10
+
11
+ .. code-block:: jinja
12
+
13
+ {% verbatim %}
14
+ <ul>
15
+ {% for item in seq %}
16
+ <li>{{ item }}</li>
17
+ {% endfor %}
18
+ </ul>
19
+ {% endverbatim %}
20
+
21
+ .. note::
22
+
23
+ The ``verbatim`` tag works in the exact same way as the old ``raw`` tag,
24
+ but was renamed to avoid confusion with the ``raw`` filter.
Twig/doc/templates.rst ADDED
@@ -0,0 +1,837 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Twig for Template Designers
2
+ ===========================
3
+
4
+ This document describes the syntax and semantics of the template engine and
5
+ will be most useful as reference to those creating Twig templates.
6
+
7
+ Synopsis
8
+ --------
9
+
10
+ A template is simply a text file. It can generate any text-based format (HTML,
11
+ XML, CSV, LaTeX, etc.). It doesn't have a specific extension, ``.html`` or
12
+ ``.xml`` are just fine.
13
+
14
+ A template contains **variables** or **expressions**, which get replaced with
15
+ values when the template is evaluated, and **tags**, which control the logic
16
+ of the template.
17
+
18
+ Below is a minimal template that illustrates a few basics. We will cover the
19
+ details later on:
20
+
21
+ .. code-block:: html+jinja
22
+
23
+ <!DOCTYPE html>
24
+ <html>
25
+ <head>
26
+ <title>My Webpage</title>
27
+ </head>
28
+ <body>
29
+ <ul id="navigation">
30
+ {% for item in navigation %}
31
+ <li><a href="{{ item.href }}">{{ item.caption }}</a></li>
32
+ {% endfor %}
33
+ </ul>
34
+
35
+ <h1>My Webpage</h1>
36
+ {{ a_variable }}
37
+ </body>
38
+ </html>
39
+
40
+ There are two kinds of delimiters: ``{% ... %}`` and ``{{ ... }}``. The first
41
+ one is used to execute statements such as for-loops, the latter prints the
42
+ result of an expression to the template.
43
+
44
+ IDEs Integration
45
+ ----------------
46
+
47
+ Many IDEs support syntax highlighting and auto-completion for Twig:
48
+
49
+ * *Textmate* via the `Twig bundle`_
50
+ * *Vim* via the `Jinja syntax plugin`_
51
+ * *Netbeans* via the `Twig syntax plugin`_ (until 7.1, native as of 7.2)
52
+ * *PhpStorm* (native as of 2.1)
53
+ * *Eclipse* via the `Twig plugin`_
54
+ * *Sublime Text* via the `Twig bundle`_
55
+ * *GtkSourceView* via the `Twig language definition`_ (used by gedit and other projects)
56
+ * *Coda* and *SubEthaEdit* via the `Twig syntax mode`_
57
+ * *Coda 2* via the `other Twig syntax mode`_
58
+ * *Komodo* and *Komodo Edit* via the Twig highlight/syntax check mode
59
+ * *Notepad++* via the `Notepad++ Twig Highlighter`_
60
+ * *Emacs* via `web-mode.el`_
61
+
62
+ Variables
63
+ ---------
64
+
65
+ The application passes variables to the templates you can mess around in the
66
+ template. Variables may have attributes or elements on them you can access
67
+ too. How a variable looks like heavily depends on the application providing
68
+ those.
69
+
70
+ You can use a dot (``.``) to access attributes of a variable (methods or
71
+ properties of a PHP object, or items of a PHP array), or the so-called
72
+ "subscript" syntax (``[]``):
73
+
74
+ .. code-block:: jinja
75
+
76
+ {{ foo.bar }}
77
+ {{ foo['bar'] }}
78
+
79
+ When the attribute contains special characters (like ``-`` that would be
80
+ interpreted as the minus operator), use the ``attribute`` function instead to
81
+ access the variable attribute:
82
+
83
+ .. code-block:: jinja
84
+
85
+ {# equivalent to the non-working foo.data-foo #}
86
+ {{ attribute(foo, 'data-foo') }}
87
+
88
+ .. note::
89
+
90
+ It's important to know that the curly braces are *not* part of the
91
+ variable but the print statement. If you access variables inside tags
92
+ don't put the braces around.
93
+
94
+ If a variable or attribute does not exist, you will get back a ``null`` value
95
+ when the ``strict_variables`` option is set to ``false``, otherwise Twig will
96
+ throw an error (see :ref:`environment options<environment_options>`).
97
+
98
+ .. sidebar:: Implementation
99
+
100
+ For convenience sake ``foo.bar`` does the following things on the PHP
101
+ layer:
102
+
103
+ * check if ``foo`` is an array and ``bar`` a valid element;
104
+ * if not, and if ``foo`` is an object, check that ``bar`` is a valid property;
105
+ * if not, and if ``foo`` is an object, check that ``bar`` is a valid method
106
+ (even if ``bar`` is the constructor - use ``__construct()`` instead);
107
+ * if not, and if ``foo`` is an object, check that ``getBar`` is a valid method;
108
+ * if not, and if ``foo`` is an object, check that ``isBar`` is a valid method;
109
+ * if not, return a ``null`` value.
110
+
111
+ ``foo['bar']`` on the other hand only works with PHP arrays:
112
+
113
+ * check if ``foo`` is an array and ``bar`` a valid element;
114
+ * if not, return a ``null`` value.
115
+
116
+ .. note::
117
+
118
+ If you want to get a dynamic attribute on a variable, use the
119
+ :doc:`attribute<functions/attribute>` function instead.
120
+
121
+ Global Variables
122
+ ~~~~~~~~~~~~~~~~
123
+
124
+ The following variables are always available in templates:
125
+
126
+ * ``_self``: references the current template;
127
+ * ``_context``: references the current context;
128
+ * ``_charset``: references the current charset.
129
+
130
+ Setting Variables
131
+ ~~~~~~~~~~~~~~~~~
132
+
133
+ You can assign values to variables inside code blocks. Assignments use the
134
+ :doc:`set<tags/set>` tag:
135
+
136
+ .. code-block:: jinja
137
+
138
+ {% set foo = 'foo' %}
139
+ {% set foo = [1, 2] %}
140
+ {% set foo = {'foo': 'bar'} %}
141
+
142
+ Filters
143
+ -------
144
+
145
+ Variables can be modified by **filters**. Filters are separated from the
146
+ variable by a pipe symbol (``|``) and may have optional arguments in
147
+ parentheses. Multiple filters can be chained. The output of one filter is
148
+ applied to the next.
149
+
150
+ The following example removes all HTML tags from the ``name`` and title-cases
151
+ it:
152
+
153
+ .. code-block:: jinja
154
+
155
+ {{ name|striptags|title }}
156
+
157
+ Filters that accept arguments have parentheses around the arguments. This
158
+ example will join a list by commas:
159
+
160
+ .. code-block:: jinja
161
+
162
+ {{ list|join(', ') }}
163
+
164
+ To apply a filter on a section of code, wrap it with the
165
+ :doc:`filter<tags/filter>` tag:
166
+
167
+ .. code-block:: jinja
168
+
169
+ {% filter upper %}
170
+ This text becomes uppercase
171
+ {% endfilter %}
172
+
173
+ Go to the :doc:`filters<filters/index>` page to learn more about the built-in
174
+ filters.
175
+
176
+ Functions
177
+ ---------
178
+
179
+ Functions can be called to generate content. Functions are called by their
180
+ name followed by parentheses (``()``) and may have arguments.
181
+
182
+ For instance, the ``range`` function returns a list containing an arithmetic
183
+ progression of integers:
184
+
185
+ .. code-block:: jinja
186
+
187
+ {% for i in range(0, 3) %}
188
+ {{ i }},
189
+ {% endfor %}
190
+
191
+ Go to the :doc:`functions<functions/index>` page to learn more about the
192
+ built-in functions.
193
+
194
+ Named Arguments
195
+ ---------------
196
+
197
+ .. versionadded:: 1.12
198
+ Support for named arguments was added in Twig 1.12.
199
+
200
+ Arguments for filters and functions can also be passed as *named arguments*:
201
+
202
+ .. code-block:: jinja
203
+
204
+ {% for i in range(low=1, high=10, step=2) %}
205
+ {{ i }},
206
+ {% endfor %}
207
+
208
+ Using named arguments makes your templates more explicit about the meaning of
209
+ the values you pass as arguments:
210
+
211
+ .. code-block:: jinja
212
+
213
+ {{ data|convert_encoding('UTF-8', 'iso-2022-jp') }}
214
+
215
+ {# versus #}
216
+
217
+ {{ data|convert_encoding(from='iso-2022-jp', to='UTF-8') }}
218
+
219
+ Named arguments also allow you to skip some arguments for which you don't want
220
+ to change the default value:
221
+
222
+ .. code-block:: jinja
223
+
224
+ {# the first argument is the date format, which defaults to the global date format if null is passed #}
225
+ {{ "now"|date(null, "Europe/Paris") }}
226
+
227
+ {# or skip the format value by using a named argument for the timezone #}
228
+ {{ "now"|date(timezone="Europe/Paris") }}
229
+
230
+ You can also use both positional and named arguments in one call, which is not
231
+ recommended as it can be confusing:
232
+
233
+ .. code-block:: jinja
234
+
235
+ {# both work #}
236
+ {{ "now"|date('d/m/Y H:i', timezone="Europe/Paris") }}
237
+ {{ "now"|date(timezone="Europe/Paris", 'd/m/Y H:i') }}
238
+
239
+ .. tip::
240
+
241
+ Each function and filter documentation page has a section where the names
242
+ of all arguments are listed when supported.
243
+
244
+ Control Structure
245
+ -----------------
246
+
247
+ A control structure refers to all those things that control the flow of a
248
+ program - conditionals (i.e. ``if``/``elseif``/``else``), ``for``-loops, as
249
+ well as things like blocks. Control structures appear inside ``{% ... %}``
250
+ blocks.
251
+
252
+ For example, to display a list of users provided in a variable called
253
+ ``users``, use the :doc:`for<tags/for>` tag:
254
+
255
+ .. code-block:: jinja
256
+
257
+ <h1>Members</h1>
258
+ <ul>
259
+ {% for user in users %}
260
+ <li>{{ user.username|e }}</li>
261
+ {% endfor %}
262
+ </ul>
263
+
264
+ The :doc:`if<tags/if>` tag can be used to test an expression:
265
+
266
+ .. code-block:: jinja
267
+
268
+ {% if users|length > 0 %}
269
+ <ul>
270
+ {% for user in users %}
271
+ <li>{{ user.username|e }}</li>
272
+ {% endfor %}
273
+ </ul>
274
+ {% endif %}
275
+
276
+ Go to the :doc:`tags<tags/index>` page to learn more about the built-in tags.
277
+
278
+ Comments
279
+ --------
280
+
281
+ To comment-out part of a line in a template, use the comment syntax ``{# ...
282
+ #}``. This is useful for debugging or to add information for other template
283
+ designers or yourself:
284
+
285
+ .. code-block:: jinja
286
+
287
+ {# note: disabled template because we no longer use this
288
+ {% for user in users %}
289
+ ...
290
+ {% endfor %}
291
+ #}
292
+
293
+ Including other Templates
294
+ -------------------------
295
+
296
+ The :doc:`include<tags/include>` tag is useful to include a template and
297
+ return the rendered content of that template into the current one:
298
+
299
+ .. code-block:: jinja
300
+
301
+ {% include 'sidebar.html' %}
302
+
303
+ Per default included templates are passed the current context.
304
+
305
+ The context that is passed to the included template includes variables defined
306
+ in the template:
307
+
308
+ .. code-block:: jinja
309
+
310
+ {% for box in boxes %}
311
+ {% include "render_box.html" %}
312
+ {% endfor %}
313
+
314
+ The included template ``render_box.html`` is able to access ``box``.
315
+
316
+ The filename of the template depends on the template loader. For instance, the
317
+ ``Twig_Loader_Filesystem`` allows you to access other templates by giving the
318
+ filename. You can access templates in subdirectories with a slash:
319
+
320
+ .. code-block:: jinja
321
+
322
+ {% include "sections/articles/sidebar.html" %}
323
+
324
+ This behavior depends on the application embedding Twig.
325
+
326
+ Template Inheritance
327
+ --------------------
328
+
329
+ The most powerful part of Twig is template inheritance. Template inheritance
330
+ allows you to build a base "skeleton" template that contains all the common
331
+ elements of your site and defines **blocks** that child templates can
332
+ override.
333
+
334
+ Sounds complicated but is very basic. It's easier to understand it by
335
+ starting with an example.
336
+
337
+ Let's define a base template, ``base.html``, which defines a simple HTML
338
+ skeleton document that you might use for a simple two-column page:
339
+
340
+ .. code-block:: html+jinja
341
+
342
+ <!DOCTYPE html>
343
+ <html>
344
+ <head>
345
+ {% block head %}
346
+ <link rel="stylesheet" href="style.css" />
347
+ <title>{% block title %}{% endblock %} - My Webpage</title>
348
+ {% endblock %}
349
+ </head>
350
+ <body>
351
+ <div id="content">{% block content %}{% endblock %}</div>
352
+ <div id="footer">
353
+ {% block footer %}
354
+ &copy; Copyright 2011 by <a href="http://domain.invalid/">you</a>.
355
+ {% endblock %}
356
+ </div>
357
+ </body>
358
+ </html>
359
+
360
+ In this example, the :doc:`block<tags/block>` tags define four blocks that
361
+ child templates can fill in. All the ``block`` tag does is to tell the
362
+ template engine that a child template may override those portions of the
363
+ template.
364
+
365
+ A child template might look like this:
366
+
367
+ .. code-block:: jinja
368
+
369
+ {% extends "base.html" %}
370
+
371
+ {% block title %}Index{% endblock %}
372
+ {% block head %}
373
+ {{ parent() }}
374
+ <style type="text/css">
375
+ .important { color: #336699; }
376
+ </style>
377
+ {% endblock %}
378
+ {% block content %}
379
+ <h1>Index</h1>
380
+ <p class="important">
381
+ Welcome to my awesome homepage.
382
+ </p>
383
+ {% endblock %}
384
+
385
+ The :doc:`extends<tags/extends>` tag is the key here. It tells the template
386
+ engine that this template "extends" another template. When the template system
387
+ evaluates this template, first it locates the parent. The extends tag should
388
+ be the first tag in the template.
389
+
390
+ Note that since the child template doesn't define the ``footer`` block, the
391
+ value from the parent template is used instead.
392
+
393
+ It's possible to render the contents of the parent block by using the
394
+ :doc:`parent<functions/parent>` function. This gives back the results of the
395
+ parent block:
396
+
397
+ .. code-block:: jinja
398
+
399
+ {% block sidebar %}
400
+ <h3>Table Of Contents</h3>
401
+ ...
402
+ {{ parent() }}
403
+ {% endblock %}
404
+
405
+ .. tip::
406
+
407
+ The documentation page for the :doc:`extends<tags/extends>` tag describes
408
+ more advanced features like block nesting, scope, dynamic inheritance, and
409
+ conditional inheritance.
410
+
411
+ .. note::
412
+
413
+ Twig also supports multiple inheritance with the so called horizontal reuse
414
+ with the help of the :doc:`use<tags/use>` tag. This is an advanced feature
415
+ hardly ever needed in regular templates.
416
+
417
+ HTML Escaping
418
+ -------------
419
+
420
+ When generating HTML from templates, there's always a risk that a variable
421
+ will include characters that affect the resulting HTML. There are two
422
+ approaches: manually escaping each variable or automatically escaping
423
+ everything by default.
424
+
425
+ Twig supports both, automatic escaping is enabled by default.
426
+
427
+ .. note::
428
+
429
+ Automatic escaping is only supported if the *escaper* extension has been
430
+ enabled (which is the default).
431
+
432
+ Working with Manual Escaping
433
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
434
+
435
+ If manual escaping is enabled, it is **your** responsibility to escape
436
+ variables if needed. What to escape? Any variable you don't trust.
437
+
438
+ Escaping works by piping the variable through the
439
+ :doc:`escape<filters/escape>` or ``e`` filter:
440
+
441
+ .. code-block:: jinja
442
+
443
+ {{ user.username|e }}
444
+
445
+ By default, the ``escape`` filter uses the ``html`` strategy, but depending on
446
+ the escaping context, you might want to explicitly use any other available
447
+ strategies:
448
+
449
+ {{ user.username|e('js') }}
450
+ {{ user.username|e('css') }}
451
+ {{ user.username|e('url') }}
452
+ {{ user.username|e('html_attr') }}
453
+
454
+ Working with Automatic Escaping
455
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
456
+
457
+ Whether automatic escaping is enabled or not, you can mark a section of a
458
+ template to be escaped or not by using the :doc:`autoescape<tags/autoescape>`
459
+ tag:
460
+
461
+ .. code-block:: jinja
462
+
463
+ {% autoescape %}
464
+ Everything will be automatically escaped in this block (using the HTML strategy)
465
+ {% endautoescape %}
466
+
467
+ By default, auto-escaping uses the ``html`` escaping strategy. If you output
468
+ variables in other contexts, you need to explicitly escape them with the
469
+ appropriate escaping strategy:
470
+
471
+ .. code-block:: jinja
472
+
473
+ {% autoescape 'js' %}
474
+ Everything will be automatically escaped in this block (using the JS strategy)
475
+ {% endautoescape %}
476
+
477
+ Escaping
478
+ --------
479
+
480
+ It is sometimes desirable or even necessary to have Twig ignore parts it would
481
+ otherwise handle as variables or blocks. For example if the default syntax is
482
+ used and you want to use ``{{`` as raw string in the template and not start a
483
+ variable you have to use a trick.
484
+
485
+ The easiest way is to output the variable delimiter (``{{``) by using a variable
486
+ expression:
487
+
488
+ .. code-block:: jinja
489
+
490
+ {{ '{{' }}
491
+
492
+ For bigger sections it makes sense to mark a block
493
+ :doc:`verbatim<tags/verbatim>`.
494
+
495
+ Macros
496
+ ------
497
+
498
+ .. versionadded:: 1.12
499
+ Support for default argument values was added in Twig 1.12.
500
+
501
+ Macros are comparable with functions in regular programming languages. They
502
+ are useful to reuse often used HTML fragments to not repeat yourself.
503
+
504
+ A macro is defined via the :doc:`macro<tags/macro>` tag. Here is a small example
505
+ (subsequently called ``forms.html``) of a macro that renders a form element:
506
+
507
+ .. code-block:: jinja
508
+
509
+ {% macro input(name, value, type, size) %}
510
+ <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
511
+ {% endmacro %}
512
+
513
+ Macros can be defined in any template, and need to be "imported" via the
514
+ :doc:`import<tags/import>` tag before being used:
515
+
516
+ .. code-block:: jinja
517
+
518
+ {% import "forms.html" as forms %}
519
+
520
+ <p>{{ forms.input('username') }}</p>
521
+
522
+ Alternatively, you can import individual macro names from a template into the
523
+ current namespace via the :doc:`from<tags/from>` tag and optionally alias them:
524
+
525
+ .. code-block:: jinja
526
+
527
+ {% from 'forms.html' import input as input_field %}
528
+
529
+ <dl>
530
+ <dt>Username</dt>
531
+ <dd>{{ input_field('username') }}</dd>
532
+ <dt>Password</dt>
533
+ <dd>{{ input_field('password', '', 'password') }}</dd>
534
+ </dl>
535
+
536
+ A default value can also be defined for macro arguments when not provided in a
537
+ macro call:
538
+
539
+ .. code-block:: jinja
540
+
541
+ {% macro input(name, value = "", type = "text", size = 20) %}
542
+ <input type="{{ type }}" name="{{ name }}" value="{{ value|e }}" size="{{ size }}" />
543
+ {% endmacro %}
544
+
545
+ Expressions
546
+ -----------
547
+
548
+ Twig allows expressions everywhere. These work very similar to regular PHP and
549
+ even if you're not working with PHP you should feel comfortable with it.
550
+
551
+ .. note::
552
+
553
+ The operator precedence is as follows, with the lowest-precedence
554
+ operators listed first: ``b-and``, ``b-xor``, ``b-or``, ``or``, ``and``,
555
+ ``==``, ``!=``, ``<``, ``>``, ``>=``, ``<=``, ``in``, ``..``, ``+``,
556
+ ``-``, ``~``, ``*``, ``/``, ``//``, ``%``, ``is``, and ``**``.
557
+
558
+ Literals
559
+ ~~~~~~~~
560
+
561
+ .. versionadded:: 1.5
562
+ Support for hash keys as names and expressions was added in Twig 1.5.
563
+
564
+ The simplest form of expressions are literals. Literals are representations
565
+ for PHP types such as strings, numbers, and arrays. The following literals
566
+ exist:
567
+
568
+ * ``"Hello World"``: Everything between two double or single quotes is a
569
+ string. They are useful whenever you need a string in the template (for
570
+ example as arguments to function calls, filters or just to extend or
571
+ include a template).
572
+
573
+ * ``42`` / ``42.23``: Integers and floating point numbers are created by just
574
+ writing the number down. If a dot is present the number is a float,
575
+ otherwise an integer.
576
+
577
+ * ``["foo", "bar"]``: Arrays are defined by a sequence of expressions
578
+ separated by a comma (``,``) and wrapped with squared brackets (``[]``).
579
+
580
+ * ``{"foo": "bar"}``: Hashes are defined by a list of keys and values
581
+ separated by a comma (``,``) and wrapped with curly braces (``{}``):
582
+
583
+ .. code-block:: jinja
584
+
585
+ {# keys as string #}
586
+ { 'foo': 'foo', 'bar': 'bar' }
587
+
588
+ {# keys as names (equivalent to the previous hash) -- as of Twig 1.5 #}
589
+ { foo: 'foo', bar: 'bar' }
590
+
591
+ {# keys as integer #}
592
+ { 2: 'foo', 4: 'bar' }
593
+
594
+ {# keys as expressions (the expression must be enclosed into parentheses) -- as of Twig 1.5 #}
595
+ { (1 + 1): 'foo', (a ~ 'b'): 'bar' }
596
+
597
+ * ``true`` / ``false``: ``true`` represents the true value, ``false``
598
+ represents the false value.
599
+
600
+ * ``null``: ``null`` represents no specific value. This is the value returned
601
+ when a variable does not exist. ``none`` is an alias for ``null``.
602
+
603
+ Arrays and hashes can be nested:
604
+
605
+ .. code-block:: jinja
606
+
607
+ {% set foo = [1, {"foo": "bar"}] %}
608
+
609
+ .. tip::
610
+
611
+ Using double-quoted or single-quoted strings has no impact on performance
612
+ but string interpolation is only supported in double-quoted strings.
613
+
614
+ Math
615
+ ~~~~
616
+
617
+ Twig allows you to calculate with values. This is rarely useful in templates
618
+ but exists for completeness' sake. The following operators are supported:
619
+
620
+ * ``+``: Adds two objects together (the operands are casted to numbers). ``{{
621
+ 1 + 1 }}`` is ``2``.
622
+
623
+ * ``-``: Subtracts the second number from the first one. ``{{ 3 - 2 }}`` is
624
+ ``1``.
625
+
626
+ * ``/``: Divides two numbers. The returned value will be a floating point
627
+ number. ``{{ 1 / 2 }}`` is ``{{ 0.5 }}``.
628
+
629
+ * ``%``: Calculates the remainder of an integer division. ``{{ 11 % 7 }}`` is
630
+ ``4``.
631
+
632
+ * ``//``: Divides two numbers and returns the truncated integer result. ``{{
633
+ 20 // 7 }}`` is ``2``.
634
+
635
+ * ``*``: Multiplies the left operand with the right one. ``{{ 2 * 2 }}`` would
636
+ return ``4``.
637
+
638
+ * ``**``: Raises the left operand to the power of the right operand. ``{{ 2 **
639
+ 3 }}`` would return ``8``.
640
+
641
+ Logic
642
+ ~~~~~
643
+
644
+ You can combine multiple expressions with the following operators:
645
+
646
+ * ``and``: Returns true if the left and the right operands are both true.
647
+
648
+ * ``or``: Returns true if the left or the right operand is true.
649
+
650
+ * ``not``: Negates a statement.
651
+
652
+ * ``(expr)``: Groups an expression.
653
+
654
+ .. note::
655
+
656
+ Twig also support bitwise operators (``b-and``, ``b-xor``, and ``b-or``).
657
+
658
+ Comparisons
659
+ ~~~~~~~~~~~
660
+
661
+ The following comparison operators are supported in any expression: ``==``,
662
+ ``!=``, ``<``, ``>``, ``>=``, and ``<=``.
663
+
664
+ Containment Operator
665
+ ~~~~~~~~~~~~~~~~~~~~
666
+
667
+ The ``in`` operator performs containment test.
668
+
669
+ It returns ``true`` if the left operand is contained in the right:
670
+
671
+ .. code-block:: jinja
672
+
673
+ {# returns true #}
674
+
675
+ {{ 1 in [1, 2, 3] }}
676
+
677
+ {{ 'cd' in 'abcde' }}
678
+
679
+ .. tip::
680
+
681
+ You can use this filter to perform a containment test on strings, arrays,
682
+ or objects implementing the ``Traversable`` interface.
683
+
684
+ To perform a negative test, use the ``not in`` operator:
685
+
686
+ .. code-block:: jinja
687
+
688
+ {% if 1 not in [1, 2, 3] %}
689
+
690
+ {# is equivalent to #}
691
+ {% if not (1 in [1, 2, 3]) %}
692
+
693
+ Test Operator
694
+ ~~~~~~~~~~~~~
695
+
696
+ The ``is`` operator performs tests. Tests can be used to test a variable against
697
+ a common expression. The right operand is name of the test:
698
+
699
+ .. code-block:: jinja
700
+
701
+ {# find out if a variable is odd #}
702
+
703
+ {{ name is odd }}
704
+
705
+ Tests can accept arguments too:
706
+
707
+ .. code-block:: jinja
708
+
709
+ {% if loop.index is divisibleby(3) %}
710
+
711
+ Tests can be negated by using the ``is not`` operator:
712
+
713
+ .. code-block:: jinja
714
+
715
+ {% if loop.index is not divisibleby(3) %}
716
+
717
+ {# is equivalent to #}
718
+ {% if not (loop.index is divisibleby(3)) %}
719
+
720
+ Go to the :doc:`tests<tests/index>` page to learn more about the built-in
721
+ tests.
722
+
723
+ Other Operators
724
+ ~~~~~~~~~~~~~~~
725
+
726
+ .. versionadded:: 1.12.0
727
+ Support for the extended ternary operator was added in Twig 1.12.0.
728
+
729
+ The following operators are very useful but don't fit into any of the other
730
+ categories:
731
+
732
+ * ``..``: Creates a sequence based on the operand before and after the
733
+ operator (this is just syntactic sugar for the :doc:`range<functions/range>`
734
+ function).
735
+
736
+ * ``|``: Applies a filter.
737
+
738
+ * ``~``: Converts all operands into strings and concatenates them. ``{{ "Hello
739
+ " ~ name ~ "!" }}`` would return (assuming ``name`` is ``'John'``) ``Hello
740
+ John!``.
741
+
742
+ * ``.``, ``[]``: Gets an attribute of an object.
743
+
744
+ * ``?:``: The ternary operator:
745
+
746
+ .. code-block:: jinja
747
+
748
+ {{ foo ? 'yes' : 'no' }}
749
+
750
+ {# as of Twig 1.12.0 #}
751
+ {{ foo ?: 'no' }} == {{ foo ? foo : 'no' }}
752
+ {{ foo ? 'yes' }} == {{ foo ? 'yes' : '' }}
753
+
754
+ String Interpolation
755
+ ~~~~~~~~~~~~~~~~~~~~
756
+
757
+ .. versionadded:: 1.5
758
+ String interpolation was added in Twig 1.5.
759
+
760
+ String interpolation (`#{expression}`) allows any valid expression to appear
761
+ within a *double-quoted string*. The result of evaluating that expression is
762
+ inserted into the string:
763
+
764
+ .. code-block:: jinja
765
+
766
+ {{ "foo #{bar} baz" }}
767
+ {{ "foo #{1 + 2} baz" }}
768
+
769
+ Whitespace Control
770
+ ------------------
771
+
772
+ .. versionadded:: 1.1
773
+ Tag level whitespace control was added in Twig 1.1.
774
+
775
+ The first newline after a template tag is removed automatically (like in PHP.)
776
+ Whitespace is not further modified by the template engine, so each whitespace
777
+ (spaces, tabs, newlines etc.) is returned unchanged.
778
+
779
+ Use the ``spaceless`` tag to remove whitespace *between HTML tags*:
780
+
781
+ .. code-block:: jinja
782
+
783
+ {% spaceless %}
784
+ <div>
785
+ <strong>foo</strong>
786
+ </div>
787
+ {% endspaceless %}
788
+
789
+ {# output will be <div><strong>foo</strong></div> #}
790
+
791
+ In addition to the spaceless tag you can also control whitespace on a per tag
792
+ level. By using the whitespace control modifier on your tags, you can trim
793
+ leading and or trailing whitespace:
794
+
795
+ .. code-block:: jinja
796
+
797
+ {% set value = 'no spaces' %}
798
+ {#- No leading/trailing whitespace -#}
799
+ {%- if true -%}
800
+ {{- value -}}
801
+ {%- endif -%}
802
+
803
+ {# output 'no spaces' #}
804
+
805
+ The above sample shows the default whitespace control modifier, and how you can
806
+ use it to remove whitespace around tags. Trimming space will consume all whitespace
807
+ for that side of the tag. It is possible to use whitespace trimming on one side
808
+ of a tag:
809
+
810
+ .. code-block:: jinja
811
+
812
+ {% set value = 'no spaces' %}
813
+ <li> {{- value }} </li>
814
+
815
+ {# outputs '<li>no spaces </li>' #}
816
+
817
+ Extensions
818
+ ----------
819
+
820
+ Twig can be easily extended.
821
+
822
+ If you are looking for new tags, filters, or functions, have a look at the Twig official
823
+ `extension repository`_.
824
+
825
+ If you want to create your own, read the :ref:`Creating an
826
+ Extension<creating_extensions>` chapter.
827
+
828
+ .. _`Twig bundle`: https://github.com/Anomareh/PHP-Twig.tmbundle
829
+ .. _`Jinja syntax plugin`: http://jinja.pocoo.org/2/documentation/integration
830
+ .. _`Twig syntax plugin`: http://plugins.netbeans.org/plugin/37069/php-twig
831
+ .. _`Twig plugin`: https://github.com/pulse00/Twig-Eclipse-Plugin
832
+ .. _`Twig language definition`: https://github.com/gabrielcorpse/gedit-twig-template-language
833
+ .. _`extension repository`: http://github.com/fabpot/Twig-extensions
834
+ .. _`Twig syntax mode`: https://github.com/bobthecow/Twig-HTML.mode
835
+ .. _`other Twig syntax mode`: https://github.com/muxx/Twig-HTML.mode
836
+ .. _`Notepad++ Twig Highlighter`: https://github.com/Banane9/notepadplusplus-twig
837
+ .. _`web-mode.el`: http://web-mode.org/
Twig/doc/tests/constant.rst ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ``constant``
2
+ ============
3
+
4
+ ``constant`` checks if a variable has the exact same value as a constant. You
5
+ can use either global constants or class constants:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% if post.status is constant('Post::PUBLISHED') %}
10
+ the status attribute is exactly the same as Post::PUBLISHED
11
+ {% endif %}
Twig/doc/tests/defined.rst ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``defined``
2
+ ===========
3
+
4
+ ``defined`` checks if a variable is defined in the current context. This is very
5
+ useful if you use the ``strict_variables`` option:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {# defined works with variable names #}
10
+ {% if foo is defined %}
11
+ ...
12
+ {% endif %}
13
+
14
+ {# and attributes on variables names #}
15
+ {% if foo.bar is defined %}
16
+ ...
17
+ {% endif %}
18
+
19
+ {% if foo['bar'] is defined %}
20
+ ...
21
+ {% endif %}
22
+
23
+ When using the ``defined`` test on an expression that uses variables in some
24
+ method calls, be sure that they are all defined first:
25
+
26
+ .. code-block:: jinja
27
+
28
+ {% if var is defined and foo.method(var) is defined %}
29
+ ...
30
+ {% endif %}
Twig/doc/tests/divisibleby.rst ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ ``divisibleby``
2
+ ===============
3
+
4
+ ``divisibleby`` checks if a variable is divisible by a number:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {% if loop.index is divisibleby(3) %}
9
+ ...
10
+ {% endif %}
Twig/doc/tests/empty.rst ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ``empty``
2
+ =========
3
+
4
+ ``empty`` checks if a variable is empty:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {# evaluates to true if the foo variable is null, false, an empty array, or the empty string #}
9
+ {% if foo is empty %}
10
+ ...
11
+ {% endif %}
Twig/doc/tests/even.rst ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ ``even``
2
+ ========
3
+
4
+ ``even`` returns ``true`` if the given number is even:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {{ var is even }}
9
+
10
+ .. seealso:: :doc:`odd<../tests/odd>`
Twig/doc/tests/index.rst ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Tests
2
+ =====
3
+
4
+ .. toctree::
5
+ :maxdepth: 1
6
+
7
+ divisibleby
8
+ null
9
+ even
10
+ odd
11
+ sameas
12
+ constant
13
+ defined
14
+ empty
15
+ iterable
Twig/doc/tests/iterable.rst ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``iterable``
2
+ ============
3
+
4
+ .. versionadded:: 1.7
5
+ The iterable test was added in Twig 1.7.
6
+
7
+ ``iterable`` checks if a variable is an array or a traversable object:
8
+
9
+ .. code-block:: jinja
10
+
11
+ {# evaluates to true if the foo variable is iterable #}
12
+ {% if users is iterable %}
13
+ {% for user in users %}
14
+ Hello {{ user }}!
15
+ {% endfor %}
16
+ {% else %}
17
+ {# users is probably a string #}
18
+ Hello {{ users }}!
19
+ {% endif %}
Twig/doc/tests/null.rst ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ``null``
2
+ ========
3
+
4
+ ``null`` returns ``true`` if the variable is ``null``:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {{ var is null }}
9
+
10
+ .. note::
11
+
12
+ ``none`` is an alias for ``null``.
Twig/doc/tests/odd.rst ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ ``odd``
2
+ =======
3
+
4
+ ``odd`` returns ``true`` if the given number is odd:
5
+
6
+ .. code-block:: jinja
7
+
8
+ {{ var is odd }}
9
+
10
+ .. seealso:: :doc:`even<../tests/even>`
Twig/doc/tests/sameas.rst ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ``sameas``
2
+ ==========
3
+
4
+ ``sameas`` checks if a variable points to the same memory address than another
5
+ variable:
6
+
7
+ .. code-block:: jinja
8
+
9
+ {% if foo.attribute is sameas(false) %}
10
+ the foo attribute really is the ``false`` PHP value
11
+ {% endif %}
Twig/ext/twig/.gitignore ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.sw*
2
+ .deps
3
+ Makefile
4
+ Makefile.fragments
5
+ Makefile.global
6
+ Makefile.objects
7
+ acinclude.m4
8
+ aclocal.m4
9
+ build/
10
+ config.cache
11
+ config.guess
12
+ config.h
13
+ config.h.in
14
+ config.log
15
+ config.nice
16
+ config.status
17
+ config.sub
18
+ configure
19
+ configure.in
20
+ install-sh
21
+ libtool
22
+ ltmain.sh
23
+ missing
24
+ mkinstalldirs
25
+ run-tests.php
26
+ twig.loT
27
+ .libs/
28
+ modules/
29
+ twig.la
30
+ twig.lo
Twig/ext/twig/LICENSE ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Copyright (c) 2011, Derick Rethans <derick@derickrethans.nl>
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice,
8
+ this list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright
10
+ notice, this list of conditions and the following disclaimer in the
11
+ documentation and/or other materials provided with the distribution.
12
+
13
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
14
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
17
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
19
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
20
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
21
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
22
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Twig/ext/twig/config.m4 ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ dnl config.m4 for extension twig
2
+
3
+ PHP_ARG_ENABLE(twig, whether to enable twig support,
4
+ [ --enable-twig Enable twig support])
5
+
6
+ if test "$PHP_TWIG" != "no"; then
7
+ PHP_NEW_EXTENSION(twig, twig.c, $ext_shared)
8
+ fi
Twig/ext/twig/config.w32 ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ // vim:ft=javascript
2
+
3
+ ARG_ENABLE("twig", "Twig support", "no");
4
+
5
+ if (PHP_TWIG != "no") {
6
+ AC_DEFINE('HAVE_TWIG', 1);
7
+ EXTENSION('twig', 'twig.c');
8
+ }
Twig/ext/twig/php_twig.h ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ +----------------------------------------------------------------------+
3
+ | Twig Extension |
4
+ +----------------------------------------------------------------------+
5
+ | Copyright (c) 2011 Derick Rethans |
6
+ +----------------------------------------------------------------------+
7
+ | Redistribution and use in source and binary forms, with or without |
8
+ | modification, are permitted provided that the conditions mentioned |
9
+ | in the accompanying LICENSE file are met (BSD, revised). |
10
+ +----------------------------------------------------------------------+
11
+ | Author: Derick Rethans <derick@derickrethans.nl> |
12
+ +----------------------------------------------------------------------+
13
+ */
14
+
15
+ #ifndef PHP_TWIG_H
16
+ #define PHP_TWIG_H
17
+
18
+ #define PHP_TWIG_VERSION "1.12.2-DEV"
19
+
20
+ #include "php.h"
21
+
22
+ extern zend_module_entry twig_module_entry;
23
+ #define phpext_twig_ptr &twig_module_entry
24
+
25
+ #ifdef ZTS
26
+ #include "TSRM.h"
27
+ #endif
28
+
29
+ PHP_FUNCTION(twig_template_get_attributes);
30
+
31
+ #endif
Twig/ext/twig/twig.c ADDED
@@ -0,0 +1,1051 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /*
2
+ +----------------------------------------------------------------------+
3
+ | Twig Extension |
4
+ +----------------------------------------------------------------------+
5
+ | Copyright (c) 2011 Derick Rethans |
6
+ +----------------------------------------------------------------------+
7
+ | Redistribution and use in source and binary forms, with or without |
8
+ | modification, are permitted provided that the conditions mentioned |
9
+ | in the accompanying LICENSE file are met (BSD, revised). |
10
+ +----------------------------------------------------------------------+
11
+ | Author: Derick Rethans <derick@derickrethans.nl> |
12
+ +----------------------------------------------------------------------+
13
+ */
14
+
15
+ #ifdef HAVE_CONFIG_H
16
+ #include "config.h"
17
+ #endif
18
+
19
+ #include "php.h"
20
+ #include "php_twig.h"
21
+ #include "ext/standard/php_string.h"
22
+ #include "ext/standard/php_smart_str.h"
23
+
24
+ #include "Zend/zend_object_handlers.h"
25
+ #include "Zend/zend_interfaces.h"
26
+ #include "Zend/zend_exceptions.h"
27
+
28
+ #ifndef Z_ADDREF_P
29
+ #define Z_ADDREF_P(pz) (pz)->refcount++
30
+ #endif
31
+
32
+ #define FREE_DTOR(z) \
33
+ zval_dtor(z); \
34
+ efree(z);
35
+
36
+ #if PHP_VERSION_ID >= 50300
37
+ #define APPLY_TSRMLS_DC TSRMLS_DC
38
+ #define APPLY_TSRMLS_CC TSRMLS_CC
39
+ #define APPLY_TSRMLS_FETCH()
40
+ #else
41
+ #define APPLY_TSRMLS_DC
42
+ #define APPLY_TSRMLS_CC
43
+ #define APPLY_TSRMLS_FETCH() TSRMLS_FETCH()
44
+ #endif
45
+
46
+ ZEND_BEGIN_ARG_INFO_EX(twig_template_get_attribute_args, ZEND_SEND_BY_VAL, ZEND_RETURN_VALUE, 6)
47
+ ZEND_ARG_INFO(0, template)
48
+ ZEND_ARG_INFO(0, object)
49
+ ZEND_ARG_INFO(0, item)
50
+ ZEND_ARG_INFO(0, arguments)
51
+ ZEND_ARG_INFO(0, type)
52
+ ZEND_ARG_INFO(0, isDefinedTest)
53
+ ZEND_END_ARG_INFO()
54
+
55
+ zend_function_entry twig_functions[] = {
56
+ PHP_FE(twig_template_get_attributes, twig_template_get_attribute_args)
57
+ {NULL, NULL, NULL}
58
+ };
59
+
60
+
61
+ zend_module_entry twig_module_entry = {
62
+ STANDARD_MODULE_HEADER,
63
+ "twig",
64
+ twig_functions,
65
+ NULL,
66
+ NULL,
67
+ NULL,
68
+ NULL,
69
+ NULL,
70
+ PHP_TWIG_VERSION,
71
+ STANDARD_MODULE_PROPERTIES
72
+ };
73
+
74
+
75
+ #ifdef COMPILE_DL_TWIG
76
+ ZEND_GET_MODULE(twig)
77
+ #endif
78
+
79
+ int TWIG_ARRAY_KEY_EXISTS(zval *array, char* key, int key_len)
80
+ {
81
+ if (Z_TYPE_P(array) != IS_ARRAY) {
82
+ return 0;
83
+ }
84
+ return zend_symtable_exists(Z_ARRVAL_P(array), key, key_len + 1);
85
+ }
86
+
87
+ int TWIG_INSTANCE_OF(zval *object, zend_class_entry *interface TSRMLS_DC)
88
+ {
89
+ if (Z_TYPE_P(object) != IS_OBJECT) {
90
+ return 0;
91
+ }
92
+ return instanceof_function(Z_OBJCE_P(object), interface TSRMLS_CC);
93
+ }
94
+
95
+ int TWIG_INSTANCE_OF_USERLAND(zval *object, char *interface TSRMLS_DC)
96
+ {
97
+ zend_class_entry **pce;
98
+ if (Z_TYPE_P(object) != IS_OBJECT) {
99
+ return 0;
100
+ }
101
+ if (zend_lookup_class(interface, strlen(interface), &pce TSRMLS_CC) == FAILURE) {
102
+ return 0;
103
+ }
104
+ return instanceof_function(Z_OBJCE_P(object), *pce TSRMLS_CC);
105
+ }
106
+
107
+ zval *TWIG_GET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC)
108
+ {
109
+ zend_class_entry *ce = Z_OBJCE_P(object);
110
+ zval *retval;
111
+
112
+ if (Z_TYPE_P(object) == IS_OBJECT) {
113
+ SEPARATE_ARG_IF_REF(offset);
114
+ zend_call_method_with_1_params(&object, ce, NULL, "offsetget", &retval, offset);
115
+
116
+ zval_ptr_dtor(&offset);
117
+
118
+ if (!retval) {
119
+ if (!EG(exception)) {
120
+ zend_error(E_ERROR, "Undefined offset for object of type %s used as array", ce->name);
121
+ }
122
+ return NULL;
123
+ }
124
+
125
+ return retval;
126
+ }
127
+ return NULL;
128
+ }
129
+
130
+ int TWIG_ISSET_ARRAYOBJECT_ELEMENT(zval *object, zval *offset TSRMLS_DC)
131
+ {
132
+ zend_class_entry *ce = Z_OBJCE_P(object);
133
+ zval *retval;
134
+
135
+ if (Z_TYPE_P(object) == IS_OBJECT) {
136
+ SEPARATE_ARG_IF_REF(offset);
137
+ zend_call_method_with_1_params(&object, ce, NULL, "offsetexists", &retval, offset);
138
+
139
+ zval_ptr_dtor(&offset);
140
+
141
+ if (!retval) {
142
+ if (!EG(exception)) {
143
+ zend_error(E_ERROR, "Undefined offset for object of type %s used as array", ce->name);
144
+ }
145
+ return 0;
146
+ }
147
+
148
+ return (retval && Z_TYPE_P(retval) == IS_BOOL && Z_LVAL_P(retval));
149
+ }
150
+ return 0;
151
+ }
152
+
153
+ char *TWIG_STRTOLOWER(const char *str, int str_len)
154
+ {
155
+ char *item_dup;
156
+
157
+ item_dup = estrndup(str, str_len);
158
+ php_strtolower(item_dup, str_len);
159
+ return item_dup;
160
+ }
161
+
162
+ zval *TWIG_CALL_USER_FUNC_ARRAY(zval *object, char *function, zval *arguments TSRMLS_DC)
163
+ {
164
+ zend_fcall_info fci;
165
+ zval ***args = NULL;
166
+ int arg_count = 0;
167
+ HashTable *table;
168
+ HashPosition pos;
169
+ int i = 0;
170
+ zval *retval_ptr;
171
+ zval *zfunction;
172
+
173
+ if (arguments) {
174
+ table = HASH_OF(arguments);
175
+ args = safe_emalloc(sizeof(zval **), table->nNumOfElements, 0);
176
+
177
+ zend_hash_internal_pointer_reset_ex(table, &pos);
178
+
179
+ while (zend_hash_get_current_data_ex(table, (void **)&args[i], &pos) == SUCCESS) {
180
+ i++;
181
+ zend_hash_move_forward_ex(table, &pos);
182
+ }
183
+ arg_count = table->nNumOfElements;
184
+ }
185
+
186
+ MAKE_STD_ZVAL(zfunction);
187
+ ZVAL_STRING(zfunction, function, 1);
188
+ fci.size = sizeof(fci);
189
+ fci.function_table = EG(function_table);
190
+ fci.function_name = zfunction;
191
+ fci.symbol_table = NULL;
192
+ #if PHP_VERSION_ID >= 50300
193
+ fci.object_ptr = object;
194
+ #else
195
+ fci.object_pp = &object;
196
+ #endif
197
+ fci.retval_ptr_ptr = &retval_ptr;
198
+ fci.param_count = arg_count;
199
+ fci.params = args;
200
+ fci.no_separation = 0;
201
+
202
+ if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) {
203
+ FREE_DTOR(zfunction)
204
+ zend_throw_exception_ex(zend_exception_get_default(TSRMLS_C), 0 TSRMLS_CC, "Could not execute %s::%s()", zend_get_class_entry(object TSRMLS_CC)->name, function TSRMLS_CC);
205
+ }
206
+
207
+ if (args) {
208
+ efree(fci.params);
209
+ }
210
+ FREE_DTOR(zfunction);
211
+ return retval_ptr;
212
+ }
213
+
214
+ int TWIG_CALL_BOOLEAN(zval *object, char *functionName TSRMLS_DC)
215
+ {
216
+ zval *ret;
217
+ int res;
218
+
219
+ ret = TWIG_CALL_USER_FUNC_ARRAY(object, functionName, NULL TSRMLS_CC);
220
+ res = Z_LVAL_P(ret);
221
+ zval_ptr_dtor(&ret);
222
+ return res;
223
+ }
224
+
225
+ zval *TWIG_GET_STATIC_PROPERTY(zval *class, char *prop_name TSRMLS_DC)
226
+ {
227
+ zval **tmp_zval;
228
+ zend_class_entry *ce;
229
+
230
+ if (class == NULL || Z_TYPE_P(class) != IS_OBJECT) {
231
+ return NULL;
232
+ }
233
+
234
+ ce = zend_get_class_entry(class TSRMLS_CC);
235
+ #if PHP_VERSION_ID >= 50400
236
+ tmp_zval = zend_std_get_static_property(ce, prop_name, strlen(prop_name), 0, NULL TSRMLS_CC);
237
+ #else
238
+ tmp_zval = zend_std_get_static_property(ce, prop_name, strlen(prop_name), 0 TSRMLS_CC);
239
+ #endif
240
+ return *tmp_zval;
241
+ }
242
+
243
+ zval *TWIG_GET_ARRAY_ELEMENT_ZVAL(zval *class, zval *prop_name TSRMLS_DC)
244
+ {
245
+ zval **tmp_zval;
246
+ char *tmp_name;
247
+
248
+ if (class == NULL || Z_TYPE_P(class) != IS_ARRAY || Z_TYPE_P(prop_name) != IS_STRING) {
249
+ if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) {
250
+ // array access object
251
+ return TWIG_GET_ARRAYOBJECT_ELEMENT(class, prop_name TSRMLS_CC);
252
+ }
253
+ return NULL;
254
+ }
255
+
256
+ convert_to_string(prop_name);
257
+ tmp_name = Z_STRVAL_P(prop_name);
258
+ if (zend_symtable_find(HASH_OF(class), tmp_name, strlen(tmp_name)+1, (void**) &tmp_zval) == SUCCESS) {
259
+ return *tmp_zval;
260
+ }
261
+ return NULL;
262
+ }
263
+
264
+ zval *TWIG_GET_ARRAY_ELEMENT(zval *class, char *prop_name, int prop_name_length TSRMLS_DC)
265
+ {
266
+ zval **tmp_zval;
267
+
268
+ if (class == NULL/* || Z_TYPE_P(class) != IS_ARRAY*/) {
269
+ return NULL;
270
+ }
271
+
272
+ if (class != NULL && Z_TYPE_P(class) == IS_OBJECT && TWIG_INSTANCE_OF(class, zend_ce_arrayaccess TSRMLS_CC)) {
273
+ // array access object
274
+ zval *tmp_name_zval;
275
+ zval *tmp_ret_zval;
276
+
277
+ ALLOC_INIT_ZVAL(tmp_name_zval);
278
+ ZVAL_STRING(tmp_name_zval, prop_name, 1);
279
+ tmp_ret_zval = TWIG_GET_ARRAYOBJECT_ELEMENT(class, tmp_name_zval TSRMLS_CC);
280
+ FREE_DTOR(tmp_name_zval);
281
+ return tmp_ret_zval;
282
+ }
283
+
284
+ if (zend_symtable_find(HASH_OF(class), prop_name, prop_name_length+1, (void**)&tmp_zval) == SUCCESS) {
285
+ return *tmp_zval;
286
+ }
287
+ return NULL;
288
+ }
289
+
290
+ zval *TWIG_PROPERTY(zval *object, zval *propname TSRMLS_DC)
291
+ {
292
+ zval *tmp = NULL;
293
+
294
+ if (Z_OBJ_HT_P(object)->read_property) {
295
+ #if PHP_VERSION_ID >= 50400
296
+ tmp = Z_OBJ_HT_P(object)->read_property(object, propname, BP_VAR_IS, NULL TSRMLS_CC);
297
+ #else
298
+ tmp = Z_OBJ_HT_P(object)->read_property(object, propname, BP_VAR_IS TSRMLS_CC);
299
+ #endif
300
+ if (tmp != EG(uninitialized_zval_ptr)) {
301
+ return tmp;
302
+ } else {
303
+ return NULL;
304
+ }
305
+ }
306
+ return tmp;
307
+ }
308
+
309
+ int TWIG_HAS_PROPERTY(zval *object, zval *propname TSRMLS_DC)
310
+ {
311
+ if (Z_OBJ_HT_P(object)->has_property) {
312
+ #if PHP_VERSION_ID >= 50400
313
+ return Z_OBJ_HT_P(object)->has_property(object, propname, 0, NULL TSRMLS_CC);
314
+ #else
315
+ return Z_OBJ_HT_P(object)->has_property(object, propname, 0 TSRMLS_CC);
316
+ #endif
317
+ }
318
+ return 0;
319
+ }
320
+
321
+ int TWIG_HAS_DYNAMIC_PROPERTY(zval *object, char *prop, int prop_len TSRMLS_DC)
322
+ {
323
+ if (Z_OBJ_HT_P(object)->get_properties) {
324
+ return zend_hash_quick_exists(
325
+ Z_OBJ_HT_P(object)->get_properties(object TSRMLS_CC), // the properties hash
326
+ prop, // property name
327
+ prop_len + 1, // property length
328
+ zend_get_hash_value(prop, prop_len + 1) // hash value
329
+ );
330
+ }
331
+ return 0;
332
+ }
333
+
334
+ zval *TWIG_PROPERTY_CHAR(zval *object, char *propname TSRMLS_DC)
335
+ {
336
+ zval *tmp_name_zval, *tmp;
337
+
338
+ ALLOC_INIT_ZVAL(tmp_name_zval);
339
+ ZVAL_STRING(tmp_name_zval, propname, 1);
340
+ tmp = TWIG_PROPERTY(object, tmp_name_zval TSRMLS_CC);
341
+ FREE_DTOR(tmp_name_zval);
342
+ return tmp;
343
+ }
344
+
345
+ int TWIG_CALL_B_0(zval *object, char *method)
346
+ {
347
+ return 0;
348
+ }
349
+
350
+ zval *TWIG_CALL_S(zval *object, char *method, char *arg0 TSRMLS_DC)
351
+ {
352
+ zend_fcall_info fci;
353
+ zval **args[1];
354
+ zval *argument;
355
+ zval *zfunction;
356
+ zval *retval_ptr;
357
+
358
+ MAKE_STD_ZVAL(argument);
359
+ ZVAL_STRING(argument, arg0, 1);
360
+ args[0] = &argument;
361
+
362
+ MAKE_STD_ZVAL(zfunction);
363
+ ZVAL_STRING(zfunction, method, 1);
364
+ fci.size = sizeof(fci);
365
+ fci.function_table = EG(function_table);
366
+ fci.function_name = zfunction;
367
+ fci.symbol_table = NULL;
368
+ #if PHP_VERSION_ID >= 50300
369
+ fci.object_ptr = object;
370
+ #else
371
+ fci.object_pp = &object;
372
+ #endif
373
+ fci.retval_ptr_ptr = &retval_ptr;
374
+ fci.param_count = 1;
375
+ fci.params = args;
376
+ fci.no_separation = 0;
377
+
378
+ if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) {
379
+ FREE_DTOR(zfunction);
380
+ zval_ptr_dtor(&argument);
381
+ return 0;
382
+ }
383
+ FREE_DTOR(zfunction);
384
+ zval_ptr_dtor(&argument);
385
+ return retval_ptr;
386
+ }
387
+
388
+ int TWIG_CALL_SB(zval *object, char *method, char *arg0 TSRMLS_DC)
389
+ {
390
+ zval *retval_ptr;
391
+ int success;
392
+
393
+ retval_ptr = TWIG_CALL_S(object, method, arg0 TSRMLS_CC);
394
+ success = (retval_ptr && (Z_TYPE_P(retval_ptr) == IS_BOOL) && Z_LVAL_P(retval_ptr));
395
+
396
+ if (retval_ptr) {
397
+ zval_ptr_dtor(&retval_ptr);
398
+ }
399
+
400
+ return success;
401
+ }
402
+
403
+ int TWIG_CALL_Z(zval *object, char *method, zval *arg1 TSRMLS_DC)
404
+ {
405
+ zend_fcall_info fci;
406
+ zval **args[1];
407
+ zval *zfunction;
408
+ zval *retval_ptr;
409
+ int success;
410
+
411
+ args[0] = &arg1;
412
+
413
+ MAKE_STD_ZVAL(zfunction);
414
+ ZVAL_STRING(zfunction, method, 1);
415
+ fci.size = sizeof(fci);
416
+ fci.function_table = EG(function_table);
417
+ fci.function_name = zfunction;
418
+ fci.symbol_table = NULL;
419
+ #if PHP_VERSION_ID >= 50300
420
+ fci.object_ptr = object;
421
+ #else
422
+ fci.object_pp = &object;
423
+ #endif
424
+ fci.retval_ptr_ptr = &retval_ptr;
425
+ fci.param_count = 1;
426
+ fci.params = args;
427
+ fci.no_separation = 0;
428
+
429
+ if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) {
430
+ FREE_DTOR(zfunction);
431
+ if (retval_ptr) {
432
+ zval_ptr_dtor(&retval_ptr);
433
+ }
434
+ return 0;
435
+ }
436
+
437
+ FREE_DTOR(zfunction);
438
+
439
+ success = (retval_ptr && (Z_TYPE_P(retval_ptr) == IS_BOOL) && Z_LVAL_P(retval_ptr));
440
+ if (retval_ptr) {
441
+ zval_ptr_dtor(&retval_ptr);
442
+ }
443
+
444
+ return success;
445
+ }
446
+
447
+ int TWIG_CALL_ZZ(zval *object, char *method, zval *arg1, zval *arg2 TSRMLS_DC)
448
+ {
449
+ zend_fcall_info fci;
450
+ zval **args[2];
451
+ zval *zfunction;
452
+ zval *retval_ptr;
453
+ int success;
454
+
455
+ args[0] = &arg1;
456
+ args[1] = &arg2;
457
+
458
+ MAKE_STD_ZVAL(zfunction);
459
+ ZVAL_STRING(zfunction, method, 1);
460
+ fci.size = sizeof(fci);
461
+ fci.function_table = EG(function_table);
462
+ fci.function_name = zfunction;
463
+ fci.symbol_table = NULL;
464
+ #if PHP_VERSION_ID >= 50300
465
+ fci.object_ptr = object;
466
+ #else
467
+ fci.object_pp = &object;
468
+ #endif
469
+ fci.retval_ptr_ptr = &retval_ptr;
470
+ fci.param_count = 2;
471
+ fci.params = args;
472
+ fci.no_separation = 0;
473
+
474
+ if (zend_call_function(&fci, NULL TSRMLS_CC) == FAILURE) {
475
+ FREE_DTOR(zfunction);
476
+ return 0;
477
+ }
478
+
479
+ FREE_DTOR(zfunction);
480
+
481
+ success = (retval_ptr && (Z_TYPE_P(retval_ptr) == IS_BOOL) && Z_LVAL_P(retval_ptr));
482
+ if (retval_ptr) {
483
+ zval_ptr_dtor(&retval_ptr);
484
+ }
485
+
486
+ return success;
487
+ }
488
+
489
+ #ifndef Z_SET_REFCOUNT_P
490
+ # define Z_SET_REFCOUNT_P(pz, rc) pz->refcount = rc
491
+ # define Z_UNSET_ISREF_P(pz) pz->is_ref = 0
492
+ #endif
493
+
494
+ void TWIG_NEW(zval *object, char *class, zval *arg0, zval *arg1 TSRMLS_DC)
495
+ {
496
+ zend_class_entry **pce;
497
+
498
+ if (zend_lookup_class(class, strlen(class), &pce TSRMLS_CC) == FAILURE) {
499
+ return;
500
+ }
501
+
502
+ Z_TYPE_P(object) = IS_OBJECT;
503
+ object_init_ex(object, *pce);
504
+ Z_SET_REFCOUNT_P(object, 1);
505
+ Z_UNSET_ISREF_P(object);
506
+
507
+ TWIG_CALL_ZZ(object, "__construct", arg0, arg1 TSRMLS_CC);
508
+ }
509
+
510
+ static int twig_add_array_key_to_string(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
511
+ {
512
+ smart_str *buf;
513
+ char *joiner;
514
+
515
+ buf = va_arg(args, smart_str*);
516
+ joiner = va_arg(args, char*);
517
+
518
+ if (buf->len != 0) {
519
+ smart_str_appends(buf, joiner);
520
+ }
521
+
522
+ if (hash_key->nKeyLength == 0) {
523
+ smart_str_append_long(buf, (long) hash_key->h);
524
+ } else {
525
+ char *key, *tmp_str;
526
+ int key_len, tmp_len;
527
+ key = php_addcslashes(hash_key->arKey, hash_key->nKeyLength - 1, &key_len, 0, "'\\", 2 TSRMLS_CC);
528
+ tmp_str = php_str_to_str_ex(key, key_len, "\0", 1, "' . \"\\0\" . '", 12, &tmp_len, 0, NULL);
529
+
530
+ smart_str_appendl(buf, tmp_str, tmp_len);
531
+ efree(key);
532
+ efree(tmp_str);
533
+ }
534
+
535
+ return 0;
536
+ }
537
+
538
+ char *TWIG_IMPLODE_ARRAY_KEYS(char *joiner, zval *array TSRMLS_DC)
539
+ {
540
+ smart_str collector = { 0, 0, 0 };
541
+
542
+ smart_str_appendl(&collector, "", 0);
543
+ zend_hash_apply_with_arguments(HASH_OF(array) APPLY_TSRMLS_CC, twig_add_array_key_to_string, 2, &collector, joiner);
544
+ smart_str_0(&collector);
545
+
546
+ return collector.c;
547
+ }
548
+
549
+ static void TWIG_THROW_EXCEPTION(char *exception_name TSRMLS_DC, char *message, ...)
550
+ {
551
+ char *buffer;
552
+ va_list args;
553
+ zend_class_entry **pce;
554
+
555
+ if (zend_lookup_class(exception_name, strlen(exception_name), &pce TSRMLS_CC) == FAILURE) {
556
+ return;
557
+ }
558
+
559
+ va_start(args, message);
560
+ vspprintf(&buffer, 0, message, args);
561
+ va_end(args);
562
+
563
+ zend_throw_exception_ex(*pce, 0 TSRMLS_CC, buffer);
564
+ efree(buffer);
565
+ }
566
+
567
+ static void TWIG_RUNTIME_ERROR(zval *template TSRMLS_DC, char *message, ...)
568
+ {
569
+ char *buffer;
570
+ va_list args;
571
+ zend_class_entry **pce;
572
+ zval *ex;
573
+ zval *constructor;
574
+ zval *zmessage;
575
+ zval *lineno;
576
+ zval *filename_func;
577
+ zval *filename;
578
+ zval *constructor_args[3];
579
+ zval *constructor_retval;
580
+
581
+ if (zend_lookup_class("Twig_Error_Runtime", strlen("Twig_Error_Runtime"), &pce TSRMLS_CC) == FAILURE) {
582
+ return;
583
+ }
584
+
585
+ va_start(args, message);
586
+ vspprintf(&buffer, 0, message, args);
587
+ va_end(args);
588
+
589
+ MAKE_STD_ZVAL(ex);
590
+ object_init_ex(ex, *pce);
591
+
592
+ // Call Twig_Error constructor
593
+ MAKE_STD_ZVAL(constructor);
594
+ MAKE_STD_ZVAL(zmessage);
595
+ MAKE_STD_ZVAL(lineno);
596
+ MAKE_STD_ZVAL(filename);
597
+ MAKE_STD_ZVAL(filename_func);
598
+ MAKE_STD_ZVAL(constructor_retval);
599
+
600
+ ZVAL_STRINGL(constructor, "__construct", sizeof("__construct")-1, 1);
601
+ ZVAL_STRING(zmessage, buffer, 1);
602
+ ZVAL_LONG(lineno, -1);
603
+
604
+ // Get template filename
605
+ ZVAL_STRINGL(filename_func, "getTemplateName", sizeof("getTemplateName")-1, 1);
606
+ call_user_function(EG(function_table), &template, filename_func, filename, 0, 0 TSRMLS_CC);
607
+
608
+ constructor_args[0] = zmessage;
609
+ constructor_args[1] = lineno;
610
+ constructor_args[2] = filename;
611
+ call_user_function(EG(function_table), &ex, constructor, constructor_retval, 3, constructor_args TSRMLS_CC);
612
+
613
+ zval_ptr_dtor(&constructor_retval);
614
+ zval_ptr_dtor(&zmessage);
615
+ zval_ptr_dtor(&lineno);
616
+ zval_ptr_dtor(&filename);
617
+ FREE_DTOR(constructor);
618
+ FREE_DTOR(filename_func);
619
+ efree(buffer);
620
+
621
+ zend_throw_exception_object(ex TSRMLS_CC);
622
+ }
623
+
624
+ static char *TWIG_GET_CLASS_NAME(zval *object TSRMLS_DC)
625
+ {
626
+ char *class_name;
627
+ zend_uint class_name_len;
628
+
629
+ if (Z_TYPE_P(object) != IS_OBJECT) {
630
+ return "";
631
+ }
632
+ #if PHP_API_VERSION >= 20100412
633
+ zend_get_object_classname(object, (const char **) &class_name, &class_name_len TSRMLS_CC);
634
+ #else
635
+ zend_get_object_classname(object, &class_name, &class_name_len TSRMLS_CC);
636
+ #endif
637
+ return class_name;
638
+ }
639
+
640
+ static int twig_add_method_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
641
+ {
642
+ zval *retval;
643
+ char *item;
644
+ size_t item_len;
645
+ zend_function *mptr = (zend_function *) pDest;
646
+
647
+ if (!(mptr->common.fn_flags & ZEND_ACC_PUBLIC)) {
648
+ return 0;
649
+ }
650
+
651
+ retval = va_arg(args, zval*);
652
+
653
+ item_len = strlen(mptr->common.function_name);
654
+ item = estrndup(mptr->common.function_name, item_len);
655
+ php_strtolower(item, item_len);
656
+
657
+ add_assoc_stringl_ex(retval, item, item_len+1, item, item_len, 0);
658
+
659
+ return 0;
660
+ }
661
+
662
+ static int twig_add_property_to_class(void *pDest APPLY_TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
663
+ {
664
+ zend_class_entry *ce;
665
+ zval *retval;
666
+ char *class_name, *prop_name;
667
+ zend_property_info *pptr = (zend_property_info *) pDest;
668
+
669
+ if (!(pptr->flags & ZEND_ACC_PUBLIC)) {
670
+ return 0;
671
+ }
672
+
673
+ ce = *va_arg(args, zend_class_entry**);
674
+ retval = va_arg(args, zval*);
675
+
676
+ #if PHP_API_VERSION >= 20100412
677
+ zend_unmangle_property_name(pptr->name, pptr->name_length, (const char **) &class_name, (const char **) &prop_name);
678
+ #else
679
+ zend_unmangle_property_name(pptr->name, pptr->name_length, &class_name, &prop_name);
680
+ #endif
681
+
682
+ add_assoc_string(retval, prop_name, prop_name, 1);
683
+
684
+ return 0;
685
+ }
686
+
687
+ static void twig_add_class_to_cache(zval *cache, zval *object, char *class_name TSRMLS_DC)
688
+ {
689
+ zval *class_info, *class_methods, *class_properties;
690
+ zend_class_entry *class_ce;
691
+
692
+ class_ce = zend_get_class_entry(object TSRMLS_CC);
693
+
694
+ ALLOC_INIT_ZVAL(class_info);
695
+ ALLOC_INIT_ZVAL(class_methods);
696
+ ALLOC_INIT_ZVAL(class_properties);
697
+ array_init(class_info);
698
+ array_init(class_methods);
699
+ array_init(class_properties);
700
+ // add all methods to self::cache[$class]['methods']
701
+ zend_hash_apply_with_arguments(&class_ce->function_table APPLY_TSRMLS_CC, twig_add_method_to_class, 1, class_methods);
702
+ zend_hash_apply_with_arguments(&class_ce->properties_info APPLY_TSRMLS_CC, twig_add_property_to_class, 2, &class_ce, class_properties);
703
+
704
+ add_assoc_zval(class_info, "methods", class_methods);
705
+ add_assoc_zval(class_info, "properties", class_properties);
706
+ add_assoc_zval(cache, class_name, class_info);
707
+ }
708
+
709
+ /* {{{ proto mixed twig_template_get_attributes(TwigTemplate template, mixed object, mixed item, array arguments, string type, boolean isDefinedTest, boolean ignoreStrictCheck)
710
+ A C implementation of TwigTemplate::getAttribute() */
711
+ PHP_FUNCTION(twig_template_get_attributes)
712
+ {
713
+ zval *template;
714
+ zval *object;
715
+ char *item;
716
+ int item_len;
717
+ zval zitem;
718
+ zval *arguments = NULL;
719
+ zval *ret = NULL;
720
+ char *type = NULL;
721
+ int type_len = 0;
722
+ zend_bool isDefinedTest = 0;
723
+ zend_bool ignoreStrictCheck = 0;
724
+ int free_ret = 0;
725
+ zval *tmp_self_cache;
726
+
727
+
728
+ if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ozs|asbb", &template, &object, &item, &item_len, &arguments, &type, &type_len, &isDefinedTest, &ignoreStrictCheck) == FAILURE) {
729
+ return;
730
+ }
731
+
732
+ INIT_PZVAL(&zitem);
733
+ ZVAL_STRINGL(&zitem, item, item_len, 0);
734
+
735
+ switch (is_numeric_string(item, item_len, &Z_LVAL(zitem), &Z_DVAL(zitem), 0)) {
736
+ case IS_LONG:
737
+ Z_TYPE(zitem) = IS_LONG;
738
+ break;
739
+ case IS_DOUBLE:
740
+ Z_TYPE(zitem) = IS_DOUBLE;
741
+ convert_to_long(&zitem);
742
+ break;
743
+ }
744
+
745
+ if (!type) {
746
+ type = "any";
747
+ }
748
+
749
+ /*
750
+ // array
751
+ if (Twig_TemplateInterface::METHOD_CALL !== $type) {
752
+ if ((is_array($object) && array_key_exists($item, $object))
753
+ || ($object instanceof ArrayAccess && isset($object[$item]))
754
+ ) {
755
+ if ($isDefinedTest) {
756
+ return true;
757
+ }
758
+
759
+ return $object[$item];
760
+ }
761
+ */
762
+ if (strcmp("method", type) != 0) {
763
+ // printf("XXXmethod: %s\n", type);
764
+ if ((TWIG_ARRAY_KEY_EXISTS(object, item, item_len))
765
+ || (TWIG_INSTANCE_OF(object, zend_ce_arrayaccess TSRMLS_CC) && TWIG_ISSET_ARRAYOBJECT_ELEMENT(object, &zitem TSRMLS_CC))
766
+ ) {
767
+ zval *ret;
768
+
769
+ if (isDefinedTest) {
770
+ RETURN_TRUE;
771
+ }
772
+
773
+ ret = TWIG_GET_ARRAY_ELEMENT(object, item, item_len TSRMLS_CC);
774
+ if (!ret) {
775
+ ret = &EG(uninitialized_zval);
776
+ }
777
+ RETVAL_ZVAL(ret, 1, 0);
778
+ if (free_ret) {
779
+ zval_ptr_dtor(&ret);
780
+ }
781
+ return;
782
+ }
783
+ /*
784
+ if (Twig_TemplateInterface::ARRAY_CALL === $type) {
785
+ if ($isDefinedTest) {
786
+ return false;
787
+ }
788
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
789
+ return null;
790
+ }
791
+ */
792
+ if (strcmp("array", type) == 0) {
793
+ if (isDefinedTest) {
794
+ RETURN_FALSE;
795
+ }
796
+ if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) {
797
+ return;
798
+ }
799
+ /*
800
+ if (is_object($object)) {
801
+ throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
802
+ } elseif (is_array($object)) {
803
+ throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName());
804
+ } else {
805
+ throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName());
806
+ }
807
+ }
808
+ }
809
+ */
810
+ if (Z_TYPE_P(object) == IS_OBJECT) {
811
+ TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" in object (with ArrayAccess) of type \"%s\" does not exist", item, TWIG_GET_CLASS_NAME(object TSRMLS_CC));
812
+ } else if (Z_TYPE_P(object) == IS_ARRAY) {
813
+ TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Key \"%s\" for array with keys \"%s\" does not exist", item, TWIG_IMPLODE_ARRAY_KEYS(", ", object TSRMLS_CC));
814
+ } else {
815
+ TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Impossible to access a key (\"%s\") on a \"%s\" variable", item, zend_zval_type_name(object));
816
+ }
817
+ return;
818
+ }
819
+ }
820
+
821
+ /*
822
+ if (!is_object($object)) {
823
+ if ($isDefinedTest) {
824
+ return false;
825
+ }
826
+ */
827
+
828
+ if (Z_TYPE_P(object) != IS_OBJECT) {
829
+ if (isDefinedTest) {
830
+ RETURN_FALSE;
831
+ }
832
+ /*
833
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
834
+ return null;
835
+ }
836
+ throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, implode(', ', array_keys($object))));
837
+ }
838
+ */
839
+ if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) {
840
+ RETURN_FALSE;
841
+ }
842
+ if (Z_TYPE_P(object) == IS_ARRAY) {
843
+ TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Item \"%s\" for \"Array\" does not exist", item);
844
+ } else {
845
+ Z_ADDREF_P(object);
846
+ convert_to_string_ex(&object);
847
+ TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Item \"%s\" for \"%s\" does not exist", item, Z_STRVAL_P(object));
848
+ zval_ptr_dtor(&object);
849
+ }
850
+ return;
851
+ }
852
+ /*
853
+ // get some information about the object
854
+ $class = get_class($object);
855
+ if (!isset(self::$cache[$class])) {
856
+ $r = new ReflectionClass($class);
857
+ self::$cache[$class] = array('methods' => array(), 'properties' => array());
858
+ foreach ($r->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
859
+ self::$cache[$class]['methods'][strtolower($method->getName())] = true;
860
+ }
861
+
862
+ foreach ($r->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
863
+ self::$cache[$class]['properties'][$property->getName()] = true;
864
+ }
865
+ }
866
+ */
867
+ if (Z_TYPE_P(object) == IS_OBJECT) {
868
+ char *class_name = NULL;
869
+
870
+ class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
871
+ tmp_self_cache = TWIG_GET_STATIC_PROPERTY(template, "cache" TSRMLS_CC);
872
+
873
+ if (!TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC)) {
874
+ twig_add_class_to_cache(tmp_self_cache, object, class_name TSRMLS_CC);
875
+ }
876
+ efree(class_name);
877
+ }
878
+
879
+ /*
880
+ // object property
881
+ if (Twig_TemplateInterface::METHOD_CALL !== $type) {
882
+ if (isset(self::$cache[$class]['properties'][$item])
883
+ || isset($object->$item) || array_key_exists($item, $object)
884
+ ) {
885
+ if ($isDefinedTest) {
886
+ return true;
887
+ }
888
+ if ($this->env->hasExtension('sandbox')) {
889
+ $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
890
+ }
891
+
892
+ return $object->$item;
893
+ }
894
+ }
895
+ */
896
+ if (strcmp("method", type) != 0) {
897
+ zval *tmp_class, *tmp_properties, *tmp_item;
898
+ char *class_name = NULL;
899
+
900
+ class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
901
+ tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
902
+ tmp_properties = TWIG_GET_ARRAY_ELEMENT(tmp_class, "properties", strlen("properties") TSRMLS_CC);
903
+ tmp_item = TWIG_GET_ARRAY_ELEMENT(tmp_properties, item, item_len TSRMLS_CC);
904
+
905
+ efree(class_name);
906
+
907
+ if (tmp_item || TWIG_HAS_PROPERTY(object, &zitem TSRMLS_CC) || TWIG_HAS_DYNAMIC_PROPERTY(object, item, item_len TSRMLS_CC)) {
908
+ if (isDefinedTest) {
909
+ RETURN_TRUE;
910
+ }
911
+ if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "sandbox" TSRMLS_CC)) {
912
+ TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkPropertyAllowed", object, &zitem TSRMLS_CC);
913
+ }
914
+ if (EG(exception)) {
915
+ return;
916
+ }
917
+
918
+ ret = TWIG_PROPERTY(object, &zitem TSRMLS_CC);
919
+ RETURN_ZVAL(ret, 1, 0);
920
+ }
921
+ }
922
+ /*
923
+ // object method
924
+ $lcItem = strtolower($item);
925
+ if (isset(self::$cache[$class]['methods'][$lcItem])) {
926
+ $method = $item;
927
+ } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
928
+ $method = 'get'.$item;
929
+ } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
930
+ $method = 'is'.$item;
931
+ } elseif (isset(self::$cache[$class]['methods']['__call'])) {
932
+ $method = $item;
933
+ */
934
+ {
935
+ char *lcItem = TWIG_STRTOLOWER(item, item_len);
936
+ int lcItem_length;
937
+ char *method = NULL;
938
+ char *tmp_method_name_get;
939
+ char *tmp_method_name_is;
940
+ zval *tmp_class, *tmp_methods;
941
+ char *class_name = NULL;
942
+
943
+ class_name = TWIG_GET_CLASS_NAME(object TSRMLS_CC);
944
+ lcItem_length = strlen(lcItem);
945
+ tmp_method_name_get = emalloc(4 + lcItem_length);
946
+ tmp_method_name_is = emalloc(3 + lcItem_length);
947
+
948
+ sprintf(tmp_method_name_get, "get%s", lcItem);
949
+ sprintf(tmp_method_name_is, "is%s", lcItem);
950
+
951
+ tmp_class = TWIG_GET_ARRAY_ELEMENT(tmp_self_cache, class_name, strlen(class_name) TSRMLS_CC);
952
+ tmp_methods = TWIG_GET_ARRAY_ELEMENT(tmp_class, "methods", strlen("methods") TSRMLS_CC);
953
+ efree(class_name);
954
+
955
+ if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, lcItem, lcItem_length TSRMLS_CC)) {
956
+ method = item;
957
+ } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, tmp_method_name_get, lcItem_length + 3 TSRMLS_CC)) {
958
+ method = tmp_method_name_get;
959
+ } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, tmp_method_name_is, lcItem_length + 2 TSRMLS_CC)) {
960
+ method = tmp_method_name_is;
961
+ } else if (TWIG_GET_ARRAY_ELEMENT(tmp_methods, "__call", 6 TSRMLS_CC)) {
962
+ method = item;
963
+ /*
964
+ } else {
965
+ if ($isDefinedTest) {
966
+ return false;
967
+ }
968
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
969
+ return null;
970
+ }
971
+ throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)));
972
+ }
973
+ if ($isDefinedTest) {
974
+ return true;
975
+ }
976
+ */
977
+ } else {
978
+ efree(tmp_method_name_get);
979
+ efree(tmp_method_name_is);
980
+ efree(lcItem);
981
+
982
+ if (isDefinedTest) {
983
+ RETURN_FALSE;
984
+ }
985
+ if (ignoreStrictCheck || !TWIG_CALL_BOOLEAN(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "isStrictVariables" TSRMLS_CC)) {
986
+ return;
987
+ }
988
+ TWIG_RUNTIME_ERROR(template TSRMLS_CC, "Method \"%s\" for object \"%s\" does not exist", item, TWIG_GET_CLASS_NAME(object TSRMLS_CC));
989
+ return;
990
+ }
991
+
992
+ if (isDefinedTest) {
993
+ efree(tmp_method_name_get);
994
+ efree(tmp_method_name_is);
995
+ efree(lcItem);
996
+ RETURN_TRUE;
997
+ }
998
+ /*
999
+ if ($this->env->hasExtension('sandbox')) {
1000
+ $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
1001
+ }
1002
+ */
1003
+ if (TWIG_CALL_SB(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "hasExtension", "sandbox" TSRMLS_CC)) {
1004
+ TWIG_CALL_ZZ(TWIG_CALL_S(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getExtension", "sandbox" TSRMLS_CC), "checkMethodAllowed", object, &zitem TSRMLS_CC);
1005
+ }
1006
+ if (EG(exception)) {
1007
+ efree(tmp_method_name_get);
1008
+ efree(tmp_method_name_is);
1009
+ efree(lcItem);
1010
+ return;
1011
+ }
1012
+ /*
1013
+ $ret = call_user_func_array(array($object, $method), $arguments);
1014
+ */
1015
+ if (Z_TYPE_P(object) == IS_OBJECT) {
1016
+ ret = TWIG_CALL_USER_FUNC_ARRAY(object, method, arguments TSRMLS_CC);
1017
+ free_ret = 1;
1018
+ }
1019
+ efree(tmp_method_name_get);
1020
+ efree(tmp_method_name_is);
1021
+ efree(lcItem);
1022
+ }
1023
+ /*
1024
+ if ($object instanceof Twig_TemplateInterface) {
1025
+ return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset());
1026
+ }
1027
+
1028
+ return $ret;
1029
+ */
1030
+ // ret can be null, if e.g. the called method throws an exception
1031
+ if (ret) {
1032
+ if (TWIG_INSTANCE_OF_USERLAND(object, "Twig_TemplateInterface" TSRMLS_CC)) {
1033
+ if (Z_STRLEN_P(ret) == 0) {
1034
+ free_ret = 1;
1035
+ } else {
1036
+ zval *charset = TWIG_CALL_USER_FUNC_ARRAY(TWIG_PROPERTY_CHAR(template, "env" TSRMLS_CC), "getCharset", NULL TSRMLS_CC);
1037
+ TWIG_NEW(return_value, "Twig_Markup", ret, charset TSRMLS_CC);
1038
+ zval_ptr_dtor(&charset);
1039
+ if (ret) {
1040
+ zval_ptr_dtor(&ret);
1041
+ }
1042
+ return;
1043
+ }
1044
+ }
1045
+
1046
+ RETVAL_ZVAL(ret, 1, 0);
1047
+ if (free_ret) {
1048
+ zval_ptr_dtor(&ret);
1049
+ }
1050
+ }
1051
+ }
Twig/lib/Twig/Autoloader.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Autoloader
19
+ {
20
+ /**
21
+ * Registers Twig_Autoloader as an SPL autoloader.
22
+ */
23
+ public static function register()
24
+ {
25
+ ini_set('unserialize_callback_func', 'spl_autoload_call');
26
+ spl_autoload_register(array(new self, 'autoload'));
27
+ }
28
+
29
+ /**
30
+ * Handles autoloading of classes.
31
+ *
32
+ * @param string $class A class name.
33
+ */
34
+ public static function autoload($class)
35
+ {
36
+ if (0 !== strpos($class, 'Twig')) {
37
+ return;
38
+ }
39
+
40
+ if (is_file($file = dirname(__FILE__).'/../'.str_replace(array('_', "\0"), array('/', ''), $class).'.php')) {
41
+ require $file;
42
+ }
43
+ }
44
+ }
Twig/lib/Twig/Compiler.php ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Compiler implements Twig_CompilerInterface
20
+ {
21
+ protected $lastLine;
22
+ protected $source;
23
+ protected $indentation;
24
+ protected $env;
25
+ protected $debugInfo;
26
+ protected $sourceOffset;
27
+ protected $sourceLine;
28
+ protected $filename;
29
+
30
+ /**
31
+ * Constructor.
32
+ *
33
+ * @param Twig_Environment $env The twig environment instance
34
+ */
35
+ public function __construct(Twig_Environment $env)
36
+ {
37
+ $this->env = $env;
38
+ $this->debugInfo = array();
39
+ }
40
+
41
+ public function getFilename()
42
+ {
43
+ return $this->filename;
44
+ }
45
+
46
+ /**
47
+ * Returns the environment instance related to this compiler.
48
+ *
49
+ * @return Twig_Environment The environment instance
50
+ */
51
+ public function getEnvironment()
52
+ {
53
+ return $this->env;
54
+ }
55
+
56
+ /**
57
+ * Gets the current PHP code after compilation.
58
+ *
59
+ * @return string The PHP code
60
+ */
61
+ public function getSource()
62
+ {
63
+ return $this->source;
64
+ }
65
+
66
+ /**
67
+ * Compiles a node.
68
+ *
69
+ * @param Twig_NodeInterface $node The node to compile
70
+ * @param integer $indentation The current indentation
71
+ *
72
+ * @return Twig_Compiler The current compiler instance
73
+ */
74
+ public function compile(Twig_NodeInterface $node, $indentation = 0)
75
+ {
76
+ $this->lastLine = null;
77
+ $this->source = '';
78
+ $this->sourceOffset = 0;
79
+ // source code starts at 1 (as we then increment it when we encounter new lines)
80
+ $this->sourceLine = 1;
81
+ $this->indentation = $indentation;
82
+
83
+ if ($node instanceof Twig_Node_Module) {
84
+ $this->filename = $node->getAttribute('filename');
85
+ }
86
+
87
+ $node->compile($this);
88
+
89
+ return $this;
90
+ }
91
+
92
+ public function subcompile(Twig_NodeInterface $node, $raw = true)
93
+ {
94
+ if (false === $raw) {
95
+ $this->addIndentation();
96
+ }
97
+
98
+ $node->compile($this);
99
+
100
+ return $this;
101
+ }
102
+
103
+ /**
104
+ * Adds a raw string to the compiled code.
105
+ *
106
+ * @param string $string The string
107
+ *
108
+ * @return Twig_Compiler The current compiler instance
109
+ */
110
+ public function raw($string)
111
+ {
112
+ $this->source .= $string;
113
+
114
+ return $this;
115
+ }
116
+
117
+ /**
118
+ * Writes a string to the compiled code by adding indentation.
119
+ *
120
+ * @return Twig_Compiler The current compiler instance
121
+ */
122
+ public function write()
123
+ {
124
+ $strings = func_get_args();
125
+ foreach ($strings as $string) {
126
+ $this->addIndentation();
127
+ $this->source .= $string;
128
+ }
129
+
130
+ return $this;
131
+ }
132
+
133
+ /**
134
+ * Appends an indentation to the current PHP code after compilation.
135
+ *
136
+ * @return Twig_Compiler The current compiler instance
137
+ */
138
+ public function addIndentation()
139
+ {
140
+ $this->source .= str_repeat(' ', $this->indentation * 4);
141
+
142
+ return $this;
143
+ }
144
+
145
+ /**
146
+ * Adds a quoted string to the compiled code.
147
+ *
148
+ * @param string $value The string
149
+ *
150
+ * @return Twig_Compiler The current compiler instance
151
+ */
152
+ public function string($value)
153
+ {
154
+ $this->source .= sprintf('"%s"', addcslashes($value, "\0\t\"\$\\"));
155
+
156
+ return $this;
157
+ }
158
+
159
+ /**
160
+ * Returns a PHP representation of a given value.
161
+ *
162
+ * @param mixed $value The value to convert
163
+ *
164
+ * @return Twig_Compiler The current compiler instance
165
+ */
166
+ public function repr($value)
167
+ {
168
+ if (is_int($value) || is_float($value)) {
169
+ if (false !== $locale = setlocale(LC_NUMERIC, 0)) {
170
+ setlocale(LC_NUMERIC, 'C');
171
+ }
172
+
173
+ $this->raw($value);
174
+
175
+ if (false !== $locale) {
176
+ setlocale(LC_NUMERIC, $locale);
177
+ }
178
+ } elseif (null === $value) {
179
+ $this->raw('null');
180
+ } elseif (is_bool($value)) {
181
+ $this->raw($value ? 'true' : 'false');
182
+ } elseif (is_array($value)) {
183
+ $this->raw('array(');
184
+ $i = 0;
185
+ foreach ($value as $key => $value) {
186
+ if ($i++) {
187
+ $this->raw(', ');
188
+ }
189
+ $this->repr($key);
190
+ $this->raw(' => ');
191
+ $this->repr($value);
192
+ }
193
+ $this->raw(')');
194
+ } else {
195
+ $this->string($value);
196
+ }
197
+
198
+ return $this;
199
+ }
200
+
201
+ /**
202
+ * Adds debugging information.
203
+ *
204
+ * @param Twig_NodeInterface $node The related twig node
205
+ *
206
+ * @return Twig_Compiler The current compiler instance
207
+ */
208
+ public function addDebugInfo(Twig_NodeInterface $node)
209
+ {
210
+ if ($node->getLine() != $this->lastLine) {
211
+ $this->write("// line {$node->getLine()}\n");
212
+
213
+ // when mbstring.func_overload is set to 2
214
+ // mb_substr_count() replaces substr_count()
215
+ // but they have different signatures!
216
+ if (((int) ini_get('mbstring.func_overload')) & 2) {
217
+ // this is much slower than the "right" version
218
+ $this->sourceLine += mb_substr_count(mb_substr($this->source, $this->sourceOffset), "\n");
219
+ } else {
220
+ $this->sourceLine += substr_count($this->source, "\n", $this->sourceOffset);
221
+ }
222
+ $this->sourceOffset = strlen($this->source);
223
+ $this->debugInfo[$this->sourceLine] = $node->getLine();
224
+
225
+ $this->lastLine = $node->getLine();
226
+ }
227
+
228
+ return $this;
229
+ }
230
+
231
+ public function getDebugInfo()
232
+ {
233
+ return $this->debugInfo;
234
+ }
235
+
236
+ /**
237
+ * Indents the generated code.
238
+ *
239
+ * @param integer $step The number of indentation to add
240
+ *
241
+ * @return Twig_Compiler The current compiler instance
242
+ */
243
+ public function indent($step = 1)
244
+ {
245
+ $this->indentation += $step;
246
+
247
+ return $this;
248
+ }
249
+
250
+ /**
251
+ * Outdents the generated code.
252
+ *
253
+ * @param integer $step The number of indentation to remove
254
+ *
255
+ * @return Twig_Compiler The current compiler instance
256
+ */
257
+ public function outdent($step = 1)
258
+ {
259
+ // can't outdent by more steps than the current indentation level
260
+ if ($this->indentation < $step) {
261
+ throw new LogicException('Unable to call outdent() as the indentation would become negative');
262
+ }
263
+
264
+ $this->indentation -= $step;
265
+
266
+ return $this;
267
+ }
268
+ }
Twig/lib/Twig/CompilerInterface.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ interface Twig_CompilerInterface
20
+ {
21
+ /**
22
+ * Compiles a node.
23
+ *
24
+ * @param Twig_NodeInterface $node The node to compile
25
+ *
26
+ * @return Twig_CompilerInterface The current compiler instance
27
+ */
28
+ public function compile(Twig_NodeInterface $node);
29
+
30
+ /**
31
+ * Gets the current PHP code after compilation.
32
+ *
33
+ * @return string The PHP code
34
+ */
35
+ public function getSource();
36
+ }
Twig/lib/Twig/Environment.php ADDED
@@ -0,0 +1,1209 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Environment
19
+ {
20
+ const VERSION = '1.12.2-DEV';
21
+
22
+ protected $charset;
23
+ protected $loader;
24
+ protected $debug;
25
+ protected $autoReload;
26
+ protected $cache;
27
+ protected $lexer;
28
+ protected $parser;
29
+ protected $compiler;
30
+ protected $baseTemplateClass;
31
+ protected $extensions;
32
+ protected $parsers;
33
+ protected $visitors;
34
+ protected $filters;
35
+ protected $tests;
36
+ protected $functions;
37
+ protected $globals;
38
+ protected $runtimeInitialized;
39
+ protected $extensionInitialized;
40
+ protected $loadedTemplates;
41
+ protected $strictVariables;
42
+ protected $unaryOperators;
43
+ protected $binaryOperators;
44
+ protected $templateClassPrefix = '__TwigTemplate_';
45
+ protected $functionCallbacks;
46
+ protected $filterCallbacks;
47
+ protected $staging;
48
+
49
+ /**
50
+ * Constructor.
51
+ *
52
+ * Available options:
53
+ *
54
+ * * debug: When set to true, it automatically set "auto_reload" to true as
55
+ * well (default to false).
56
+ *
57
+ * * charset: The charset used by the templates (default to utf-8).
58
+ *
59
+ * * base_template_class: The base template class to use for generated
60
+ * templates (default to Twig_Template).
61
+ *
62
+ * * cache: An absolute path where to store the compiled templates, or
63
+ * false to disable compilation cache (default).
64
+ *
65
+ * * auto_reload: Whether to reload the template is the original source changed.
66
+ * If you don't provide the auto_reload option, it will be
67
+ * determined automatically base on the debug value.
68
+ *
69
+ * * strict_variables: Whether to ignore invalid variables in templates
70
+ * (default to false).
71
+ *
72
+ * * autoescape: Whether to enable auto-escaping (default to html):
73
+ * * false: disable auto-escaping
74
+ * * true: equivalent to html
75
+ * * html, js: set the autoescaping to one of the supported strategies
76
+ * * PHP callback: a PHP callback that returns an escaping strategy based on the template "filename"
77
+ *
78
+ * * optimizations: A flag that indicates which optimizations to apply
79
+ * (default to -1 which means that all optimizations are enabled;
80
+ * set it to 0 to disable).
81
+ *
82
+ * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
83
+ * @param array $options An array of options
84
+ */
85
+ public function __construct(Twig_LoaderInterface $loader = null, $options = array())
86
+ {
87
+ if (null !== $loader) {
88
+ $this->setLoader($loader);
89
+ }
90
+
91
+ $options = array_merge(array(
92
+ 'debug' => false,
93
+ 'charset' => 'UTF-8',
94
+ 'base_template_class' => 'Twig_Template',
95
+ 'strict_variables' => false,
96
+ 'autoescape' => 'html',
97
+ 'cache' => false,
98
+ 'auto_reload' => null,
99
+ 'optimizations' => -1,
100
+ ), $options);
101
+
102
+ $this->debug = (bool) $options['debug'];
103
+ $this->charset = $options['charset'];
104
+ $this->baseTemplateClass = $options['base_template_class'];
105
+ $this->autoReload = null === $options['auto_reload'] ? $this->debug : (bool) $options['auto_reload'];
106
+ $this->strictVariables = (bool) $options['strict_variables'];
107
+ $this->runtimeInitialized = false;
108
+ $this->setCache($options['cache']);
109
+ $this->functionCallbacks = array();
110
+ $this->filterCallbacks = array();
111
+
112
+ $this->addExtension(new Twig_Extension_Core());
113
+ $this->addExtension(new Twig_Extension_Escaper($options['autoescape']));
114
+ $this->addExtension(new Twig_Extension_Optimizer($options['optimizations']));
115
+ $this->extensionInitialized = false;
116
+ $this->staging = new Twig_Extension_Staging();
117
+ }
118
+
119
+ /**
120
+ * Gets the base template class for compiled templates.
121
+ *
122
+ * @return string The base template class name
123
+ */
124
+ public function getBaseTemplateClass()
125
+ {
126
+ return $this->baseTemplateClass;
127
+ }
128
+
129
+ /**
130
+ * Sets the base template class for compiled templates.
131
+ *
132
+ * @param string $class The base template class name
133
+ */
134
+ public function setBaseTemplateClass($class)
135
+ {
136
+ $this->baseTemplateClass = $class;
137
+ }
138
+
139
+ /**
140
+ * Enables debugging mode.
141
+ */
142
+ public function enableDebug()
143
+ {
144
+ $this->debug = true;
145
+ }
146
+
147
+ /**
148
+ * Disables debugging mode.
149
+ */
150
+ public function disableDebug()
151
+ {
152
+ $this->debug = false;
153
+ }
154
+
155
+ /**
156
+ * Checks if debug mode is enabled.
157
+ *
158
+ * @return Boolean true if debug mode is enabled, false otherwise
159
+ */
160
+ public function isDebug()
161
+ {
162
+ return $this->debug;
163
+ }
164
+
165
+ /**
166
+ * Enables the auto_reload option.
167
+ */
168
+ public function enableAutoReload()
169
+ {
170
+ $this->autoReload = true;
171
+ }
172
+
173
+ /**
174
+ * Disables the auto_reload option.
175
+ */
176
+ public function disableAutoReload()
177
+ {
178
+ $this->autoReload = false;
179
+ }
180
+
181
+ /**
182
+ * Checks if the auto_reload option is enabled.
183
+ *
184
+ * @return Boolean true if auto_reload is enabled, false otherwise
185
+ */
186
+ public function isAutoReload()
187
+ {
188
+ return $this->autoReload;
189
+ }
190
+
191
+ /**
192
+ * Enables the strict_variables option.
193
+ */
194
+ public function enableStrictVariables()
195
+ {
196
+ $this->strictVariables = true;
197
+ }
198
+
199
+ /**
200
+ * Disables the strict_variables option.
201
+ */
202
+ public function disableStrictVariables()
203
+ {
204
+ $this->strictVariables = false;
205
+ }
206
+
207
+ /**
208
+ * Checks if the strict_variables option is enabled.
209
+ *
210
+ * @return Boolean true if strict_variables is enabled, false otherwise
211
+ */
212
+ public function isStrictVariables()
213
+ {
214
+ return $this->strictVariables;
215
+ }
216
+
217
+ /**
218
+ * Gets the cache directory or false if cache is disabled.
219
+ *
220
+ * @return string|false
221
+ */
222
+ public function getCache()
223
+ {
224
+ return $this->cache;
225
+ }
226
+
227
+ /**
228
+ * Sets the cache directory or false if cache is disabled.
229
+ *
230
+ * @param string|false $cache The absolute path to the compiled templates,
231
+ * or false to disable cache
232
+ */
233
+ public function setCache($cache)
234
+ {
235
+ $this->cache = $cache ? $cache : false;
236
+ }
237
+
238
+ /**
239
+ * Gets the cache filename for a given template.
240
+ *
241
+ * @param string $name The template name
242
+ *
243
+ * @return string The cache file name
244
+ */
245
+ public function getCacheFilename($name)
246
+ {
247
+ if (false === $this->cache) {
248
+ return false;
249
+ }
250
+
251
+ $class = substr($this->getTemplateClass($name), strlen($this->templateClassPrefix));
252
+
253
+ return $this->getCache().'/'.substr($class, 0, 2).'/'.substr($class, 2, 2).'/'.substr($class, 4).'.php';
254
+ }
255
+
256
+ /**
257
+ * Gets the template class associated with the given string.
258
+ *
259
+ * @param string $name The name for which to calculate the template class name
260
+ * @param integer $index The index if it is an embedded template
261
+ *
262
+ * @return string The template class name
263
+ */
264
+ public function getTemplateClass($name, $index = null)
265
+ {
266
+ return $this->templateClassPrefix.md5($this->loader->getCacheKey($name)).(null === $index ? '' : '_'.$index);
267
+ }
268
+
269
+ /**
270
+ * Gets the template class prefix.
271
+ *
272
+ * @return string The template class prefix
273
+ */
274
+ public function getTemplateClassPrefix()
275
+ {
276
+ return $this->templateClassPrefix;
277
+ }
278
+
279
+ /**
280
+ * Renders a template.
281
+ *
282
+ * @param string $name The template name
283
+ * @param array $context An array of parameters to pass to the template
284
+ *
285
+ * @return string The rendered template
286
+ */
287
+ public function render($name, array $context = array())
288
+ {
289
+ return $this->loadTemplate($name)->render($context);
290
+ }
291
+
292
+ /**
293
+ * Displays a template.
294
+ *
295
+ * @param string $name The template name
296
+ * @param array $context An array of parameters to pass to the template
297
+ */
298
+ public function display($name, array $context = array())
299
+ {
300
+ $this->loadTemplate($name)->display($context);
301
+ }
302
+
303
+ /**
304
+ * Loads a template by name.
305
+ *
306
+ * @param string $name The template name
307
+ * @param integer $index The index if it is an embedded template
308
+ *
309
+ * @return Twig_TemplateInterface A template instance representing the given template name
310
+ */
311
+ public function loadTemplate($name, $index = null)
312
+ {
313
+ $cls = $this->getTemplateClass($name, $index);
314
+
315
+ if (isset($this->loadedTemplates[$cls])) {
316
+ return $this->loadedTemplates[$cls];
317
+ }
318
+
319
+ if (!class_exists($cls, false)) {
320
+ if (false === $cache = $this->getCacheFilename($name)) {
321
+ eval('?>'.$this->compileSource($this->loader->getSource($name), $name));
322
+ } else {
323
+ if (!is_file($cache) || ($this->isAutoReload() && !$this->isTemplateFresh($name, filemtime($cache)))) {
324
+ $this->writeCacheFile($cache, $this->compileSource($this->loader->getSource($name), $name));
325
+ }
326
+
327
+ require_once $cache;
328
+ }
329
+ }
330
+
331
+ if (!$this->runtimeInitialized) {
332
+ $this->initRuntime();
333
+ }
334
+
335
+ return $this->loadedTemplates[$cls] = new $cls($this);
336
+ }
337
+
338
+ /**
339
+ * Returns true if the template is still fresh.
340
+ *
341
+ * Besides checking the loader for freshness information,
342
+ * this method also checks if the enabled extensions have
343
+ * not changed.
344
+ *
345
+ * @param string $name The template name
346
+ * @param timestamp $time The last modification time of the cached template
347
+ *
348
+ * @return Boolean true if the template is fresh, false otherwise
349
+ */
350
+ public function isTemplateFresh($name, $time)
351
+ {
352
+ foreach ($this->extensions as $extension) {
353
+ $r = new ReflectionObject($extension);
354
+ if (filemtime($r->getFileName()) > $time) {
355
+ return false;
356
+ }
357
+ }
358
+
359
+ return $this->loader->isFresh($name, $time);
360
+ }
361
+
362
+ public function resolveTemplate($names)
363
+ {
364
+ if (!is_array($names)) {
365
+ $names = array($names);
366
+ }
367
+
368
+ foreach ($names as $name) {
369
+ if ($name instanceof Twig_Template) {
370
+ return $name;
371
+ }
372
+
373
+ try {
374
+ return $this->loadTemplate($name);
375
+ } catch (Twig_Error_Loader $e) {
376
+ }
377
+ }
378
+
379
+ if (1 === count($names)) {
380
+ throw $e;
381
+ }
382
+
383
+ throw new Twig_Error_Loader(sprintf('Unable to find one of the following templates: "%s".', implode('", "', $names)));
384
+ }
385
+
386
+ /**
387
+ * Clears the internal template cache.
388
+ */
389
+ public function clearTemplateCache()
390
+ {
391
+ $this->loadedTemplates = array();
392
+ }
393
+
394
+ /**
395
+ * Clears the template cache files on the filesystem.
396
+ */
397
+ public function clearCacheFiles()
398
+ {
399
+ if (false === $this->cache) {
400
+ return;
401
+ }
402
+
403
+ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($this->cache), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
404
+ if ($file->isFile()) {
405
+ @unlink($file->getPathname());
406
+ }
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Gets the Lexer instance.
412
+ *
413
+ * @return Twig_LexerInterface A Twig_LexerInterface instance
414
+ */
415
+ public function getLexer()
416
+ {
417
+ if (null === $this->lexer) {
418
+ $this->lexer = new Twig_Lexer($this);
419
+ }
420
+
421
+ return $this->lexer;
422
+ }
423
+
424
+ /**
425
+ * Sets the Lexer instance.
426
+ *
427
+ * @param Twig_LexerInterface A Twig_LexerInterface instance
428
+ */
429
+ public function setLexer(Twig_LexerInterface $lexer)
430
+ {
431
+ $this->lexer = $lexer;
432
+ }
433
+
434
+ /**
435
+ * Tokenizes a source code.
436
+ *
437
+ * @param string $source The template source code
438
+ * @param string $name The template name
439
+ *
440
+ * @return Twig_TokenStream A Twig_TokenStream instance
441
+ */
442
+ public function tokenize($source, $name = null)
443
+ {
444
+ return $this->getLexer()->tokenize($source, $name);
445
+ }
446
+
447
+ /**
448
+ * Gets the Parser instance.
449
+ *
450
+ * @return Twig_ParserInterface A Twig_ParserInterface instance
451
+ */
452
+ public function getParser()
453
+ {
454
+ if (null === $this->parser) {
455
+ $this->parser = new Twig_Parser($this);
456
+ }
457
+
458
+ return $this->parser;
459
+ }
460
+
461
+ /**
462
+ * Sets the Parser instance.
463
+ *
464
+ * @param Twig_ParserInterface A Twig_ParserInterface instance
465
+ */
466
+ public function setParser(Twig_ParserInterface $parser)
467
+ {
468
+ $this->parser = $parser;
469
+ }
470
+
471
+ /**
472
+ * Parses a token stream.
473
+ *
474
+ * @param Twig_TokenStream $tokens A Twig_TokenStream instance
475
+ *
476
+ * @return Twig_Node_Module A Node tree
477
+ */
478
+ public function parse(Twig_TokenStream $tokens)
479
+ {
480
+ return $this->getParser()->parse($tokens);
481
+ }
482
+
483
+ /**
484
+ * Gets the Compiler instance.
485
+ *
486
+ * @return Twig_CompilerInterface A Twig_CompilerInterface instance
487
+ */
488
+ public function getCompiler()
489
+ {
490
+ if (null === $this->compiler) {
491
+ $this->compiler = new Twig_Compiler($this);
492
+ }
493
+
494
+ return $this->compiler;
495
+ }
496
+
497
+ /**
498
+ * Sets the Compiler instance.
499
+ *
500
+ * @param Twig_CompilerInterface $compiler A Twig_CompilerInterface instance
501
+ */
502
+ public function setCompiler(Twig_CompilerInterface $compiler)
503
+ {
504
+ $this->compiler = $compiler;
505
+ }
506
+
507
+ /**
508
+ * Compiles a Node.
509
+ *
510
+ * @param Twig_NodeInterface $node A Twig_NodeInterface instance
511
+ *
512
+ * @return string The compiled PHP source code
513
+ */
514
+ public function compile(Twig_NodeInterface $node)
515
+ {
516
+ return $this->getCompiler()->compile($node)->getSource();
517
+ }
518
+
519
+ /**
520
+ * Compiles a template source code.
521
+ *
522
+ * @param string $source The template source code
523
+ * @param string $name The template name
524
+ *
525
+ * @return string The compiled PHP source code
526
+ */
527
+ public function compileSource($source, $name = null)
528
+ {
529
+ try {
530
+ return $this->compile($this->parse($this->tokenize($source, $name)));
531
+ } catch (Twig_Error $e) {
532
+ $e->setTemplateFile($name);
533
+ throw $e;
534
+ } catch (Exception $e) {
535
+ throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the compilation of a template ("%s").', $e->getMessage()), -1, $name, $e);
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Sets the Loader instance.
541
+ *
542
+ * @param Twig_LoaderInterface $loader A Twig_LoaderInterface instance
543
+ */
544
+ public function setLoader(Twig_LoaderInterface $loader)
545
+ {
546
+ $this->loader = $loader;
547
+ }
548
+
549
+ /**
550
+ * Gets the Loader instance.
551
+ *
552
+ * @return Twig_LoaderInterface A Twig_LoaderInterface instance
553
+ */
554
+ public function getLoader()
555
+ {
556
+ return $this->loader;
557
+ }
558
+
559
+ /**
560
+ * Sets the default template charset.
561
+ *
562
+ * @param string $charset The default charset
563
+ */
564
+ public function setCharset($charset)
565
+ {
566
+ $this->charset = $charset;
567
+ }
568
+
569
+ /**
570
+ * Gets the default template charset.
571
+ *
572
+ * @return string The default charset
573
+ */
574
+ public function getCharset()
575
+ {
576
+ return $this->charset;
577
+ }
578
+
579
+ /**
580
+ * Initializes the runtime environment.
581
+ */
582
+ public function initRuntime()
583
+ {
584
+ $this->runtimeInitialized = true;
585
+
586
+ foreach ($this->getExtensions() as $extension) {
587
+ $extension->initRuntime($this);
588
+ }
589
+ }
590
+
591
+ /**
592
+ * Returns true if the given extension is registered.
593
+ *
594
+ * @param string $name The extension name
595
+ *
596
+ * @return Boolean Whether the extension is registered or not
597
+ */
598
+ public function hasExtension($name)
599
+ {
600
+ return isset($this->extensions[$name]);
601
+ }
602
+
603
+ /**
604
+ * Gets an extension by name.
605
+ *
606
+ * @param string $name The extension name
607
+ *
608
+ * @return Twig_ExtensionInterface A Twig_ExtensionInterface instance
609
+ */
610
+ public function getExtension($name)
611
+ {
612
+ if (!isset($this->extensions[$name])) {
613
+ throw new Twig_Error_Runtime(sprintf('The "%s" extension is not enabled.', $name));
614
+ }
615
+
616
+ return $this->extensions[$name];
617
+ }
618
+
619
+ /**
620
+ * Registers an extension.
621
+ *
622
+ * @param Twig_ExtensionInterface $extension A Twig_ExtensionInterface instance
623
+ */
624
+ public function addExtension(Twig_ExtensionInterface $extension)
625
+ {
626
+ if ($this->extensionInitialized) {
627
+ throw new LogicException(sprintf('Unable to register extension "%s" as extensions have already been initialized.', $extension->getName()));
628
+ }
629
+
630
+ $this->extensions[$extension->getName()] = $extension;
631
+ }
632
+
633
+ /**
634
+ * Removes an extension by name.
635
+ *
636
+ * This method is deprecated and you should not use it.
637
+ *
638
+ * @param string $name The extension name
639
+ *
640
+ * @deprecated since 1.12 (to be removed in 2.0)
641
+ */
642
+ public function removeExtension($name)
643
+ {
644
+ if ($this->extensionInitialized) {
645
+ throw new LogicException(sprintf('Unable to remove extension "%s" as extensions have already been initialized.', $name));
646
+ }
647
+
648
+ unset($this->extensions[$name]);
649
+ }
650
+
651
+ /**
652
+ * Registers an array of extensions.
653
+ *
654
+ * @param array $extensions An array of extensions
655
+ */
656
+ public function setExtensions(array $extensions)
657
+ {
658
+ foreach ($extensions as $extension) {
659
+ $this->addExtension($extension);
660
+ }
661
+ }
662
+
663
+ /**
664
+ * Returns all registered extensions.
665
+ *
666
+ * @return array An array of extensions
667
+ */
668
+ public function getExtensions()
669
+ {
670
+ return $this->extensions;
671
+ }
672
+
673
+ /**
674
+ * Registers a Token Parser.
675
+ *
676
+ * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
677
+ */
678
+ public function addTokenParser(Twig_TokenParserInterface $parser)
679
+ {
680
+ if ($this->extensionInitialized) {
681
+ throw new LogicException('Unable to add a token parser as extensions have already been initialized.');
682
+ }
683
+
684
+ $this->staging->addTokenParser($parser);
685
+ }
686
+
687
+ /**
688
+ * Gets the registered Token Parsers.
689
+ *
690
+ * @return Twig_TokenParserBrokerInterface A broker containing token parsers
691
+ */
692
+ public function getTokenParsers()
693
+ {
694
+ if (!$this->extensionInitialized) {
695
+ $this->initExtensions();
696
+ }
697
+
698
+ return $this->parsers;
699
+ }
700
+
701
+ /**
702
+ * Gets registered tags.
703
+ *
704
+ * Be warned that this method cannot return tags defined by Twig_TokenParserBrokerInterface classes.
705
+ *
706
+ * @return Twig_TokenParserInterface[] An array of Twig_TokenParserInterface instances
707
+ */
708
+ public function getTags()
709
+ {
710
+ $tags = array();
711
+ foreach ($this->getTokenParsers()->getParsers() as $parser) {
712
+ if ($parser instanceof Twig_TokenParserInterface) {
713
+ $tags[$parser->getTag()] = $parser;
714
+ }
715
+ }
716
+
717
+ return $tags;
718
+ }
719
+
720
+ /**
721
+ * Registers a Node Visitor.
722
+ *
723
+ * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
724
+ */
725
+ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
726
+ {
727
+ if ($this->extensionInitialized) {
728
+ throw new LogicException('Unable to add a node visitor as extensions have already been initialized.', $extension->getName());
729
+ }
730
+
731
+ $this->staging->addNodeVisitor($visitor);
732
+ }
733
+
734
+ /**
735
+ * Gets the registered Node Visitors.
736
+ *
737
+ * @return Twig_NodeVisitorInterface[] An array of Twig_NodeVisitorInterface instances
738
+ */
739
+ public function getNodeVisitors()
740
+ {
741
+ if (!$this->extensionInitialized) {
742
+ $this->initExtensions();
743
+ }
744
+
745
+ return $this->visitors;
746
+ }
747
+
748
+ /**
749
+ * Registers a Filter.
750
+ *
751
+ * @param string|Twig_SimpleFilter $name The filter name or a Twig_SimpleFilter instance
752
+ * @param Twig_FilterInterface|Twig_SimpleFilter $filter A Twig_FilterInterface instance or a Twig_SimpleFilter instance
753
+ */
754
+ public function addFilter($name, $filter = null)
755
+ {
756
+ if ($this->extensionInitialized) {
757
+ throw new LogicException(sprintf('Unable to add filter "%s" as extensions have already been initialized.', $name));
758
+ }
759
+
760
+ if (!$name instanceof Twig_SimpleFilter && !($filter instanceof Twig_SimpleFilter || $filter instanceof Twig_FilterInterface)) {
761
+ throw new LogicException('A filter must be an instance of Twig_FilterInterface or Twig_SimpleFilter');
762
+ }
763
+
764
+ if ($name instanceof Twig_SimpleFilter) {
765
+ $filter = $name;
766
+ $name = $filter->getName();
767
+ }
768
+
769
+ $this->staging->addFilter($name, $filter);
770
+ }
771
+
772
+ /**
773
+ * Get a filter by name.
774
+ *
775
+ * Subclasses may override this method and load filters differently;
776
+ * so no list of filters is available.
777
+ *
778
+ * @param string $name The filter name
779
+ *
780
+ * @return Twig_Filter|false A Twig_Filter instance or false if the filter does not exist
781
+ */
782
+ public function getFilter($name)
783
+ {
784
+ if (!$this->extensionInitialized) {
785
+ $this->initExtensions();
786
+ }
787
+
788
+ if (isset($this->filters[$name])) {
789
+ return $this->filters[$name];
790
+ }
791
+
792
+ foreach ($this->filters as $pattern => $filter) {
793
+ $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
794
+
795
+ if ($count) {
796
+ if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
797
+ array_shift($matches);
798
+ $filter->setArguments($matches);
799
+
800
+ return $filter;
801
+ }
802
+ }
803
+ }
804
+
805
+ foreach ($this->filterCallbacks as $callback) {
806
+ if (false !== $filter = call_user_func($callback, $name)) {
807
+ return $filter;
808
+ }
809
+ }
810
+
811
+ return false;
812
+ }
813
+
814
+ public function registerUndefinedFilterCallback($callable)
815
+ {
816
+ $this->filterCallbacks[] = $callable;
817
+ }
818
+
819
+ /**
820
+ * Gets the registered Filters.
821
+ *
822
+ * Be warned that this method cannot return filters defined with registerUndefinedFunctionCallback.
823
+ *
824
+ * @return Twig_FilterInterface[] An array of Twig_FilterInterface instances
825
+ *
826
+ * @see registerUndefinedFilterCallback
827
+ */
828
+ public function getFilters()
829
+ {
830
+ if (!$this->extensionInitialized) {
831
+ $this->initExtensions();
832
+ }
833
+
834
+ return $this->filters;
835
+ }
836
+
837
+ /**
838
+ * Registers a Test.
839
+ *
840
+ * @param string|Twig_SimpleTest $name The test name or a Twig_SimpleTest instance
841
+ * @param Twig_TestInterface|Twig_SimpleTest $test A Twig_TestInterface instance or a Twig_SimpleTest instance
842
+ */
843
+ public function addTest($name, $test = null)
844
+ {
845
+ if ($this->extensionInitialized) {
846
+ throw new LogicException(sprintf('Unable to add test "%s" as extensions have already been initialized.', $name));
847
+ }
848
+
849
+ if (!$name instanceof Twig_SimpleTest && !($test instanceof Twig_SimpleTest || $test instanceof Twig_TestInterface)) {
850
+ throw new LogicException('A test must be an instance of Twig_TestInterface or Twig_SimpleTest');
851
+ }
852
+
853
+ if ($name instanceof Twig_SimpleTest) {
854
+ $test = $name;
855
+ $name = $test->getName();
856
+ }
857
+
858
+ $this->staging->addTest($name, $test);
859
+ }
860
+
861
+ /**
862
+ * Gets the registered Tests.
863
+ *
864
+ * @return Twig_TestInterface[] An array of Twig_TestInterface instances
865
+ */
866
+ public function getTests()
867
+ {
868
+ if (!$this->extensionInitialized) {
869
+ $this->initExtensions();
870
+ }
871
+
872
+ return $this->tests;
873
+ }
874
+
875
+ /**
876
+ * Gets a test by name.
877
+ *
878
+ * @param string $name The test name
879
+ *
880
+ * @return Twig_Test|false A Twig_Test instance or false if the test does not exist
881
+ */
882
+ public function getTest($name)
883
+ {
884
+ if (!$this->extensionInitialized) {
885
+ $this->initExtensions();
886
+ }
887
+
888
+ if (isset($this->tests[$name])) {
889
+ return $this->tests[$name];
890
+ }
891
+
892
+ return false;
893
+ }
894
+
895
+ /**
896
+ * Registers a Function.
897
+ *
898
+ * @param string|Twig_SimpleFunction $name The function name or a Twig_SimpleFunction instance
899
+ * @param Twig_FunctionInterface|Twig_SimpleFunction $function A Twig_FunctionInterface instance or a Twig_SimpleFunction instance
900
+ */
901
+ public function addFunction($name, $function = null)
902
+ {
903
+ if ($this->extensionInitialized) {
904
+ throw new LogicException(sprintf('Unable to add function "%s" as extensions have already been initialized.', $name));
905
+ }
906
+
907
+ if (!$name instanceof Twig_SimpleFunction && !($function instanceof Twig_SimpleFunction || $function instanceof Twig_FunctionInterface)) {
908
+ throw new LogicException('A function must be an instance of Twig_FunctionInterface or Twig_SimpleFunction');
909
+ }
910
+
911
+ if ($name instanceof Twig_SimpleFunction) {
912
+ $function = $name;
913
+ $name = $function->getName();
914
+ }
915
+
916
+ $this->staging->addFunction($name, $function);
917
+ }
918
+
919
+ /**
920
+ * Get a function by name.
921
+ *
922
+ * Subclasses may override this method and load functions differently;
923
+ * so no list of functions is available.
924
+ *
925
+ * @param string $name function name
926
+ *
927
+ * @return Twig_Function|false A Twig_Function instance or false if the function does not exist
928
+ */
929
+ public function getFunction($name)
930
+ {
931
+ if (!$this->extensionInitialized) {
932
+ $this->initExtensions();
933
+ }
934
+
935
+ if (isset($this->functions[$name])) {
936
+ return $this->functions[$name];
937
+ }
938
+
939
+ foreach ($this->functions as $pattern => $function) {
940
+ $pattern = str_replace('\\*', '(.*?)', preg_quote($pattern, '#'), $count);
941
+
942
+ if ($count) {
943
+ if (preg_match('#^'.$pattern.'$#', $name, $matches)) {
944
+ array_shift($matches);
945
+ $function->setArguments($matches);
946
+
947
+ return $function;
948
+ }
949
+ }
950
+ }
951
+
952
+ foreach ($this->functionCallbacks as $callback) {
953
+ if (false !== $function = call_user_func($callback, $name)) {
954
+ return $function;
955
+ }
956
+ }
957
+
958
+ return false;
959
+ }
960
+
961
+ public function registerUndefinedFunctionCallback($callable)
962
+ {
963
+ $this->functionCallbacks[] = $callable;
964
+ }
965
+
966
+ /**
967
+ * Gets registered functions.
968
+ *
969
+ * Be warned that this method cannot return functions defined with registerUndefinedFunctionCallback.
970
+ *
971
+ * @return Twig_FunctionInterface[] An array of Twig_FunctionInterface instances
972
+ *
973
+ * @see registerUndefinedFunctionCallback
974
+ */
975
+ public function getFunctions()
976
+ {
977
+ if (!$this->extensionInitialized) {
978
+ $this->initExtensions();
979
+ }
980
+
981
+ return $this->functions;
982
+ }
983
+
984
+ /**
985
+ * Registers a Global.
986
+ *
987
+ * New globals can be added before compiling or rendering a template;
988
+ * but after, you can only update existing globals.
989
+ *
990
+ * @param string $name The global name
991
+ * @param mixed $value The global value
992
+ */
993
+ public function addGlobal($name, $value)
994
+ {
995
+ if ($this->extensionInitialized || $this->runtimeInitialized) {
996
+ if (null === $this->globals) {
997
+ $this->initGlobals();
998
+ }
999
+
1000
+ /* This condition must be uncommented in Twig 2.0
1001
+ if (!array_key_exists($name, $this->globals)) {
1002
+ throw new LogicException(sprintf('Unable to add global "%s" as the runtime or the extensions have already been initialized.', $name));
1003
+ }
1004
+ */
1005
+ }
1006
+
1007
+ if ($this->extensionInitialized || $this->runtimeInitialized) {
1008
+ // update the value
1009
+ $this->globals[$name] = $value;
1010
+ } else {
1011
+ $this->staging->addGlobal($name, $value);
1012
+ }
1013
+ }
1014
+
1015
+ /**
1016
+ * Gets the registered Globals.
1017
+ *
1018
+ * @return array An array of globals
1019
+ */
1020
+ public function getGlobals()
1021
+ {
1022
+ if (null === $this->globals || !($this->runtimeInitialized || $this->extensionInitialized)) {
1023
+ $this->initGlobals();
1024
+ }
1025
+
1026
+ return $this->globals;
1027
+ }
1028
+
1029
+ /**
1030
+ * Merges a context with the defined globals.
1031
+ *
1032
+ * @param array $context An array representing the context
1033
+ *
1034
+ * @return array The context merged with the globals
1035
+ */
1036
+ public function mergeGlobals(array $context)
1037
+ {
1038
+ // we don't use array_merge as the context being generally
1039
+ // bigger than globals, this code is faster.
1040
+ foreach ($this->getGlobals() as $key => $value) {
1041
+ if (!array_key_exists($key, $context)) {
1042
+ $context[$key] = $value;
1043
+ }
1044
+ }
1045
+
1046
+ return $context;
1047
+ }
1048
+
1049
+ /**
1050
+ * Gets the registered unary Operators.
1051
+ *
1052
+ * @return array An array of unary operators
1053
+ */
1054
+ public function getUnaryOperators()
1055
+ {
1056
+ if (!$this->extensionInitialized) {
1057
+ $this->initExtensions();
1058
+ }
1059
+
1060
+ return $this->unaryOperators;
1061
+ }
1062
+
1063
+ /**
1064
+ * Gets the registered binary Operators.
1065
+ *
1066
+ * @return array An array of binary operators
1067
+ */
1068
+ public function getBinaryOperators()
1069
+ {
1070
+ if (!$this->extensionInitialized) {
1071
+ $this->initExtensions();
1072
+ }
1073
+
1074
+ return $this->binaryOperators;
1075
+ }
1076
+
1077
+ public function computeAlternatives($name, $items)
1078
+ {
1079
+ $alternatives = array();
1080
+ foreach ($items as $item) {
1081
+ $lev = levenshtein($name, $item);
1082
+ if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) {
1083
+ $alternatives[$item] = $lev;
1084
+ }
1085
+ }
1086
+ asort($alternatives);
1087
+
1088
+ return array_keys($alternatives);
1089
+ }
1090
+
1091
+ protected function initGlobals()
1092
+ {
1093
+ $this->globals = array();
1094
+ foreach ($this->extensions as $extension) {
1095
+ $this->globals = array_merge($this->globals, $extension->getGlobals());
1096
+ }
1097
+ $this->globals = array_merge($this->globals, $this->staging->getGlobals());
1098
+ }
1099
+
1100
+ protected function initExtensions()
1101
+ {
1102
+ if ($this->extensionInitialized) {
1103
+ return;
1104
+ }
1105
+
1106
+ $this->extensionInitialized = true;
1107
+ $this->parsers = new Twig_TokenParserBroker();
1108
+ $this->filters = array();
1109
+ $this->functions = array();
1110
+ $this->tests = array();
1111
+ $this->visitors = array();
1112
+ $this->unaryOperators = array();
1113
+ $this->binaryOperators = array();
1114
+
1115
+ foreach ($this->extensions as $extension) {
1116
+ $this->initExtension($extension);
1117
+ }
1118
+ $this->initExtension($this->staging);
1119
+ }
1120
+
1121
+ protected function initExtension(Twig_ExtensionInterface $extension)
1122
+ {
1123
+ // filters
1124
+ foreach ($extension->getFilters() as $name => $filter) {
1125
+ if ($name instanceof Twig_SimpleFilter) {
1126
+ $filter = $name;
1127
+ $name = $filter->getName();
1128
+ } elseif ($filter instanceof Twig_SimpleFilter) {
1129
+ $name = $filter->getName();
1130
+ }
1131
+
1132
+ $this->filters[$name] = $filter;
1133
+ }
1134
+
1135
+ // functions
1136
+ foreach ($extension->getFunctions() as $name => $function) {
1137
+ if ($name instanceof Twig_SimpleFunction) {
1138
+ $function = $name;
1139
+ $name = $function->getName();
1140
+ } elseif ($function instanceof Twig_SimpleFunction) {
1141
+ $name = $function->getName();
1142
+ }
1143
+
1144
+ $this->functions[$name] = $function;
1145
+ }
1146
+
1147
+ // tests
1148
+ foreach ($extension->getTests() as $name => $test) {
1149
+ if ($name instanceof Twig_SimpleTest) {
1150
+ $test = $name;
1151
+ $name = $test->getName();
1152
+ } elseif ($test instanceof Twig_SimpleTest) {
1153
+ $name = $test->getName();
1154
+ }
1155
+
1156
+ $this->tests[$name] = $test;
1157
+ }
1158
+
1159
+ // token parsers
1160
+ foreach ($extension->getTokenParsers() as $parser) {
1161
+ if ($parser instanceof Twig_TokenParserInterface) {
1162
+ $this->parsers->addTokenParser($parser);
1163
+ } elseif ($parser instanceof Twig_TokenParserBrokerInterface) {
1164
+ $this->parsers->addTokenParserBroker($parser);
1165
+ } else {
1166
+ throw new LogicException('getTokenParsers() must return an array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances');
1167
+ }
1168
+ }
1169
+
1170
+ // node visitors
1171
+ foreach ($extension->getNodeVisitors() as $visitor) {
1172
+ $this->visitors[] = $visitor;
1173
+ }
1174
+
1175
+ // operators
1176
+ if ($operators = $extension->getOperators()) {
1177
+ if (2 !== count($operators)) {
1178
+ throw new InvalidArgumentException(sprintf('"%s::getOperators()" does not return a valid operators array.', get_class($extension)));
1179
+ }
1180
+
1181
+ $this->unaryOperators = array_merge($this->unaryOperators, $operators[0]);
1182
+ $this->binaryOperators = array_merge($this->binaryOperators, $operators[1]);
1183
+ }
1184
+ }
1185
+
1186
+ protected function writeCacheFile($file, $content)
1187
+ {
1188
+ $dir = dirname($file);
1189
+ if (!is_dir($dir)) {
1190
+ if (false === @mkdir($dir, 0777, true) && !is_dir($dir)) {
1191
+ throw new RuntimeException(sprintf("Unable to create the cache directory (%s).", $dir));
1192
+ }
1193
+ } elseif (!is_writable($dir)) {
1194
+ throw new RuntimeException(sprintf("Unable to write in the cache directory (%s).", $dir));
1195
+ }
1196
+
1197
+ $tmpFile = tempnam(dirname($file), basename($file));
1198
+ if (false !== @file_put_contents($tmpFile, $content)) {
1199
+ // rename does not work on Win32 before 5.2.6
1200
+ if (@rename($tmpFile, $file) || (@copy($tmpFile, $file) && unlink($tmpFile))) {
1201
+ @chmod($file, 0666 & ~umask());
1202
+
1203
+ return;
1204
+ }
1205
+ }
1206
+
1207
+ throw new RuntimeException(sprintf('Failed to write cache file "%s".', $file));
1208
+ }
1209
+ }
Twig/lib/Twig/Error.php ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
33
+ * @author Fabien Potencier <fabien@symfony.com>
34
+ */
35
+ class Twig_Error extends Exception
36
+ {
37
+ protected $lineno;
38
+ protected $filename;
39
+ protected $rawMessage;
40
+ protected $previous;
41
+
42
+ /**
43
+ * Constructor.
44
+ *
45
+ * Set both the line number and the filename to false to
46
+ * disable automatic guessing of the original template name
47
+ * and line number.
48
+ *
49
+ * Set the line number to -1 to enable its automatic guessing.
50
+ * Set the filename to null to enable its automatic guessing.
51
+ *
52
+ * By default, automatic guessing is enabled.
53
+ *
54
+ * @param string $message The error message
55
+ * @param integer $lineno The template line where the error occurred
56
+ * @param string $filename The template file name where the error occurred
57
+ * @param Exception $previous The previous exception
58
+ */
59
+ public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
60
+ {
61
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
62
+ $this->previous = $previous;
63
+ parent::__construct('');
64
+ } else {
65
+ parent::__construct('', 0, $previous);
66
+ }
67
+
68
+ $this->lineno = $lineno;
69
+ $this->filename = $filename;
70
+
71
+ if (-1 === $this->lineno || null === $this->filename) {
72
+ $this->guessTemplateInfo();
73
+ }
74
+
75
+ $this->rawMessage = $message;
76
+
77
+ $this->updateRepr();
78
+ }
79
+
80
+ /**
81
+ * Gets the raw message.
82
+ *
83
+ * @return string The raw message
84
+ */
85
+ public function getRawMessage()
86
+ {
87
+ return $this->rawMessage;
88
+ }
89
+
90
+ /**
91
+ * Gets the filename where the error occurred.
92
+ *
93
+ * @return string The filename
94
+ */
95
+ public function getTemplateFile()
96
+ {
97
+ return $this->filename;
98
+ }
99
+
100
+ /**
101
+ * Sets the filename where the error occurred.
102
+ *
103
+ * @param string $filename The filename
104
+ */
105
+ public function setTemplateFile($filename)
106
+ {
107
+ $this->filename = $filename;
108
+
109
+ $this->updateRepr();
110
+ }
111
+
112
+ /**
113
+ * Gets the template line where the error occurred.
114
+ *
115
+ * @return integer The template line
116
+ */
117
+ public function getTemplateLine()
118
+ {
119
+ return $this->lineno;
120
+ }
121
+
122
+ /**
123
+ * Sets the template line where the error occurred.
124
+ *
125
+ * @param integer $lineno The template line
126
+ */
127
+ public function setTemplateLine($lineno)
128
+ {
129
+ $this->lineno = $lineno;
130
+
131
+ $this->updateRepr();
132
+ }
133
+
134
+ public function guess()
135
+ {
136
+ $this->guessTemplateInfo();
137
+ $this->updateRepr();
138
+ }
139
+
140
+ /**
141
+ * For PHP < 5.3.0, provides access to the getPrevious() method.
142
+ *
143
+ * @param string $method The method name
144
+ * @param array $arguments The parameters to be passed to the method
145
+ *
146
+ * @return Exception The previous exception or null
147
+ *
148
+ * @throws BadMethodCallException
149
+ */
150
+ public function __call($method, $arguments)
151
+ {
152
+ if ('getprevious' == strtolower($method)) {
153
+ return $this->previous;
154
+ }
155
+
156
+ throw new BadMethodCallException(sprintf('Method "Twig_Error::%s()" does not exist.', $method));
157
+ }
158
+
159
+ protected function updateRepr()
160
+ {
161
+ $this->message = $this->rawMessage;
162
+
163
+ $dot = false;
164
+ if ('.' === substr($this->message, -1)) {
165
+ $this->message = substr($this->message, 0, -1);
166
+ $dot = true;
167
+ }
168
+
169
+ if ($this->filename) {
170
+ if (is_string($this->filename) || (is_object($this->filename) && method_exists($this->filename, '__toString'))) {
171
+ $filename = sprintf('"%s"', $this->filename);
172
+ } else {
173
+ $filename = json_encode($this->filename);
174
+ }
175
+ $this->message .= sprintf(' in %s', $filename);
176
+ }
177
+
178
+ if ($this->lineno && $this->lineno >= 0) {
179
+ $this->message .= sprintf(' at line %d', $this->lineno);
180
+ }
181
+
182
+ if ($dot) {
183
+ $this->message .= '.';
184
+ }
185
+ }
186
+
187
+ protected function guessTemplateInfo()
188
+ {
189
+ $template = null;
190
+ foreach (debug_backtrace() as $trace) {
191
+ if (isset($trace['object']) && $trace['object'] instanceof Twig_Template && 'Twig_Template' !== get_class($trace['object'])) {
192
+ if (null === $this->filename || $this->filename == $trace['object']->getTemplateName()) {
193
+ $template = $trace['object'];
194
+ }
195
+ }
196
+ }
197
+
198
+ // update template filename
199
+ if (null !== $template && null === $this->filename) {
200
+ $this->filename = $template->getTemplateName();
201
+ }
202
+
203
+ if (null === $template || $this->lineno > -1) {
204
+ return;
205
+ }
206
+
207
+ $r = new ReflectionObject($template);
208
+ $file = $r->getFileName();
209
+
210
+ $exceptions = array($e = $this);
211
+ while (($e instanceof self || method_exists($e, 'getPrevious')) && $e = $e->getPrevious()) {
212
+ $exceptions[] = $e;
213
+ }
214
+
215
+ while ($e = array_pop($exceptions)) {
216
+ $traces = $e->getTrace();
217
+ while ($trace = array_shift($traces)) {
218
+ if (!isset($trace['file']) || !isset($trace['line']) || $file != $trace['file']) {
219
+ continue;
220
+ }
221
+
222
+ foreach ($template->getDebugInfo() as $codeLine => $templateLine) {
223
+ if ($codeLine <= $trace['line']) {
224
+ // update template line
225
+ $this->lineno = $templateLine;
226
+
227
+ return;
228
+ }
229
+ }
230
+ }
231
+ }
232
+ }
233
+ }
Twig/lib/Twig/Error/Loader.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
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
+ * @package twig
24
+ * @author Fabien Potencier <fabien@symfony.com>
25
+ */
26
+ class Twig_Error_Loader extends Twig_Error
27
+ {
28
+ public function __construct($message, $lineno = -1, $filename = null, Exception $previous = null)
29
+ {
30
+ parent::__construct($message, false, false, $previous);
31
+ }
32
+ }
Twig/lib/Twig/Error/Runtime.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Error_Runtime extends Twig_Error
20
+ {
21
+ }
Twig/lib/Twig/Error/Syntax.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Error_Syntax extends Twig_Error
20
+ {
21
+ }
Twig/lib/Twig/ExistsLoaderInterface.php ADDED
@@ -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
+ * Adds an exists() method for loaders.
14
+ *
15
+ * @package twig
16
+ * @author Florin Patan <florinpatan@gmail.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ interface Twig_ExistsLoaderInterface
20
+ {
21
+ /**
22
+ * Check if we have the source code of a template, given its name.
23
+ *
24
+ * @param string $name The name of the template to check if we can load
25
+ *
26
+ * @return boolean If the template source code is handled by this loader or not
27
+ */
28
+ public function exists($name);
29
+ }
Twig/lib/Twig/ExpressionParser.php ADDED
@@ -0,0 +1,601 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
22
+ * @author Fabien Potencier <fabien@symfony.com>
23
+ */
24
+ class Twig_ExpressionParser
25
+ {
26
+ const OPERATOR_LEFT = 1;
27
+ const OPERATOR_RIGHT = 2;
28
+
29
+ protected $parser;
30
+ protected $unaryOperators;
31
+ protected $binaryOperators;
32
+
33
+ public function __construct(Twig_Parser $parser, array $unaryOperators, array $binaryOperators)
34
+ {
35
+ $this->parser = $parser;
36
+ $this->unaryOperators = $unaryOperators;
37
+ $this->binaryOperators = $binaryOperators;
38
+ }
39
+
40
+ public function parseExpression($precedence = 0)
41
+ {
42
+ $expr = $this->getPrimary();
43
+ $token = $this->parser->getCurrentToken();
44
+ while ($this->isBinary($token) && $this->binaryOperators[$token->getValue()]['precedence'] >= $precedence) {
45
+ $op = $this->binaryOperators[$token->getValue()];
46
+ $this->parser->getStream()->next();
47
+
48
+ if (isset($op['callable'])) {
49
+ $expr = call_user_func($op['callable'], $this->parser, $expr);
50
+ } else {
51
+ $expr1 = $this->parseExpression(self::OPERATOR_LEFT === $op['associativity'] ? $op['precedence'] + 1 : $op['precedence']);
52
+ $class = $op['class'];
53
+ $expr = new $class($expr, $expr1, $token->getLine());
54
+ }
55
+
56
+ $token = $this->parser->getCurrentToken();
57
+ }
58
+
59
+ if (0 === $precedence) {
60
+ return $this->parseConditionalExpression($expr);
61
+ }
62
+
63
+ return $expr;
64
+ }
65
+
66
+ protected function getPrimary()
67
+ {
68
+ $token = $this->parser->getCurrentToken();
69
+
70
+ if ($this->isUnary($token)) {
71
+ $operator = $this->unaryOperators[$token->getValue()];
72
+ $this->parser->getStream()->next();
73
+ $expr = $this->parseExpression($operator['precedence']);
74
+ $class = $operator['class'];
75
+
76
+ return $this->parsePostfixExpression(new $class($expr, $token->getLine()));
77
+ } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
78
+ $this->parser->getStream()->next();
79
+ $expr = $this->parseExpression();
80
+ $this->parser->getStream()->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'An opened parenthesis is not properly closed');
81
+
82
+ return $this->parsePostfixExpression($expr);
83
+ }
84
+
85
+ return $this->parsePrimaryExpression();
86
+ }
87
+
88
+ protected function parseConditionalExpression($expr)
89
+ {
90
+ while ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '?')) {
91
+ $this->parser->getStream()->next();
92
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
93
+ $expr2 = $this->parseExpression();
94
+ if ($this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
95
+ $this->parser->getStream()->next();
96
+ $expr3 = $this->parseExpression();
97
+ } else {
98
+ $expr3 = new Twig_Node_Expression_Constant('', $this->parser->getCurrentToken()->getLine());
99
+ }
100
+ } else {
101
+ $this->parser->getStream()->next();
102
+ $expr2 = $expr;
103
+ $expr3 = $this->parseExpression();
104
+ }
105
+
106
+ $expr = new Twig_Node_Expression_Conditional($expr, $expr2, $expr3, $this->parser->getCurrentToken()->getLine());
107
+ }
108
+
109
+ return $expr;
110
+ }
111
+
112
+ protected function isUnary(Twig_Token $token)
113
+ {
114
+ return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->unaryOperators[$token->getValue()]);
115
+ }
116
+
117
+ protected function isBinary(Twig_Token $token)
118
+ {
119
+ return $token->test(Twig_Token::OPERATOR_TYPE) && isset($this->binaryOperators[$token->getValue()]);
120
+ }
121
+
122
+ public function parsePrimaryExpression()
123
+ {
124
+ $token = $this->parser->getCurrentToken();
125
+ switch ($token->getType()) {
126
+ case Twig_Token::NAME_TYPE:
127
+ $this->parser->getStream()->next();
128
+ switch ($token->getValue()) {
129
+ case 'true':
130
+ case 'TRUE':
131
+ $node = new Twig_Node_Expression_Constant(true, $token->getLine());
132
+ break;
133
+
134
+ case 'false':
135
+ case 'FALSE':
136
+ $node = new Twig_Node_Expression_Constant(false, $token->getLine());
137
+ break;
138
+
139
+ case 'none':
140
+ case 'NONE':
141
+ case 'null':
142
+ case 'NULL':
143
+ $node = new Twig_Node_Expression_Constant(null, $token->getLine());
144
+ break;
145
+
146
+ default:
147
+ if ('(' === $this->parser->getCurrentToken()->getValue()) {
148
+ $node = $this->getFunctionNode($token->getValue(), $token->getLine());
149
+ } else {
150
+ $node = new Twig_Node_Expression_Name($token->getValue(), $token->getLine());
151
+ }
152
+ }
153
+ break;
154
+
155
+ case Twig_Token::NUMBER_TYPE:
156
+ $this->parser->getStream()->next();
157
+ $node = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
158
+ break;
159
+
160
+ case Twig_Token::STRING_TYPE:
161
+ case Twig_Token::INTERPOLATION_START_TYPE:
162
+ $node = $this->parseStringExpression();
163
+ break;
164
+
165
+ default:
166
+ if ($token->test(Twig_Token::PUNCTUATION_TYPE, '[')) {
167
+ $node = $this->parseArrayExpression();
168
+ } elseif ($token->test(Twig_Token::PUNCTUATION_TYPE, '{')) {
169
+ $node = $this->parseHashExpression();
170
+ } else {
171
+ 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());
172
+ }
173
+ }
174
+
175
+ return $this->parsePostfixExpression($node);
176
+ }
177
+
178
+ public function parseStringExpression()
179
+ {
180
+ $stream = $this->parser->getStream();
181
+
182
+ $nodes = array();
183
+ // a string cannot be followed by another string in a single expression
184
+ $nextCanBeString = true;
185
+ while (true) {
186
+ if ($stream->test(Twig_Token::STRING_TYPE) && $nextCanBeString) {
187
+ $token = $stream->next();
188
+ $nodes[] = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
189
+ $nextCanBeString = false;
190
+ } elseif ($stream->test(Twig_Token::INTERPOLATION_START_TYPE)) {
191
+ $stream->next();
192
+ $nodes[] = $this->parseExpression();
193
+ $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
194
+ $nextCanBeString = true;
195
+ } else {
196
+ break;
197
+ }
198
+ }
199
+
200
+ $expr = array_shift($nodes);
201
+ foreach ($nodes as $node) {
202
+ $expr = new Twig_Node_Expression_Binary_Concat($expr, $node, $node->getLine());
203
+ }
204
+
205
+ return $expr;
206
+ }
207
+
208
+ public function parseArrayExpression()
209
+ {
210
+ $stream = $this->parser->getStream();
211
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '[', 'An array element was expected');
212
+
213
+ $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
214
+ $first = true;
215
+ while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
216
+ if (!$first) {
217
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'An array element must be followed by a comma');
218
+
219
+ // trailing ,?
220
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
221
+ break;
222
+ }
223
+ }
224
+ $first = false;
225
+
226
+ $node->addElement($this->parseExpression());
227
+ }
228
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']', 'An opened array is not properly closed');
229
+
230
+ return $node;
231
+ }
232
+
233
+ public function parseHashExpression()
234
+ {
235
+ $stream = $this->parser->getStream();
236
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '{', 'A hash element was expected');
237
+
238
+ $node = new Twig_Node_Expression_Array(array(), $stream->getCurrent()->getLine());
239
+ $first = true;
240
+ while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
241
+ if (!$first) {
242
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'A hash value must be followed by a comma');
243
+
244
+ // trailing ,?
245
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '}')) {
246
+ break;
247
+ }
248
+ }
249
+ $first = false;
250
+
251
+ // a hash key can be:
252
+ //
253
+ // * a number -- 12
254
+ // * a string -- 'a'
255
+ // * a name, which is equivalent to a string -- a
256
+ // * an expression, which must be enclosed in parentheses -- (1 + 2)
257
+ if ($stream->test(Twig_Token::STRING_TYPE) || $stream->test(Twig_Token::NAME_TYPE) || $stream->test(Twig_Token::NUMBER_TYPE)) {
258
+ $token = $stream->next();
259
+ $key = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
260
+ } elseif ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
261
+ $key = $this->parseExpression();
262
+ } else {
263
+ $current = $stream->getCurrent();
264
+
265
+ 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());
266
+ }
267
+
268
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ':', 'A hash key must be followed by a colon (:)');
269
+ $value = $this->parseExpression();
270
+
271
+ $node->addElement($value, $key);
272
+ }
273
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '}', 'An opened hash is not properly closed');
274
+
275
+ return $node;
276
+ }
277
+
278
+ public function parsePostfixExpression($node)
279
+ {
280
+ while (true) {
281
+ $token = $this->parser->getCurrentToken();
282
+ if ($token->getType() == Twig_Token::PUNCTUATION_TYPE) {
283
+ if ('.' == $token->getValue() || '[' == $token->getValue()) {
284
+ $node = $this->parseSubscriptExpression($node);
285
+ } elseif ('|' == $token->getValue()) {
286
+ $node = $this->parseFilterExpression($node);
287
+ } else {
288
+ break;
289
+ }
290
+ } else {
291
+ break;
292
+ }
293
+ }
294
+
295
+ return $node;
296
+ }
297
+
298
+ public function getFunctionNode($name, $line)
299
+ {
300
+ switch ($name) {
301
+ case 'parent':
302
+ $args = $this->parseArguments();
303
+ if (!count($this->parser->getBlockStack())) {
304
+ throw new Twig_Error_Syntax('Calling "parent" outside a block is forbidden', $line, $this->parser->getFilename());
305
+ }
306
+
307
+ if (!$this->parser->getParent() && !$this->parser->hasTraits()) {
308
+ throw new Twig_Error_Syntax('Calling "parent" on a template that does not extend nor "use" another template is forbidden', $line, $this->parser->getFilename());
309
+ }
310
+
311
+ return new Twig_Node_Expression_Parent($this->parser->peekBlockStack(), $line);
312
+ case 'block':
313
+ return new Twig_Node_Expression_BlockReference($this->parseArguments()->getNode(0), false, $line);
314
+ case 'attribute':
315
+ $args = $this->parseArguments();
316
+ if (count($args) < 2) {
317
+ throw new Twig_Error_Syntax('The "attribute" function takes at least two arguments (the variable and the attributes)', $line, $this->parser->getFilename());
318
+ }
319
+
320
+ 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);
321
+ default:
322
+ if (null !== $alias = $this->parser->getImportedSymbol('function', $name)) {
323
+ $arguments = new Twig_Node_Expression_Array(array(), $line);
324
+ foreach ($this->parseArguments() as $n) {
325
+ $arguments->addElement($n);
326
+ }
327
+
328
+ $node = new Twig_Node_Expression_MethodCall($alias['node'], $alias['name'], $arguments, $line);
329
+ $node->setAttribute('safe', true);
330
+
331
+ return $node;
332
+ }
333
+
334
+ $args = $this->parseArguments(true);
335
+ $class = $this->getFunctionNodeClass($name, $line);
336
+
337
+ return new $class($name, $args, $line);
338
+ }
339
+ }
340
+
341
+ public function parseSubscriptExpression($node)
342
+ {
343
+ $stream = $this->parser->getStream();
344
+ $token = $stream->next();
345
+ $lineno = $token->getLine();
346
+ $arguments = new Twig_Node_Expression_Array(array(), $lineno);
347
+ $type = Twig_TemplateInterface::ANY_CALL;
348
+ if ($token->getValue() == '.') {
349
+ $token = $stream->next();
350
+ if (
351
+ $token->getType() == Twig_Token::NAME_TYPE
352
+ ||
353
+ $token->getType() == Twig_Token::NUMBER_TYPE
354
+ ||
355
+ ($token->getType() == Twig_Token::OPERATOR_TYPE && preg_match(Twig_Lexer::REGEX_NAME, $token->getValue()))
356
+ ) {
357
+ $arg = new Twig_Node_Expression_Constant($token->getValue(), $lineno);
358
+
359
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
360
+ $type = Twig_TemplateInterface::METHOD_CALL;
361
+ foreach ($this->parseArguments() as $n) {
362
+ $arguments->addElement($n);
363
+ }
364
+ }
365
+ } else {
366
+ throw new Twig_Error_Syntax('Expected name or number', $lineno, $this->parser->getFilename());
367
+ }
368
+
369
+ if ($node instanceof Twig_Node_Expression_Name && null !== $alias = $this->parser->getImportedSymbol('template', $node->getAttribute('name'))) {
370
+ if (!$arg instanceof Twig_Node_Expression_Constant) {
371
+ throw new Twig_Error_Syntax(sprintf('Dynamic macro names are not supported (called on "%s")', $node->getAttribute('name')), $token->getLine(), $this->parser->getFilename());
372
+ }
373
+
374
+ $node = new Twig_Node_Expression_MethodCall($node, 'get'.$arg->getAttribute('value'), $arguments, $lineno);
375
+ $node->setAttribute('safe', true);
376
+
377
+ return $node;
378
+ }
379
+ } else {
380
+ $type = Twig_TemplateInterface::ARRAY_CALL;
381
+
382
+ // slice?
383
+ $slice = false;
384
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
385
+ $slice = true;
386
+ $arg = new Twig_Node_Expression_Constant(0, $token->getLine());
387
+ } else {
388
+ $arg = $this->parseExpression();
389
+ }
390
+
391
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ':')) {
392
+ $slice = true;
393
+ $stream->next();
394
+ }
395
+
396
+ if ($slice) {
397
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, ']')) {
398
+ $length = new Twig_Node_Expression_Constant(null, $token->getLine());
399
+ } else {
400
+ $length = $this->parseExpression();
401
+ }
402
+
403
+ $class = $this->getFilterNodeClass('slice', $token->getLine());
404
+ $arguments = new Twig_Node(array($arg, $length));
405
+ $filter = new $class($node, new Twig_Node_Expression_Constant('slice', $token->getLine()), $arguments, $token->getLine());
406
+
407
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
408
+
409
+ return $filter;
410
+ }
411
+
412
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ']');
413
+ }
414
+
415
+ return new Twig_Node_Expression_GetAttr($node, $arg, $arguments, $type, $lineno);
416
+ }
417
+
418
+ public function parseFilterExpression($node)
419
+ {
420
+ $this->parser->getStream()->next();
421
+
422
+ return $this->parseFilterExpressionRaw($node);
423
+ }
424
+
425
+ public function parseFilterExpressionRaw($node, $tag = null)
426
+ {
427
+ while (true) {
428
+ $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE);
429
+
430
+ $name = new Twig_Node_Expression_Constant($token->getValue(), $token->getLine());
431
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
432
+ $arguments = new Twig_Node();
433
+ } else {
434
+ $arguments = $this->parseArguments(true);
435
+ }
436
+
437
+ $class = $this->getFilterNodeClass($name->getAttribute('value'), $token->getLine());
438
+
439
+ $node = new $class($node, $name, $arguments, $token->getLine(), $tag);
440
+
441
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, '|')) {
442
+ break;
443
+ }
444
+
445
+ $this->parser->getStream()->next();
446
+ }
447
+
448
+ return $node;
449
+ }
450
+
451
+ /**
452
+ * Parses arguments.
453
+ *
454
+ * @param Boolean $namedArguments Whether to allow named arguments or not
455
+ * @param Boolean $definition Whether we are parsing arguments for a function definition
456
+ */
457
+ public function parseArguments($namedArguments = false, $definition = false)
458
+ {
459
+ $args = array();
460
+ $stream = $this->parser->getStream();
461
+
462
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, '(', 'A list of arguments must begin with an opening parenthesis');
463
+ while (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ')')) {
464
+ if (!empty($args)) {
465
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ',', 'Arguments must be separated by a comma');
466
+ }
467
+
468
+ if ($definition) {
469
+ $token = $stream->expect(Twig_Token::NAME_TYPE, null, 'An argument must be a name');
470
+ $value = new Twig_Node_Expression_Name($token->getValue(), $this->parser->getCurrentToken()->getLine());
471
+ } else {
472
+ $value = $this->parseExpression();
473
+ }
474
+
475
+ $name = null;
476
+ if ($namedArguments && $stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
477
+ $token = $stream->next();
478
+ if (!$value instanceof Twig_Node_Expression_Name) {
479
+ throw new Twig_Error_Syntax(sprintf('A parameter name must be a string, "%s" given', get_class($value)), $token->getLine(), $this->parser->getFilename());
480
+ }
481
+ $name = $value->getAttribute('name');
482
+
483
+ if ($definition) {
484
+ $value = $this->parsePrimaryExpression();
485
+
486
+ if (!$this->checkConstantExpression($value)) {
487
+ 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());
488
+ }
489
+ } else {
490
+ $value = $this->parseExpression();
491
+ }
492
+ }
493
+
494
+ if ($definition) {
495
+ if (null === $name) {
496
+ $name = $value->getAttribute('name');
497
+ $value = new Twig_Node_Expression_Constant(null, $this->parser->getCurrentToken()->getLine());
498
+ }
499
+ $args[$name] = $value;
500
+ } else {
501
+ if (null === $name) {
502
+ $args[] = $value;
503
+ } else {
504
+ $args[$name] = $value;
505
+ }
506
+ }
507
+ }
508
+ $stream->expect(Twig_Token::PUNCTUATION_TYPE, ')', 'A list of arguments must be closed by a parenthesis');
509
+
510
+ return new Twig_Node($args);
511
+ }
512
+
513
+ public function parseAssignmentExpression()
514
+ {
515
+ $targets = array();
516
+ while (true) {
517
+ $token = $this->parser->getStream()->expect(Twig_Token::NAME_TYPE, null, 'Only variables can be assigned to');
518
+ if (in_array($token->getValue(), array('true', 'false', 'none'))) {
519
+ throw new Twig_Error_Syntax(sprintf('You cannot assign a value to "%s"', $token->getValue()), $token->getLine(), $this->parser->getFilename());
520
+ }
521
+ $targets[] = new Twig_Node_Expression_AssignName($token->getValue(), $token->getLine());
522
+
523
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
524
+ break;
525
+ }
526
+ $this->parser->getStream()->next();
527
+ }
528
+
529
+ return new Twig_Node($targets);
530
+ }
531
+
532
+ public function parseMultitargetExpression()
533
+ {
534
+ $targets = array();
535
+ while (true) {
536
+ $targets[] = $this->parseExpression();
537
+ if (!$this->parser->getStream()->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
538
+ break;
539
+ }
540
+ $this->parser->getStream()->next();
541
+ }
542
+
543
+ return new Twig_Node($targets);
544
+ }
545
+
546
+ protected function getFunctionNodeClass($name, $line)
547
+ {
548
+ $env = $this->parser->getEnvironment();
549
+
550
+ if (false === $function = $env->getFunction($name)) {
551
+ $message = sprintf('The function "%s" does not exist', $name);
552
+ if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFunctions()))) {
553
+ $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
554
+ }
555
+
556
+ throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
557
+ }
558
+
559
+ if ($function instanceof Twig_SimpleFunction) {
560
+ return $function->getNodeClass();
561
+ }
562
+
563
+ return $function instanceof Twig_Function_Node ? $function->getClass() : 'Twig_Node_Expression_Function';
564
+ }
565
+
566
+ protected function getFilterNodeClass($name, $line)
567
+ {
568
+ $env = $this->parser->getEnvironment();
569
+
570
+ if (false === $filter = $env->getFilter($name)) {
571
+ $message = sprintf('The filter "%s" does not exist', $name);
572
+ if ($alternatives = $env->computeAlternatives($name, array_keys($env->getFilters()))) {
573
+ $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
574
+ }
575
+
576
+ throw new Twig_Error_Syntax($message, $line, $this->parser->getFilename());
577
+ }
578
+
579
+ if ($filter instanceof Twig_SimpleFilter) {
580
+ return $filter->getNodeClass();
581
+ }
582
+
583
+ return $filter instanceof Twig_Filter_Node ? $filter->getClass() : 'Twig_Node_Expression_Filter';
584
+ }
585
+
586
+ // checks that the node only contains "constant" elements
587
+ protected function checkConstantExpression(Twig_NodeInterface $node)
588
+ {
589
+ if (!($node instanceof Twig_Node_Expression_Constant || $node instanceof Twig_Node_Expression_Array)) {
590
+ return false;
591
+ }
592
+
593
+ foreach ($node as $n) {
594
+ if (!$this->checkConstantExpression($n)) {
595
+ return false;
596
+ }
597
+ }
598
+
599
+ return true;
600
+ }
601
+ }
Twig/lib/Twig/Extension.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ abstract class Twig_Extension implements Twig_ExtensionInterface
12
+ {
13
+ /**
14
+ * Initializes the runtime environment.
15
+ *
16
+ * This is where you can load some file that contains filter functions for instance.
17
+ *
18
+ * @param Twig_Environment $environment The current Twig_Environment instance
19
+ */
20
+ public function initRuntime(Twig_Environment $environment)
21
+ {
22
+ }
23
+
24
+ /**
25
+ * Returns the token parser instances to add to the existing list.
26
+ *
27
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
28
+ */
29
+ public function getTokenParsers()
30
+ {
31
+ return array();
32
+ }
33
+
34
+ /**
35
+ * Returns the node visitor instances to add to the existing list.
36
+ *
37
+ * @return array An array of Twig_NodeVisitorInterface instances
38
+ */
39
+ public function getNodeVisitors()
40
+ {
41
+ return array();
42
+ }
43
+
44
+ /**
45
+ * Returns a list of filters to add to the existing list.
46
+ *
47
+ * @return array An array of filters
48
+ */
49
+ public function getFilters()
50
+ {
51
+ return array();
52
+ }
53
+
54
+ /**
55
+ * Returns a list of tests to add to the existing list.
56
+ *
57
+ * @return array An array of tests
58
+ */
59
+ public function getTests()
60
+ {
61
+ return array();
62
+ }
63
+
64
+ /**
65
+ * Returns a list of functions to add to the existing list.
66
+ *
67
+ * @return array An array of functions
68
+ */
69
+ public function getFunctions()
70
+ {
71
+ return array();
72
+ }
73
+
74
+ /**
75
+ * Returns a list of operators to add to the existing list.
76
+ *
77
+ * @return array An array of operators
78
+ */
79
+ public function getOperators()
80
+ {
81
+ return array();
82
+ }
83
+
84
+ /**
85
+ * Returns a list of global variables to add to the existing list.
86
+ *
87
+ * @return array An array of global variables
88
+ */
89
+ public function getGlobals()
90
+ {
91
+ return array();
92
+ }
93
+ }
Twig/lib/Twig/Extension/Core.php ADDED
@@ -0,0 +1,1276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ if (!defined('ENT_SUBSTITUTE')) {
4
+ define('ENT_SUBSTITUTE', 8);
5
+ }
6
+
7
+ /*
8
+ * This file is part of Twig.
9
+ *
10
+ * (c) 2009 Fabien Potencier
11
+ *
12
+ * For the full copyright and license information, please view the LICENSE
13
+ * file that was distributed with this source code.
14
+ */
15
+ class Twig_Extension_Core extends Twig_Extension
16
+ {
17
+ protected $dateFormats = array('F j, Y H:i', '%d days');
18
+ protected $numberFormat = array(0, '.', ',');
19
+ protected $timezone = null;
20
+
21
+ /**
22
+ * Sets the default format to be used by the date filter.
23
+ *
24
+ * @param string $format The default date format string
25
+ * @param string $dateIntervalFormat The default date interval format string
26
+ */
27
+ public function setDateFormat($format = null, $dateIntervalFormat = null)
28
+ {
29
+ if (null !== $format) {
30
+ $this->dateFormats[0] = $format;
31
+ }
32
+
33
+ if (null !== $dateIntervalFormat) {
34
+ $this->dateFormats[1] = $dateIntervalFormat;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Gets the default format to be used by the date filter.
40
+ *
41
+ * @return array The default date format string and the default date interval format string
42
+ */
43
+ public function getDateFormat()
44
+ {
45
+ return $this->dateFormats;
46
+ }
47
+
48
+ /**
49
+ * Sets the default timezone to be used by the date filter.
50
+ *
51
+ * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object
52
+ */
53
+ public function setTimezone($timezone)
54
+ {
55
+ $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
56
+ }
57
+
58
+ /**
59
+ * Gets the default timezone to be used by the date filter.
60
+ *
61
+ * @return DateTimeZone The default timezone currently in use
62
+ */
63
+ public function getTimezone()
64
+ {
65
+ if (null === $this->timezone) {
66
+ $this->timezone = new DateTimeZone(date_default_timezone_get());
67
+ }
68
+
69
+ return $this->timezone;
70
+ }
71
+
72
+ /**
73
+ * Sets the default format to be used by the number_format filter.
74
+ *
75
+ * @param integer $decimal The number of decimal places to use.
76
+ * @param string $decimalPoint The character(s) to use for the decimal point.
77
+ * @param string $thousandSep The character(s) to use for the thousands separator.
78
+ */
79
+ public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
80
+ {
81
+ $this->numberFormat = array($decimal, $decimalPoint, $thousandSep);
82
+ }
83
+
84
+ /**
85
+ * Get the default format used by the number_format filter.
86
+ *
87
+ * @return array The arguments for number_format()
88
+ */
89
+ public function getNumberFormat()
90
+ {
91
+ return $this->numberFormat;
92
+ }
93
+
94
+ /**
95
+ * Returns the token parser instance to add to the existing list.
96
+ *
97
+ * @return array An array of Twig_TokenParser instances
98
+ */
99
+ public function getTokenParsers()
100
+ {
101
+ return array(
102
+ new Twig_TokenParser_For(),
103
+ new Twig_TokenParser_If(),
104
+ new Twig_TokenParser_Extends(),
105
+ new Twig_TokenParser_Include(),
106
+ new Twig_TokenParser_Block(),
107
+ new Twig_TokenParser_Use(),
108
+ new Twig_TokenParser_Filter(),
109
+ new Twig_TokenParser_Macro(),
110
+ new Twig_TokenParser_Import(),
111
+ new Twig_TokenParser_From(),
112
+ new Twig_TokenParser_Set(),
113
+ new Twig_TokenParser_Spaceless(),
114
+ new Twig_TokenParser_Flush(),
115
+ new Twig_TokenParser_Do(),
116
+ new Twig_TokenParser_Embed(),
117
+ );
118
+ }
119
+
120
+ /**
121
+ * Returns a list of filters to add to the existing list.
122
+ *
123
+ * @return array An array of filters
124
+ */
125
+ public function getFilters()
126
+ {
127
+ $filters = array(
128
+ // formatting filters
129
+ new Twig_SimpleFilter('date', 'twig_date_format_filter', array('needs_environment' => true)),
130
+ new Twig_SimpleFilter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)),
131
+ new Twig_SimpleFilter('format', 'sprintf'),
132
+ new Twig_SimpleFilter('replace', 'strtr'),
133
+ new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)),
134
+ new Twig_SimpleFilter('abs', 'abs'),
135
+
136
+ // encoding
137
+ new Twig_SimpleFilter('url_encode', 'twig_urlencode_filter'),
138
+ new Twig_SimpleFilter('json_encode', 'twig_jsonencode_filter'),
139
+ new Twig_SimpleFilter('convert_encoding', 'twig_convert_encoding'),
140
+
141
+ // string filters
142
+ new Twig_SimpleFilter('title', 'twig_title_string_filter', array('needs_environment' => true)),
143
+ new Twig_SimpleFilter('capitalize', 'twig_capitalize_string_filter', array('needs_environment' => true)),
144
+ new Twig_SimpleFilter('upper', 'strtoupper'),
145
+ new Twig_SimpleFilter('lower', 'strtolower'),
146
+ new Twig_SimpleFilter('striptags', 'strip_tags'),
147
+ new Twig_SimpleFilter('trim', 'trim'),
148
+ new Twig_SimpleFilter('nl2br', 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
149
+
150
+ // array helpers
151
+ new Twig_SimpleFilter('join', 'twig_join_filter'),
152
+ new Twig_SimpleFilter('split', 'twig_split_filter'),
153
+ new Twig_SimpleFilter('sort', 'twig_sort_filter'),
154
+ new Twig_SimpleFilter('merge', 'twig_array_merge'),
155
+
156
+ // string/array filters
157
+ new Twig_SimpleFilter('reverse', 'twig_reverse_filter', array('needs_environment' => true)),
158
+ new Twig_SimpleFilter('length', 'twig_length_filter', array('needs_environment' => true)),
159
+ new Twig_SimpleFilter('slice', 'twig_slice', array('needs_environment' => true)),
160
+
161
+ // iteration and runtime
162
+ new Twig_SimpleFilter('default', '_twig_default_filter', array('node_class' => 'Twig_Node_Expression_Filter_Default')),
163
+ new Twig_SimpleFilter('keys', 'twig_get_array_keys_filter'),
164
+
165
+ // escaping
166
+ new Twig_SimpleFilter('escape', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
167
+ new Twig_SimpleFilter('e', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
168
+ );
169
+
170
+ if (function_exists('mb_get_info')) {
171
+ $filters['upper'] = new Twig_Filter_Function('twig_upper_filter', array('needs_environment' => true));
172
+ $filters['lower'] = new Twig_Filter_Function('twig_lower_filter', array('needs_environment' => true));
173
+ }
174
+
175
+ return $filters;
176
+ }
177
+
178
+ /**
179
+ * Returns a list of global functions to add to the existing list.
180
+ *
181
+ * @return array An array of global functions
182
+ */
183
+ public function getFunctions()
184
+ {
185
+ return array(
186
+ new Twig_SimpleFunction('range', 'range'),
187
+ new Twig_SimpleFunction('constant', 'twig_constant'),
188
+ new Twig_SimpleFunction('cycle', 'twig_cycle'),
189
+ new Twig_SimpleFunction('random', 'twig_random', array('needs_environment' => true)),
190
+ new Twig_SimpleFunction('date', 'twig_date_converter', array('needs_environment' => true)),
191
+ new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true)),
192
+ );
193
+ }
194
+
195
+ /**
196
+ * Returns a list of tests to add to the existing list.
197
+ *
198
+ * @return array An array of tests
199
+ */
200
+ public function getTests()
201
+ {
202
+ return array(
203
+ new Twig_SimpleTest('even', null, array('node_class' => 'Twig_Node_Expression_Test_Even')),
204
+ new Twig_SimpleTest('odd', null, array('node_class' => 'Twig_Node_Expression_Test_Odd')),
205
+ new Twig_SimpleTest('defined', null, array('node_class' => 'Twig_Node_Expression_Test_Defined')),
206
+ new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')),
207
+ new Twig_SimpleTest('none', null, array('node_class' => 'Twig_Node_Expression_Test_Null')),
208
+ new Twig_SimpleTest('null', null, array('node_class' => 'Twig_Node_Expression_Test_Null')),
209
+ new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')),
210
+ new Twig_SimpleTest('constant', null, array('node_class' => 'Twig_Node_Expression_Test_Constant')),
211
+ new Twig_SimpleTest('empty', 'twig_test_empty'),
212
+ new Twig_SimpleTest('iterable', 'twig_test_iterable'),
213
+ );
214
+ }
215
+
216
+ /**
217
+ * Returns a list of operators to add to the existing list.
218
+ *
219
+ * @return array An array of operators
220
+ */
221
+ public function getOperators()
222
+ {
223
+ return array(
224
+ array(
225
+ 'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
226
+ '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'),
227
+ '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
228
+ ),
229
+ array(
230
+ 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
231
+ 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
232
+ 'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
233
+ 'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
234
+ 'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
235
+ '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
236
+ '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
237
+ '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
238
+ '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
239
+ '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
240
+ '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
241
+ 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
242
+ 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
243
+ '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
244
+ '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
245
+ '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
246
+ '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
247
+ '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
248
+ '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
249
+ '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
250
+ '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
251
+ 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
252
+ 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
253
+ '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
254
+ ),
255
+ );
256
+ }
257
+
258
+ public function parseNotTestExpression(Twig_Parser $parser, $node)
259
+ {
260
+ return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine());
261
+ }
262
+
263
+ public function parseTestExpression(Twig_Parser $parser, $node)
264
+ {
265
+ $stream = $parser->getStream();
266
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
267
+ $arguments = null;
268
+ if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
269
+ $arguments = $parser->getExpressionParser()->parseArguments(true);
270
+ }
271
+
272
+ $class = $this->getTestNodeClass($parser, $name, $node->getLine());
273
+
274
+ return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine());
275
+ }
276
+
277
+ protected function getTestNodeClass(Twig_Parser $parser, $name, $line)
278
+ {
279
+ $env = $parser->getEnvironment();
280
+ $testMap = $env->getTests();
281
+ if (!isset($testMap[$name])) {
282
+ $message = sprintf('The test "%s" does not exist', $name);
283
+ if ($alternatives = $env->computeAlternatives($name, array_keys($env->getTests()))) {
284
+ $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
285
+ }
286
+
287
+ throw new Twig_Error_Syntax($message, $line, $parser->getFilename());
288
+ }
289
+
290
+ if ($testMap[$name] instanceof Twig_SimpleTest) {
291
+ return $testMap[$name]->getNodeClass();
292
+ }
293
+
294
+ return $testMap[$name] instanceof Twig_Test_Node ? $testMap[$name]->getClass() : 'Twig_Node_Expression_Test';
295
+ }
296
+
297
+ /**
298
+ * Returns the name of the extension.
299
+ *
300
+ * @return string The extension name
301
+ */
302
+ public function getName()
303
+ {
304
+ return 'core';
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Cycles over a value.
310
+ *
311
+ * @param ArrayAccess|array $values An array or an ArrayAccess instance
312
+ * @param integer $position The cycle position
313
+ *
314
+ * @return string The next value in the cycle
315
+ */
316
+ function twig_cycle($values, $position)
317
+ {
318
+ if (!is_array($values) && !$values instanceof ArrayAccess) {
319
+ return $values;
320
+ }
321
+
322
+ return $values[$position % count($values)];
323
+ }
324
+
325
+ /**
326
+ * Returns a random value depending on the supplied parameter type:
327
+ * - a random item from a Traversable or array
328
+ * - a random character from a string
329
+ * - a random integer between 0 and the integer parameter
330
+ *
331
+ * @param Twig_Environment $env A Twig_Environment instance
332
+ * @param Traversable|array|integer|string $values The values to pick a random item from
333
+ *
334
+ * @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is).
335
+ *
336
+ * @return mixed A random value from the given sequence
337
+ */
338
+ function twig_random(Twig_Environment $env, $values = null)
339
+ {
340
+ if (null === $values) {
341
+ return mt_rand();
342
+ }
343
+
344
+ if (is_int($values) || is_float($values)) {
345
+ return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
346
+ }
347
+
348
+ if ($values instanceof Traversable) {
349
+ $values = iterator_to_array($values);
350
+ } elseif (is_string($values)) {
351
+ if ('' === $values) {
352
+ return '';
353
+ }
354
+ if (null !== $charset = $env->getCharset()) {
355
+ if ('UTF-8' != $charset) {
356
+ $values = twig_convert_encoding($values, 'UTF-8', $charset);
357
+ }
358
+
359
+ // unicode version of str_split()
360
+ // split at all positions, but not after the start and not before the end
361
+ $values = preg_split('/(?<!^)(?!$)/u', $values);
362
+
363
+ if ('UTF-8' != $charset) {
364
+ foreach ($values as $i => $value) {
365
+ $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8');
366
+ }
367
+ }
368
+ } else {
369
+ return $values[mt_rand(0, strlen($values) - 1)];
370
+ }
371
+ }
372
+
373
+ if (!is_array($values)) {
374
+ return $values;
375
+ }
376
+
377
+ if (0 === count($values)) {
378
+ throw new Twig_Error_Runtime('The random function cannot pick from an empty array.');
379
+ }
380
+
381
+ return $values[array_rand($values, 1)];
382
+ }
383
+
384
+ /**
385
+ * Converts a date to the given format.
386
+ *
387
+ * <pre>
388
+ * {{ post.published_at|date("m/d/Y") }}
389
+ * </pre>
390
+ *
391
+ * @param Twig_Environment $env A Twig_Environment instance
392
+ * @param DateTime|DateInterval|string $date A date
393
+ * @param string $format A format
394
+ * @param DateTimeZone|string $timezone A timezone
395
+ *
396
+ * @return string The formatted date
397
+ */
398
+ function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null)
399
+ {
400
+ if (null === $format) {
401
+ $formats = $env->getExtension('core')->getDateFormat();
402
+ $format = $date instanceof DateInterval ? $formats[1] : $formats[0];
403
+ }
404
+
405
+ if ($date instanceof DateInterval) {
406
+ return $date->format($format);
407
+ }
408
+
409
+ return twig_date_converter($env, $date, $timezone)->format($format);
410
+ }
411
+
412
+ /**
413
+ * Returns a new date object modified
414
+ *
415
+ * <pre>
416
+ * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
417
+ * </pre>
418
+ *
419
+ * @param Twig_Environment $env A Twig_Environment instance
420
+ * @param DateTime|string $date A date
421
+ * @param string $modifier A modifier string
422
+ *
423
+ * @return DateTime A new date object
424
+ */
425
+ function twig_date_modify_filter(Twig_Environment $env, $date, $modifier)
426
+ {
427
+ $date = twig_date_converter($env, $date, false);
428
+ $date->modify($modifier);
429
+
430
+ return $date;
431
+ }
432
+
433
+ /**
434
+ * Converts an input to a DateTime instance.
435
+ *
436
+ * <pre>
437
+ * {% if date(user.created_at) < date('+2days') %}
438
+ * {# do something #}
439
+ * {% endif %}
440
+ * </pre>
441
+ *
442
+ * @param Twig_Environment $env A Twig_Environment instance
443
+ * @param DateTime|string $date A date
444
+ * @param DateTimeZone|string $timezone A timezone
445
+ *
446
+ * @return DateTime A DateTime instance
447
+ */
448
+ function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null)
449
+ {
450
+ // determine the timezone
451
+ if (!$timezone) {
452
+ $defaultTimezone = $env->getExtension('core')->getTimezone();
453
+ } elseif (!$timezone instanceof DateTimeZone) {
454
+ $defaultTimezone = new DateTimeZone($timezone);
455
+ } else {
456
+ $defaultTimezone = $timezone;
457
+ }
458
+
459
+ if ($date instanceof DateTime) {
460
+ $date = clone $date;
461
+ if (false !== $timezone) {
462
+ $date->setTimezone($defaultTimezone);
463
+ }
464
+
465
+ return $date;
466
+ }
467
+
468
+ $asString = (string) $date;
469
+ if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {
470
+ $date = new DateTime('@'.$date);
471
+ $date->setTimezone($defaultTimezone);
472
+
473
+ return $date;
474
+ }
475
+
476
+ return new DateTime($date, $defaultTimezone);
477
+ }
478
+
479
+ /**
480
+ * Number format filter.
481
+ *
482
+ * All of the formatting options can be left null, in that case the defaults will
483
+ * be used. Supplying any of the parameters will override the defaults set in the
484
+ * environment object.
485
+ *
486
+ * @param Twig_Environment $env A Twig_Environment instance
487
+ * @param mixed $number A float/int/string of the number to format
488
+ * @param integer $decimal The number of decimal points to display.
489
+ * @param string $decimalPoint The character(s) to use for the decimal point.
490
+ * @param string $thousandSep The character(s) to use for the thousands separator.
491
+ *
492
+ * @return string The formatted number
493
+ */
494
+ function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)
495
+ {
496
+ $defaults = $env->getExtension('core')->getNumberFormat();
497
+ if (null === $decimal) {
498
+ $decimal = $defaults[0];
499
+ }
500
+
501
+ if (null === $decimalPoint) {
502
+ $decimalPoint = $defaults[1];
503
+ }
504
+
505
+ if (null === $thousandSep) {
506
+ $thousandSep = $defaults[2];
507
+ }
508
+
509
+ return number_format((float) $number, $decimal, $decimalPoint, $thousandSep);
510
+ }
511
+
512
+ /**
513
+ * URL encodes a string.
514
+ *
515
+ * @param string $url A URL
516
+ * @param bool $raw true to use rawurlencode() instead of urlencode
517
+ *
518
+ * @return string The URL encoded value
519
+ */
520
+ function twig_urlencode_filter($url, $raw = false)
521
+ {
522
+ if ($raw) {
523
+ return rawurlencode($url);
524
+ }
525
+
526
+ return urlencode($url);
527
+ }
528
+
529
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
530
+ /**
531
+ * JSON encodes a variable.
532
+ *
533
+ * @param mixed $value The value to encode.
534
+ * @param integer $options Not used on PHP 5.2.x
535
+ *
536
+ * @return mixed The JSON encoded value
537
+ */
538
+ function twig_jsonencode_filter($value, $options = 0)
539
+ {
540
+ if ($value instanceof Twig_Markup) {
541
+ $value = (string) $value;
542
+ } elseif (is_array($value)) {
543
+ array_walk_recursive($value, '_twig_markup2string');
544
+ }
545
+
546
+ return json_encode($value);
547
+ }
548
+ } else {
549
+ /**
550
+ * JSON encodes a variable.
551
+ *
552
+ * @param mixed $value The value to encode.
553
+ * @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
554
+ *
555
+ * @return mixed The JSON encoded value
556
+ */
557
+ function twig_jsonencode_filter($value, $options = 0)
558
+ {
559
+ if ($value instanceof Twig_Markup) {
560
+ $value = (string) $value;
561
+ } elseif (is_array($value)) {
562
+ array_walk_recursive($value, '_twig_markup2string');
563
+ }
564
+
565
+ return json_encode($value, $options);
566
+ }
567
+ }
568
+
569
+ function _twig_markup2string(&$value)
570
+ {
571
+ if ($value instanceof Twig_Markup) {
572
+ $value = (string) $value;
573
+ }
574
+ }
575
+
576
+ /**
577
+ * Merges an array with another one.
578
+ *
579
+ * <pre>
580
+ * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
581
+ *
582
+ * {% set items = items|merge({ 'peugeot': 'car' }) %}
583
+ *
584
+ * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
585
+ * </pre>
586
+ *
587
+ * @param array $arr1 An array
588
+ * @param array $arr2 An array
589
+ *
590
+ * @return array The merged array
591
+ */
592
+ function twig_array_merge($arr1, $arr2)
593
+ {
594
+ if (!is_array($arr1) || !is_array($arr2)) {
595
+ throw new Twig_Error_Runtime('The merge filter only works with arrays or hashes.');
596
+ }
597
+
598
+ return array_merge($arr1, $arr2);
599
+ }
600
+
601
+ /**
602
+ * Slices a variable.
603
+ *
604
+ * @param Twig_Environment $env A Twig_Environment instance
605
+ * @param mixed $item A variable
606
+ * @param integer $start Start of the slice
607
+ * @param integer $length Size of the slice
608
+ * @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array)
609
+ *
610
+ * @return mixed The sliced variable
611
+ */
612
+ function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
613
+ {
614
+ if ($item instanceof Traversable) {
615
+ $item = iterator_to_array($item, false);
616
+ }
617
+
618
+ if (is_array($item)) {
619
+ return array_slice($item, $start, $length, $preserveKeys);
620
+ }
621
+
622
+ $item = (string) $item;
623
+
624
+ if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) {
625
+ return mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset);
626
+ }
627
+
628
+ return null === $length ? substr($item, $start) : substr($item, $start, $length);
629
+ }
630
+
631
+ /**
632
+ * Joins the values to a string.
633
+ *
634
+ * The separator between elements is an empty string per default, you can define it with the optional parameter.
635
+ *
636
+ * <pre>
637
+ * {{ [1, 2, 3]|join('|') }}
638
+ * {# returns 1|2|3 #}
639
+ *
640
+ * {{ [1, 2, 3]|join }}
641
+ * {# returns 123 #}
642
+ * </pre>
643
+ *
644
+ * @param array $value An array
645
+ * @param string $glue The separator
646
+ *
647
+ * @return string The concatenated string
648
+ */
649
+ function twig_join_filter($value, $glue = '')
650
+ {
651
+ if ($value instanceof Traversable) {
652
+ $value = iterator_to_array($value, false);
653
+ }
654
+
655
+ return implode($glue, (array) $value);
656
+ }
657
+
658
+ /**
659
+ * Splits the string into an array.
660
+ *
661
+ * <pre>
662
+ * {{ "one,two,three"|split(',') }}
663
+ * {# returns [one, two, three] #}
664
+ *
665
+ * {{ "one,two,three,four,five"|split(',', 3) }}
666
+ * {# returns [one, two, "three,four,five"] #}
667
+ *
668
+ * {{ "123"|split('') }}
669
+ * {# returns [1, 2, 3] #}
670
+ *
671
+ * {{ "aabbcc"|split('', 2) }}
672
+ * {# returns [aa, bb, cc] #}
673
+ * </pre>
674
+ *
675
+ * @param string $value A string
676
+ * @param string $delimiter The delimiter
677
+ * @param integer $limit The limit
678
+ *
679
+ * @return array The split string as an array
680
+ */
681
+ function twig_split_filter($value, $delimiter, $limit = null)
682
+ {
683
+ if (empty($delimiter)) {
684
+ return str_split($value, null === $limit ? 1 : $limit);
685
+ }
686
+
687
+ return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
688
+ }
689
+
690
+ // The '_default' filter is used internally to avoid using the ternary operator
691
+ // which costs a lot for big contexts (before PHP 5.4). So, on average,
692
+ // a function call is cheaper.
693
+ function _twig_default_filter($value, $default = '')
694
+ {
695
+ if (twig_test_empty($value)) {
696
+ return $default;
697
+ }
698
+
699
+ return $value;
700
+ }
701
+
702
+ /**
703
+ * Returns the keys for the given array.
704
+ *
705
+ * It is useful when you want to iterate over the keys of an array:
706
+ *
707
+ * <pre>
708
+ * {% for key in array|keys %}
709
+ * {# ... #}
710
+ * {% endfor %}
711
+ * </pre>
712
+ *
713
+ * @param array $array An array
714
+ *
715
+ * @return array The keys
716
+ */
717
+ function twig_get_array_keys_filter($array)
718
+ {
719
+ if (is_object($array) && $array instanceof Traversable) {
720
+ return array_keys(iterator_to_array($array));
721
+ }
722
+
723
+ if (!is_array($array)) {
724
+ return array();
725
+ }
726
+
727
+ return array_keys($array);
728
+ }
729
+
730
+ /**
731
+ * Reverses a variable.
732
+ *
733
+ * @param Twig_Environment $env A Twig_Environment instance
734
+ * @param array|Traversable|string $item An array, a Traversable instance, or a string
735
+ * @param Boolean $preserveKeys Whether to preserve key or not
736
+ *
737
+ * @return mixed The reversed input
738
+ */
739
+ function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false)
740
+ {
741
+ if (is_object($item) && $item instanceof Traversable) {
742
+ return array_reverse(iterator_to_array($item), $preserveKeys);
743
+ }
744
+
745
+ if (is_array($item)) {
746
+ return array_reverse($item, $preserveKeys);
747
+ }
748
+
749
+ if (null !== $charset = $env->getCharset()) {
750
+ $string = (string) $item;
751
+
752
+ if ('UTF-8' != $charset) {
753
+ $item = twig_convert_encoding($string, 'UTF-8', $charset);
754
+ }
755
+
756
+ preg_match_all('/./us', $item, $matches);
757
+
758
+ $string = implode('', array_reverse($matches[0]));
759
+
760
+ if ('UTF-8' != $charset) {
761
+ $string = twig_convert_encoding($string, $charset, 'UTF-8');
762
+ }
763
+
764
+ return $string;
765
+ }
766
+
767
+ return strrev((string) $item);
768
+ }
769
+
770
+ /**
771
+ * Sorts an array.
772
+ *
773
+ * @param array $array An array
774
+ */
775
+ function twig_sort_filter($array)
776
+ {
777
+ asort($array);
778
+
779
+ return $array;
780
+ }
781
+
782
+ /* used internally */
783
+ function twig_in_filter($value, $compare)
784
+ {
785
+ if (is_array($compare)) {
786
+ return in_array($value, $compare, is_object($value));
787
+ } elseif (is_string($compare)) {
788
+ if (!strlen($value)) {
789
+ return empty($compare);
790
+ }
791
+
792
+ return false !== strpos($compare, (string) $value);
793
+ } elseif ($compare instanceof Traversable) {
794
+ return in_array($value, iterator_to_array($compare, false), is_object($value));
795
+ }
796
+
797
+ return false;
798
+ }
799
+
800
+ /**
801
+ * Escapes a string.
802
+ *
803
+ * @param Twig_Environment $env A Twig_Environment instance
804
+ * @param string $string The value to be escaped
805
+ * @param string $strategy The escaping strategy
806
+ * @param string $charset The charset
807
+ * @param Boolean $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
808
+ */
809
+ function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
810
+ {
811
+ if ($autoescape && is_object($string) && $string instanceof Twig_Markup) {
812
+ return $string;
813
+ }
814
+
815
+ if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) {
816
+ return $string;
817
+ }
818
+
819
+ if (null === $charset) {
820
+ $charset = $env->getCharset();
821
+ }
822
+
823
+ $string = (string) $string;
824
+
825
+ switch ($strategy) {
826
+ case 'js':
827
+ // escape all non-alphanumeric characters
828
+ // into their \xHH or \uHHHH representations
829
+ if ('UTF-8' != $charset) {
830
+ $string = twig_convert_encoding($string, 'UTF-8', $charset);
831
+ }
832
+
833
+ if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
834
+ throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
835
+ }
836
+
837
+ $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string);
838
+
839
+ if ('UTF-8' != $charset) {
840
+ $string = twig_convert_encoding($string, $charset, 'UTF-8');
841
+ }
842
+
843
+ return $string;
844
+
845
+ case 'css':
846
+ if ('UTF-8' != $charset) {
847
+ $string = twig_convert_encoding($string, 'UTF-8', $charset);
848
+ }
849
+
850
+ if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
851
+ throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
852
+ }
853
+
854
+ $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string);
855
+
856
+ if ('UTF-8' != $charset) {
857
+ $string = twig_convert_encoding($string, $charset, 'UTF-8');
858
+ }
859
+
860
+ return $string;
861
+
862
+ case 'html_attr':
863
+ if ('UTF-8' != $charset) {
864
+ $string = twig_convert_encoding($string, 'UTF-8', $charset);
865
+ }
866
+
867
+ if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
868
+ throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
869
+ }
870
+
871
+ $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string);
872
+
873
+ if ('UTF-8' != $charset) {
874
+ $string = twig_convert_encoding($string, $charset, 'UTF-8');
875
+ }
876
+
877
+ return $string;
878
+
879
+ case 'html':
880
+ // see http://php.net/htmlspecialchars
881
+
882
+ // Using a static variable to avoid initializing the array
883
+ // each time the function is called. Moving the declaration on the
884
+ // top of the function slow downs other escaping strategies.
885
+ static $htmlspecialcharsCharsets = array(
886
+ 'iso-8859-1' => true, 'iso8859-1' => true,
887
+ 'iso-8859-15' => true, 'iso8859-15' => true,
888
+ 'utf-8' => true,
889
+ 'cp866' => true, 'ibm866' => true, '866' => true,
890
+ 'cp1251' => true, 'windows-1251' => true, 'win-1251' => true,
891
+ '1251' => true,
892
+ 'cp1252' => true, 'windows-1252' => true, '1252' => true,
893
+ 'koi8-r' => true, 'koi8-ru' => true, 'koi8r' => true,
894
+ 'big5' => true, '950' => true,
895
+ 'gb2312' => true, '936' => true,
896
+ 'big5-hkscs' => true,
897
+ 'shift_jis' => true, 'sjis' => true, '932' => true,
898
+ 'euc-jp' => true, 'eucjp' => true,
899
+ 'iso8859-5' => true, 'iso-8859-5' => true, 'macroman' => true,
900
+ );
901
+
902
+ if (isset($htmlspecialcharsCharsets[strtolower($charset)])) {
903
+ return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
904
+ }
905
+
906
+ $string = twig_convert_encoding($string, 'UTF-8', $charset);
907
+ $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
908
+
909
+ return twig_convert_encoding($string, $charset, 'UTF-8');
910
+
911
+ case 'url':
912
+ if (version_compare(PHP_VERSION, '5.3.0', '<')) {
913
+ return str_replace('%7E', '~', rawurlencode($string));
914
+ }
915
+
916
+ return rawurlencode($string);
917
+
918
+ default:
919
+ throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: html, js, url, css, and html_attr).', $strategy));
920
+ }
921
+ }
922
+
923
+ /* used internally */
924
+ function twig_escape_filter_is_safe(Twig_Node $filterArgs)
925
+ {
926
+ foreach ($filterArgs as $arg) {
927
+ if ($arg instanceof Twig_Node_Expression_Constant) {
928
+ return array($arg->getAttribute('value'));
929
+ }
930
+
931
+ return array();
932
+ }
933
+
934
+ return array('html');
935
+ }
936
+
937
+ if (function_exists('mb_convert_encoding')) {
938
+ function twig_convert_encoding($string, $to, $from)
939
+ {
940
+ return mb_convert_encoding($string, $to, $from);
941
+ }
942
+ } elseif (function_exists('iconv')) {
943
+ function twig_convert_encoding($string, $to, $from)
944
+ {
945
+ return iconv($from, $to, $string);
946
+ }
947
+ } else {
948
+ function twig_convert_encoding($string, $to, $from)
949
+ {
950
+ throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
951
+ }
952
+ }
953
+
954
+ function _twig_escape_js_callback($matches)
955
+ {
956
+ $char = $matches[0];
957
+
958
+ // \xHH
959
+ if (!isset($char[1])) {
960
+ return '\\x'.strtoupper(substr('00'.bin2hex($char), -2));
961
+ }
962
+
963
+ // \uHHHH
964
+ $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
965
+
966
+ return '\\u'.strtoupper(substr('0000'.bin2hex($char), -4));
967
+ }
968
+
969
+ function _twig_escape_css_callback($matches)
970
+ {
971
+ $char = $matches[0];
972
+
973
+ // \xHH
974
+ if (!isset($char[1])) {
975
+ $hex = ltrim(strtoupper(bin2hex($char)), '0');
976
+ if (0 === strlen($hex)) {
977
+ $hex = '0';
978
+ }
979
+
980
+ return '\\'.$hex.' ';
981
+ }
982
+
983
+ // \uHHHH
984
+ $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
985
+
986
+ return '\\'.ltrim(strtoupper(bin2hex($char)), '0').' ';
987
+ }
988
+
989
+ /**
990
+ * This function is adapted from code coming from Zend Framework.
991
+ *
992
+ * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
993
+ * @license http://framework.zend.com/license/new-bsd New BSD License
994
+ */
995
+ function _twig_escape_html_attr_callback($matches)
996
+ {
997
+ /*
998
+ * While HTML supports far more named entities, the lowest common denominator
999
+ * has become HTML5's XML Serialisation which is restricted to the those named
1000
+ * entities that XML supports. Using HTML entities would result in this error:
1001
+ * XML Parsing Error: undefined entity
1002
+ */
1003
+ static $entityMap = array(
1004
+ 34 => 'quot', /* quotation mark */
1005
+ 38 => 'amp', /* ampersand */
1006
+ 60 => 'lt', /* less-than sign */
1007
+ 62 => 'gt', /* greater-than sign */
1008
+ );
1009
+
1010
+ $chr = $matches[0];
1011
+ $ord = ord($chr);
1012
+
1013
+ /**
1014
+ * The following replaces characters undefined in HTML with the
1015
+ * hex entity for the Unicode replacement character.
1016
+ */
1017
+ if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) {
1018
+ return '&#xFFFD;';
1019
+ }
1020
+
1021
+ /**
1022
+ * Check if the current character to escape has a name entity we should
1023
+ * replace it with while grabbing the hex value of the character.
1024
+ */
1025
+ if (strlen($chr) == 1) {
1026
+ $hex = strtoupper(substr('00'.bin2hex($chr), -2));
1027
+ } else {
1028
+ $chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8');
1029
+ $hex = strtoupper(substr('0000'.bin2hex($chr), -4));
1030
+ }
1031
+
1032
+ $int = hexdec($hex);
1033
+ if (array_key_exists($int, $entityMap)) {
1034
+ return sprintf('&%s;', $entityMap[$int]);
1035
+ }
1036
+
1037
+ /**
1038
+ * Per OWASP recommendations, we'll use hex entities for any other
1039
+ * characters where a named entity does not exist.
1040
+ */
1041
+
1042
+ return sprintf('&#x%s;', $hex);
1043
+ }
1044
+
1045
+ // add multibyte extensions if possible
1046
+ if (function_exists('mb_get_info')) {
1047
+ /**
1048
+ * Returns the length of a variable.
1049
+ *
1050
+ * @param Twig_Environment $env A Twig_Environment instance
1051
+ * @param mixed $thing A variable
1052
+ *
1053
+ * @return integer The length of the value
1054
+ */
1055
+ function twig_length_filter(Twig_Environment $env, $thing)
1056
+ {
1057
+ return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing);
1058
+ }
1059
+
1060
+ /**
1061
+ * Converts a string to uppercase.
1062
+ *
1063
+ * @param Twig_Environment $env A Twig_Environment instance
1064
+ * @param string $string A string
1065
+ *
1066
+ * @return string The uppercased string
1067
+ */
1068
+ function twig_upper_filter(Twig_Environment $env, $string)
1069
+ {
1070
+ if (null !== ($charset = $env->getCharset())) {
1071
+ return mb_strtoupper($string, $charset);
1072
+ }
1073
+
1074
+ return strtoupper($string);
1075
+ }
1076
+
1077
+ /**
1078
+ * Converts a string to lowercase.
1079
+ *
1080
+ * @param Twig_Environment $env A Twig_Environment instance
1081
+ * @param string $string A string
1082
+ *
1083
+ * @return string The lowercased string
1084
+ */
1085
+ function twig_lower_filter(Twig_Environment $env, $string)
1086
+ {
1087
+ if (null !== ($charset = $env->getCharset())) {
1088
+ return mb_strtolower($string, $charset);
1089
+ }
1090
+
1091
+ return strtolower($string);
1092
+ }
1093
+
1094
+ /**
1095
+ * Returns a titlecased string.
1096
+ *
1097
+ * @param Twig_Environment $env A Twig_Environment instance
1098
+ * @param string $string A string
1099
+ *
1100
+ * @return string The titlecased string
1101
+ */
1102
+ function twig_title_string_filter(Twig_Environment $env, $string)
1103
+ {
1104
+ if (null !== ($charset = $env->getCharset())) {
1105
+ return mb_convert_case($string, MB_CASE_TITLE, $charset);
1106
+ }
1107
+
1108
+ return ucwords(strtolower($string));
1109
+ }
1110
+
1111
+ /**
1112
+ * Returns a capitalized string.
1113
+ *
1114
+ * @param Twig_Environment $env A Twig_Environment instance
1115
+ * @param string $string A string
1116
+ *
1117
+ * @return string The capitalized string
1118
+ */
1119
+ function twig_capitalize_string_filter(Twig_Environment $env, $string)
1120
+ {
1121
+ if (null !== ($charset = $env->getCharset())) {
1122
+ return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).
1123
+ mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset);
1124
+ }
1125
+
1126
+ return ucfirst(strtolower($string));
1127
+ }
1128
+ }
1129
+ // and byte fallback
1130
+ else {
1131
+ /**
1132
+ * Returns the length of a variable.
1133
+ *
1134
+ * @param Twig_Environment $env A Twig_Environment instance
1135
+ * @param mixed $thing A variable
1136
+ *
1137
+ * @return integer The length of the value
1138
+ */
1139
+ function twig_length_filter(Twig_Environment $env, $thing)
1140
+ {
1141
+ return is_scalar($thing) ? strlen($thing) : count($thing);
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
+ return ucwords(strtolower($string));
1155
+ }
1156
+
1157
+ /**
1158
+ * Returns a capitalized string.
1159
+ *
1160
+ * @param Twig_Environment $env A Twig_Environment instance
1161
+ * @param string $string A string
1162
+ *
1163
+ * @return string The capitalized string
1164
+ */
1165
+ function twig_capitalize_string_filter(Twig_Environment $env, $string)
1166
+ {
1167
+ return ucfirst(strtolower($string));
1168
+ }
1169
+ }
1170
+
1171
+ /* used internally */
1172
+ function twig_ensure_traversable($seq)
1173
+ {
1174
+ if ($seq instanceof Traversable || is_array($seq)) {
1175
+ return $seq;
1176
+ }
1177
+
1178
+ return array();
1179
+ }
1180
+
1181
+ /**
1182
+ * Checks if a variable is empty.
1183
+ *
1184
+ * <pre>
1185
+ * {# evaluates to true if the foo variable is null, false, or the empty string #}
1186
+ * {% if foo is empty %}
1187
+ * {# ... #}
1188
+ * {% endif %}
1189
+ * </pre>
1190
+ *
1191
+ * @param mixed $value A variable
1192
+ *
1193
+ * @return Boolean true if the value is empty, false otherwise
1194
+ */
1195
+ function twig_test_empty($value)
1196
+ {
1197
+ if ($value instanceof Countable) {
1198
+ return 0 == count($value);
1199
+ }
1200
+
1201
+ return '' === $value || false === $value || null === $value || array() === $value;
1202
+ }
1203
+
1204
+ /**
1205
+ * Checks if a variable is traversable.
1206
+ *
1207
+ * <pre>
1208
+ * {# evaluates to true if the foo variable is an array or a traversable object #}
1209
+ * {% if foo is traversable %}
1210
+ * {# ... #}
1211
+ * {% endif %}
1212
+ * </pre>
1213
+ *
1214
+ * @param mixed $value A variable
1215
+ *
1216
+ * @return Boolean true if the value is traversable
1217
+ */
1218
+ function twig_test_iterable($value)
1219
+ {
1220
+ return $value instanceof Traversable || is_array($value);
1221
+ }
1222
+
1223
+ /**
1224
+ * Renders a template.
1225
+ *
1226
+ * @param string template The template to render
1227
+ * @param array variables The variables to pass to the template
1228
+ * @param Boolean with_context Whether to pass the current context variables or not
1229
+ * @param Boolean ignore_missing Whether to ignore missing templates or not
1230
+ * @param Boolean sandboxed Whether to sandbox the template or not
1231
+ *
1232
+ * @return string The rendered template
1233
+ */
1234
+ function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false)
1235
+ {
1236
+ if ($withContext) {
1237
+ $variables = array_merge($context, $variables);
1238
+ }
1239
+
1240
+ if ($isSandboxed = $sandboxed && $env->hasExtension('sandbox')) {
1241
+ $sandbox = $env->getExtension('sandbox');
1242
+ if (!$alreadySandboxed = $sandbox->isSandboxed()) {
1243
+ $sandbox->enableSandbox();
1244
+ }
1245
+ }
1246
+
1247
+ try {
1248
+ return $env->resolveTemplate($template)->display($variables);
1249
+ } catch (Twig_Error_Loader $e) {
1250
+ if (!$ignoreMissing) {
1251
+ throw $e;
1252
+ }
1253
+ }
1254
+
1255
+ if ($isSandboxed && !$alreadySandboxed) {
1256
+ $sandbox->disableSandbox();
1257
+ }
1258
+ }
1259
+
1260
+ /**
1261
+ * Provides the ability to get constants from instances as well as class/global constants.
1262
+ *
1263
+ * @param string $constant The name of the constant
1264
+ * @param null|object $object The object to get the constant from
1265
+ *
1266
+ * @return string
1267
+ */
1268
+ function twig_constant($constant, $object = null)
1269
+ {
1270
+ if (!$object) {
1271
+ return constant($constant);
1272
+ }
1273
+ $class = get_class($object);
1274
+
1275
+ return constant($class.'::'.$constant);
1276
+ }
Twig/lib/Twig/Extension/Debug.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2011 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Extension_Debug extends Twig_Extension
12
+ {
13
+ /**
14
+ * Returns a list of global functions to add to the existing list.
15
+ *
16
+ * @return array An array of global functions
17
+ */
18
+ public function getFunctions()
19
+ {
20
+ // dump is safe if var_dump is overridden by xdebug
21
+ $isDumpOutputHtmlSafe = extension_loaded('xdebug')
22
+ // false means that it was not set (and the default is on) or it explicitly enabled
23
+ && (false === ini_get('xdebug.overload_var_dump') || ini_get('xdebug.overload_var_dump'))
24
+ // false means that it was not set (and the default is on) or it explicitly enabled
25
+ // xdebug.overload_var_dump produces HTML only when html_errors is also enabled
26
+ && (false === ini_get('html_errors') || ini_get('html_errors'))
27
+ ;
28
+
29
+ return array(
30
+ new Twig_SimpleFunction('dump', 'twig_var_dump', array('is_safe' => $isDumpOutputHtmlSafe ? array('html') : array(), 'needs_context' => true, 'needs_environment' => true)),
31
+ );
32
+ }
33
+
34
+ /**
35
+ * Returns the name of the extension.
36
+ *
37
+ * @return string The extension name
38
+ */
39
+ public function getName()
40
+ {
41
+ return 'debug';
42
+ }
43
+ }
44
+
45
+ function twig_var_dump(Twig_Environment $env, $context)
46
+ {
47
+ if (!$env->isDebug()) {
48
+ return;
49
+ }
50
+
51
+ ob_start();
52
+
53
+ $count = func_num_args();
54
+ if (2 === $count) {
55
+ $vars = array();
56
+ foreach ($context as $key => $value) {
57
+ if (!$value instanceof Twig_Template) {
58
+ $vars[$key] = $value;
59
+ }
60
+ }
61
+
62
+ var_dump($vars);
63
+ } else {
64
+ for ($i = 2; $i < $count; $i++) {
65
+ var_dump(func_get_arg($i));
66
+ }
67
+ }
68
+
69
+ return ob_get_clean();
70
+ }
Twig/lib/Twig/Extension/Escaper.php ADDED
@@ -0,0 +1,107 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Extension_Escaper extends Twig_Extension
12
+ {
13
+ protected $defaultStrategy;
14
+
15
+ public function __construct($defaultStrategy = 'html')
16
+ {
17
+ $this->setDefaultStrategy($defaultStrategy);
18
+ }
19
+
20
+ /**
21
+ * Returns the token parser instances to add to the existing list.
22
+ *
23
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
24
+ */
25
+ public function getTokenParsers()
26
+ {
27
+ return array(new Twig_TokenParser_AutoEscape());
28
+ }
29
+
30
+ /**
31
+ * Returns the node visitor instances to add to the existing list.
32
+ *
33
+ * @return array An array of Twig_NodeVisitorInterface instances
34
+ */
35
+ public function getNodeVisitors()
36
+ {
37
+ return array(new Twig_NodeVisitor_Escaper());
38
+ }
39
+
40
+ /**
41
+ * Returns a list of filters to add to the existing list.
42
+ *
43
+ * @return array An array of filters
44
+ */
45
+ public function getFilters()
46
+ {
47
+ return array(
48
+ new Twig_SimpleFilter('raw', 'twig_raw_filter', array('is_safe' => array('all'))),
49
+ );
50
+ }
51
+
52
+ /**
53
+ * Sets the default strategy to use when not defined by the user.
54
+ *
55
+ * The strategy can be a valid PHP callback that takes the template
56
+ * "filename" as an argument and returns the strategy to use.
57
+ *
58
+ * @param mixed $defaultStrategy An escaping strategy
59
+ */
60
+ public function setDefaultStrategy($defaultStrategy)
61
+ {
62
+ // for BC
63
+ if (true === $defaultStrategy) {
64
+ $defaultStrategy = 'html';
65
+ }
66
+
67
+ $this->defaultStrategy = $defaultStrategy;
68
+ }
69
+
70
+ /**
71
+ * Gets the default strategy to use when not defined by the user.
72
+ *
73
+ * @param string $filename The template "filename"
74
+ *
75
+ * @return string The default strategy to use for the template
76
+ */
77
+ public function getDefaultStrategy($filename)
78
+ {
79
+ // disable string callables to avoid calling a function named html or js,
80
+ // or any other upcoming escaping strategy
81
+ if (!is_string($this->defaultStrategy) && is_callable($this->defaultStrategy)) {
82
+ return call_user_func($this->defaultStrategy, $filename);
83
+ }
84
+
85
+ return $this->defaultStrategy;
86
+ }
87
+
88
+ /**
89
+ * Returns the name of the extension.
90
+ *
91
+ * @return string The extension name
92
+ */
93
+ public function getName()
94
+ {
95
+ return 'escaper';
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Marks a variable as being safe.
101
+ *
102
+ * @param string $string A PHP variable
103
+ */
104
+ function twig_raw_filter($string)
105
+ {
106
+ return $string;
107
+ }
Twig/lib/Twig/Extension/Optimizer.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Extension_Optimizer extends Twig_Extension
12
+ {
13
+ protected $optimizers;
14
+
15
+ public function __construct($optimizers = -1)
16
+ {
17
+ $this->optimizers = $optimizers;
18
+ }
19
+
20
+ /**
21
+ * {@inheritdoc}
22
+ */
23
+ public function getNodeVisitors()
24
+ {
25
+ return array(new Twig_NodeVisitor_Optimizer($this->optimizers));
26
+ }
27
+
28
+ /**
29
+ * {@inheritdoc}
30
+ */
31
+ public function getName()
32
+ {
33
+ return 'optimizer';
34
+ }
35
+ }
Twig/lib/Twig/Extension/Sandbox.php ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Extension_Sandbox extends Twig_Extension
12
+ {
13
+ protected $sandboxedGlobally;
14
+ protected $sandboxed;
15
+ protected $policy;
16
+
17
+ public function __construct(Twig_Sandbox_SecurityPolicyInterface $policy, $sandboxed = false)
18
+ {
19
+ $this->policy = $policy;
20
+ $this->sandboxedGlobally = $sandboxed;
21
+ }
22
+
23
+ /**
24
+ * Returns the token parser instances to add to the existing list.
25
+ *
26
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
27
+ */
28
+ public function getTokenParsers()
29
+ {
30
+ return array(new Twig_TokenParser_Sandbox());
31
+ }
32
+
33
+ /**
34
+ * Returns the node visitor instances to add to the existing list.
35
+ *
36
+ * @return array An array of Twig_NodeVisitorInterface instances
37
+ */
38
+ public function getNodeVisitors()
39
+ {
40
+ return array(new Twig_NodeVisitor_Sandbox());
41
+ }
42
+
43
+ public function enableSandbox()
44
+ {
45
+ $this->sandboxed = true;
46
+ }
47
+
48
+ public function disableSandbox()
49
+ {
50
+ $this->sandboxed = false;
51
+ }
52
+
53
+ public function isSandboxed()
54
+ {
55
+ return $this->sandboxedGlobally || $this->sandboxed;
56
+ }
57
+
58
+ public function isSandboxedGlobally()
59
+ {
60
+ return $this->sandboxedGlobally;
61
+ }
62
+
63
+ public function setSecurityPolicy(Twig_Sandbox_SecurityPolicyInterface $policy)
64
+ {
65
+ $this->policy = $policy;
66
+ }
67
+
68
+ public function getSecurityPolicy()
69
+ {
70
+ return $this->policy;
71
+ }
72
+
73
+ public function checkSecurity($tags, $filters, $functions)
74
+ {
75
+ if ($this->isSandboxed()) {
76
+ $this->policy->checkSecurity($tags, $filters, $functions);
77
+ }
78
+ }
79
+
80
+ public function checkMethodAllowed($obj, $method)
81
+ {
82
+ if ($this->isSandboxed()) {
83
+ $this->policy->checkMethodAllowed($obj, $method);
84
+ }
85
+ }
86
+
87
+ public function checkPropertyAllowed($obj, $method)
88
+ {
89
+ if ($this->isSandboxed()) {
90
+ $this->policy->checkPropertyAllowed($obj, $method);
91
+ }
92
+ }
93
+
94
+ public function ensureToStringAllowed($obj)
95
+ {
96
+ if (is_object($obj)) {
97
+ $this->policy->checkMethodAllowed($obj, '__toString');
98
+ }
99
+
100
+ return $obj;
101
+ }
102
+
103
+ /**
104
+ * Returns the name of the extension.
105
+ *
106
+ * @return string The extension name
107
+ */
108
+ public function getName()
109
+ {
110
+ return 'sandbox';
111
+ }
112
+ }
Twig/lib/Twig/Extension/Staging.php ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ */
20
+ class Twig_Extension_Staging extends Twig_Extension
21
+ {
22
+ protected $functions = array();
23
+ protected $filters = array();
24
+ protected $visitors = array();
25
+ protected $tokenParsers = array();
26
+ protected $globals = array();
27
+ protected $tests = array();
28
+
29
+ public function addFunction($name, $function)
30
+ {
31
+ $this->functions[$name] = $function;
32
+ }
33
+
34
+ /**
35
+ * {@inheritdoc}
36
+ */
37
+ public function getFunctions()
38
+ {
39
+ return $this->functions;
40
+ }
41
+
42
+ public function addFilter($name, $filter)
43
+ {
44
+ $this->filters[$name] = $filter;
45
+ }
46
+
47
+ /**
48
+ * {@inheritdoc}
49
+ */
50
+ public function getFilters()
51
+ {
52
+ return $this->filters;
53
+ }
54
+
55
+ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
56
+ {
57
+ $this->visitors[] = $visitor;
58
+ }
59
+
60
+ /**
61
+ * {@inheritdoc}
62
+ */
63
+ public function getNodeVisitors()
64
+ {
65
+ return $this->visitors;
66
+ }
67
+
68
+ public function addTokenParser(Twig_TokenParserInterface $parser)
69
+ {
70
+ $this->tokenParsers[] = $parser;
71
+ }
72
+
73
+ /**
74
+ * {@inheritdoc}
75
+ */
76
+ public function getTokenParsers()
77
+ {
78
+ return $this->tokenParsers;
79
+ }
80
+
81
+ public function addGlobal($name, $value)
82
+ {
83
+ $this->globals[$name] = $value;
84
+ }
85
+
86
+ /**
87
+ * {@inheritdoc}
88
+ */
89
+ public function getGlobals()
90
+ {
91
+ return $this->globals;
92
+ }
93
+
94
+ public function addTest($name, $test)
95
+ {
96
+ $this->tests[$name] = $test;
97
+ }
98
+
99
+ /**
100
+ * {@inheritdoc}
101
+ */
102
+ public function getTests()
103
+ {
104
+ return $this->tests;
105
+ }
106
+
107
+ /**
108
+ * {@inheritdoc}
109
+ */
110
+ public function getName()
111
+ {
112
+ return 'staging';
113
+ }
114
+ }
Twig/lib/Twig/Extension/StringLoader.php ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2012 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Extension_StringLoader extends Twig_Extension
12
+ {
13
+ /**
14
+ * {@inheritdoc}
15
+ */
16
+ public function getFunctions()
17
+ {
18
+ return array(
19
+ new Twig_SimpleFunction('template_from_string', 'twig_template_from_string', array('needs_environment' => true)),
20
+ );
21
+ }
22
+
23
+ /**
24
+ * {@inheritdoc}
25
+ */
26
+ public function getName()
27
+ {
28
+ return 'string_loader';
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Loads a template from a string.
34
+ *
35
+ * <pre>
36
+ * {% include template_from_string("Hello {{ name }}") }}
37
+ * </pre>
38
+ *
39
+ * @param Twig_Environment $env A Twig_Environment instance
40
+ * @param string $template A template as a string
41
+ *
42
+ * @return Twig_Template A Twig_Template instance
43
+ */
44
+ function twig_template_from_string(Twig_Environment $env, $template)
45
+ {
46
+ static $loader;
47
+
48
+ if (null === $loader) {
49
+ $loader = new Twig_Loader_String();
50
+ }
51
+
52
+ $current = $env->getLoader();
53
+ $env->setLoader($loader);
54
+ try {
55
+ $template = $env->loadTemplate($template);
56
+ } catch (Exception $e) {
57
+ $env->setLoader($current);
58
+
59
+ throw $e;
60
+ }
61
+ $env->setLoader($current);
62
+
63
+ return $template;
64
+ }
Twig/lib/Twig/ExtensionInterface.php ADDED
@@ -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
+ * Interface implemented by extension classes.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ interface Twig_ExtensionInterface
19
+ {
20
+ /**
21
+ * Initializes the runtime environment.
22
+ *
23
+ * This is where you can load some file that contains filter functions for instance.
24
+ *
25
+ * @param Twig_Environment $environment The current Twig_Environment instance
26
+ */
27
+ public function initRuntime(Twig_Environment $environment);
28
+
29
+ /**
30
+ * Returns the token parser instances to add to the existing list.
31
+ *
32
+ * @return array An array of Twig_TokenParserInterface or Twig_TokenParserBrokerInterface instances
33
+ */
34
+ public function getTokenParsers();
35
+
36
+ /**
37
+ * Returns the node visitor instances to add to the existing list.
38
+ *
39
+ * @return array An array of Twig_NodeVisitorInterface instances
40
+ */
41
+ public function getNodeVisitors();
42
+
43
+ /**
44
+ * Returns a list of filters to add to the existing list.
45
+ *
46
+ * @return array An array of filters
47
+ */
48
+ public function getFilters();
49
+
50
+ /**
51
+ * Returns a list of tests to add to the existing list.
52
+ *
53
+ * @return array An array of tests
54
+ */
55
+ public function getTests();
56
+
57
+ /**
58
+ * Returns a list of functions to add to the existing list.
59
+ *
60
+ * @return array An array of functions
61
+ */
62
+ public function getFunctions();
63
+
64
+ /**
65
+ * Returns a list of operators to add to the existing list.
66
+ *
67
+ * @return array An array of operators
68
+ */
69
+ public function getOperators();
70
+
71
+ /**
72
+ * Returns a list of global variables to add to the existing list.
73
+ *
74
+ * @return array An array of global variables
75
+ */
76
+ public function getGlobals();
77
+
78
+ /**
79
+ * Returns the name of the extension.
80
+ *
81
+ * @return string The extension name
82
+ */
83
+ public function getName();
84
+ }
Twig/lib/Twig/Filter.php ADDED
@@ -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
+ * Represents a template filter.
14
+ *
15
+ * Use Twig_SimpleFilter instead.
16
+ *
17
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ abstract class Twig_Filter implements Twig_FilterInterface, Twig_FilterCallableInterface
22
+ {
23
+ protected $options;
24
+ protected $arguments = array();
25
+
26
+ public function __construct(array $options = array())
27
+ {
28
+ $this->options = array_merge(array(
29
+ 'needs_environment' => false,
30
+ 'needs_context' => false,
31
+ 'pre_escape' => null,
32
+ 'preserves_safety' => null,
33
+ 'callable' => null,
34
+ ), $options);
35
+ }
36
+
37
+ public function setArguments($arguments)
38
+ {
39
+ $this->arguments = $arguments;
40
+ }
41
+
42
+ public function getArguments()
43
+ {
44
+ return $this->arguments;
45
+ }
46
+
47
+ public function needsEnvironment()
48
+ {
49
+ return $this->options['needs_environment'];
50
+ }
51
+
52
+ public function needsContext()
53
+ {
54
+ return $this->options['needs_context'];
55
+ }
56
+
57
+ public function getSafe(Twig_Node $filterArgs)
58
+ {
59
+ if (isset($this->options['is_safe'])) {
60
+ return $this->options['is_safe'];
61
+ }
62
+
63
+ if (isset($this->options['is_safe_callback'])) {
64
+ return call_user_func($this->options['is_safe_callback'], $filterArgs);
65
+ }
66
+
67
+ return null;
68
+ }
69
+
70
+ public function getPreservesSafety()
71
+ {
72
+ return $this->options['preserves_safety'];
73
+ }
74
+
75
+ public function getPreEscape()
76
+ {
77
+ return $this->options['pre_escape'];
78
+ }
79
+
80
+ public function getCallable()
81
+ {
82
+ return $this->options['callable'];
83
+ }
84
+ }
Twig/lib/Twig/Filter/Function.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ class Twig_Filter_Function extends Twig_Filter
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
+ }
Twig/lib/Twig/Filter/Method.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ class Twig_Filter_Method extends Twig_Filter
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
+ }
Twig/lib/Twig/Filter/Node.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ class Twig_Filter_Node extends Twig_Filter
22
+ {
23
+ protected $class;
24
+
25
+ public function __construct($class, array $options = array())
26
+ {
27
+ parent::__construct($options);
28
+
29
+ $this->class = $class;
30
+ }
31
+
32
+ public function getClass()
33
+ {
34
+ return $this->class;
35
+ }
36
+
37
+ public function compile()
38
+ {
39
+ }
40
+ }
Twig/lib/Twig/FilterCallableInterface.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ interface Twig_FilterCallableInterface
22
+ {
23
+ public function getCallable();
24
+ }
Twig/lib/Twig/FilterInterface.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ interface Twig_FilterInterface
22
+ {
23
+ /**
24
+ * Compiles a filter.
25
+ *
26
+ * @return string The PHP code for the filter
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 getPreservesSafety();
37
+
38
+ public function getPreEscape();
39
+
40
+ public function setArguments($arguments);
41
+
42
+ public function getArguments();
43
+ }
Twig/lib/Twig/Function.php ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ abstract class Twig_Function implements Twig_FunctionInterface, Twig_FunctionCallableInterface
22
+ {
23
+ protected $options;
24
+ protected $arguments = array();
25
+
26
+ public function __construct(array $options = array())
27
+ {
28
+ $this->options = array_merge(array(
29
+ 'needs_environment' => false,
30
+ 'needs_context' => false,
31
+ 'callable' => null,
32
+ ), $options);
33
+ }
34
+
35
+ public function setArguments($arguments)
36
+ {
37
+ $this->arguments = $arguments;
38
+ }
39
+
40
+ public function getArguments()
41
+ {
42
+ return $this->arguments;
43
+ }
44
+
45
+ public function needsEnvironment()
46
+ {
47
+ return $this->options['needs_environment'];
48
+ }
49
+
50
+ public function needsContext()
51
+ {
52
+ return $this->options['needs_context'];
53
+ }
54
+
55
+ public function getSafe(Twig_Node $functionArgs)
56
+ {
57
+ if (isset($this->options['is_safe'])) {
58
+ return $this->options['is_safe'];
59
+ }
60
+
61
+ if (isset($this->options['is_safe_callback'])) {
62
+ return call_user_func($this->options['is_safe_callback'], $functionArgs);
63
+ }
64
+
65
+ return array();
66
+ }
67
+
68
+ public function getCallable()
69
+ {
70
+ return $this->options['callable'];
71
+ }
72
+ }
Twig/lib/Twig/Function/Function.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
19
+ * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
20
+ * @deprecated since 1.12 (to be removed in 2.0)
21
+ */
22
+ class Twig_Function_Function extends Twig_Function
23
+ {
24
+ protected $function;
25
+
26
+ public function __construct($function, array $options = array())
27
+ {
28
+ $options['callable'] = $function;
29
+
30
+ parent::__construct($options);
31
+
32
+ $this->function = $function;
33
+ }
34
+
35
+ public function compile()
36
+ {
37
+ return $this->function;
38
+ }
39
+ }
Twig/lib/Twig/Function/Method.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
19
+ * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
20
+ * @deprecated since 1.12 (to be removed in 2.0)
21
+ */
22
+ class Twig_Function_Method extends Twig_Function
23
+ {
24
+ protected $extension;
25
+ protected $method;
26
+
27
+ public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
28
+ {
29
+ $options['callable'] = array($extension, $method);
30
+
31
+ parent::__construct($options);
32
+
33
+ $this->extension = $extension;
34
+ $this->method = $method;
35
+ }
36
+
37
+ public function compile()
38
+ {
39
+ return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
40
+ }
41
+ }
Twig/lib/Twig/Function/Node.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ class Twig_Function_Node extends Twig_Function
22
+ {
23
+ protected $class;
24
+
25
+ public function __construct($class, array $options = array())
26
+ {
27
+ parent::__construct($options);
28
+
29
+ $this->class = $class;
30
+ }
31
+
32
+ public function getClass()
33
+ {
34
+ return $this->class;
35
+ }
36
+
37
+ public function compile()
38
+ {
39
+ }
40
+ }
Twig/lib/Twig/FunctionCallableInterface.php ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ * @deprecated since 1.12 (to be removed in 2.0)
20
+ */
21
+ interface Twig_FunctionCallableInterface
22
+ {
23
+ public function getCallable();
24
+ }
Twig/lib/Twig/FunctionInterface.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
19
+ * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
20
+ * @deprecated since 1.12 (to be removed in 2.0)
21
+ */
22
+ interface Twig_FunctionInterface
23
+ {
24
+ /**
25
+ * Compiles a function.
26
+ *
27
+ * @return string The PHP code for the function
28
+ */
29
+ public function compile();
30
+
31
+ public function needsEnvironment();
32
+
33
+ public function needsContext();
34
+
35
+ public function getSafe(Twig_Node $filterArgs);
36
+
37
+ public function setArguments($arguments);
38
+
39
+ public function getArguments();
40
+ }
Twig/lib/Twig/Lexer.php ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Lexer implements Twig_LexerInterface
20
+ {
21
+ protected $tokens;
22
+ protected $code;
23
+ protected $cursor;
24
+ protected $lineno;
25
+ protected $end;
26
+ protected $state;
27
+ protected $states;
28
+ protected $brackets;
29
+ protected $env;
30
+ protected $filename;
31
+ protected $options;
32
+ protected $regexes;
33
+ protected $position;
34
+ protected $positions;
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
+ }
190
+ break;
191
+
192
+ case $this->options['tag_variable'][0]:
193
+ $this->pushToken(Twig_Token::VAR_START_TYPE);
194
+ $this->pushState(self::STATE_VAR);
195
+ break;
196
+ }
197
+ }
198
+
199
+ protected function lexBlock()
200
+ {
201
+ if (empty($this->brackets) && preg_match($this->regexes['lex_block'], $this->code, $match, null, $this->cursor)) {
202
+ $this->pushToken(Twig_Token::BLOCK_END_TYPE);
203
+ $this->moveCursor($match[0]);
204
+ $this->popState();
205
+ } else {
206
+ $this->lexExpression();
207
+ }
208
+ }
209
+
210
+ protected function lexVar()
211
+ {
212
+ if (empty($this->brackets) && preg_match($this->regexes['lex_var'], $this->code, $match, null, $this->cursor)) {
213
+ $this->pushToken(Twig_Token::VAR_END_TYPE);
214
+ $this->moveCursor($match[0]);
215
+ $this->popState();
216
+ } else {
217
+ $this->lexExpression();
218
+ }
219
+ }
220
+
221
+ protected function lexExpression()
222
+ {
223
+ // whitespace
224
+ if (preg_match('/\s+/A', $this->code, $match, null, $this->cursor)) {
225
+ $this->moveCursor($match[0]);
226
+
227
+ if ($this->cursor >= $this->end) {
228
+ throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s"', $this->state === self::STATE_BLOCK ? 'block' : 'variable'), $this->lineno, $this->filename);
229
+ }
230
+ }
231
+
232
+ // operators
233
+ if (preg_match($this->regexes['operator'], $this->code, $match, null, $this->cursor)) {
234
+ $this->pushToken(Twig_Token::OPERATOR_TYPE, $match[0]);
235
+ $this->moveCursor($match[0]);
236
+ }
237
+ // names
238
+ elseif (preg_match(self::REGEX_NAME, $this->code, $match, null, $this->cursor)) {
239
+ $this->pushToken(Twig_Token::NAME_TYPE, $match[0]);
240
+ $this->moveCursor($match[0]);
241
+ }
242
+ // numbers
243
+ elseif (preg_match(self::REGEX_NUMBER, $this->code, $match, null, $this->cursor)) {
244
+ $number = (float) $match[0]; // floats
245
+ if (ctype_digit($match[0]) && $number <= PHP_INT_MAX) {
246
+ $number = (int) $match[0]; // integers lower than the maximum
247
+ }
248
+ $this->pushToken(Twig_Token::NUMBER_TYPE, $number);
249
+ $this->moveCursor($match[0]);
250
+ }
251
+ // punctuation
252
+ elseif (false !== strpos(self::PUNCTUATION, $this->code[$this->cursor])) {
253
+ // opening bracket
254
+ if (false !== strpos('([{', $this->code[$this->cursor])) {
255
+ $this->brackets[] = array($this->code[$this->cursor], $this->lineno);
256
+ }
257
+ // closing bracket
258
+ elseif (false !== strpos(')]}', $this->code[$this->cursor])) {
259
+ if (empty($this->brackets)) {
260
+ throw new Twig_Error_Syntax(sprintf('Unexpected "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
261
+ }
262
+
263
+ list($expect, $lineno) = array_pop($this->brackets);
264
+ if ($this->code[$this->cursor] != strtr($expect, '([{', ')]}')) {
265
+ throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
266
+ }
267
+ }
268
+
269
+ $this->pushToken(Twig_Token::PUNCTUATION_TYPE, $this->code[$this->cursor]);
270
+ ++$this->cursor;
271
+ }
272
+ // strings
273
+ elseif (preg_match(self::REGEX_STRING, $this->code, $match, null, $this->cursor)) {
274
+ $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes(substr($match[0], 1, -1)));
275
+ $this->moveCursor($match[0]);
276
+ }
277
+ // opening double quoted string
278
+ elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
279
+ $this->brackets[] = array('"', $this->lineno);
280
+ $this->pushState(self::STATE_STRING);
281
+ $this->moveCursor($match[0]);
282
+ }
283
+ // unlexable
284
+ else {
285
+ throw new Twig_Error_Syntax(sprintf('Unexpected character "%s"', $this->code[$this->cursor]), $this->lineno, $this->filename);
286
+ }
287
+ }
288
+
289
+ protected function lexRawData($tag)
290
+ {
291
+ if (!preg_match(str_replace('%s', $tag, $this->regexes['lex_raw_data']), $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
292
+ throw new Twig_Error_Syntax(sprintf('Unexpected end of file: Unclosed "%s" block', $tag), $this->lineno, $this->filename);
293
+ }
294
+
295
+ $text = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
296
+ $this->moveCursor($text.$match[0][0]);
297
+
298
+ if (false !== strpos($match[1][0], $this->options['whitespace_trim'])) {
299
+ $text = rtrim($text);
300
+ }
301
+
302
+ $this->pushToken(Twig_Token::TEXT_TYPE, $text);
303
+ }
304
+
305
+ protected function lexComment()
306
+ {
307
+ if (!preg_match($this->regexes['lex_comment'], $this->code, $match, PREG_OFFSET_CAPTURE, $this->cursor)) {
308
+ throw new Twig_Error_Syntax('Unclosed comment', $this->lineno, $this->filename);
309
+ }
310
+
311
+ $this->moveCursor(substr($this->code, $this->cursor, $match[0][1] - $this->cursor).$match[0][0]);
312
+ }
313
+
314
+ protected function lexString()
315
+ {
316
+ if (preg_match($this->regexes['interpolation_start'], $this->code, $match, null, $this->cursor)) {
317
+ $this->brackets[] = array($this->options['interpolation'][0], $this->lineno);
318
+ $this->pushToken(Twig_Token::INTERPOLATION_START_TYPE);
319
+ $this->moveCursor($match[0]);
320
+ $this->pushState(self::STATE_INTERPOLATION);
321
+
322
+ } elseif (preg_match(self::REGEX_DQ_STRING_PART, $this->code, $match, null, $this->cursor) && strlen($match[0]) > 0) {
323
+ $this->pushToken(Twig_Token::STRING_TYPE, stripcslashes($match[0]));
324
+ $this->moveCursor($match[0]);
325
+
326
+ } elseif (preg_match(self::REGEX_DQ_STRING_DELIM, $this->code, $match, null, $this->cursor)) {
327
+
328
+ list($expect, $lineno) = array_pop($this->brackets);
329
+ if ($this->code[$this->cursor] != '"') {
330
+ throw new Twig_Error_Syntax(sprintf('Unclosed "%s"', $expect), $lineno, $this->filename);
331
+ }
332
+
333
+ $this->popState();
334
+ ++$this->cursor;
335
+ }
336
+ }
337
+
338
+ protected function lexInterpolation()
339
+ {
340
+ $bracket = end($this->brackets);
341
+ if ($this->options['interpolation'][0] === $bracket[0] && preg_match($this->regexes['interpolation_end'], $this->code, $match, null, $this->cursor)) {
342
+ array_pop($this->brackets);
343
+ $this->pushToken(Twig_Token::INTERPOLATION_END_TYPE);
344
+ $this->moveCursor($match[0]);
345
+ $this->popState();
346
+ } else {
347
+ $this->lexExpression();
348
+ }
349
+ }
350
+
351
+ protected function pushToken($type, $value = '')
352
+ {
353
+ // do not push empty text tokens
354
+ if (Twig_Token::TEXT_TYPE === $type && '' === $value) {
355
+ return;
356
+ }
357
+
358
+ $this->tokens[] = new Twig_Token($type, $value, $this->lineno);
359
+ }
360
+
361
+ protected function moveCursor($text)
362
+ {
363
+ $this->cursor += strlen($text);
364
+ $this->lineno += substr_count($text, "\n");
365
+ }
366
+
367
+ protected function getOperatorRegex()
368
+ {
369
+ $operators = array_merge(
370
+ array('='),
371
+ array_keys($this->env->getUnaryOperators()),
372
+ array_keys($this->env->getBinaryOperators())
373
+ );
374
+
375
+ $operators = array_combine($operators, array_map('strlen', $operators));
376
+ arsort($operators);
377
+
378
+ $regex = array();
379
+ foreach ($operators as $operator => $length) {
380
+ // an operator that ends with a character must be followed by
381
+ // a whitespace or a parenthesis
382
+ if (ctype_alpha($operator[$length - 1])) {
383
+ $regex[] = preg_quote($operator, '/').'(?=[\s()])';
384
+ } else {
385
+ $regex[] = preg_quote($operator, '/');
386
+ }
387
+ }
388
+
389
+ return '/'.implode('|', $regex).'/A';
390
+ }
391
+
392
+ protected function pushState($state)
393
+ {
394
+ $this->states[] = $this->state;
395
+ $this->state = $state;
396
+ }
397
+
398
+ protected function popState()
399
+ {
400
+ if (0 === count($this->states)) {
401
+ throw new Exception('Cannot pop state without a previous state');
402
+ }
403
+
404
+ $this->state = array_pop($this->states);
405
+ }
406
+ }
Twig/lib/Twig/LexerInterface.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ interface Twig_LexerInterface
20
+ {
21
+ /**
22
+ * Tokenizes a source code.
23
+ *
24
+ * @param string $code The source code
25
+ * @param string $filename A unique identifier for the source code
26
+ *
27
+ * @return Twig_TokenStream A token stream instance
28
+ */
29
+ public function tokenize($code, $filename = null);
30
+ }
Twig/lib/Twig/Loader/Array.php ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
21
+ * @author Fabien Potencier <fabien@symfony.com>
22
+ */
23
+ class Twig_Loader_Array implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
24
+ {
25
+ protected $templates;
26
+
27
+ /**
28
+ * Constructor.
29
+ *
30
+ * @param array $templates An array of templates (keys are the names, and values are the source code)
31
+ *
32
+ * @see Twig_Loader
33
+ */
34
+ public function __construct(array $templates)
35
+ {
36
+ $this->templates = array();
37
+ foreach ($templates as $name => $template) {
38
+ $this->templates[$name] = $template;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Adds or overrides a template.
44
+ *
45
+ * @param string $name The template name
46
+ * @param string $template The template source
47
+ */
48
+ public function setTemplate($name, $template)
49
+ {
50
+ $this->templates[(string) $name] = $template;
51
+ }
52
+
53
+ /**
54
+ * {@inheritdoc}
55
+ */
56
+ public function getSource($name)
57
+ {
58
+ $name = (string) $name;
59
+ if (!isset($this->templates[$name])) {
60
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
61
+ }
62
+
63
+ return $this->templates[$name];
64
+ }
65
+
66
+ /**
67
+ * {@inheritdoc}
68
+ */
69
+ public function exists($name)
70
+ {
71
+ return isset($this->templates[(string) $name]);
72
+ }
73
+
74
+ /**
75
+ * {@inheritdoc}
76
+ */
77
+ public function getCacheKey($name)
78
+ {
79
+ $name = (string) $name;
80
+ if (!isset($this->templates[$name])) {
81
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
82
+ }
83
+
84
+ return $this->templates[$name];
85
+ }
86
+
87
+ /**
88
+ * {@inheritdoc}
89
+ */
90
+ public function isFresh($name, $time)
91
+ {
92
+ $name = (string) $name;
93
+ if (!isset($this->templates[$name])) {
94
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined.', $name));
95
+ }
96
+
97
+ return true;
98
+ }
99
+ }
Twig/lib/Twig/Loader/Chain.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Loader_Chain implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
19
+ {
20
+ private $hasSourceCache = array();
21
+ protected $loaders;
22
+
23
+ /**
24
+ * Constructor.
25
+ *
26
+ * @param Twig_LoaderInterface[] $loaders An array of loader instances
27
+ */
28
+ public function __construct(array $loaders = array())
29
+ {
30
+ $this->loaders = array();
31
+ foreach ($loaders as $loader) {
32
+ $this->addLoader($loader);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Adds a loader instance.
38
+ *
39
+ * @param Twig_LoaderInterface $loader A Loader instance
40
+ */
41
+ public function addLoader(Twig_LoaderInterface $loader)
42
+ {
43
+ $this->loaders[] = $loader;
44
+ $this->hasSourceCache = array();
45
+ }
46
+
47
+ /**
48
+ * {@inheritdoc}
49
+ */
50
+ public function getSource($name)
51
+ {
52
+ $exceptions = array();
53
+ foreach ($this->loaders as $loader) {
54
+ if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) {
55
+ continue;
56
+ }
57
+
58
+ try {
59
+ return $loader->getSource($name);
60
+ } catch (Twig_Error_Loader $e) {
61
+ $exceptions[] = $e->getMessage();
62
+ }
63
+ }
64
+
65
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(', ', $exceptions)));
66
+ }
67
+
68
+ /**
69
+ * {@inheritdoc}
70
+ */
71
+ public function exists($name)
72
+ {
73
+ $name = (string) $name;
74
+
75
+ if (isset($this->hasSourceCache[$name])) {
76
+ return $this->hasSourceCache[$name];
77
+ }
78
+
79
+ foreach ($this->loaders as $loader) {
80
+ if ($loader instanceof Twig_ExistsLoaderInterface && $loader->exists($name)) {
81
+ return $this->hasSourceCache[$name] = true;
82
+ }
83
+
84
+ try {
85
+ $loader->getSource($name);
86
+
87
+ return $this->hasSourceCache[$name] = true;
88
+ } catch (Twig_Error_Loader $e) {
89
+ }
90
+ }
91
+
92
+ return $this->hasSourceCache[$name] = false;
93
+ }
94
+
95
+ /**
96
+ * {@inheritdoc}
97
+ */
98
+ public function getCacheKey($name)
99
+ {
100
+ $exceptions = array();
101
+ foreach ($this->loaders as $loader) {
102
+ if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) {
103
+ continue;
104
+ }
105
+
106
+ try {
107
+ return $loader->getCacheKey($name);
108
+ } catch (Twig_Error_Loader $e) {
109
+ $exceptions[] = get_class($loader).': '.$e->getMessage();
110
+ }
111
+ }
112
+
113
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(' ', $exceptions)));
114
+ }
115
+
116
+ /**
117
+ * {@inheritdoc}
118
+ */
119
+ public function isFresh($name, $time)
120
+ {
121
+ $exceptions = array();
122
+ foreach ($this->loaders as $loader) {
123
+ if ($loader instanceof Twig_ExistsLoaderInterface && !$loader->exists($name)) {
124
+ continue;
125
+ }
126
+
127
+ try {
128
+ return $loader->isFresh($name, $time);
129
+ } catch (Twig_Error_Loader $e) {
130
+ $exceptions[] = get_class($loader).': '.$e->getMessage();
131
+ }
132
+ }
133
+
134
+ throw new Twig_Error_Loader(sprintf('Template "%s" is not defined (%s).', $name, implode(' ', $exceptions)));
135
+ }
136
+ }
Twig/lib/Twig/Loader/Filesystem.php ADDED
@@ -0,0 +1,221 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
19
+ {
20
+ protected $paths;
21
+ protected $cache;
22
+
23
+ /**
24
+ * Constructor.
25
+ *
26
+ * @param string|array $paths A path or an array of paths where to look for templates
27
+ */
28
+ public function __construct($paths)
29
+ {
30
+ $this->setPaths($paths);
31
+ }
32
+
33
+ /**
34
+ * Returns the paths to the templates.
35
+ *
36
+ * @param string $namespace A path namespace
37
+ *
38
+ * @return array The array of paths where to look for templates
39
+ */
40
+ public function getPaths($namespace = '__main__')
41
+ {
42
+ return isset($this->paths[$namespace]) ? $this->paths[$namespace] : array();
43
+ }
44
+
45
+ /**
46
+ * Returns the path namespaces.
47
+ *
48
+ * The "__main__" namespace is always defined.
49
+ *
50
+ * @return array The array of defined namespaces
51
+ */
52
+ public function getNamespaces()
53
+ {
54
+ return array_keys($this->paths);
55
+ }
56
+
57
+ /**
58
+ * Sets the paths where templates are stored.
59
+ *
60
+ * @param string|array $paths A path or an array of paths where to look for templates
61
+ * @param string $namespace A path namespace
62
+ */
63
+ public function setPaths($paths, $namespace = '__main__')
64
+ {
65
+ if (!is_array($paths)) {
66
+ $paths = array($paths);
67
+ }
68
+
69
+ $this->paths[$namespace] = array();
70
+ foreach ($paths as $path) {
71
+ $this->addPath($path, $namespace);
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Adds a path where templates are stored.
77
+ *
78
+ * @param string $path A path where to look for templates
79
+ * @param string $namespace A path name
80
+ *
81
+ * @throws Twig_Error_Loader
82
+ */
83
+ public function addPath($path, $namespace = '__main__')
84
+ {
85
+ // invalidate the cache
86
+ $this->cache = array();
87
+
88
+ if (!is_dir($path)) {
89
+ throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
90
+ }
91
+
92
+ $this->paths[$namespace][] = rtrim($path, '/\\');
93
+ }
94
+
95
+ /**
96
+ * Prepends a path where templates are stored.
97
+ *
98
+ * @param string $path A path where to look for templates
99
+ * @param string $namespace A path name
100
+ *
101
+ * @throws Twig_Error_Loader
102
+ */
103
+ public function prependPath($path, $namespace = '__main__')
104
+ {
105
+ // invalidate the cache
106
+ $this->cache = array();
107
+
108
+ if (!is_dir($path)) {
109
+ throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
110
+ }
111
+
112
+ $path = rtrim($path, '/\\');
113
+
114
+ if (!isset($this->paths[$namespace])) {
115
+ $this->paths[$namespace][] = $path;
116
+ } else {
117
+ array_unshift($this->paths[$namespace], $path);
118
+ }
119
+ }
120
+
121
+ /**
122
+ * {@inheritdoc}
123
+ */
124
+ public function getSource($name)
125
+ {
126
+ return file_get_contents($this->findTemplate($name));
127
+ }
128
+
129
+ /**
130
+ * {@inheritdoc}
131
+ */
132
+ public function getCacheKey($name)
133
+ {
134
+ return $this->findTemplate($name);
135
+ }
136
+
137
+ /**
138
+ * {@inheritdoc}
139
+ */
140
+ public function exists($name)
141
+ {
142
+ $name = (string) $name;
143
+ if (isset($this->cache[$name])) {
144
+ return true;
145
+ }
146
+
147
+ try {
148
+ $this->findTemplate($name);
149
+
150
+ return true;
151
+ } catch (Twig_Error_Loader $exception) {
152
+ return false;
153
+ }
154
+ }
155
+
156
+ /**
157
+ * {@inheritdoc}
158
+ */
159
+ public function isFresh($name, $time)
160
+ {
161
+ return filemtime($this->findTemplate($name)) <= $time;
162
+ }
163
+
164
+ protected function findTemplate($name)
165
+ {
166
+ $name = (string) $name;
167
+
168
+ // normalize name
169
+ $name = preg_replace('#/{2,}#', '/', strtr($name, '\\', '/'));
170
+
171
+ if (isset($this->cache[$name])) {
172
+ return $this->cache[$name];
173
+ }
174
+
175
+ $this->validateName($name);
176
+
177
+ $namespace = '__main__';
178
+ if (isset($name[0]) && '@' == $name[0]) {
179
+ if (false === $pos = strpos($name, '/')) {
180
+ throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
181
+ }
182
+
183
+ $namespace = substr($name, 1, $pos - 1);
184
+
185
+ $name = substr($name, $pos + 1);
186
+ }
187
+
188
+ if (!isset($this->paths[$namespace])) {
189
+ throw new Twig_Error_Loader(sprintf('There are no registered paths for namespace "%s".', $namespace));
190
+ }
191
+
192
+ foreach ($this->paths[$namespace] as $path) {
193
+ if (is_file($path.'/'.$name)) {
194
+ return $this->cache[$name] = $path.'/'.$name;
195
+ }
196
+ }
197
+
198
+ throw new Twig_Error_Loader(sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace])));
199
+ }
200
+
201
+ protected function validateName($name)
202
+ {
203
+ if (false !== strpos($name, "\0")) {
204
+ throw new Twig_Error_Loader('A template name cannot contain NUL bytes.');
205
+ }
206
+
207
+ $parts = explode('/', $name);
208
+ $level = 0;
209
+ foreach ($parts as $part) {
210
+ if ('..' === $part) {
211
+ --$level;
212
+ } elseif ('.' !== $part) {
213
+ ++$level;
214
+ }
215
+
216
+ if ($level < 0) {
217
+ throw new Twig_Error_Loader(sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
218
+ }
219
+ }
220
+ }
221
+ }
Twig/lib/Twig/Loader/String.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
25
+ * @author Fabien Potencier <fabien@symfony.com>
26
+ */
27
+ class Twig_Loader_String implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
28
+ {
29
+ /**
30
+ * {@inheritdoc}
31
+ */
32
+ public function getSource($name)
33
+ {
34
+ return $name;
35
+ }
36
+
37
+ /**
38
+ * {@inheritdoc}
39
+ */
40
+ public function exists($name)
41
+ {
42
+ return true;
43
+ }
44
+
45
+ /**
46
+ * {@inheritdoc}
47
+ */
48
+ public function getCacheKey($name)
49
+ {
50
+ return $name;
51
+ }
52
+
53
+ /**
54
+ * {@inheritdoc}
55
+ */
56
+ public function isFresh($name, $time)
57
+ {
58
+ return true;
59
+ }
60
+ }
Twig/lib/Twig/LoaderInterface.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ interface Twig_LoaderInterface
19
+ {
20
+ /**
21
+ * Gets the source code of a template, given its name.
22
+ *
23
+ * @param string $name The name of the template to load
24
+ *
25
+ * @return string The template source code
26
+ *
27
+ * @throws Twig_Error_Loader When $name is not found
28
+ */
29
+ public function getSource($name);
30
+
31
+ /**
32
+ * Gets the cache key to use for the cache for a given template name.
33
+ *
34
+ * @param string $name The name of the template to load
35
+ *
36
+ * @return string The cache key
37
+ *
38
+ * @throws Twig_Error_Loader When $name is not found
39
+ */
40
+ public function getCacheKey($name);
41
+
42
+ /**
43
+ * Returns true if the template is still fresh.
44
+ *
45
+ * @param string $name The template name
46
+ * @param timestamp $time The last modification time of the cached template
47
+ *
48
+ * @return Boolean true if the template is fresh, false otherwise
49
+ *
50
+ * @throws Twig_Error_Loader When $name is not found
51
+ */
52
+ public function isFresh($name, $time);
53
+ }
Twig/lib/Twig/Markup.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Markup implements Countable
19
+ {
20
+ protected $content;
21
+ protected $charset;
22
+
23
+ public function __construct($content, $charset)
24
+ {
25
+ $this->content = (string) $content;
26
+ $this->charset = $charset;
27
+ }
28
+
29
+ public function __toString()
30
+ {
31
+ return $this->content;
32
+ }
33
+
34
+ public function count()
35
+ {
36
+ return function_exists('mb_get_info') ? mb_strlen($this->content, $this->charset) : strlen($this->content);
37
+ }
38
+ }
Twig/lib/Twig/Node.php ADDED
@@ -0,0 +1,227 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node implements Twig_NodeInterface
20
+ {
21
+ protected $nodes;
22
+ protected $attributes;
23
+ protected $lineno;
24
+ protected $tag;
25
+
26
+ /**
27
+ * Constructor.
28
+ *
29
+ * The nodes are automatically made available as properties ($this->node).
30
+ * The attributes are automatically made available as array items ($this['name']).
31
+ *
32
+ * @param array $nodes An array of named nodes
33
+ * @param array $attributes An array of attributes (should not be nodes)
34
+ * @param integer $lineno The line number
35
+ * @param string $tag The tag name associated with the Node
36
+ */
37
+ public function __construct(array $nodes = array(), array $attributes = array(), $lineno = 0, $tag = null)
38
+ {
39
+ $this->nodes = $nodes;
40
+ $this->attributes = $attributes;
41
+ $this->lineno = $lineno;
42
+ $this->tag = $tag;
43
+ }
44
+
45
+ public function __toString()
46
+ {
47
+ $attributes = array();
48
+ foreach ($this->attributes as $name => $value) {
49
+ $attributes[] = sprintf('%s: %s', $name, str_replace("\n", '', var_export($value, true)));
50
+ }
51
+
52
+ $repr = array(get_class($this).'('.implode(', ', $attributes));
53
+
54
+ if (count($this->nodes)) {
55
+ foreach ($this->nodes as $name => $node) {
56
+ $len = strlen($name) + 4;
57
+ $noderepr = array();
58
+ foreach (explode("\n", (string) $node) as $line) {
59
+ $noderepr[] = str_repeat(' ', $len).$line;
60
+ }
61
+
62
+ $repr[] = sprintf(' %s: %s', $name, ltrim(implode("\n", $noderepr)));
63
+ }
64
+
65
+ $repr[] = ')';
66
+ } else {
67
+ $repr[0] .= ')';
68
+ }
69
+
70
+ return implode("\n", $repr);
71
+ }
72
+
73
+ public function toXml($asDom = false)
74
+ {
75
+ $dom = new DOMDocument('1.0', 'UTF-8');
76
+ $dom->formatOutput = true;
77
+ $dom->appendChild($xml = $dom->createElement('twig'));
78
+
79
+ $xml->appendChild($node = $dom->createElement('node'));
80
+ $node->setAttribute('class', get_class($this));
81
+
82
+ foreach ($this->attributes as $name => $value) {
83
+ $node->appendChild($attribute = $dom->createElement('attribute'));
84
+ $attribute->setAttribute('name', $name);
85
+ $attribute->appendChild($dom->createTextNode($value));
86
+ }
87
+
88
+ foreach ($this->nodes as $name => $n) {
89
+ if (null === $n) {
90
+ continue;
91
+ }
92
+
93
+ $child = $n->toXml(true)->getElementsByTagName('node')->item(0);
94
+ $child = $dom->importNode($child, true);
95
+ $child->setAttribute('name', $name);
96
+
97
+ $node->appendChild($child);
98
+ }
99
+
100
+ return $asDom ? $dom : $dom->saveXml();
101
+ }
102
+
103
+ public function compile(Twig_Compiler $compiler)
104
+ {
105
+ foreach ($this->nodes as $node) {
106
+ $node->compile($compiler);
107
+ }
108
+ }
109
+
110
+ public function getLine()
111
+ {
112
+ return $this->lineno;
113
+ }
114
+
115
+ public function getNodeTag()
116
+ {
117
+ return $this->tag;
118
+ }
119
+
120
+ /**
121
+ * Returns true if the attribute is defined.
122
+ *
123
+ * @param string The attribute name
124
+ *
125
+ * @return Boolean true if the attribute is defined, false otherwise
126
+ */
127
+ public function hasAttribute($name)
128
+ {
129
+ return array_key_exists($name, $this->attributes);
130
+ }
131
+
132
+ /**
133
+ * Gets an attribute.
134
+ *
135
+ * @param string The attribute name
136
+ *
137
+ * @return mixed The attribute value
138
+ */
139
+ public function getAttribute($name)
140
+ {
141
+ if (!array_key_exists($name, $this->attributes)) {
142
+ throw new LogicException(sprintf('Attribute "%s" does not exist for Node "%s".', $name, get_class($this)));
143
+ }
144
+
145
+ return $this->attributes[$name];
146
+ }
147
+
148
+ /**
149
+ * Sets an attribute.
150
+ *
151
+ * @param string The attribute name
152
+ * @param mixed The attribute value
153
+ */
154
+ public function setAttribute($name, $value)
155
+ {
156
+ $this->attributes[$name] = $value;
157
+ }
158
+
159
+ /**
160
+ * Removes an attribute.
161
+ *
162
+ * @param string The attribute name
163
+ */
164
+ public function removeAttribute($name)
165
+ {
166
+ unset($this->attributes[$name]);
167
+ }
168
+
169
+ /**
170
+ * Returns true if the node with the given identifier exists.
171
+ *
172
+ * @param string The node name
173
+ *
174
+ * @return Boolean true if the node with the given name exists, false otherwise
175
+ */
176
+ public function hasNode($name)
177
+ {
178
+ return array_key_exists($name, $this->nodes);
179
+ }
180
+
181
+ /**
182
+ * Gets a node by name.
183
+ *
184
+ * @param string The node name
185
+ *
186
+ * @return Twig_Node A Twig_Node instance
187
+ */
188
+ public function getNode($name)
189
+ {
190
+ if (!array_key_exists($name, $this->nodes)) {
191
+ throw new LogicException(sprintf('Node "%s" does not exist for Node "%s".', $name, get_class($this)));
192
+ }
193
+
194
+ return $this->nodes[$name];
195
+ }
196
+
197
+ /**
198
+ * Sets a node.
199
+ *
200
+ * @param string The node name
201
+ * @param Twig_Node A Twig_Node instance
202
+ */
203
+ public function setNode($name, $node = null)
204
+ {
205
+ $this->nodes[$name] = $node;
206
+ }
207
+
208
+ /**
209
+ * Removes a node by name.
210
+ *
211
+ * @param string The node name
212
+ */
213
+ public function removeNode($name)
214
+ {
215
+ unset($this->nodes[$name]);
216
+ }
217
+
218
+ public function count()
219
+ {
220
+ return count($this->nodes);
221
+ }
222
+
223
+ public function getIterator()
224
+ {
225
+ return new ArrayIterator($this->nodes);
226
+ }
227
+ }
Twig/lib/Twig/Node/AutoEscape.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
22
+ * @author Fabien Potencier <fabien@symfony.com>
23
+ */
24
+ class Twig_Node_AutoEscape extends Twig_Node
25
+ {
26
+ public function __construct($value, Twig_NodeInterface $body, $lineno, $tag = 'autoescape')
27
+ {
28
+ parent::__construct(array('body' => $body), array('value' => $value), $lineno, $tag);
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
+ $compiler->subcompile($this->getNode('body'));
39
+ }
40
+ }
Twig/lib/Twig/Node/Block.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_Block extends Twig_Node
20
+ {
21
+ public function __construct($name, Twig_NodeInterface $body, $lineno, $tag = null)
22
+ {
23
+ parent::__construct(array('body' => $body), array('name' => $name), $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(sprintf("public function block_%s(\$context, array \$blocks = array())\n", $this->getAttribute('name')), "{\n")
36
+ ->indent()
37
+ ;
38
+
39
+ $compiler
40
+ ->subcompile($this->getNode('body'))
41
+ ->outdent()
42
+ ->write("}\n\n")
43
+ ;
44
+ }
45
+ }
Twig/lib/Twig/Node/BlockReference.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_BlockReference extends Twig_Node implements Twig_NodeOutputInterface
20
+ {
21
+ public function __construct($name, $lineno, $tag = null)
22
+ {
23
+ parent::__construct(array(), array('name' => $name), $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(sprintf("\$this->displayBlock('%s', \$context, \$blocks);\n", $this->getAttribute('name')))
36
+ ;
37
+ }
38
+ }
Twig/lib/Twig/Node/Body.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Body extends Twig_Node
19
+ {
20
+ }
Twig/lib/Twig/Node/Do.php ADDED
@@ -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 do node.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Do extends Twig_Node
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('')
35
+ ->subcompile($this->getNode('expr'))
36
+ ->raw(";\n")
37
+ ;
38
+ }
39
+ }
Twig/lib/Twig/Node/Embed.php ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Embed extends Twig_Node_Include
19
+ {
20
+ // we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)
21
+ public function __construct($filename, $index, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
22
+ {
23
+ parent::__construct(new Twig_Node_Expression_Constant('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
24
+
25
+ $this->setAttribute('filename', $filename);
26
+ $this->setAttribute('index', $index);
27
+ }
28
+
29
+ protected function addGetTemplate(Twig_Compiler $compiler)
30
+ {
31
+ $compiler
32
+ ->write("\$this->env->loadTemplate(")
33
+ ->string($this->getAttribute('filename'))
34
+ ->raw(', ')
35
+ ->string($this->getAttribute('index'))
36
+ ->raw(")")
37
+ ;
38
+ }
39
+ }
Twig/lib/Twig/Node/Expression.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ abstract class Twig_Node_Expression extends Twig_Node
20
+ {
21
+ }
Twig/lib/Twig/Node/Expression/Array.php ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Array extends Twig_Node_Expression
12
+ {
13
+ protected $index;
14
+
15
+ public function __construct(array $elements, $lineno)
16
+ {
17
+ parent::__construct($elements, array(), $lineno);
18
+
19
+ $this->index = -1;
20
+ foreach ($this->getKeyValuePairs() as $pair) {
21
+ if ($pair['key'] instanceof Twig_Node_Expression_Constant && ctype_digit((string) $pair['key']->getAttribute('value')) && $pair['key']->getAttribute('value') > $this->index) {
22
+ $this->index = $pair['key']->getAttribute('value');
23
+ }
24
+ }
25
+ }
26
+
27
+ public function getKeyValuePairs()
28
+ {
29
+ $pairs = array();
30
+
31
+ foreach (array_chunk($this->nodes, 2) as $pair) {
32
+ $pairs[] = array(
33
+ 'key' => $pair[0],
34
+ 'value' => $pair[1],
35
+ );
36
+ }
37
+
38
+ return $pairs;
39
+ }
40
+
41
+ public function hasElement(Twig_Node_Expression $key)
42
+ {
43
+ foreach ($this->getKeyValuePairs() as $pair) {
44
+ // we compare the string representation of the keys
45
+ // to avoid comparing the line numbers which are not relevant here.
46
+ if ((string) $key == (string) $pair['key']) {
47
+ return true;
48
+ }
49
+ }
50
+
51
+ return false;
52
+ }
53
+
54
+ public function addElement(Twig_Node_Expression $value, Twig_Node_Expression $key = null)
55
+ {
56
+ if (null === $key) {
57
+ $key = new Twig_Node_Expression_Constant(++$this->index, $value->getLine());
58
+ }
59
+
60
+ array_push($this->nodes, $key, $value);
61
+ }
62
+
63
+ /**
64
+ * Compiles the node to PHP.
65
+ *
66
+ * @param Twig_Compiler A Twig_Compiler instance
67
+ */
68
+ public function compile(Twig_Compiler $compiler)
69
+ {
70
+ $compiler->raw('array(');
71
+ $first = true;
72
+ foreach ($this->getKeyValuePairs() as $pair) {
73
+ if (!$first) {
74
+ $compiler->raw(', ');
75
+ }
76
+ $first = false;
77
+
78
+ $compiler
79
+ ->subcompile($pair['key'])
80
+ ->raw(' => ')
81
+ ->subcompile($pair['value'])
82
+ ;
83
+ }
84
+ $compiler->raw(')');
85
+ }
86
+ }
Twig/lib/Twig/Node/Expression/AssignName.php ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ class Twig_Node_Expression_AssignName extends Twig_Node_Expression_Name
14
+ {
15
+ /**
16
+ * Compiles the node to PHP.
17
+ *
18
+ * @param Twig_Compiler A Twig_Compiler instance
19
+ */
20
+ public function compile(Twig_Compiler $compiler)
21
+ {
22
+ $compiler
23
+ ->raw('$context[')
24
+ ->string($this->getAttribute('name'))
25
+ ->raw(']')
26
+ ;
27
+ }
28
+ }
Twig/lib/Twig/Node/Expression/Binary.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ abstract class Twig_Node_Expression_Binary extends Twig_Node_Expression
13
+ {
14
+ public function __construct(Twig_NodeInterface $left, Twig_NodeInterface $right, $lineno)
15
+ {
16
+ parent::__construct(array('left' => $left, 'right' => $right), array(), $lineno);
17
+ }
18
+
19
+ /**
20
+ * Compiles the node to PHP.
21
+ *
22
+ * @param Twig_Compiler A Twig_Compiler instance
23
+ */
24
+ public function compile(Twig_Compiler $compiler)
25
+ {
26
+ $compiler
27
+ ->raw('(')
28
+ ->subcompile($this->getNode('left'))
29
+ ->raw(' ')
30
+ ;
31
+ $this->operator($compiler);
32
+ $compiler
33
+ ->raw(' ')
34
+ ->subcompile($this->getNode('right'))
35
+ ->raw(')')
36
+ ;
37
+ }
38
+
39
+ abstract public function operator(Twig_Compiler $compiler);
40
+ }
Twig/lib/Twig/Node/Expression/Binary/Add.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_Add extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('+');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/And.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_And extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('&&');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/BitwiseAnd.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_BitwiseAnd extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('&');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/BitwiseOr.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_BitwiseOr extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('|');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/BitwiseXor.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_BitwiseXor extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('^');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/Concat.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_Concat extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('.');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/Div.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_Div extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('/');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/Equal.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_Equal extends Twig_Node_Expression_Binary
12
+ {
13
+ public function operator(Twig_Compiler $compiler)
14
+ {
15
+ return $compiler->raw('==');
16
+ }
17
+ }
Twig/lib/Twig/Node/Expression/Binary/FloorDiv.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_FloorDiv extends Twig_Node_Expression_Binary
12
+ {
13
+ /**
14
+ * Compiles the node to PHP.
15
+ *
16
+ * @param Twig_Compiler A Twig_Compiler instance
17
+ */
18
+ public function compile(Twig_Compiler $compiler)
19
+ {
20
+ $compiler->raw('intval(floor(');
21
+ parent::compile($compiler);
22
+ $compiler->raw('))');
23
+ }
24
+
25
+ public function operator(Twig_Compiler $compiler)
26
+ {
27
+ return $compiler->raw('/');
28
+ }
29
+ }
Twig/lib/Twig/Node/Expression/Binary/Greater.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_Greater extends Twig_Node_Expression_Binary
12
+ {
13
+ public function operator(Twig_Compiler $compiler)
14
+ {
15
+ return $compiler->raw('>');
16
+ }
17
+ }
Twig/lib/Twig/Node/Expression/Binary/GreaterEqual.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_GreaterEqual extends Twig_Node_Expression_Binary
12
+ {
13
+ public function operator(Twig_Compiler $compiler)
14
+ {
15
+ return $compiler->raw('>=');
16
+ }
17
+ }
Twig/lib/Twig/Node/Expression/Binary/In.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_In extends Twig_Node_Expression_Binary
12
+ {
13
+ /**
14
+ * Compiles the node to PHP.
15
+ *
16
+ * @param Twig_Compiler A Twig_Compiler instance
17
+ */
18
+ public function compile(Twig_Compiler $compiler)
19
+ {
20
+ $compiler
21
+ ->raw('twig_in_filter(')
22
+ ->subcompile($this->getNode('left'))
23
+ ->raw(', ')
24
+ ->subcompile($this->getNode('right'))
25
+ ->raw(')')
26
+ ;
27
+ }
28
+
29
+ public function operator(Twig_Compiler $compiler)
30
+ {
31
+ return $compiler->raw('in');
32
+ }
33
+ }
Twig/lib/Twig/Node/Expression/Binary/Less.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_Less extends Twig_Node_Expression_Binary
12
+ {
13
+ public function operator(Twig_Compiler $compiler)
14
+ {
15
+ return $compiler->raw('<');
16
+ }
17
+ }
Twig/lib/Twig/Node/Expression/Binary/LessEqual.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_LessEqual extends Twig_Node_Expression_Binary
12
+ {
13
+ public function operator(Twig_Compiler $compiler)
14
+ {
15
+ return $compiler->raw('<=');
16
+ }
17
+ }
Twig/lib/Twig/Node/Expression/Binary/Mod.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_Mod extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('%');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/Mul.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_Mul extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('*');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/NotEqual.php ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_NotEqual extends Twig_Node_Expression_Binary
12
+ {
13
+ public function operator(Twig_Compiler $compiler)
14
+ {
15
+ return $compiler->raw('!=');
16
+ }
17
+ }
Twig/lib/Twig/Node/Expression/Binary/NotIn.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_NotIn extends Twig_Node_Expression_Binary
12
+ {
13
+ /**
14
+ * Compiles the node to PHP.
15
+ *
16
+ * @param Twig_Compiler A Twig_Compiler instance
17
+ */
18
+ public function compile(Twig_Compiler $compiler)
19
+ {
20
+ $compiler
21
+ ->raw('!twig_in_filter(')
22
+ ->subcompile($this->getNode('left'))
23
+ ->raw(', ')
24
+ ->subcompile($this->getNode('right'))
25
+ ->raw(')')
26
+ ;
27
+ }
28
+
29
+ public function operator(Twig_Compiler $compiler)
30
+ {
31
+ return $compiler->raw('not in');
32
+ }
33
+ }
Twig/lib/Twig/Node/Expression/Binary/Or.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_Or extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('||');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Binary/Power.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_Power extends Twig_Node_Expression_Binary
12
+ {
13
+ /**
14
+ * Compiles the node to PHP.
15
+ *
16
+ * @param Twig_Compiler A Twig_Compiler instance
17
+ */
18
+ public function compile(Twig_Compiler $compiler)
19
+ {
20
+ $compiler
21
+ ->raw('pow(')
22
+ ->subcompile($this->getNode('left'))
23
+ ->raw(', ')
24
+ ->subcompile($this->getNode('right'))
25
+ ->raw(')')
26
+ ;
27
+ }
28
+
29
+ public function operator(Twig_Compiler $compiler)
30
+ {
31
+ return $compiler->raw('**');
32
+ }
33
+ }
Twig/lib/Twig/Node/Expression/Binary/Range.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Binary_Range extends Twig_Node_Expression_Binary
12
+ {
13
+ /**
14
+ * Compiles the node to PHP.
15
+ *
16
+ * @param Twig_Compiler A Twig_Compiler instance
17
+ */
18
+ public function compile(Twig_Compiler $compiler)
19
+ {
20
+ $compiler
21
+ ->raw('range(')
22
+ ->subcompile($this->getNode('left'))
23
+ ->raw(', ')
24
+ ->subcompile($this->getNode('right'))
25
+ ->raw(')')
26
+ ;
27
+ }
28
+
29
+ public function operator(Twig_Compiler $compiler)
30
+ {
31
+ return $compiler->raw('..');
32
+ }
33
+ }
Twig/lib/Twig/Node/Expression/Binary/Sub.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Binary_Sub extends Twig_Node_Expression_Binary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ return $compiler->raw('-');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/BlockReference.php ADDED
@@ -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
+ * Represents a block call node.
15
+ *
16
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_Expression_BlockReference extends Twig_Node_Expression
20
+ {
21
+ public function __construct(Twig_NodeInterface $name, $asString = false, $lineno, $tag = null)
22
+ {
23
+ parent::__construct(array('name' => $name), array('as_string' => $asString, 'output' => false), $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
+ if ($this->getAttribute('as_string')) {
34
+ $compiler->raw('(string) ');
35
+ }
36
+
37
+ if ($this->getAttribute('output')) {
38
+ $compiler
39
+ ->addDebugInfo($this)
40
+ ->write("\$this->displayBlock(")
41
+ ->subcompile($this->getNode('name'))
42
+ ->raw(", \$context, \$blocks);\n")
43
+ ;
44
+ } else {
45
+ $compiler
46
+ ->raw("\$this->renderBlock(")
47
+ ->subcompile($this->getNode('name'))
48
+ ->raw(", \$context, \$blocks)")
49
+ ;
50
+ }
51
+ }
52
+ }
Twig/lib/Twig/Node/Expression/Call.php ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2012 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ abstract class Twig_Node_Expression_Call extends Twig_Node_Expression
12
+ {
13
+ protected function compileCallable(Twig_Compiler $compiler)
14
+ {
15
+ $callable = $this->getAttribute('callable');
16
+
17
+ $closingParenthesis = false;
18
+ if ($callable) {
19
+ if (is_string($callable)) {
20
+ $compiler->raw($callable);
21
+ } elseif (is_array($callable) && $callable[0] instanceof Twig_ExtensionInterface) {
22
+ $compiler->raw(sprintf('$this->env->getExtension(\'%s\')->%s', $callable[0]->getName(), $callable[1]));
23
+ } else {
24
+ $type = ucfirst($this->getAttribute('type'));
25
+ $compiler->raw(sprintf('call_user_func_array($this->env->get%s(\'%s\')->getCallable(), array', $type, $this->getAttribute('name')));
26
+ $closingParenthesis = true;
27
+ }
28
+ } else {
29
+ $compiler->raw($this->getAttribute('thing')->compile());
30
+ }
31
+
32
+ $this->compileArguments($compiler);
33
+
34
+ if ($closingParenthesis) {
35
+ $compiler->raw(')');
36
+ }
37
+ }
38
+
39
+ protected function compileArguments(Twig_Compiler $compiler)
40
+ {
41
+ $compiler->raw('(');
42
+
43
+ $first = true;
44
+
45
+ if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
46
+ $compiler->raw('$this->env');
47
+ $first = false;
48
+ }
49
+
50
+ if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
51
+ if (!$first) {
52
+ $compiler->raw(', ');
53
+ }
54
+ $compiler->raw('$context');
55
+ $first = false;
56
+ }
57
+
58
+ if ($this->hasAttribute('arguments')) {
59
+ foreach ($this->getAttribute('arguments') as $argument) {
60
+ if (!$first) {
61
+ $compiler->raw(', ');
62
+ }
63
+ $compiler->string($argument);
64
+ $first = false;
65
+ }
66
+ }
67
+
68
+ if ($this->hasNode('node')) {
69
+ if (!$first) {
70
+ $compiler->raw(', ');
71
+ }
72
+ $compiler->subcompile($this->getNode('node'));
73
+ $first = false;
74
+ }
75
+
76
+ if ($this->hasNode('arguments') && null !== $this->getNode('arguments')) {
77
+ $callable = $this->hasAttribute('callable') ? $this->getAttribute('callable') : null;
78
+
79
+ $arguments = $this->getArguments($callable, $this->getNode('arguments'));
80
+
81
+ foreach ($arguments as $node) {
82
+ if (!$first) {
83
+ $compiler->raw(', ');
84
+ }
85
+ $compiler->subcompile($node);
86
+ $first = false;
87
+ }
88
+ }
89
+
90
+ $compiler->raw(')');
91
+ }
92
+
93
+ protected function getArguments($callable, $arguments)
94
+ {
95
+ $parameters = array();
96
+ $named = false;
97
+ foreach ($arguments as $name => $node) {
98
+ if (!is_int($name)) {
99
+ $named = true;
100
+ $name = $this->normalizeName($name);
101
+ }
102
+ $parameters[$name] = $node;
103
+ }
104
+
105
+ if (!$named) {
106
+ return $parameters;
107
+ }
108
+
109
+ if (!$callable) {
110
+ throw new LogicException(sprintf('Named arguments are not supported for %s "%s".', $this->getAttribute('type'), $this->getAttribute('name')));
111
+ }
112
+
113
+ // manage named arguments
114
+ if (is_array($callable)) {
115
+ $r = new ReflectionMethod($callable[0], $callable[1]);
116
+ } elseif (is_object($callable) && !$callable instanceof Closure) {
117
+ $r = new ReflectionObject($callable);
118
+ $r = $r->getMethod('__invoke');
119
+ } else {
120
+ $r = new ReflectionFunction($callable);
121
+ }
122
+
123
+ $definition = $r->getParameters();
124
+ if ($this->hasNode('node')) {
125
+ array_shift($definition);
126
+ }
127
+ if ($this->hasAttribute('needs_environment') && $this->getAttribute('needs_environment')) {
128
+ array_shift($definition);
129
+ }
130
+ if ($this->hasAttribute('needs_context') && $this->getAttribute('needs_context')) {
131
+ array_shift($definition);
132
+ }
133
+ if ($this->hasAttribute('arguments') && null !== $this->getAttribute('arguments')) {
134
+ foreach ($this->getAttribute('arguments') as $argument) {
135
+ array_shift($definition);
136
+ }
137
+ }
138
+
139
+ $arguments = array();
140
+ $pos = 0;
141
+ foreach ($definition as $param) {
142
+ $name = $this->normalizeName($param->name);
143
+
144
+ if (array_key_exists($name, $parameters)) {
145
+ $arguments[] = $parameters[$name];
146
+ unset($parameters[$name]);
147
+ } elseif (array_key_exists($pos, $parameters)) {
148
+ $arguments[] = $parameters[$pos];
149
+ unset($parameters[$pos]);
150
+ ++$pos;
151
+ } elseif ($param->isDefaultValueAvailable()) {
152
+ $arguments[] = new Twig_Node_Expression_Constant($param->getDefaultValue(), -1);
153
+ } elseif ($param->isOptional()) {
154
+ break;
155
+ } else {
156
+ throw new Twig_Error_Syntax(sprintf('Value for argument "%s" is required for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
157
+ }
158
+ }
159
+
160
+ foreach (array_keys($parameters) as $name) {
161
+ throw new Twig_Error_Syntax(sprintf('Unknown argument "%s" for %s "%s".', $name, $this->getAttribute('type'), $this->getAttribute('name')));
162
+ }
163
+
164
+ return $arguments;
165
+ }
166
+
167
+ protected function normalizeName($name)
168
+ {
169
+ return strtolower(preg_replace(array('/([A-Z]+)([A-Z][a-z])/', '/([a-z\d])([A-Z])/'), array('\\1_\\2', '\\1_\\2'), $name));
170
+ }
171
+ }
Twig/lib/Twig/Node/Expression/Conditional.php ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Conditional extends Twig_Node_Expression
13
+ {
14
+ public function __construct(Twig_Node_Expression $expr1, Twig_Node_Expression $expr2, Twig_Node_Expression $expr3, $lineno)
15
+ {
16
+ parent::__construct(array('expr1' => $expr1, 'expr2' => $expr2, 'expr3' => $expr3), array(), $lineno);
17
+ }
18
+
19
+ public function compile(Twig_Compiler $compiler)
20
+ {
21
+ $compiler
22
+ ->raw('((')
23
+ ->subcompile($this->getNode('expr1'))
24
+ ->raw(') ? (')
25
+ ->subcompile($this->getNode('expr2'))
26
+ ->raw(') : (')
27
+ ->subcompile($this->getNode('expr3'))
28
+ ->raw('))')
29
+ ;
30
+ }
31
+ }
Twig/lib/Twig/Node/Expression/Constant.php ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Constant extends Twig_Node_Expression
13
+ {
14
+ public function __construct($value, $lineno)
15
+ {
16
+ parent::__construct(array(), array('value' => $value), $lineno);
17
+ }
18
+
19
+ public function compile(Twig_Compiler $compiler)
20
+ {
21
+ $compiler->repr($this->getAttribute('value'));
22
+ }
23
+ }
Twig/lib/Twig/Node/Expression/ExtensionReference.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Expression_ExtensionReference extends Twig_Node_Expression
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->raw(sprintf("\$this->env->getExtension('%s')", $this->getAttribute('name')));
33
+ }
34
+ }
Twig/lib/Twig/Node/Expression/Filter.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Filter extends Twig_Node_Expression_Call
13
+ {
14
+ public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null)
15
+ {
16
+ parent::__construct(array('node' => $node, 'filter' => $filterName, 'arguments' => $arguments), array(), $lineno, $tag);
17
+ }
18
+
19
+ public function compile(Twig_Compiler $compiler)
20
+ {
21
+ $name = $this->getNode('filter')->getAttribute('value');
22
+ $filter = $compiler->getEnvironment()->getFilter($name);
23
+
24
+ $this->setAttribute('name', $name);
25
+ $this->setAttribute('type', 'filter');
26
+ $this->setAttribute('thing', $filter);
27
+ $this->setAttribute('needs_environment', $filter->needsEnvironment());
28
+ $this->setAttribute('needs_context', $filter->needsContext());
29
+ $this->setAttribute('arguments', $filter->getArguments());
30
+ if ($filter instanceof Twig_FilterCallableInterface || $filter instanceof Twig_SimpleFilter) {
31
+ $this->setAttribute('callable', $filter->getCallable());
32
+ }
33
+
34
+ $this->compileCallable($compiler);
35
+ }
36
+ }
Twig/lib/Twig/Node/Expression/Filter/Default.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
20
+ * @author Fabien Potencier <fabien@symfony.com>
21
+ */
22
+ class Twig_Node_Expression_Filter_Default extends Twig_Node_Expression_Filter
23
+ {
24
+ public function __construct(Twig_NodeInterface $node, Twig_Node_Expression_Constant $filterName, Twig_NodeInterface $arguments, $lineno, $tag = null)
25
+ {
26
+ $default = new Twig_Node_Expression_Filter($node, new Twig_Node_Expression_Constant('default', $node->getLine()), $arguments, $node->getLine());
27
+
28
+ if ('default' === $filterName->getAttribute('value') && ($node instanceof Twig_Node_Expression_Name || $node instanceof Twig_Node_Expression_GetAttr)) {
29
+ $test = new Twig_Node_Expression_Test_Defined(clone $node, 'defined', new Twig_Node(), $node->getLine());
30
+ $false = count($arguments) ? $arguments->getNode(0) : new Twig_Node_Expression_Constant('', $node->getLine());
31
+
32
+ $node = new Twig_Node_Expression_Conditional($test, $default, $false, $node->getLine());
33
+ } else {
34
+ $node = $default;
35
+ }
36
+
37
+ parent::__construct($node, $filterName, $arguments, $lineno, $tag);
38
+ }
39
+
40
+ public function compile(Twig_Compiler $compiler)
41
+ {
42
+ $compiler->subcompile($this->getNode('node'));
43
+ }
44
+ }
Twig/lib/Twig/Node/Expression/Function.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Function extends Twig_Node_Expression_Call
12
+ {
13
+ public function __construct($name, Twig_NodeInterface $arguments, $lineno)
14
+ {
15
+ parent::__construct(array('arguments' => $arguments), array('name' => $name), $lineno);
16
+ }
17
+
18
+ public function compile(Twig_Compiler $compiler)
19
+ {
20
+ $name = $this->getAttribute('name');
21
+ $function = $compiler->getEnvironment()->getFunction($name);
22
+
23
+ $this->setAttribute('name', $name);
24
+ $this->setAttribute('type', 'function');
25
+ $this->setAttribute('thing', $function);
26
+ $this->setAttribute('needs_environment', $function->needsEnvironment());
27
+ $this->setAttribute('needs_context', $function->needsContext());
28
+ $this->setAttribute('arguments', $function->getArguments());
29
+ if ($function instanceof Twig_FunctionCallableInterface || $function instanceof Twig_SimpleFunction) {
30
+ $this->setAttribute('callable', $function->getCallable());
31
+ }
32
+
33
+ $this->compileCallable($compiler);
34
+ }
35
+ }
Twig/lib/Twig/Node/Expression/GetAttr.php ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_GetAttr extends Twig_Node_Expression
13
+ {
14
+ public function __construct(Twig_Node_Expression $node, Twig_Node_Expression $attribute, Twig_Node_Expression_Array $arguments, $type, $lineno)
15
+ {
16
+ parent::__construct(array('node' => $node, 'attribute' => $attribute, 'arguments' => $arguments), array('type' => $type, 'is_defined_test' => false, 'ignore_strict_check' => false, 'disable_c_ext' => false), $lineno);
17
+ }
18
+
19
+ public function compile(Twig_Compiler $compiler)
20
+ {
21
+ if (function_exists('twig_template_get_attributes') && !$this->getAttribute('disable_c_ext')) {
22
+ $compiler->raw('twig_template_get_attributes($this, ');
23
+ } else {
24
+ $compiler->raw('$this->getAttribute(');
25
+ }
26
+
27
+ if ($this->getAttribute('ignore_strict_check')) {
28
+ $this->getNode('node')->setAttribute('ignore_strict_check', true);
29
+ }
30
+
31
+ $compiler->subcompile($this->getNode('node'));
32
+
33
+ $compiler->raw(', ')->subcompile($this->getNode('attribute'));
34
+
35
+ if (count($this->getNode('arguments')) || Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
36
+ $compiler->raw(', ')->subcompile($this->getNode('arguments'));
37
+
38
+ if (Twig_TemplateInterface::ANY_CALL !== $this->getAttribute('type') || $this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
39
+ $compiler->raw(', ')->repr($this->getAttribute('type'));
40
+ }
41
+
42
+ if ($this->getAttribute('is_defined_test') || $this->getAttribute('ignore_strict_check')) {
43
+ $compiler->raw(', '.($this->getAttribute('is_defined_test') ? 'true' : 'false'));
44
+ }
45
+
46
+ if ($this->getAttribute('ignore_strict_check')) {
47
+ $compiler->raw(', '.($this->getAttribute('ignore_strict_check') ? 'true' : 'false'));
48
+ }
49
+ }
50
+
51
+ $compiler->raw(')');
52
+ }
53
+ }
Twig/lib/Twig/Node/Expression/MethodCall.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2012 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_MethodCall extends Twig_Node_Expression
12
+ {
13
+ public function __construct(Twig_Node_Expression $node, $method, Twig_Node_Expression_Array $arguments, $lineno)
14
+ {
15
+ parent::__construct(array('node' => $node, 'arguments' => $arguments), array('method' => $method, 'safe' => false), $lineno);
16
+
17
+ if ($node instanceof Twig_Node_Expression_Name) {
18
+ $node->setAttribute('always_defined', true);
19
+ }
20
+ }
21
+
22
+ public function compile(Twig_Compiler $compiler)
23
+ {
24
+ $compiler
25
+ ->subcompile($this->getNode('node'))
26
+ ->raw('->')
27
+ ->raw($this->getAttribute('method'))
28
+ ->raw('(')
29
+ ;
30
+ $first = true;
31
+ foreach ($this->getNode('arguments')->getKeyValuePairs() as $pair) {
32
+ if (!$first) {
33
+ $compiler->raw(', ');
34
+ }
35
+ $first = false;
36
+
37
+ $compiler->subcompile($pair['value']);
38
+ }
39
+ $compiler->raw(')');
40
+ }
41
+ }
Twig/lib/Twig/Node/Expression/Name.php ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Name extends Twig_Node_Expression
13
+ {
14
+ protected $specialVars = array(
15
+ '_self' => '$this',
16
+ '_context' => '$context',
17
+ '_charset' => '$this->env->getCharset()',
18
+ );
19
+
20
+ public function __construct($name, $lineno)
21
+ {
22
+ parent::__construct(array(), array('name' => $name, 'is_defined_test' => false, 'ignore_strict_check' => false, 'always_defined' => false), $lineno);
23
+ }
24
+
25
+ public function compile(Twig_Compiler $compiler)
26
+ {
27
+ $name = $this->getAttribute('name');
28
+
29
+ if ($this->getAttribute('is_defined_test')) {
30
+ if ($this->isSpecial()) {
31
+ $compiler->repr(true);
32
+ } else {
33
+ $compiler->raw('array_key_exists(')->repr($name)->raw(', $context)');
34
+ }
35
+ } elseif ($this->isSpecial()) {
36
+ $compiler->raw($this->specialVars[$name]);
37
+ } elseif ($this->getAttribute('always_defined')) {
38
+ $compiler
39
+ ->raw('$context[')
40
+ ->string($name)
41
+ ->raw(']')
42
+ ;
43
+ } else {
44
+ // remove the non-PHP 5.4 version when PHP 5.3 support is dropped
45
+ // as the non-optimized version is just a workaround for slow ternary operator
46
+ // when the context has a lot of variables
47
+ if (version_compare(phpversion(), '5.4.0RC1', '>=')) {
48
+ // PHP 5.4 ternary operator performance was optimized
49
+ $compiler
50
+ ->raw('(isset($context[')
51
+ ->string($name)
52
+ ->raw(']) ? $context[')
53
+ ->string($name)
54
+ ->raw('] : ')
55
+ ;
56
+
57
+ if ($this->getAttribute('ignore_strict_check') || !$compiler->getEnvironment()->isStrictVariables()) {
58
+ $compiler->raw('null)');
59
+ } else {
60
+ $compiler->raw('$this->getContext($context, ')->string($name)->raw('))');
61
+ }
62
+ } else {
63
+ $compiler
64
+ ->raw('$this->getContext($context, ')
65
+ ->string($name)
66
+ ;
67
+
68
+ if ($this->getAttribute('ignore_strict_check')) {
69
+ $compiler->raw(', true');
70
+ }
71
+
72
+ $compiler
73
+ ->raw(')')
74
+ ;
75
+ }
76
+ }
77
+ }
78
+
79
+ public function isSpecial()
80
+ {
81
+ return isset($this->specialVars[$this->getAttribute('name')]);
82
+ }
83
+
84
+ public function isSimple()
85
+ {
86
+ return !$this->isSpecial() && !$this->getAttribute('is_defined_test');
87
+ }
88
+ }
Twig/lib/Twig/Node/Expression/Parent.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_Expression_Parent extends Twig_Node_Expression
20
+ {
21
+ public function __construct($name, $lineno, $tag = null)
22
+ {
23
+ parent::__construct(array(), array('output' => false, 'name' => $name), $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
+ if ($this->getAttribute('output')) {
34
+ $compiler
35
+ ->addDebugInfo($this)
36
+ ->write("\$this->displayParentBlock(")
37
+ ->string($this->getAttribute('name'))
38
+ ->raw(", \$context, \$blocks);\n")
39
+ ;
40
+ } else {
41
+ $compiler
42
+ ->raw("\$this->renderParentBlock(")
43
+ ->string($this->getAttribute('name'))
44
+ ->raw(", \$context, \$blocks)")
45
+ ;
46
+ }
47
+ }
48
+ }
Twig/lib/Twig/Node/Expression/TempName.php ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2011 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_TempName extends Twig_Node_Expression
12
+ {
13
+ public function __construct($name, $lineno)
14
+ {
15
+ parent::__construct(array(), array('name' => $name), $lineno);
16
+ }
17
+
18
+ public function compile(Twig_Compiler $compiler)
19
+ {
20
+ $compiler
21
+ ->raw('$_')
22
+ ->raw($this->getAttribute('name'))
23
+ ->raw('_')
24
+ ;
25
+ }
26
+ }
Twig/lib/Twig/Node/Expression/Test.php ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Node_Expression_Test extends Twig_Node_Expression_Call
12
+ {
13
+ public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno)
14
+ {
15
+ parent::__construct(array('node' => $node, 'arguments' => $arguments), array('name' => $name), $lineno);
16
+ }
17
+
18
+ public function compile(Twig_Compiler $compiler)
19
+ {
20
+ $name = $this->getAttribute('name');
21
+ $test = $compiler->getEnvironment()->getTest($name);
22
+
23
+ $this->setAttribute('name', $name);
24
+ $this->setAttribute('type', 'test');
25
+ $this->setAttribute('thing', $test);
26
+ if ($test instanceof Twig_TestCallableInterface || $test instanceof Twig_SimpleTest) {
27
+ $this->setAttribute('callable', $test->getCallable());
28
+ }
29
+
30
+ $this->compileCallable($compiler);
31
+ }
32
+ }
Twig/lib/Twig/Node/Expression/Test/Constant.php ADDED
@@ -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
+ * 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
+ * @package twig
22
+ * @author Fabien Potencier <fabien@symfony.com>
23
+ */
24
+ class Twig_Node_Expression_Test_Constant extends Twig_Node_Expression_Test
25
+ {
26
+ public function compile(Twig_Compiler $compiler)
27
+ {
28
+ $compiler
29
+ ->raw('(')
30
+ ->subcompile($this->getNode('node'))
31
+ ->raw(' === constant(')
32
+ ->subcompile($this->getNode('arguments')->getNode(0))
33
+ ->raw('))')
34
+ ;
35
+ }
36
+ }
Twig/lib/Twig/Node/Expression/Test/Defined.php ADDED
@@ -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
+ * 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
+ * @package twig
23
+ * @author Fabien Potencier <fabien@symfony.com>
24
+ */
25
+ class Twig_Node_Expression_Test_Defined extends Twig_Node_Expression_Test
26
+ {
27
+ public function __construct(Twig_NodeInterface $node, $name, Twig_NodeInterface $arguments = null, $lineno)
28
+ {
29
+ parent::__construct($node, $name, $arguments, $lineno);
30
+
31
+ if ($node instanceof Twig_Node_Expression_Name) {
32
+ $node->setAttribute('is_defined_test', true);
33
+ } elseif ($node instanceof Twig_Node_Expression_GetAttr) {
34
+ $node->setAttribute('is_defined_test', true);
35
+
36
+ $this->changeIgnoreStrictCheck($node);
37
+ } else {
38
+ throw new Twig_Error_Syntax('The "defined" test only works with simple variables', $this->getLine(), $compiler->getFilename());
39
+ }
40
+ }
41
+
42
+ protected function changeIgnoreStrictCheck(Twig_Node_Expression_GetAttr $node)
43
+ {
44
+ $node->setAttribute('ignore_strict_check', true);
45
+
46
+ if ($node->getNode('node') instanceof Twig_Node_Expression_GetAttr) {
47
+ $this->changeIgnoreStrictCheck($node->getNode('node'));
48
+ }
49
+ }
50
+
51
+ public function compile(Twig_Compiler $compiler)
52
+ {
53
+ $compiler->subcompile($this->getNode('node'));
54
+ }
55
+ }
Twig/lib/Twig/Node/Expression/Test/Divisibleby.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
20
+ * @author Fabien Potencier <fabien@symfony.com>
21
+ */
22
+ class Twig_Node_Expression_Test_Divisibleby extends Twig_Node_Expression_Test
23
+ {
24
+ public function compile(Twig_Compiler $compiler)
25
+ {
26
+ $compiler
27
+ ->raw('(0 == ')
28
+ ->subcompile($this->getNode('node'))
29
+ ->raw(' % ')
30
+ ->subcompile($this->getNode('arguments')->getNode(0))
31
+ ->raw(')')
32
+ ;
33
+ }
34
+ }
Twig/lib/Twig/Node/Expression/Test/Even.php ADDED
@@ -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 number is even.
14
+ *
15
+ * <pre>
16
+ * {{ var is even }}
17
+ * </pre>
18
+ *
19
+ * @package twig
20
+ * @author Fabien Potencier <fabien@symfony.com>
21
+ */
22
+ class Twig_Node_Expression_Test_Even extends Twig_Node_Expression_Test
23
+ {
24
+ public function compile(Twig_Compiler $compiler)
25
+ {
26
+ $compiler
27
+ ->raw('(')
28
+ ->subcompile($this->getNode('node'))
29
+ ->raw(' % 2 == 0')
30
+ ->raw(')')
31
+ ;
32
+ }
33
+ }
Twig/lib/Twig/Node/Expression/Test/Null.php ADDED
@@ -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 that a variable is null.
14
+ *
15
+ * <pre>
16
+ * {{ var is none }}
17
+ * </pre>
18
+ *
19
+ * @package twig
20
+ * @author Fabien Potencier <fabien@symfony.com>
21
+ */
22
+ class Twig_Node_Expression_Test_Null extends Twig_Node_Expression_Test
23
+ {
24
+ public function compile(Twig_Compiler $compiler)
25
+ {
26
+ $compiler
27
+ ->raw('(null === ')
28
+ ->subcompile($this->getNode('node'))
29
+ ->raw(')')
30
+ ;
31
+ }
32
+ }
Twig/lib/Twig/Node/Expression/Test/Odd.php ADDED
@@ -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 number is odd.
14
+ *
15
+ * <pre>
16
+ * {{ var is odd }}
17
+ * </pre>
18
+ *
19
+ * @package twig
20
+ * @author Fabien Potencier <fabien@symfony.com>
21
+ */
22
+ class Twig_Node_Expression_Test_Odd extends Twig_Node_Expression_Test
23
+ {
24
+ public function compile(Twig_Compiler $compiler)
25
+ {
26
+ $compiler
27
+ ->raw('(')
28
+ ->subcompile($this->getNode('node'))
29
+ ->raw(' % 2 == 1')
30
+ ->raw(')')
31
+ ;
32
+ }
33
+ }
Twig/lib/Twig/Node/Expression/Test/Sameas.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Expression_Test_Sameas extends Twig_Node_Expression_Test
19
+ {
20
+ public function compile(Twig_Compiler $compiler)
21
+ {
22
+ $compiler
23
+ ->raw('(')
24
+ ->subcompile($this->getNode('node'))
25
+ ->raw(' === ')
26
+ ->subcompile($this->getNode('arguments')->getNode(0))
27
+ ->raw(')')
28
+ ;
29
+ }
30
+ }
Twig/lib/Twig/Node/Expression/Unary.php ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ abstract class Twig_Node_Expression_Unary extends Twig_Node_Expression
13
+ {
14
+ public function __construct(Twig_NodeInterface $node, $lineno)
15
+ {
16
+ parent::__construct(array('node' => $node), array(), $lineno);
17
+ }
18
+
19
+ public function compile(Twig_Compiler $compiler)
20
+ {
21
+ $compiler->raw('(');
22
+ $this->operator($compiler);
23
+ $compiler
24
+ ->subcompile($this->getNode('node'))
25
+ ->raw(')')
26
+ ;
27
+ }
28
+
29
+ abstract public function operator(Twig_Compiler $compiler);
30
+ }
Twig/lib/Twig/Node/Expression/Unary/Neg.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Unary_Neg extends Twig_Node_Expression_Unary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ $compiler->raw('-');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Unary/Not.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Unary_Not extends Twig_Node_Expression_Unary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ $compiler->raw('!');
17
+ }
18
+ }
Twig/lib/Twig/Node/Expression/Unary/Pos.php ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+ class Twig_Node_Expression_Unary_Pos extends Twig_Node_Expression_Unary
13
+ {
14
+ public function operator(Twig_Compiler $compiler)
15
+ {
16
+ $compiler->raw('+');
17
+ }
18
+ }
Twig/lib/Twig/Node/Flush.php ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Flush extends Twig_Node
19
+ {
20
+ public function __construct($lineno, $tag)
21
+ {
22
+ parent::__construct(array(), 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("flush();\n")
35
+ ;
36
+ }
37
+ }
Twig/lib/Twig/Node/For.php ADDED
@@ -0,0 +1,113 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_For extends Twig_Node
20
+ {
21
+ protected $loop;
22
+
23
+ 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)
24
+ {
25
+ $body = new Twig_Node(array($body, $this->loop = new Twig_Node_ForLoop($lineno, $tag)));
26
+
27
+ if (null !== $ifexpr) {
28
+ $body = new Twig_Node_If(new Twig_Node(array($ifexpr, $body)), null, $lineno, $tag);
29
+ }
30
+
31
+ parent::__construct(array('key_target' => $keyTarget, 'value_target' => $valueTarget, 'seq' => $seq, 'body' => $body, 'else' => $else), array('with_loop' => true, 'ifexpr' => null !== $ifexpr), $lineno, $tag);
32
+ }
33
+
34
+ /**
35
+ * Compiles the node to PHP.
36
+ *
37
+ * @param Twig_Compiler A Twig_Compiler instance
38
+ */
39
+ public function compile(Twig_Compiler $compiler)
40
+ {
41
+ $compiler
42
+ ->addDebugInfo($this)
43
+ // the (array) cast bypasses a PHP 5.2.6 bug
44
+ ->write("\$context['_parent'] = (array) \$context;\n")
45
+ ->write("\$context['_seq'] = twig_ensure_traversable(")
46
+ ->subcompile($this->getNode('seq'))
47
+ ->raw(");\n")
48
+ ;
49
+
50
+ if (null !== $this->getNode('else')) {
51
+ $compiler->write("\$context['_iterated'] = false;\n");
52
+ }
53
+
54
+ if ($this->getAttribute('with_loop')) {
55
+ $compiler
56
+ ->write("\$context['loop'] = array(\n")
57
+ ->write(" 'parent' => \$context['_parent'],\n")
58
+ ->write(" 'index0' => 0,\n")
59
+ ->write(" 'index' => 1,\n")
60
+ ->write(" 'first' => true,\n")
61
+ ->write(");\n")
62
+ ;
63
+
64
+ if (!$this->getAttribute('ifexpr')) {
65
+ $compiler
66
+ ->write("if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {\n")
67
+ ->indent()
68
+ ->write("\$length = count(\$context['_seq']);\n")
69
+ ->write("\$context['loop']['revindex0'] = \$length - 1;\n")
70
+ ->write("\$context['loop']['revindex'] = \$length;\n")
71
+ ->write("\$context['loop']['length'] = \$length;\n")
72
+ ->write("\$context['loop']['last'] = 1 === \$length;\n")
73
+ ->outdent()
74
+ ->write("}\n")
75
+ ;
76
+ }
77
+ }
78
+
79
+ $this->loop->setAttribute('else', null !== $this->getNode('else'));
80
+ $this->loop->setAttribute('with_loop', $this->getAttribute('with_loop'));
81
+ $this->loop->setAttribute('ifexpr', $this->getAttribute('ifexpr'));
82
+
83
+ $compiler
84
+ ->write("foreach (\$context['_seq'] as ")
85
+ ->subcompile($this->getNode('key_target'))
86
+ ->raw(" => ")
87
+ ->subcompile($this->getNode('value_target'))
88
+ ->raw(") {\n")
89
+ ->indent()
90
+ ->subcompile($this->getNode('body'))
91
+ ->outdent()
92
+ ->write("}\n")
93
+ ;
94
+
95
+ if (null !== $this->getNode('else')) {
96
+ $compiler
97
+ ->write("if (!\$context['_iterated']) {\n")
98
+ ->indent()
99
+ ->subcompile($this->getNode('else'))
100
+ ->outdent()
101
+ ->write("}\n")
102
+ ;
103
+ }
104
+
105
+ $compiler->write("\$_parent = \$context['_parent'];\n");
106
+
107
+ // remove some "private" loop variables (needed for nested loops)
108
+ $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");
109
+
110
+ // keep the values set in the inner context for variables defined in the outer context
111
+ $compiler->write("\$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));\n");
112
+ }
113
+ }
Twig/lib/Twig/Node/ForLoop.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_ForLoop extends Twig_Node
19
+ {
20
+ public function __construct($lineno, $tag = null)
21
+ {
22
+ parent::__construct(array(), array('with_loop' => false, 'ifexpr' => false, 'else' => 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('else')) {
33
+ $compiler->write("\$context['_iterated'] = true;\n");
34
+ }
35
+
36
+ if ($this->getAttribute('with_loop')) {
37
+ $compiler
38
+ ->write("++\$context['loop']['index0'];\n")
39
+ ->write("++\$context['loop']['index'];\n")
40
+ ->write("\$context['loop']['first'] = false;\n")
41
+ ;
42
+
43
+ if (!$this->getAttribute('ifexpr')) {
44
+ $compiler
45
+ ->write("if (isset(\$context['loop']['length'])) {\n")
46
+ ->indent()
47
+ ->write("--\$context['loop']['revindex0'];\n")
48
+ ->write("--\$context['loop']['revindex'];\n")
49
+ ->write("\$context['loop']['last'] = 0 === \$context['loop']['revindex0'];\n")
50
+ ->outdent()
51
+ ->write("}\n")
52
+ ;
53
+ }
54
+ }
55
+ }
56
+ }
Twig/lib/Twig/Node/If.php ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_If extends Twig_Node
20
+ {
21
+ public function __construct(Twig_NodeInterface $tests, Twig_NodeInterface $else = null, $lineno, $tag = null)
22
+ {
23
+ parent::__construct(array('tests' => $tests, 'else' => $else), 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->addDebugInfo($this);
34
+ for ($i = 0; $i < count($this->getNode('tests')); $i += 2) {
35
+ if ($i > 0) {
36
+ $compiler
37
+ ->outdent()
38
+ ->write("} elseif (")
39
+ ;
40
+ } else {
41
+ $compiler
42
+ ->write('if (')
43
+ ;
44
+ }
45
+
46
+ $compiler
47
+ ->subcompile($this->getNode('tests')->getNode($i))
48
+ ->raw(") {\n")
49
+ ->indent()
50
+ ->subcompile($this->getNode('tests')->getNode($i + 1))
51
+ ;
52
+ }
53
+
54
+ if ($this->hasNode('else') && null !== $this->getNode('else')) {
55
+ $compiler
56
+ ->outdent()
57
+ ->write("} else {\n")
58
+ ->indent()
59
+ ->subcompile($this->getNode('else'))
60
+ ;
61
+ }
62
+
63
+ $compiler
64
+ ->outdent()
65
+ ->write("}\n");
66
+ }
67
+ }
Twig/lib/Twig/Node/Import.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Import extends Twig_Node
19
+ {
20
+ public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $var, $lineno, $tag = null)
21
+ {
22
+ parent::__construct(array('expr' => $expr, 'var' => $var), 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('')
35
+ ->subcompile($this->getNode('var'))
36
+ ->raw(' = ')
37
+ ;
38
+
39
+ if ($this->getNode('expr') instanceof Twig_Node_Expression_Name && '_self' === $this->getNode('expr')->getAttribute('name')) {
40
+ $compiler->raw("\$this");
41
+ } else {
42
+ $compiler
43
+ ->raw('$this->env->loadTemplate(')
44
+ ->subcompile($this->getNode('expr'))
45
+ ->raw(")")
46
+ ;
47
+ }
48
+
49
+ $compiler->raw(";\n");
50
+ }
51
+ }
Twig/lib/Twig/Node/Include.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_Include extends Twig_Node implements Twig_NodeOutputInterface
20
+ {
21
+ public function __construct(Twig_Node_Expression $expr, Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
22
+ {
23
+ parent::__construct(array('expr' => $expr, 'variables' => $variables), array('only' => (Boolean) $only, 'ignore_missing' => (Boolean) $ignoreMissing), $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->addDebugInfo($this);
34
+
35
+ if ($this->getAttribute('ignore_missing')) {
36
+ $compiler
37
+ ->write("try {\n")
38
+ ->indent()
39
+ ;
40
+ }
41
+
42
+ $this->addGetTemplate($compiler);
43
+
44
+ $compiler->raw('->display(');
45
+
46
+ $this->addTemplateArguments($compiler);
47
+
48
+ $compiler->raw(");\n");
49
+
50
+ if ($this->getAttribute('ignore_missing')) {
51
+ $compiler
52
+ ->outdent()
53
+ ->write("} catch (Twig_Error_Loader \$e) {\n")
54
+ ->indent()
55
+ ->write("// ignore missing template\n")
56
+ ->outdent()
57
+ ->write("}\n\n")
58
+ ;
59
+ }
60
+ }
61
+
62
+ protected function addGetTemplate(Twig_Compiler $compiler)
63
+ {
64
+ if ($this->getNode('expr') instanceof Twig_Node_Expression_Constant) {
65
+ $compiler
66
+ ->write("\$this->env->loadTemplate(")
67
+ ->subcompile($this->getNode('expr'))
68
+ ->raw(")")
69
+ ;
70
+ } else {
71
+ $compiler
72
+ ->write("\$template = \$this->env->resolveTemplate(")
73
+ ->subcompile($this->getNode('expr'))
74
+ ->raw(");\n")
75
+ ->write('$template')
76
+ ;
77
+ }
78
+ }
79
+
80
+ protected function addTemplateArguments(Twig_Compiler $compiler)
81
+ {
82
+ if (false === $this->getAttribute('only')) {
83
+ if (null === $this->getNode('variables')) {
84
+ $compiler->raw('$context');
85
+ } else {
86
+ $compiler
87
+ ->raw('array_merge($context, ')
88
+ ->subcompile($this->getNode('variables'))
89
+ ->raw(')')
90
+ ;
91
+ }
92
+ } else {
93
+ if (null === $this->getNode('variables')) {
94
+ $compiler->raw('array()');
95
+ } else {
96
+ $compiler->subcompile($this->getNode('variables'));
97
+ }
98
+ }
99
+ }
100
+ }
Twig/lib/Twig/Node/Macro.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Macro extends Twig_Node
19
+ {
20
+ public function __construct($name, Twig_NodeInterface $body, Twig_NodeInterface $arguments, $lineno, $tag = null)
21
+ {
22
+ parent::__construct(array('body' => $body, 'arguments' => $arguments), 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 get%s(", $this->getAttribute('name')))
35
+ ;
36
+
37
+ $count = count($this->getNode('arguments'));
38
+ $pos = 0;
39
+ foreach ($this->getNode('arguments') as $name => $default) {
40
+ $compiler
41
+ ->raw('$_'.$name.' = ')
42
+ ->subcompile($default)
43
+ ;
44
+
45
+ if (++$pos < $count) {
46
+ $compiler->raw(', ');
47
+ }
48
+ }
49
+
50
+ $compiler
51
+ ->raw(")\n")
52
+ ->write("{\n")
53
+ ->indent()
54
+ ;
55
+
56
+ if (!count($this->getNode('arguments'))) {
57
+ $compiler->write("\$context = \$this->env->getGlobals();\n\n");
58
+ } else {
59
+ $compiler
60
+ ->write("\$context = \$this->env->mergeGlobals(array(\n")
61
+ ->indent()
62
+ ;
63
+
64
+ foreach ($this->getNode('arguments') as $name => $default) {
65
+ $compiler
66
+ ->write('')
67
+ ->string($name)
68
+ ->raw(' => $_'.$name)
69
+ ->raw(",\n")
70
+ ;
71
+ }
72
+
73
+ $compiler
74
+ ->outdent()
75
+ ->write("));\n\n")
76
+ ;
77
+ }
78
+
79
+ $compiler
80
+ ->write("\$blocks = array();\n\n")
81
+ ->write("ob_start();\n")
82
+ ->write("try {\n")
83
+ ->indent()
84
+ ->subcompile($this->getNode('body'))
85
+ ->outdent()
86
+ ->write("} catch (Exception \$e) {\n")
87
+ ->indent()
88
+ ->write("ob_end_clean();\n\n")
89
+ ->write("throw \$e;\n")
90
+ ->outdent()
91
+ ->write("}\n\n")
92
+ ->write("return ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset());\n")
93
+ ->outdent()
94
+ ->write("}\n\n")
95
+ ;
96
+ }
97
+ }
Twig/lib/Twig/Node/Module.php ADDED
@@ -0,0 +1,372 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_Module extends Twig_Node
20
+ {
21
+ public function __construct(Twig_NodeInterface $body, Twig_Node_Expression $parent = null, Twig_NodeInterface $blocks, Twig_NodeInterface $macros, Twig_NodeInterface $traits, $embeddedTemplates, $filename)
22
+ {
23
+ // embedded templates are set as attributes so that they are only visited once by the visitors
24
+ parent::__construct(array('parent' => $parent, 'body' => $body, 'blocks' => $blocks, 'macros' => $macros, 'traits' => $traits), array('filename' => $filename, 'index' => null, 'embedded_templates' => $embeddedTemplates), 1);
25
+ }
26
+
27
+ public function setIndex($index)
28
+ {
29
+ $this->setAttribute('index', $index);
30
+ }
31
+
32
+ /**
33
+ * Compiles the node to PHP.
34
+ *
35
+ * @param Twig_Compiler A Twig_Compiler instance
36
+ */
37
+ public function compile(Twig_Compiler $compiler)
38
+ {
39
+ $this->compileTemplate($compiler);
40
+
41
+ foreach ($this->getAttribute('embedded_templates') as $template) {
42
+ $compiler->subcompile($template);
43
+ }
44
+ }
45
+
46
+ protected function compileTemplate(Twig_Compiler $compiler)
47
+ {
48
+ if (!$this->getAttribute('index')) {
49
+ $compiler->write('<?php');
50
+ }
51
+
52
+ $this->compileClassHeader($compiler);
53
+
54
+ if (count($this->getNode('blocks')) || count($this->getNode('traits')) || null === $this->getNode('parent') || $this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
55
+ $this->compileConstructor($compiler);
56
+ }
57
+
58
+ $this->compileGetParent($compiler);
59
+
60
+ $this->compileDisplayHeader($compiler);
61
+
62
+ $this->compileDisplayBody($compiler);
63
+
64
+ $this->compileDisplayFooter($compiler);
65
+
66
+ $compiler->subcompile($this->getNode('blocks'));
67
+
68
+ $this->compileMacros($compiler);
69
+
70
+ $this->compileGetTemplateName($compiler);
71
+
72
+ $this->compileIsTraitable($compiler);
73
+
74
+ $this->compileDebugInfo($compiler);
75
+
76
+ $this->compileClassFooter($compiler);
77
+ }
78
+
79
+ protected function compileGetParent(Twig_Compiler $compiler)
80
+ {
81
+ if (null === $this->getNode('parent')) {
82
+ return;
83
+ }
84
+
85
+ $compiler
86
+ ->write("protected function doGetParent(array \$context)\n", "{\n")
87
+ ->indent()
88
+ ->write("return ")
89
+ ;
90
+
91
+ if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
92
+ $compiler->subcompile($this->getNode('parent'));
93
+ } else {
94
+ $compiler
95
+ ->raw("\$this->env->resolveTemplate(")
96
+ ->subcompile($this->getNode('parent'))
97
+ ->raw(")")
98
+ ;
99
+ }
100
+
101
+ $compiler
102
+ ->raw(";\n")
103
+ ->outdent()
104
+ ->write("}\n\n")
105
+ ;
106
+ }
107
+
108
+ protected function compileDisplayBody(Twig_Compiler $compiler)
109
+ {
110
+ $compiler->subcompile($this->getNode('body'));
111
+
112
+ if (null !== $this->getNode('parent')) {
113
+ if ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
114
+ $compiler->write("\$this->parent");
115
+ } else {
116
+ $compiler->write("\$this->getParent(\$context)");
117
+ }
118
+ $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
119
+ }
120
+ }
121
+
122
+ protected function compileClassHeader(Twig_Compiler $compiler)
123
+ {
124
+ $compiler
125
+ ->write("\n\n")
126
+ // if the filename contains */, add a blank to avoid a PHP parse error
127
+ ->write("/* ".str_replace('*/', '* /', $this->getAttribute('filename'))." */\n")
128
+ ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getAttribute('filename'), $this->getAttribute('index')))
129
+ ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass()))
130
+ ->write("{\n")
131
+ ->indent()
132
+ ;
133
+ }
134
+
135
+ protected function compileConstructor(Twig_Compiler $compiler)
136
+ {
137
+ $compiler
138
+ ->write("public function __construct(Twig_Environment \$env)\n", "{\n")
139
+ ->indent()
140
+ ->write("parent::__construct(\$env);\n\n")
141
+ ;
142
+
143
+ // parent
144
+ if (null === $this->getNode('parent')) {
145
+ $compiler->write("\$this->parent = false;\n\n");
146
+ } elseif ($this->getNode('parent') instanceof Twig_Node_Expression_Constant) {
147
+ $compiler
148
+ ->write("\$this->parent = \$this->env->loadTemplate(")
149
+ ->subcompile($this->getNode('parent'))
150
+ ->raw(");\n\n")
151
+ ;
152
+ }
153
+
154
+ $countTraits = count($this->getNode('traits'));
155
+ if ($countTraits) {
156
+ // traits
157
+ foreach ($this->getNode('traits') as $i => $trait) {
158
+ $this->compileLoadTemplate($compiler, $trait->getNode('template'), sprintf('$_trait_%s', $i));
159
+
160
+ $compiler
161
+ ->addDebugInfo($trait->getNode('template'))
162
+ ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
163
+ ->indent()
164
+ ->write("throw new Twig_Error_Runtime('Template \"'.")
165
+ ->subcompile($trait->getNode('template'))
166
+ ->raw(".'\" cannot be used as a trait.');\n")
167
+ ->outdent()
168
+ ->write("}\n")
169
+ ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
170
+ ;
171
+
172
+ foreach ($trait->getNode('targets') as $key => $value) {
173
+ $compiler
174
+ ->write(sprintf("\$_trait_%s_blocks[", $i))
175
+ ->subcompile($value)
176
+ ->raw(sprintf("] = \$_trait_%s_blocks[", $i))
177
+ ->string($key)
178
+ ->raw(sprintf("]; unset(\$_trait_%s_blocks[", $i))
179
+ ->string($key)
180
+ ->raw("]);\n\n")
181
+ ;
182
+ }
183
+ }
184
+
185
+ if ($countTraits > 1) {
186
+ $compiler
187
+ ->write("\$this->traits = array_merge(\n")
188
+ ->indent()
189
+ ;
190
+
191
+ for ($i = 0; $i < $countTraits; $i++) {
192
+ $compiler
193
+ ->write(sprintf("\$_trait_%s_blocks".($i == $countTraits - 1 ? '' : ',')."\n", $i))
194
+ ;
195
+ }
196
+
197
+ $compiler
198
+ ->outdent()
199
+ ->write(");\n\n")
200
+ ;
201
+ } else {
202
+ $compiler
203
+ ->write("\$this->traits = \$_trait_0_blocks;\n\n")
204
+ ;
205
+ }
206
+
207
+ $compiler
208
+ ->write("\$this->blocks = array_merge(\n")
209
+ ->indent()
210
+ ->write("\$this->traits,\n")
211
+ ->write("array(\n")
212
+ ;
213
+ } else {
214
+ $compiler
215
+ ->write("\$this->blocks = array(\n")
216
+ ;
217
+ }
218
+
219
+ // blocks
220
+ $compiler
221
+ ->indent()
222
+ ;
223
+
224
+ foreach ($this->getNode('blocks') as $name => $node) {
225
+ $compiler
226
+ ->write(sprintf("'%s' => array(\$this, 'block_%s'),\n", $name, $name))
227
+ ;
228
+ }
229
+
230
+ if ($countTraits) {
231
+ $compiler
232
+ ->outdent()
233
+ ->write(")\n")
234
+ ;
235
+ }
236
+
237
+ $compiler
238
+ ->outdent()
239
+ ->write(");\n")
240
+ ->outdent()
241
+ ->write("}\n\n");
242
+ ;
243
+ }
244
+
245
+ protected function compileDisplayHeader(Twig_Compiler $compiler)
246
+ {
247
+ $compiler
248
+ ->write("protected function doDisplay(array \$context, array \$blocks = array())\n", "{\n")
249
+ ->indent()
250
+ ;
251
+ }
252
+
253
+ protected function compileDisplayFooter(Twig_Compiler $compiler)
254
+ {
255
+ $compiler
256
+ ->outdent()
257
+ ->write("}\n\n")
258
+ ;
259
+ }
260
+
261
+ protected function compileClassFooter(Twig_Compiler $compiler)
262
+ {
263
+ $compiler
264
+ ->outdent()
265
+ ->write("}\n")
266
+ ;
267
+ }
268
+
269
+ protected function compileMacros(Twig_Compiler $compiler)
270
+ {
271
+ $compiler->subcompile($this->getNode('macros'));
272
+ }
273
+
274
+ protected function compileGetTemplateName(Twig_Compiler $compiler)
275
+ {
276
+ $compiler
277
+ ->write("public function getTemplateName()\n", "{\n")
278
+ ->indent()
279
+ ->write('return ')
280
+ ->repr($this->getAttribute('filename'))
281
+ ->raw(";\n")
282
+ ->outdent()
283
+ ->write("}\n\n")
284
+ ;
285
+ }
286
+
287
+ protected function compileIsTraitable(Twig_Compiler $compiler)
288
+ {
289
+ // A template can be used as a trait if:
290
+ // * it has no parent
291
+ // * it has no macros
292
+ // * it has no body
293
+ //
294
+ // Put another way, a template can be used as a trait if it
295
+ // only contains blocks and use statements.
296
+ $traitable = null === $this->getNode('parent') && 0 === count($this->getNode('macros'));
297
+ if ($traitable) {
298
+ if ($this->getNode('body') instanceof Twig_Node_Body) {
299
+ $nodes = $this->getNode('body')->getNode(0);
300
+ } else {
301
+ $nodes = $this->getNode('body');
302
+ }
303
+
304
+ if (!count($nodes)) {
305
+ $nodes = new Twig_Node(array($nodes));
306
+ }
307
+
308
+ foreach ($nodes as $node) {
309
+ if (!count($node)) {
310
+ continue;
311
+ }
312
+
313
+ if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
314
+ continue;
315
+ }
316
+
317
+ if ($node instanceof Twig_Node_BlockReference) {
318
+ continue;
319
+ }
320
+
321
+ $traitable = false;
322
+ break;
323
+ }
324
+ }
325
+
326
+ if ($traitable) {
327
+ return;
328
+ }
329
+
330
+ $compiler
331
+ ->write("public function isTraitable()\n", "{\n")
332
+ ->indent()
333
+ ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
334
+ ->outdent()
335
+ ->write("}\n\n")
336
+ ;
337
+ }
338
+
339
+ protected function compileDebugInfo(Twig_Compiler $compiler)
340
+ {
341
+ $compiler
342
+ ->write("public function getDebugInfo()\n", "{\n")
343
+ ->indent()
344
+ ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
345
+ ->outdent()
346
+ ->write("}\n")
347
+ ;
348
+ }
349
+
350
+ protected function compileLoadTemplate(Twig_Compiler $compiler, $node, $var)
351
+ {
352
+ if ($node instanceof Twig_Node_Expression_Constant) {
353
+ $compiler
354
+ ->write(sprintf("%s = \$this->env->loadTemplate(", $var))
355
+ ->subcompile($node)
356
+ ->raw(");\n")
357
+ ;
358
+ } else {
359
+ $compiler
360
+ ->write(sprintf("%s = ", $var))
361
+ ->subcompile($node)
362
+ ->raw(";\n")
363
+ ->write(sprintf("if (!%s", $var))
364
+ ->raw(" instanceof Twig_Template) {\n")
365
+ ->indent()
366
+ ->write(sprintf("%s = \$this->env->loadTemplate(%s);\n", $var, $var))
367
+ ->outdent()
368
+ ->write("}\n")
369
+ ;
370
+ }
371
+ }
372
+ }
Twig/lib/Twig/Node/Print.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ /**
14
+ * Represents a node that outputs an expression.
15
+ *
16
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_Print extends Twig_Node implements Twig_NodeOutputInterface
20
+ {
21
+ public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null)
22
+ {
23
+ parent::__construct(array('expr' => $expr), 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('echo ')
36
+ ->subcompile($this->getNode('expr'))
37
+ ->raw(";\n")
38
+ ;
39
+ }
40
+ }
Twig/lib/Twig/Node/Sandbox.php ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Sandbox extends Twig_Node
19
+ {
20
+ public function __construct(Twig_NodeInterface $body, $lineno, $tag = null)
21
+ {
22
+ parent::__construct(array('body' => $body), 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("\$sandbox = \$this->env->getExtension('sandbox');\n")
35
+ ->write("if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {\n")
36
+ ->indent()
37
+ ->write("\$sandbox->enableSandbox();\n")
38
+ ->outdent()
39
+ ->write("}\n")
40
+ ->subcompile($this->getNode('body'))
41
+ ->write("if (!\$alreadySandboxed) {\n")
42
+ ->indent()
43
+ ->write("\$sandbox->disableSandbox();\n")
44
+ ->outdent()
45
+ ->write("}\n")
46
+ ;
47
+ }
48
+ }
Twig/lib/Twig/Node/SandboxedModule.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_SandboxedModule extends Twig_Node_Module
20
+ {
21
+ protected $usedFilters;
22
+ protected $usedTags;
23
+ protected $usedFunctions;
24
+
25
+ public function __construct(Twig_Node_Module $node, array $usedFilters, array $usedTags, array $usedFunctions)
26
+ {
27
+ 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());
28
+
29
+ $this->setAttribute('index', $node->getAttribute('index'));
30
+
31
+ $this->usedFilters = $usedFilters;
32
+ $this->usedTags = $usedTags;
33
+ $this->usedFunctions = $usedFunctions;
34
+ }
35
+
36
+ protected function compileDisplayBody(Twig_Compiler $compiler)
37
+ {
38
+ $compiler->write("\$this->checkSecurity();\n");
39
+
40
+ parent::compileDisplayBody($compiler);
41
+ }
42
+
43
+ protected function compileDisplayFooter(Twig_Compiler $compiler)
44
+ {
45
+ parent::compileDisplayFooter($compiler);
46
+
47
+ $compiler
48
+ ->write("protected function checkSecurity()\n", "{\n")
49
+ ->indent()
50
+ ->write("\$this->env->getExtension('sandbox')->checkSecurity(\n")
51
+ ->indent()
52
+ ->write(!$this->usedTags ? "array(),\n" : "array('".implode('\', \'', $this->usedTags)."'),\n")
53
+ ->write(!$this->usedFilters ? "array(),\n" : "array('".implode('\', \'', $this->usedFilters)."'),\n")
54
+ ->write(!$this->usedFunctions ? "array()\n" : "array('".implode('\', \'', $this->usedFunctions)."')\n")
55
+ ->outdent()
56
+ ->write(");\n")
57
+ ->outdent()
58
+ ->write("}\n\n")
59
+ ;
60
+ }
61
+ }
Twig/lib/Twig/Node/SandboxedPrint.php ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
21
+ * @author Fabien Potencier <fabien@symfony.com>
22
+ */
23
+ class Twig_Node_SandboxedPrint extends Twig_Node_Print
24
+ {
25
+ public function __construct(Twig_Node_Expression $expr, $lineno, $tag = null)
26
+ {
27
+ parent::__construct($expr, $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
38
+ ->addDebugInfo($this)
39
+ ->write('echo $this->env->getExtension(\'sandbox\')->ensureToStringAllowed(')
40
+ ->subcompile($this->getNode('expr'))
41
+ ->raw(");\n")
42
+ ;
43
+ }
44
+
45
+ /**
46
+ * Removes node filters.
47
+ *
48
+ * This is mostly needed when another visitor adds filters (like the escaper one).
49
+ *
50
+ * @param Twig_Node $node A Node
51
+ */
52
+ protected function removeNodeFilter($node)
53
+ {
54
+ if ($node instanceof Twig_Node_Expression_Filter) {
55
+ return $this->removeNodeFilter($node->getNode('node'));
56
+ }
57
+
58
+ return $node;
59
+ }
60
+ }
Twig/lib/Twig/Node/Set.php ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Node_Set extends Twig_Node
19
+ {
20
+ public function __construct($capture, Twig_NodeInterface $names, Twig_NodeInterface $values, $lineno, $tag = null)
21
+ {
22
+ parent::__construct(array('names' => $names, 'values' => $values), array('capture' => $capture, 'safe' => false), $lineno, $tag);
23
+
24
+ /*
25
+ * Optimizes the node when capture is used for a large block of text.
26
+ *
27
+ * {% set foo %}foo{% endset %} is compiled to $context['foo'] = new Twig_Markup("foo");
28
+ */
29
+ if ($this->getAttribute('capture')) {
30
+ $this->setAttribute('safe', true);
31
+
32
+ $values = $this->getNode('values');
33
+ if ($values instanceof Twig_Node_Text) {
34
+ $this->setNode('values', new Twig_Node_Expression_Constant($values->getAttribute('data'), $values->getLine()));
35
+ $this->setAttribute('capture', false);
36
+ }
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Compiles the node to PHP.
42
+ *
43
+ * @param Twig_Compiler A Twig_Compiler instance
44
+ */
45
+ public function compile(Twig_Compiler $compiler)
46
+ {
47
+ $compiler->addDebugInfo($this);
48
+
49
+ if (count($this->getNode('names')) > 1) {
50
+ $compiler->write('list(');
51
+ foreach ($this->getNode('names') as $idx => $node) {
52
+ if ($idx) {
53
+ $compiler->raw(', ');
54
+ }
55
+
56
+ $compiler->subcompile($node);
57
+ }
58
+ $compiler->raw(')');
59
+ } else {
60
+ if ($this->getAttribute('capture')) {
61
+ $compiler
62
+ ->write("ob_start();\n")
63
+ ->subcompile($this->getNode('values'))
64
+ ;
65
+ }
66
+
67
+ $compiler->subcompile($this->getNode('names'), false);
68
+
69
+ if ($this->getAttribute('capture')) {
70
+ $compiler->raw(" = ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())");
71
+ }
72
+ }
73
+
74
+ if (!$this->getAttribute('capture')) {
75
+ $compiler->raw(' = ');
76
+
77
+ if (count($this->getNode('names')) > 1) {
78
+ $compiler->write('array(');
79
+ foreach ($this->getNode('values') as $idx => $value) {
80
+ if ($idx) {
81
+ $compiler->raw(', ');
82
+ }
83
+
84
+ $compiler->subcompile($value);
85
+ }
86
+ $compiler->raw(')');
87
+ } else {
88
+ if ($this->getAttribute('safe')) {
89
+ $compiler
90
+ ->raw("('' === \$tmp = ")
91
+ ->subcompile($this->getNode('values'))
92
+ ->raw(") ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset())")
93
+ ;
94
+ } else {
95
+ $compiler->subcompile($this->getNode('values'));
96
+ }
97
+ }
98
+ }
99
+
100
+ $compiler->raw(";\n");
101
+ }
102
+ }
Twig/lib/Twig/Node/SetTemp.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2011 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Node_SetTemp extends Twig_Node
13
+ {
14
+ public function __construct($name, $lineno)
15
+ {
16
+ parent::__construct(array(), array('name' => $name), $lineno);
17
+ }
18
+
19
+ public function compile(Twig_Compiler $compiler)
20
+ {
21
+ $name = $this->getAttribute('name');
22
+ $compiler
23
+ ->addDebugInfo($this)
24
+ ->write('if (isset($context[')
25
+ ->string($name)
26
+ ->raw('])) { $_')
27
+ ->raw($name)
28
+ ->raw('_ = $context[')
29
+ ->repr($name)
30
+ ->raw(']; } else { $_')
31
+ ->raw($name)
32
+ ->raw("_ = null; }\n")
33
+ ;
34
+ }
35
+ }
Twig/lib/Twig/Node/Spaceless.php ADDED
@@ -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
+ * Represents a spaceless node.
14
+ *
15
+ * It removes spaces between HTML tags.
16
+ *
17
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ */
20
+ class Twig_Node_Spaceless extends Twig_Node
21
+ {
22
+ public function __construct(Twig_NodeInterface $body, $lineno, $tag = 'spaceless')
23
+ {
24
+ parent::__construct(array('body' => $body), 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
35
+ ->addDebugInfo($this)
36
+ ->write("ob_start();\n")
37
+ ->subcompile($this->getNode('body'))
38
+ ->write("echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));\n")
39
+ ;
40
+ }
41
+ }
Twig/lib/Twig/Node/Text.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ /**
14
+ * Represents a text node.
15
+ *
16
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Node_Text extends Twig_Node implements Twig_NodeOutputInterface
20
+ {
21
+ public function __construct($data, $lineno)
22
+ {
23
+ parent::__construct(array(), array('data' => $data), $lineno);
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('echo ')
36
+ ->string($this->getAttribute('data'))
37
+ ->raw(";\n")
38
+ ;
39
+ }
40
+ }
Twig/lib/Twig/NodeInterface.php ADDED
@@ -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
+ * Represents a node in the AST.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ interface Twig_NodeInterface extends Countable, IteratorAggregate
20
+ {
21
+ /**
22
+ * Compiles the node to PHP.
23
+ *
24
+ * @param Twig_Compiler A Twig_Compiler instance
25
+ */
26
+ public function compile(Twig_Compiler $compiler);
27
+
28
+ public function getLine();
29
+
30
+ public function getNodeTag();
31
+ }
Twig/lib/Twig/NodeOutputInterface.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ interface Twig_NodeOutputInterface
19
+ {
20
+ }
Twig/lib/Twig/NodeTraverser.php ADDED
@@ -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
+ * Twig_NodeTraverser is a node traverser.
14
+ *
15
+ * It visits all nodes and their children and call the given visitor for each.
16
+ *
17
+ * @package twig
18
+ * @author Fabien Potencier <fabien@symfony.com>
19
+ */
20
+ class Twig_NodeTraverser
21
+ {
22
+ protected $env;
23
+ protected $visitors;
24
+
25
+ /**
26
+ * Constructor.
27
+ *
28
+ * @param Twig_Environment $env A Twig_Environment instance
29
+ * @param array $visitors An array of Twig_NodeVisitorInterface instances
30
+ */
31
+ public function __construct(Twig_Environment $env, array $visitors = array())
32
+ {
33
+ $this->env = $env;
34
+ $this->visitors = array();
35
+ foreach ($visitors as $visitor) {
36
+ $this->addVisitor($visitor);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Adds a visitor.
42
+ *
43
+ * @param Twig_NodeVisitorInterface $visitor A Twig_NodeVisitorInterface instance
44
+ */
45
+ public function addVisitor(Twig_NodeVisitorInterface $visitor)
46
+ {
47
+ if (!isset($this->visitors[$visitor->getPriority()])) {
48
+ $this->visitors[$visitor->getPriority()] = array();
49
+ }
50
+
51
+ $this->visitors[$visitor->getPriority()][] = $visitor;
52
+ }
53
+
54
+ /**
55
+ * Traverses a node and calls the registered visitors.
56
+ *
57
+ * @param Twig_NodeInterface $node A Twig_NodeInterface instance
58
+ */
59
+ public function traverse(Twig_NodeInterface $node)
60
+ {
61
+ ksort($this->visitors);
62
+ foreach ($this->visitors as $visitors) {
63
+ foreach ($visitors as $visitor) {
64
+ $node = $this->traverseForVisitor($visitor, $node);
65
+ }
66
+ }
67
+
68
+ return $node;
69
+ }
70
+
71
+ protected function traverseForVisitor(Twig_NodeVisitorInterface $visitor, Twig_NodeInterface $node = null)
72
+ {
73
+ if (null === $node) {
74
+ return null;
75
+ }
76
+
77
+ $node = $visitor->enterNode($node, $this->env);
78
+
79
+ foreach ($node as $k => $n) {
80
+ if (false !== $n = $this->traverseForVisitor($visitor, $n)) {
81
+ $node->setNode($k, $n);
82
+ } else {
83
+ $node->removeNode($k);
84
+ }
85
+ }
86
+
87
+ return $visitor->leaveNode($node, $this->env);
88
+ }
89
+ }
Twig/lib/Twig/NodeVisitor/Escaper.php ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Twig_NodeVisitor_Escaper implements output escaping.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_NodeVisitor_Escaper implements Twig_NodeVisitorInterface
19
+ {
20
+ protected $statusStack = array();
21
+ protected $blocks = array();
22
+ protected $safeAnalysis;
23
+ protected $traverser;
24
+ protected $defaultStrategy = false;
25
+ protected $safeVars = array();
26
+
27
+ public function __construct()
28
+ {
29
+ $this->safeAnalysis = new Twig_NodeVisitor_SafeAnalysis();
30
+ }
31
+
32
+ /**
33
+ * Called before child nodes are visited.
34
+ *
35
+ * @param Twig_NodeInterface $node The node to visit
36
+ * @param Twig_Environment $env The Twig environment instance
37
+ *
38
+ * @return Twig_NodeInterface The modified node
39
+ */
40
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
41
+ {
42
+ if ($node instanceof Twig_Node_Module) {
43
+ if ($env->hasExtension('escaper') && $defaultStrategy = $env->getExtension('escaper')->getDefaultStrategy($node->getAttribute('filename'))) {
44
+ $this->defaultStrategy = $defaultStrategy;
45
+ }
46
+ $this->safeVars = array();
47
+ } elseif ($node instanceof Twig_Node_AutoEscape) {
48
+ $this->statusStack[] = $node->getAttribute('value');
49
+ } elseif ($node instanceof Twig_Node_Block) {
50
+ $this->statusStack[] = isset($this->blocks[$node->getAttribute('name')]) ? $this->blocks[$node->getAttribute('name')] : $this->needEscaping($env);
51
+ } elseif ($node instanceof Twig_Node_Import) {
52
+ $this->safeVars[] = $node->getNode('var')->getAttribute('name');
53
+ }
54
+
55
+ return $node;
56
+ }
57
+
58
+ /**
59
+ * Called after child nodes are visited.
60
+ *
61
+ * @param Twig_NodeInterface $node The node to visit
62
+ * @param Twig_Environment $env The Twig environment instance
63
+ *
64
+ * @return Twig_NodeInterface The modified node
65
+ */
66
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
67
+ {
68
+ if ($node instanceof Twig_Node_Module) {
69
+ $this->defaultStrategy = false;
70
+ $this->safeVars = array();
71
+ } elseif ($node instanceof Twig_Node_Expression_Filter) {
72
+ return $this->preEscapeFilterNode($node, $env);
73
+ } elseif ($node instanceof Twig_Node_Print) {
74
+ return $this->escapePrintNode($node, $env, $this->needEscaping($env));
75
+ }
76
+
77
+ if ($node instanceof Twig_Node_AutoEscape || $node instanceof Twig_Node_Block) {
78
+ array_pop($this->statusStack);
79
+ } elseif ($node instanceof Twig_Node_BlockReference) {
80
+ $this->blocks[$node->getAttribute('name')] = $this->needEscaping($env);
81
+ }
82
+
83
+ return $node;
84
+ }
85
+
86
+ protected function escapePrintNode(Twig_Node_Print $node, Twig_Environment $env, $type)
87
+ {
88
+ if (false === $type) {
89
+ return $node;
90
+ }
91
+
92
+ $expression = $node->getNode('expr');
93
+
94
+ if ($this->isSafeFor($type, $expression, $env)) {
95
+ return $node;
96
+ }
97
+
98
+ $class = get_class($node);
99
+
100
+ return new $class(
101
+ $this->getEscaperFilter($type, $expression),
102
+ $node->getLine()
103
+ );
104
+ }
105
+
106
+ protected function preEscapeFilterNode(Twig_Node_Expression_Filter $filter, Twig_Environment $env)
107
+ {
108
+ $name = $filter->getNode('filter')->getAttribute('value');
109
+
110
+ $type = $env->getFilter($name)->getPreEscape();
111
+ if (null === $type) {
112
+ return $filter;
113
+ }
114
+
115
+ $node = $filter->getNode('node');
116
+ if ($this->isSafeFor($type, $node, $env)) {
117
+ return $filter;
118
+ }
119
+
120
+ $filter->setNode('node', $this->getEscaperFilter($type, $node));
121
+
122
+ return $filter;
123
+ }
124
+
125
+ protected function isSafeFor($type, Twig_NodeInterface $expression, $env)
126
+ {
127
+ $safe = $this->safeAnalysis->getSafe($expression);
128
+
129
+ if (null === $safe) {
130
+ if (null === $this->traverser) {
131
+ $this->traverser = new Twig_NodeTraverser($env, array($this->safeAnalysis));
132
+ }
133
+
134
+ $this->safeAnalysis->setSafeVars($this->safeVars);
135
+
136
+ $this->traverser->traverse($expression);
137
+ $safe = $this->safeAnalysis->getSafe($expression);
138
+ }
139
+
140
+ return in_array($type, $safe) || in_array('all', $safe);
141
+ }
142
+
143
+ protected function needEscaping(Twig_Environment $env)
144
+ {
145
+ if (count($this->statusStack)) {
146
+ return $this->statusStack[count($this->statusStack) - 1];
147
+ }
148
+
149
+ return $this->defaultStrategy ? $this->defaultStrategy : false;
150
+ }
151
+
152
+ protected function getEscaperFilter($type, Twig_NodeInterface $node)
153
+ {
154
+ $line = $node->getLine();
155
+ $name = new Twig_Node_Expression_Constant('escape', $line);
156
+ $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)));
157
+
158
+ return new Twig_Node_Expression_Filter($node, $name, $args, $line);
159
+ }
160
+
161
+ /**
162
+ * {@inheritdoc}
163
+ */
164
+ public function getPriority()
165
+ {
166
+ return 0;
167
+ }
168
+ }
Twig/lib/Twig/NodeVisitor/Optimizer.php ADDED
@@ -0,0 +1,247 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
21
+ * @author Fabien Potencier <fabien@symfony.com>
22
+ */
23
+ class Twig_NodeVisitor_Optimizer implements Twig_NodeVisitorInterface
24
+ {
25
+ const OPTIMIZE_ALL = -1;
26
+ const OPTIMIZE_NONE = 0;
27
+ const OPTIMIZE_FOR = 2;
28
+ const OPTIMIZE_RAW_FILTER = 4;
29
+ const OPTIMIZE_VAR_ACCESS = 8;
30
+
31
+ protected $loops = array();
32
+ protected $optimizers;
33
+ protected $prependedNodes = array();
34
+ protected $inABody = false;
35
+
36
+ /**
37
+ * Constructor.
38
+ *
39
+ * @param integer $optimizers The optimizer mode
40
+ */
41
+ public function __construct($optimizers = -1)
42
+ {
43
+ if (!is_int($optimizers) || $optimizers > 2) {
44
+ throw new InvalidArgumentException(sprintf('Optimizer mode "%s" is not valid.', $optimizers));
45
+ }
46
+
47
+ $this->optimizers = $optimizers;
48
+ }
49
+
50
+ /**
51
+ * {@inheritdoc}
52
+ */
53
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
54
+ {
55
+ if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
56
+ $this->enterOptimizeFor($node, $env);
57
+ }
58
+
59
+ if (!version_compare(phpversion(), '5.4.0RC1', '>=') && self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
60
+ if ($this->inABody) {
61
+ if (!$node instanceof Twig_Node_Expression) {
62
+ if (get_class($node) !== 'Twig_Node') {
63
+ array_unshift($this->prependedNodes, array());
64
+ }
65
+ } else {
66
+ $node = $this->optimizeVariables($node, $env);
67
+ }
68
+ } elseif ($node instanceof Twig_Node_Body) {
69
+ $this->inABody = true;
70
+ }
71
+ }
72
+
73
+ return $node;
74
+ }
75
+
76
+ /**
77
+ * {@inheritdoc}
78
+ */
79
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
80
+ {
81
+ $expression = $node instanceof Twig_Node_Expression;
82
+
83
+ if (self::OPTIMIZE_FOR === (self::OPTIMIZE_FOR & $this->optimizers)) {
84
+ $this->leaveOptimizeFor($node, $env);
85
+ }
86
+
87
+ if (self::OPTIMIZE_RAW_FILTER === (self::OPTIMIZE_RAW_FILTER & $this->optimizers)) {
88
+ $node = $this->optimizeRawFilter($node, $env);
89
+ }
90
+
91
+ $node = $this->optimizePrintNode($node, $env);
92
+
93
+ if (self::OPTIMIZE_VAR_ACCESS === (self::OPTIMIZE_VAR_ACCESS & $this->optimizers) && !$env->isStrictVariables() && !$env->hasExtension('sandbox')) {
94
+ if ($node instanceof Twig_Node_Body) {
95
+ $this->inABody = false;
96
+ } elseif ($this->inABody) {
97
+ if (!$expression && get_class($node) !== 'Twig_Node' && $prependedNodes = array_shift($this->prependedNodes)) {
98
+ $nodes = array();
99
+ foreach (array_unique($prependedNodes) as $name) {
100
+ $nodes[] = new Twig_Node_SetTemp($name, $node->getLine());
101
+ }
102
+
103
+ $nodes[] = $node;
104
+ $node = new Twig_Node($nodes);
105
+ }
106
+ }
107
+ }
108
+
109
+ return $node;
110
+ }
111
+
112
+ protected function optimizeVariables($node, $env)
113
+ {
114
+ if ('Twig_Node_Expression_Name' === get_class($node) && $node->isSimple()) {
115
+ $this->prependedNodes[0][] = $node->getAttribute('name');
116
+
117
+ return new Twig_Node_Expression_TempName($node->getAttribute('name'), $node->getLine());
118
+ }
119
+
120
+ return $node;
121
+ }
122
+
123
+ /**
124
+ * Optimizes print nodes.
125
+ *
126
+ * It replaces:
127
+ *
128
+ * * "echo $this->render(Parent)Block()" with "$this->display(Parent)Block()"
129
+ *
130
+ * @param Twig_NodeInterface $node A Node
131
+ * @param Twig_Environment $env The current Twig environment
132
+ */
133
+ protected function optimizePrintNode($node, $env)
134
+ {
135
+ if (!$node instanceof Twig_Node_Print) {
136
+ return $node;
137
+ }
138
+
139
+ if (
140
+ $node->getNode('expr') instanceof Twig_Node_Expression_BlockReference ||
141
+ $node->getNode('expr') instanceof Twig_Node_Expression_Parent
142
+ ) {
143
+ $node->getNode('expr')->setAttribute('output', true);
144
+
145
+ return $node->getNode('expr');
146
+ }
147
+
148
+ return $node;
149
+ }
150
+
151
+ /**
152
+ * Removes "raw" filters.
153
+ *
154
+ * @param Twig_NodeInterface $node A Node
155
+ * @param Twig_Environment $env The current Twig environment
156
+ */
157
+ protected function optimizeRawFilter($node, $env)
158
+ {
159
+ if ($node instanceof Twig_Node_Expression_Filter && 'raw' == $node->getNode('filter')->getAttribute('value')) {
160
+ return $node->getNode('node');
161
+ }
162
+
163
+ return $node;
164
+ }
165
+
166
+ /**
167
+ * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
168
+ *
169
+ * @param Twig_NodeInterface $node A Node
170
+ * @param Twig_Environment $env The current Twig environment
171
+ */
172
+ protected function enterOptimizeFor($node, $env)
173
+ {
174
+ if ($node instanceof Twig_Node_For) {
175
+ // disable the loop variable by default
176
+ $node->setAttribute('with_loop', false);
177
+ array_unshift($this->loops, $node);
178
+ } elseif (!$this->loops) {
179
+ // we are outside a loop
180
+ return;
181
+ }
182
+
183
+ // when do we need to add the loop variable back?
184
+
185
+ // the loop variable is referenced for the current loop
186
+ elseif ($node instanceof Twig_Node_Expression_Name && 'loop' === $node->getAttribute('name')) {
187
+ $this->addLoopToCurrent();
188
+ }
189
+
190
+ // block reference
191
+ elseif ($node instanceof Twig_Node_BlockReference || $node instanceof Twig_Node_Expression_BlockReference) {
192
+ $this->addLoopToCurrent();
193
+ }
194
+
195
+ // include without the only attribute
196
+ elseif ($node instanceof Twig_Node_Include && !$node->getAttribute('only')) {
197
+ $this->addLoopToAll();
198
+ }
199
+
200
+ // the loop variable is referenced via an attribute
201
+ elseif ($node instanceof Twig_Node_Expression_GetAttr
202
+ && (!$node->getNode('attribute') instanceof Twig_Node_Expression_Constant
203
+ || 'parent' === $node->getNode('attribute')->getAttribute('value')
204
+ )
205
+ && (true === $this->loops[0]->getAttribute('with_loop')
206
+ || ($node->getNode('node') instanceof Twig_Node_Expression_Name
207
+ && 'loop' === $node->getNode('node')->getAttribute('name')
208
+ )
209
+ )
210
+ ) {
211
+ $this->addLoopToAll();
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Optimizes "for" tag by removing the "loop" variable creation whenever possible.
217
+ *
218
+ * @param Twig_NodeInterface $node A Node
219
+ * @param Twig_Environment $env The current Twig environment
220
+ */
221
+ protected function leaveOptimizeFor($node, $env)
222
+ {
223
+ if ($node instanceof Twig_Node_For) {
224
+ array_shift($this->loops);
225
+ }
226
+ }
227
+
228
+ protected function addLoopToCurrent()
229
+ {
230
+ $this->loops[0]->setAttribute('with_loop', true);
231
+ }
232
+
233
+ protected function addLoopToAll()
234
+ {
235
+ foreach ($this->loops as $loop) {
236
+ $loop->setAttribute('with_loop', true);
237
+ }
238
+ }
239
+
240
+ /**
241
+ * {@inheritdoc}
242
+ */
243
+ public function getPriority()
244
+ {
245
+ return 255;
246
+ }
247
+ }
Twig/lib/Twig/NodeVisitor/SafeAnalysis.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Twig_NodeVisitor_SafeAnalysis implements Twig_NodeVisitorInterface
4
+ {
5
+ protected $data = array();
6
+ protected $safeVars = array();
7
+
8
+ public function setSafeVars($safeVars)
9
+ {
10
+ $this->safeVars = $safeVars;
11
+ }
12
+
13
+ public function getSafe(Twig_NodeInterface $node)
14
+ {
15
+ $hash = spl_object_hash($node);
16
+ if (isset($this->data[$hash])) {
17
+ foreach ($this->data[$hash] as $bucket) {
18
+ if ($bucket['key'] === $node) {
19
+ return $bucket['value'];
20
+ }
21
+ }
22
+ }
23
+
24
+ return null;
25
+ }
26
+
27
+ protected function setSafe(Twig_NodeInterface $node, array $safe)
28
+ {
29
+ $hash = spl_object_hash($node);
30
+ if (isset($this->data[$hash])) {
31
+ foreach ($this->data[$hash] as &$bucket) {
32
+ if ($bucket['key'] === $node) {
33
+ $bucket['value'] = $safe;
34
+
35
+ return;
36
+ }
37
+ }
38
+ }
39
+ $this->data[$hash][] = array(
40
+ 'key' => $node,
41
+ 'value' => $safe,
42
+ );
43
+ }
44
+
45
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
46
+ {
47
+ return $node;
48
+ }
49
+
50
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
51
+ {
52
+ if ($node instanceof Twig_Node_Expression_Constant) {
53
+ // constants are marked safe for all
54
+ $this->setSafe($node, array('all'));
55
+ } elseif ($node instanceof Twig_Node_Expression_BlockReference) {
56
+ // blocks are safe by definition
57
+ $this->setSafe($node, array('all'));
58
+ } elseif ($node instanceof Twig_Node_Expression_Parent) {
59
+ // parent block is safe by definition
60
+ $this->setSafe($node, array('all'));
61
+ } elseif ($node instanceof Twig_Node_Expression_Conditional) {
62
+ // intersect safeness of both operands
63
+ $safe = $this->intersectSafe($this->getSafe($node->getNode('expr2')), $this->getSafe($node->getNode('expr3')));
64
+ $this->setSafe($node, $safe);
65
+ } elseif ($node instanceof Twig_Node_Expression_Filter) {
66
+ // filter expression is safe when the filter is safe
67
+ $name = $node->getNode('filter')->getAttribute('value');
68
+ $args = $node->getNode('arguments');
69
+ if (false !== $filter = $env->getFilter($name)) {
70
+ $safe = $filter->getSafe($args);
71
+ if (null === $safe) {
72
+ $safe = $this->intersectSafe($this->getSafe($node->getNode('node')), $filter->getPreservesSafety());
73
+ }
74
+ $this->setSafe($node, $safe);
75
+ } else {
76
+ $this->setSafe($node, array());
77
+ }
78
+ } elseif ($node instanceof Twig_Node_Expression_Function) {
79
+ // function expression is safe when the function is safe
80
+ $name = $node->getAttribute('name');
81
+ $args = $node->getNode('arguments');
82
+ $function = $env->getFunction($name);
83
+ if (false !== $function) {
84
+ $this->setSafe($node, $function->getSafe($args));
85
+ } else {
86
+ $this->setSafe($node, array());
87
+ }
88
+ } elseif ($node instanceof Twig_Node_Expression_MethodCall) {
89
+ if ($node->getAttribute('safe')) {
90
+ $this->setSafe($node, array('all'));
91
+ } else {
92
+ $this->setSafe($node, array());
93
+ }
94
+ } elseif ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name) {
95
+ $name = $node->getNode('node')->getAttribute('name');
96
+ // attributes on template instances are safe
97
+ if ('_self' == $name || in_array($name, $this->safeVars)) {
98
+ $this->setSafe($node, array('all'));
99
+ } else {
100
+ $this->setSafe($node, array());
101
+ }
102
+ } else {
103
+ $this->setSafe($node, array());
104
+ }
105
+
106
+ return $node;
107
+ }
108
+
109
+ protected function intersectSafe(array $a = null, array $b = null)
110
+ {
111
+ if (null === $a || null === $b) {
112
+ return array();
113
+ }
114
+
115
+ if (in_array('all', $a)) {
116
+ return $b;
117
+ }
118
+
119
+ if (in_array('all', $b)) {
120
+ return $a;
121
+ }
122
+
123
+ return array_intersect($a, $b);
124
+ }
125
+
126
+ /**
127
+ * {@inheritdoc}
128
+ */
129
+ public function getPriority()
130
+ {
131
+ return 0;
132
+ }
133
+ }
Twig/lib/Twig/NodeVisitor/Sandbox.php ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Twig_NodeVisitor_Sandbox implements sandboxing.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_NodeVisitor_Sandbox implements Twig_NodeVisitorInterface
19
+ {
20
+ protected $inAModule = false;
21
+ protected $tags;
22
+ protected $filters;
23
+ protected $functions;
24
+
25
+ /**
26
+ * Called before child nodes are visited.
27
+ *
28
+ * @param Twig_NodeInterface $node The node to visit
29
+ * @param Twig_Environment $env The Twig environment instance
30
+ *
31
+ * @return Twig_NodeInterface The modified node
32
+ */
33
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
34
+ {
35
+ if ($node instanceof Twig_Node_Module) {
36
+ $this->inAModule = true;
37
+ $this->tags = array();
38
+ $this->filters = array();
39
+ $this->functions = array();
40
+
41
+ return $node;
42
+ } elseif ($this->inAModule) {
43
+ // look for tags
44
+ if ($node->getNodeTag()) {
45
+ $this->tags[] = $node->getNodeTag();
46
+ }
47
+
48
+ // look for filters
49
+ if ($node instanceof Twig_Node_Expression_Filter) {
50
+ $this->filters[] = $node->getNode('filter')->getAttribute('value');
51
+ }
52
+
53
+ // look for functions
54
+ if ($node instanceof Twig_Node_Expression_Function) {
55
+ $this->functions[] = $node->getAttribute('name');
56
+ }
57
+
58
+ // wrap print to check __toString() calls
59
+ if ($node instanceof Twig_Node_Print) {
60
+ return new Twig_Node_SandboxedPrint($node->getNode('expr'), $node->getLine(), $node->getNodeTag());
61
+ }
62
+ }
63
+
64
+ return $node;
65
+ }
66
+
67
+ /**
68
+ * Called after child nodes are visited.
69
+ *
70
+ * @param Twig_NodeInterface $node The node to visit
71
+ * @param Twig_Environment $env The Twig environment instance
72
+ *
73
+ * @return Twig_NodeInterface The modified node
74
+ */
75
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
76
+ {
77
+ if ($node instanceof Twig_Node_Module) {
78
+ $this->inAModule = false;
79
+
80
+ return new Twig_Node_SandboxedModule($node, array_unique($this->filters), array_unique($this->tags), array_unique($this->functions));
81
+ }
82
+
83
+ return $node;
84
+ }
85
+
86
+ /**
87
+ * {@inheritdoc}
88
+ */
89
+ public function getPriority()
90
+ {
91
+ return 0;
92
+ }
93
+ }
Twig/lib/Twig/NodeVisitorInterface.php ADDED
@@ -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
+ * Twig_NodeVisitorInterface is the interface the all node visitor classes must implement.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ interface Twig_NodeVisitorInterface
19
+ {
20
+ /**
21
+ * Called before child nodes are visited.
22
+ *
23
+ * @param Twig_NodeInterface $node The node to visit
24
+ * @param Twig_Environment $env The Twig environment instance
25
+ *
26
+ * @return Twig_NodeInterface The modified node
27
+ */
28
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env);
29
+
30
+ /**
31
+ * Called after child nodes are visited.
32
+ *
33
+ * @param Twig_NodeInterface $node The node to visit
34
+ * @param Twig_Environment $env The Twig environment instance
35
+ *
36
+ * @return Twig_NodeInterface The modified node
37
+ */
38
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env);
39
+
40
+ /**
41
+ * Returns the priority for this visitor.
42
+ *
43
+ * Priority should be between -10 and 10 (0 is the default).
44
+ *
45
+ * @return integer The priority level
46
+ */
47
+ public function getPriority();
48
+ }
Twig/lib/Twig/Parser.php ADDED
@@ -0,0 +1,395 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Parser implements Twig_ParserInterface
20
+ {
21
+ protected $stack = array();
22
+ protected $stream;
23
+ protected $parent;
24
+ protected $handlers;
25
+ protected $visitors;
26
+ protected $expressionParser;
27
+ protected $blocks;
28
+ protected $blockStack;
29
+ protected $macros;
30
+ protected $env;
31
+ protected $reservedMacroNames;
32
+ protected $importedSymbols;
33
+ protected $traits;
34
+ protected $embeddedTemplates = array();
35
+
36
+ /**
37
+ * Constructor.
38
+ *
39
+ * @param Twig_Environment $env A Twig_Environment instance
40
+ */
41
+ public function __construct(Twig_Environment $env)
42
+ {
43
+ $this->env = $env;
44
+ }
45
+
46
+ public function getEnvironment()
47
+ {
48
+ return $this->env;
49
+ }
50
+
51
+ public function getVarName()
52
+ {
53
+ return sprintf('__internal_%s', hash('sha1', uniqid(mt_rand(), true), false));
54
+ }
55
+
56
+ public function getFilename()
57
+ {
58
+ return $this->stream->getFilename();
59
+ }
60
+
61
+ /**
62
+ * Converts a token stream to a node tree.
63
+ *
64
+ * @param Twig_TokenStream $stream A token stream instance
65
+ *
66
+ * @return Twig_Node_Module A node tree
67
+ */
68
+ public function parse(Twig_TokenStream $stream, $test = null, $dropNeedle = false)
69
+ {
70
+ // push all variables into the stack to keep the current state of the parser
71
+ $vars = get_object_vars($this);
72
+ unset($vars['stack'], $vars['env'], $vars['handlers'], $vars['visitors'], $vars['expressionParser']);
73
+ $this->stack[] = $vars;
74
+
75
+ // tag handlers
76
+ if (null === $this->handlers) {
77
+ $this->handlers = $this->env->getTokenParsers();
78
+ $this->handlers->setParser($this);
79
+ }
80
+
81
+ // node visitors
82
+ if (null === $this->visitors) {
83
+ $this->visitors = $this->env->getNodeVisitors();
84
+ }
85
+
86
+ if (null === $this->expressionParser) {
87
+ $this->expressionParser = new Twig_ExpressionParser($this, $this->env->getUnaryOperators(), $this->env->getBinaryOperators());
88
+ }
89
+
90
+ $this->stream = $stream;
91
+ $this->parent = null;
92
+ $this->blocks = array();
93
+ $this->macros = array();
94
+ $this->traits = array();
95
+ $this->blockStack = array();
96
+ $this->importedSymbols = array(array());
97
+ $this->embeddedTemplates = array();
98
+
99
+ try {
100
+ $body = $this->subparse($test, $dropNeedle);
101
+
102
+ if (null !== $this->parent) {
103
+ if (null === $body = $this->filterBodyNodes($body)) {
104
+ $body = new Twig_Node();
105
+ }
106
+ }
107
+ } catch (Twig_Error_Syntax $e) {
108
+ if (!$e->getTemplateFile()) {
109
+ $e->setTemplateFile($this->getFilename());
110
+ }
111
+
112
+ if (!$e->getTemplateLine()) {
113
+ $e->setTemplateLine($this->stream->getCurrent()->getLine());
114
+ }
115
+
116
+ throw $e;
117
+ }
118
+
119
+ $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());
120
+
121
+ $traverser = new Twig_NodeTraverser($this->env, $this->visitors);
122
+
123
+ $node = $traverser->traverse($node);
124
+
125
+ // restore previous stack so previous parse() call can resume working
126
+ foreach (array_pop($this->stack) as $key => $val) {
127
+ $this->$key = $val;
128
+ }
129
+
130
+ return $node;
131
+ }
132
+
133
+ public function subparse($test, $dropNeedle = false)
134
+ {
135
+ $lineno = $this->getCurrentToken()->getLine();
136
+ $rv = array();
137
+ while (!$this->stream->isEOF()) {
138
+ switch ($this->getCurrentToken()->getType()) {
139
+ case Twig_Token::TEXT_TYPE:
140
+ $token = $this->stream->next();
141
+ $rv[] = new Twig_Node_Text($token->getValue(), $token->getLine());
142
+ break;
143
+
144
+ case Twig_Token::VAR_START_TYPE:
145
+ $token = $this->stream->next();
146
+ $expr = $this->expressionParser->parseExpression();
147
+ $this->stream->expect(Twig_Token::VAR_END_TYPE);
148
+ $rv[] = new Twig_Node_Print($expr, $token->getLine());
149
+ break;
150
+
151
+ case Twig_Token::BLOCK_START_TYPE:
152
+ $this->stream->next();
153
+ $token = $this->getCurrentToken();
154
+
155
+ if ($token->getType() !== Twig_Token::NAME_TYPE) {
156
+ throw new Twig_Error_Syntax('A block must start with a tag name', $token->getLine(), $this->getFilename());
157
+ }
158
+
159
+ if (null !== $test && call_user_func($test, $token)) {
160
+ if ($dropNeedle) {
161
+ $this->stream->next();
162
+ }
163
+
164
+ if (1 === count($rv)) {
165
+ return $rv[0];
166
+ }
167
+
168
+ return new Twig_Node($rv, array(), $lineno);
169
+ }
170
+
171
+ $subparser = $this->handlers->getTokenParser($token->getValue());
172
+ if (null === $subparser) {
173
+ if (null !== $test) {
174
+ $error = sprintf('Unexpected tag name "%s"', $token->getValue());
175
+ if (is_array($test) && isset($test[0]) && $test[0] instanceof Twig_TokenParserInterface) {
176
+ $error .= sprintf(' (expecting closing tag for the "%s" tag defined near line %s)', $test[0]->getTag(), $lineno);
177
+ }
178
+
179
+ throw new Twig_Error_Syntax($error, $token->getLine(), $this->getFilename());
180
+ }
181
+
182
+ $message = sprintf('Unknown tag name "%s"', $token->getValue());
183
+ if ($alternatives = $this->env->computeAlternatives($token->getValue(), array_keys($this->env->getTags()))) {
184
+ $message = sprintf('%s. Did you mean "%s"', $message, implode('", "', $alternatives));
185
+ }
186
+
187
+ throw new Twig_Error_Syntax($message, $token->getLine(), $this->getFilename());
188
+ }
189
+
190
+ $this->stream->next();
191
+
192
+ $node = $subparser->parse($token);
193
+ if (null !== $node) {
194
+ $rv[] = $node;
195
+ }
196
+ break;
197
+
198
+ default:
199
+ throw new Twig_Error_Syntax('Lexer or parser ended up in unsupported state.', 0, $this->getFilename());
200
+ }
201
+ }
202
+
203
+ if (1 === count($rv)) {
204
+ return $rv[0];
205
+ }
206
+
207
+ return new Twig_Node($rv, array(), $lineno);
208
+ }
209
+
210
+ public function addHandler($name, $class)
211
+ {
212
+ $this->handlers[$name] = $class;
213
+ }
214
+
215
+ public function addNodeVisitor(Twig_NodeVisitorInterface $visitor)
216
+ {
217
+ $this->visitors[] = $visitor;
218
+ }
219
+
220
+ public function getBlockStack()
221
+ {
222
+ return $this->blockStack;
223
+ }
224
+
225
+ public function peekBlockStack()
226
+ {
227
+ return $this->blockStack[count($this->blockStack) - 1];
228
+ }
229
+
230
+ public function popBlockStack()
231
+ {
232
+ array_pop($this->blockStack);
233
+ }
234
+
235
+ public function pushBlockStack($name)
236
+ {
237
+ $this->blockStack[] = $name;
238
+ }
239
+
240
+ public function hasBlock($name)
241
+ {
242
+ return isset($this->blocks[$name]);
243
+ }
244
+
245
+ public function getBlock($name)
246
+ {
247
+ return $this->blocks[$name];
248
+ }
249
+
250
+ public function setBlock($name, $value)
251
+ {
252
+ $this->blocks[$name] = new Twig_Node_Body(array($value), array(), $value->getLine());
253
+ }
254
+
255
+ public function hasMacro($name)
256
+ {
257
+ return isset($this->macros[$name]);
258
+ }
259
+
260
+ public function setMacro($name, Twig_Node_Macro $node)
261
+ {
262
+ if (null === $this->reservedMacroNames) {
263
+ $this->reservedMacroNames = array();
264
+ $r = new ReflectionClass($this->env->getBaseTemplateClass());
265
+ foreach ($r->getMethods() as $method) {
266
+ $this->reservedMacroNames[] = $method->getName();
267
+ }
268
+ }
269
+
270
+ if (in_array($name, $this->reservedMacroNames)) {
271
+ 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());
272
+ }
273
+
274
+ $this->macros[$name] = $node;
275
+ }
276
+
277
+ public function addTrait($trait)
278
+ {
279
+ $this->traits[] = $trait;
280
+ }
281
+
282
+ public function hasTraits()
283
+ {
284
+ return count($this->traits) > 0;
285
+ }
286
+
287
+ public function embedTemplate(Twig_Node_Module $template)
288
+ {
289
+ $template->setIndex(mt_rand());
290
+
291
+ $this->embeddedTemplates[] = $template;
292
+ }
293
+
294
+ public function addImportedSymbol($type, $alias, $name = null, Twig_Node_Expression $node = null)
295
+ {
296
+ $this->importedSymbols[0][$type][$alias] = array('name' => $name, 'node' => $node);
297
+ }
298
+
299
+ public function getImportedSymbol($type, $alias)
300
+ {
301
+ foreach ($this->importedSymbols as $functions) {
302
+ if (isset($functions[$type][$alias])) {
303
+ return $functions[$type][$alias];
304
+ }
305
+ }
306
+ }
307
+
308
+ public function isMainScope()
309
+ {
310
+ return 1 === count($this->importedSymbols);
311
+ }
312
+
313
+ public function pushLocalScope()
314
+ {
315
+ array_unshift($this->importedSymbols, array());
316
+ }
317
+
318
+ public function popLocalScope()
319
+ {
320
+ array_shift($this->importedSymbols);
321
+ }
322
+
323
+ /**
324
+ * Gets the expression parser.
325
+ *
326
+ * @return Twig_ExpressionParser The expression parser
327
+ */
328
+ public function getExpressionParser()
329
+ {
330
+ return $this->expressionParser;
331
+ }
332
+
333
+ public function getParent()
334
+ {
335
+ return $this->parent;
336
+ }
337
+
338
+ public function setParent($parent)
339
+ {
340
+ $this->parent = $parent;
341
+ }
342
+
343
+ /**
344
+ * Gets the token stream.
345
+ *
346
+ * @return Twig_TokenStream The token stream
347
+ */
348
+ public function getStream()
349
+ {
350
+ return $this->stream;
351
+ }
352
+
353
+ /**
354
+ * Gets the current token.
355
+ *
356
+ * @return Twig_Token The current token
357
+ */
358
+ public function getCurrentToken()
359
+ {
360
+ return $this->stream->getCurrent();
361
+ }
362
+
363
+ protected function filterBodyNodes(Twig_NodeInterface $node)
364
+ {
365
+ // check that the body does not contain non-empty output nodes
366
+ if (
367
+ ($node instanceof Twig_Node_Text && !ctype_space($node->getAttribute('data')))
368
+ ||
369
+ (!$node instanceof Twig_Node_Text && !$node instanceof Twig_Node_BlockReference && $node instanceof Twig_NodeOutputInterface)
370
+ ) {
371
+ if (false !== strpos((string) $node, chr(0xEF).chr(0xBB).chr(0xBF))) {
372
+ 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());
373
+ }
374
+
375
+ throw new Twig_Error_Syntax('A template that extends another one cannot have a body.', $node->getLine(), $this->getFilename());
376
+ }
377
+
378
+ // bypass "set" nodes as they "capture" the output
379
+ if ($node instanceof Twig_Node_Set) {
380
+ return $node;
381
+ }
382
+
383
+ if ($node instanceof Twig_NodeOutputInterface) {
384
+ return;
385
+ }
386
+
387
+ foreach ($node as $k => $n) {
388
+ if (null !== $n && null === $n = $this->filterBodyNodes($n)) {
389
+ $node->removeNode($k);
390
+ }
391
+ }
392
+
393
+ return $node;
394
+ }
395
+ }
Twig/lib/Twig/ParserInterface.php ADDED
@@ -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 parser classes.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ interface Twig_ParserInterface
20
+ {
21
+ /**
22
+ * Converts a token stream to a node tree.
23
+ *
24
+ * @param Twig_TokenStream $stream A token stream instance
25
+ *
26
+ * @return Twig_Node_Module A node tree
27
+ */
28
+ public function parse(Twig_TokenStream $stream);
29
+ }
Twig/lib/Twig/Sandbox/SecurityError.php ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Sandbox_SecurityError extends Twig_Error
19
+ {
20
+ }
Twig/lib/Twig/Sandbox/SecurityPolicy.php ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_Sandbox_SecurityPolicy implements Twig_Sandbox_SecurityPolicyInterface
19
+ {
20
+ protected $allowedTags;
21
+ protected $allowedFilters;
22
+ protected $allowedMethods;
23
+ protected $allowedProperties;
24
+ protected $allowedFunctions;
25
+
26
+ public function __construct(array $allowedTags = array(), array $allowedFilters = array(), array $allowedMethods = array(), array $allowedProperties = array(), array $allowedFunctions = array())
27
+ {
28
+ $this->allowedTags = $allowedTags;
29
+ $this->allowedFilters = $allowedFilters;
30
+ $this->setAllowedMethods($allowedMethods);
31
+ $this->allowedProperties = $allowedProperties;
32
+ $this->allowedFunctions = $allowedFunctions;
33
+ }
34
+
35
+ public function setAllowedTags(array $tags)
36
+ {
37
+ $this->allowedTags = $tags;
38
+ }
39
+
40
+ public function setAllowedFilters(array $filters)
41
+ {
42
+ $this->allowedFilters = $filters;
43
+ }
44
+
45
+ public function setAllowedMethods(array $methods)
46
+ {
47
+ $this->allowedMethods = array();
48
+ foreach ($methods as $class => $m) {
49
+ $this->allowedMethods[$class] = array_map('strtolower', is_array($m) ? $m : array($m));
50
+ }
51
+ }
52
+
53
+ public function setAllowedProperties(array $properties)
54
+ {
55
+ $this->allowedProperties = $properties;
56
+ }
57
+
58
+ public function setAllowedFunctions(array $functions)
59
+ {
60
+ $this->allowedFunctions = $functions;
61
+ }
62
+
63
+ public function checkSecurity($tags, $filters, $functions)
64
+ {
65
+ foreach ($tags as $tag) {
66
+ if (!in_array($tag, $this->allowedTags)) {
67
+ throw new Twig_Sandbox_SecurityError(sprintf('Tag "%s" is not allowed.', $tag));
68
+ }
69
+ }
70
+
71
+ foreach ($filters as $filter) {
72
+ if (!in_array($filter, $this->allowedFilters)) {
73
+ throw new Twig_Sandbox_SecurityError(sprintf('Filter "%s" is not allowed.', $filter));
74
+ }
75
+ }
76
+
77
+ foreach ($functions as $function) {
78
+ if (!in_array($function, $this->allowedFunctions)) {
79
+ throw new Twig_Sandbox_SecurityError(sprintf('Function "%s" is not allowed.', $function));
80
+ }
81
+ }
82
+ }
83
+
84
+ public function checkMethodAllowed($obj, $method)
85
+ {
86
+ if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) {
87
+ return true;
88
+ }
89
+
90
+ $allowed = false;
91
+ $method = strtolower($method);
92
+ foreach ($this->allowedMethods as $class => $methods) {
93
+ if ($obj instanceof $class) {
94
+ $allowed = in_array($method, $methods);
95
+
96
+ break;
97
+ }
98
+ }
99
+
100
+ if (!$allowed) {
101
+ throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" method on a "%s" object is not allowed.', $method, get_class($obj)));
102
+ }
103
+ }
104
+
105
+ public function checkPropertyAllowed($obj, $property)
106
+ {
107
+ $allowed = false;
108
+ foreach ($this->allowedProperties as $class => $properties) {
109
+ if ($obj instanceof $class) {
110
+ $allowed = in_array($property, is_array($properties) ? $properties : array($properties));
111
+
112
+ break;
113
+ }
114
+ }
115
+
116
+ if (!$allowed) {
117
+ throw new Twig_Sandbox_SecurityError(sprintf('Calling "%s" property on a "%s" object is not allowed.', $property, get_class($obj)));
118
+ }
119
+ }
120
+ }
Twig/lib/Twig/Sandbox/SecurityPolicyInterface.php ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ interface Twig_Sandbox_SecurityPolicyInterface
19
+ {
20
+ public function checkSecurity($tags, $filters, $functions);
21
+
22
+ public function checkMethodAllowed($obj, $method);
23
+
24
+ public function checkPropertyAllowed($obj, $method);
25
+ }
Twig/lib/Twig/SimpleFilter.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_SimpleFilter
19
+ {
20
+ protected $name;
21
+ protected $callable;
22
+ protected $options;
23
+ protected $arguments = array();
24
+
25
+ public function __construct($name, $callable, array $options = array())
26
+ {
27
+ $this->name = $name;
28
+ $this->callable = $callable;
29
+ $this->options = array_merge(array(
30
+ 'needs_environment' => false,
31
+ 'needs_context' => false,
32
+ 'is_safe' => null,
33
+ 'is_safe_callback' => null,
34
+ 'pre_escape' => null,
35
+ 'preserves_safety' => null,
36
+ 'node_class' => 'Twig_Node_Expression_Filter',
37
+ ), $options);
38
+ }
39
+
40
+ public function getName()
41
+ {
42
+ return $this->name;
43
+ }
44
+
45
+ public function getCallable()
46
+ {
47
+ return $this->callable;
48
+ }
49
+
50
+ public function getNodeClass()
51
+ {
52
+ return $this->options['node_class'];
53
+ }
54
+
55
+ public function setArguments($arguments)
56
+ {
57
+ $this->arguments = $arguments;
58
+ }
59
+
60
+ public function getArguments()
61
+ {
62
+ return $this->arguments;
63
+ }
64
+
65
+ public function needsEnvironment()
66
+ {
67
+ return $this->options['needs_environment'];
68
+ }
69
+
70
+ public function needsContext()
71
+ {
72
+ return $this->options['needs_context'];
73
+ }
74
+
75
+ public function getSafe(Twig_Node $filterArgs)
76
+ {
77
+ if (null !== $this->options['is_safe']) {
78
+ return $this->options['is_safe'];
79
+ }
80
+
81
+ if (null !== $this->options['is_safe_callback']) {
82
+ return call_user_func($this->options['is_safe_callback'], $filterArgs);
83
+ }
84
+
85
+ return null;
86
+ }
87
+
88
+ public function getPreservesSafety()
89
+ {
90
+ return $this->options['preserves_safety'];
91
+ }
92
+
93
+ public function getPreEscape()
94
+ {
95
+ return $this->options['pre_escape'];
96
+ }
97
+ }
Twig/lib/Twig/SimpleFunction.php ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_SimpleFunction
19
+ {
20
+ protected $name;
21
+ protected $callable;
22
+ protected $options;
23
+ protected $arguments = array();
24
+
25
+ public function __construct($name, $callable, array $options = array())
26
+ {
27
+ $this->name = $name;
28
+ $this->callable = $callable;
29
+ $this->options = array_merge(array(
30
+ 'needs_environment' => false,
31
+ 'needs_context' => false,
32
+ 'is_safe' => null,
33
+ 'is_safe_callback' => null,
34
+ 'node_class' => 'Twig_Node_Expression_Function',
35
+ ), $options);
36
+ }
37
+
38
+ public function getName()
39
+ {
40
+ return $this->name;
41
+ }
42
+
43
+ public function getCallable()
44
+ {
45
+ return $this->callable;
46
+ }
47
+
48
+ public function getNodeClass()
49
+ {
50
+ return $this->options['node_class'];
51
+ }
52
+
53
+ public function setArguments($arguments)
54
+ {
55
+ $this->arguments = $arguments;
56
+ }
57
+
58
+ public function getArguments()
59
+ {
60
+ return $this->arguments;
61
+ }
62
+
63
+ public function needsEnvironment()
64
+ {
65
+ return $this->options['needs_environment'];
66
+ }
67
+
68
+ public function needsContext()
69
+ {
70
+ return $this->options['needs_context'];
71
+ }
72
+
73
+ public function getSafe(Twig_Node $functionArgs)
74
+ {
75
+ if (null !== $this->options['is_safe']) {
76
+ return $this->options['is_safe'];
77
+ }
78
+
79
+ if (null !== $this->options['is_safe_callback']) {
80
+ return call_user_func($this->options['is_safe_callback'], $functionArgs);
81
+ }
82
+
83
+ return array();
84
+ }
85
+ }
Twig/lib/Twig/SimpleTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ class Twig_SimpleTest
19
+ {
20
+ protected $name;
21
+ protected $callable;
22
+ protected $options;
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
+ 'node_class' => 'Twig_Node_Expression_Test',
30
+ ), $options);
31
+ }
32
+
33
+ public function getName()
34
+ {
35
+ return $this->name;
36
+ }
37
+
38
+ public function getCallable()
39
+ {
40
+ return $this->callable;
41
+ }
42
+
43
+ public function getNodeClass()
44
+ {
45
+ return $this->options['node_class'];
46
+ }
47
+ }
Twig/lib/Twig/Template.php ADDED
@@ -0,0 +1,454 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ abstract class Twig_Template implements Twig_TemplateInterface
20
+ {
21
+ protected static $cache = array();
22
+
23
+ protected $parent;
24
+ protected $parents;
25
+ protected $env;
26
+ protected $blocks;
27
+ protected $traits;
28
+
29
+ /**
30
+ * Constructor.
31
+ *
32
+ * @param Twig_Environment $env A Twig_Environment instance
33
+ */
34
+ public function __construct(Twig_Environment $env)
35
+ {
36
+ $this->env = $env;
37
+ $this->blocks = array();
38
+ $this->traits = array();
39
+ }
40
+
41
+ /**
42
+ * Returns the template name.
43
+ *
44
+ * @return string The template name
45
+ */
46
+ abstract public function getTemplateName();
47
+
48
+ /**
49
+ * {@inheritdoc}
50
+ */
51
+ public function getEnvironment()
52
+ {
53
+ return $this->env;
54
+ }
55
+
56
+ /**
57
+ * Returns the parent template.
58
+ *
59
+ * This method is for internal use only and should never be called
60
+ * directly.
61
+ *
62
+ * @return Twig_TemplateInterface|false The parent template or false if there is no parent
63
+ */
64
+ public function getParent(array $context)
65
+ {
66
+ if (null !== $this->parent) {
67
+ return $this->parent;
68
+ }
69
+
70
+ $parent = $this->doGetParent($context);
71
+ if (false === $parent) {
72
+ return false;
73
+ } elseif ($parent instanceof Twig_Template) {
74
+ $name = $parent->getTemplateName();
75
+ $this->parents[$name] = $parent;
76
+ $parent = $name;
77
+ } elseif (!isset($this->parents[$parent])) {
78
+ $this->parents[$parent] = $this->env->loadTemplate($parent);
79
+ }
80
+
81
+ return $this->parents[$parent];
82
+ }
83
+
84
+ protected function doGetParent(array $context)
85
+ {
86
+ return false;
87
+ }
88
+
89
+ public function isTraitable()
90
+ {
91
+ return true;
92
+ }
93
+
94
+ /**
95
+ * Displays a parent block.
96
+ *
97
+ * This method is for internal use only and should never be called
98
+ * directly.
99
+ *
100
+ * @param string $name The block name to display from the parent
101
+ * @param array $context The context
102
+ * @param array $blocks The current set of blocks
103
+ */
104
+ public function displayParentBlock($name, array $context, array $blocks = array())
105
+ {
106
+ $name = (string) $name;
107
+
108
+ if (isset($this->traits[$name])) {
109
+ $this->traits[$name][0]->displayBlock($name, $context, $blocks);
110
+ } elseif (false !== $parent = $this->getParent($context)) {
111
+ $parent->displayBlock($name, $context, $blocks);
112
+ } else {
113
+ throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName());
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Displays a block.
119
+ *
120
+ * This method is for internal use only and should never be called
121
+ * directly.
122
+ *
123
+ * @param string $name The block name to display
124
+ * @param array $context The context
125
+ * @param array $blocks The current set of blocks
126
+ */
127
+ public function displayBlock($name, array $context, array $blocks = array())
128
+ {
129
+ $name = (string) $name;
130
+
131
+ if (isset($blocks[$name])) {
132
+ $b = $blocks;
133
+ unset($b[$name]);
134
+ call_user_func($blocks[$name], $context, $b);
135
+ } elseif (isset($this->blocks[$name])) {
136
+ call_user_func($this->blocks[$name], $context, $blocks);
137
+ } elseif (false !== $parent = $this->getParent($context)) {
138
+ $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks));
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Renders a parent block.
144
+ *
145
+ * This method is for internal use only and should never be called
146
+ * directly.
147
+ *
148
+ * @param string $name The block name to render from the parent
149
+ * @param array $context The context
150
+ * @param array $blocks The current set of blocks
151
+ *
152
+ * @return string The rendered block
153
+ */
154
+ public function renderParentBlock($name, array $context, array $blocks = array())
155
+ {
156
+ ob_start();
157
+ $this->displayParentBlock($name, $context, $blocks);
158
+
159
+ return ob_get_clean();
160
+ }
161
+
162
+ /**
163
+ * Renders a block.
164
+ *
165
+ * This method is for internal use only and should never be called
166
+ * directly.
167
+ *
168
+ * @param string $name The block name to render
169
+ * @param array $context The context
170
+ * @param array $blocks The current set of blocks
171
+ *
172
+ * @return string The rendered block
173
+ */
174
+ public function renderBlock($name, array $context, array $blocks = array())
175
+ {
176
+ ob_start();
177
+ $this->displayBlock($name, $context, $blocks);
178
+
179
+ return ob_get_clean();
180
+ }
181
+
182
+ /**
183
+ * Returns whether a block exists or not.
184
+ *
185
+ * This method is for internal use only and should never be called
186
+ * directly.
187
+ *
188
+ * This method does only return blocks defined in the current template
189
+ * or defined in "used" traits.
190
+ *
191
+ * It does not return blocks from parent templates as the parent
192
+ * template name can be dynamic, which is only known based on the
193
+ * current context.
194
+ *
195
+ * @param string $name The block name
196
+ *
197
+ * @return Boolean true if the block exists, false otherwise
198
+ */
199
+ public function hasBlock($name)
200
+ {
201
+ return isset($this->blocks[(string) $name]);
202
+ }
203
+
204
+ /**
205
+ * Returns all block names.
206
+ *
207
+ * This method is for internal use only and should never be called
208
+ * directly.
209
+ *
210
+ * @return array An array of block names
211
+ *
212
+ * @see hasBlock
213
+ */
214
+ public function getBlockNames()
215
+ {
216
+ return array_keys($this->blocks);
217
+ }
218
+
219
+ /**
220
+ * Returns all blocks.
221
+ *
222
+ * This method is for internal use only and should never be called
223
+ * directly.
224
+ *
225
+ * @return array An array of blocks
226
+ *
227
+ * @see hasBlock
228
+ */
229
+ public function getBlocks()
230
+ {
231
+ return $this->blocks;
232
+ }
233
+
234
+ /**
235
+ * {@inheritdoc}
236
+ */
237
+ public function display(array $context, array $blocks = array())
238
+ {
239
+ $this->displayWithErrorHandling($this->env->mergeGlobals($context), $blocks);
240
+ }
241
+
242
+ /**
243
+ * {@inheritdoc}
244
+ */
245
+ public function render(array $context)
246
+ {
247
+ $level = ob_get_level();
248
+ ob_start();
249
+ try {
250
+ $this->display($context);
251
+ } catch (Exception $e) {
252
+ while (ob_get_level() > $level) {
253
+ ob_end_clean();
254
+ }
255
+
256
+ throw $e;
257
+ }
258
+
259
+ return ob_get_clean();
260
+ }
261
+
262
+ protected function displayWithErrorHandling(array $context, array $blocks = array())
263
+ {
264
+ try {
265
+ $this->doDisplay($context, $blocks);
266
+ } catch (Twig_Error $e) {
267
+ if (!$e->getTemplateFile()) {
268
+ $e->setTemplateFile($this->getTemplateName());
269
+ }
270
+
271
+ // this is mostly useful for Twig_Error_Loader exceptions
272
+ // see Twig_Error_Loader
273
+ if (false === $e->getTemplateLine()) {
274
+ $e->setTemplateLine(-1);
275
+ $e->guess();
276
+ }
277
+
278
+ throw $e;
279
+ } catch (Exception $e) {
280
+ throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e);
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Auto-generated method to display the template with the given context.
286
+ *
287
+ * @param array $context An array of parameters to pass to the template
288
+ * @param array $blocks An array of blocks to pass to the template
289
+ */
290
+ abstract protected function doDisplay(array $context, array $blocks = array());
291
+
292
+ /**
293
+ * Returns a variable from the context.
294
+ *
295
+ * This method is for internal use only and should never be called
296
+ * directly.
297
+ *
298
+ * This method should not be overridden in a sub-class as this is an
299
+ * implementation detail that has been introduced to optimize variable
300
+ * access for versions of PHP before 5.4. This is not a way to override
301
+ * the way to get a variable value.
302
+ *
303
+ * @param array $context The context
304
+ * @param string $item The variable to return from the context
305
+ * @param Boolean $ignoreStrictCheck Whether to ignore the strict variable check or not
306
+ *
307
+ * @return The content of the context variable
308
+ *
309
+ * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode
310
+ */
311
+ final protected function getContext($context, $item, $ignoreStrictCheck = false)
312
+ {
313
+ if (!array_key_exists($item, $context)) {
314
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
315
+ return null;
316
+ }
317
+
318
+ throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item), -1, $this->getTemplateName());
319
+ }
320
+
321
+ return $context[$item];
322
+ }
323
+
324
+ /**
325
+ * Returns the attribute value for a given array/object.
326
+ *
327
+ * @param mixed $object The object or array from where to get the item
328
+ * @param mixed $item The item to get from the array or object
329
+ * @param array $arguments An array of arguments to pass if the item is an object method
330
+ * @param string $type The type of attribute (@see Twig_TemplateInterface)
331
+ * @param Boolean $isDefinedTest Whether this is only a defined check
332
+ * @param Boolean $ignoreStrictCheck Whether to ignore the strict attribute check or not
333
+ *
334
+ * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
335
+ *
336
+ * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
337
+ */
338
+ protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
339
+ {
340
+ $item = ctype_digit((string) $item) ? (int) $item : (string) $item;
341
+
342
+ // array
343
+ if (Twig_TemplateInterface::METHOD_CALL !== $type) {
344
+ if ((is_array($object) && array_key_exists($item, $object))
345
+ || ($object instanceof ArrayAccess && isset($object[$item]))
346
+ ) {
347
+ if ($isDefinedTest) {
348
+ return true;
349
+ }
350
+
351
+ return $object[$item];
352
+ }
353
+
354
+ if (Twig_TemplateInterface::ARRAY_CALL === $type) {
355
+ if ($isDefinedTest) {
356
+ return false;
357
+ }
358
+
359
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
360
+ return null;
361
+ }
362
+
363
+ if (is_object($object)) {
364
+ throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
365
+ } elseif (is_array($object)) {
366
+ throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName());
367
+ } else {
368
+ throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName());
369
+ }
370
+ }
371
+ }
372
+
373
+ if (!is_object($object)) {
374
+ if ($isDefinedTest) {
375
+ return false;
376
+ }
377
+
378
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
379
+ return null;
380
+ }
381
+
382
+ throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object), -1, $this->getTemplateName());
383
+ }
384
+
385
+ $class = get_class($object);
386
+
387
+ // object property
388
+ if (Twig_TemplateInterface::METHOD_CALL !== $type) {
389
+ if (isset($object->$item) || array_key_exists($item, $object)) {
390
+ if ($isDefinedTest) {
391
+ return true;
392
+ }
393
+
394
+ if ($this->env->hasExtension('sandbox')) {
395
+ $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item);
396
+ }
397
+
398
+ return $object->$item;
399
+ }
400
+ }
401
+
402
+ // object method
403
+ if (!isset(self::$cache[$class]['methods'])) {
404
+ self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object)));
405
+ }
406
+
407
+ $lcItem = strtolower($item);
408
+ if (isset(self::$cache[$class]['methods'][$lcItem])) {
409
+ $method = $item;
410
+ } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) {
411
+ $method = 'get'.$item;
412
+ } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) {
413
+ $method = 'is'.$item;
414
+ } elseif (isset(self::$cache[$class]['methods']['__call'])) {
415
+ $method = $item;
416
+ } else {
417
+ if ($isDefinedTest) {
418
+ return false;
419
+ }
420
+
421
+ if ($ignoreStrictCheck || !$this->env->isStrictVariables()) {
422
+ return null;
423
+ }
424
+
425
+ throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName());
426
+ }
427
+
428
+ if ($isDefinedTest) {
429
+ return true;
430
+ }
431
+
432
+ if ($this->env->hasExtension('sandbox')) {
433
+ $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method);
434
+ }
435
+
436
+ $ret = call_user_func_array(array($object, $method), $arguments);
437
+
438
+ // useful when calling a template method from a template
439
+ // this is not supported but unfortunately heavily used in the Symfony profiler
440
+ if ($object instanceof Twig_TemplateInterface) {
441
+ return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset());
442
+ }
443
+
444
+ return $ret;
445
+ }
446
+
447
+ /**
448
+ * This method is only useful when testing Twig. Do not use it.
449
+ */
450
+ public static function clearCache()
451
+ {
452
+ self::$cache = array();
453
+ }
454
+ }
Twig/lib/Twig/TemplateInterface.php ADDED
@@ -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
+ * Interface implemented by all compiled templates.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ interface Twig_TemplateInterface
20
+ {
21
+ const ANY_CALL = 'any';
22
+ const ARRAY_CALL = 'array';
23
+ const METHOD_CALL = 'method';
24
+
25
+ /**
26
+ * Renders the template with the given context and returns it as string.
27
+ *
28
+ * @param array $context An array of parameters to pass to the template
29
+ *
30
+ * @return string The rendered template
31
+ */
32
+ public function render(array $context);
33
+
34
+ /**
35
+ * Displays the template with the given context.
36
+ *
37
+ * @param array $context An array of parameters to pass to the template
38
+ * @param array $blocks An array of blocks to pass to the template
39
+ */
40
+ public function display(array $context, array $blocks = array());
41
+
42
+ /**
43
+ * Returns the bound environment for this template.
44
+ *
45
+ * @return Twig_Environment The current environment
46
+ */
47
+ public function getEnvironment();
48
+ }
Twig/lib/Twig/Test.php ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ abstract class Twig_Test implements Twig_TestInterface, Twig_TestCallableInterface
20
+ {
21
+ protected $options;
22
+ protected $arguments = array();
23
+
24
+ public function __construct(array $options = array())
25
+ {
26
+ $this->options = array_merge(array(
27
+ 'callable' => null,
28
+ ), $options);
29
+ }
30
+
31
+ public function getCallable()
32
+ {
33
+ return $this->options['callable'];
34
+ }
35
+ }
Twig/lib/Twig/Test/Function.php ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ class Twig_Test_Function extends Twig_Test
20
+ {
21
+ protected $function;
22
+
23
+ public function __construct($function, array $options = array())
24
+ {
25
+ $options['callable'] = $function;
26
+
27
+ parent::__construct($options);
28
+
29
+ $this->function = $function;
30
+ }
31
+
32
+ public function compile()
33
+ {
34
+ return $this->function;
35
+ }
36
+ }
Twig/lib/Twig/Test/IntegrationTestCase.php ADDED
@@ -0,0 +1,155 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @author Karma Dordrak <drak@zikula.org>
18
+ */
19
+ abstract class Twig_Test_IntegrationTestCase extends PHPUnit_Framework_TestCase
20
+ {
21
+ abstract protected function getExtensions();
22
+ abstract protected function getFixturesDir();
23
+
24
+ /**
25
+ * @dataProvider getTests
26
+ */
27
+ public function testIntegration($file, $message, $condition, $templates, $exception, $outputs)
28
+ {
29
+ $this->doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs);
30
+ }
31
+
32
+ public function getTests()
33
+ {
34
+ $fixturesDir = realpath($this->getFixturesDir());
35
+ $tests = array();
36
+
37
+ foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixturesDir), RecursiveIteratorIterator::LEAVES_ONLY) as $file) {
38
+ if (!preg_match('/\.test$/', $file)) {
39
+ continue;
40
+ }
41
+
42
+ $test = file_get_contents($file->getRealpath());
43
+
44
+ if (preg_match('/
45
+ --TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)\s*(?:--DATA--\s*(.*))?\s*--EXCEPTION--\s*(.*)/sx', $test, $match)) {
46
+ $message = $match[1];
47
+ $condition = $match[2];
48
+ $templates = $this->parseTemplates($match[3]);
49
+ $exception = $match[5];
50
+ $outputs = array(array(null, $match[4], null, ''));
51
+ } elseif (preg_match('/--TEST--\s*(.*?)\s*(?:--CONDITION--\s*(.*))?\s*((?:--TEMPLATE(?:\(.*?\))?--(?:.*?))+)--DATA--.*?--EXPECT--.*/s', $test, $match)) {
52
+ $message = $match[1];
53
+ $condition = $match[2];
54
+ $templates = $this->parseTemplates($match[3]);
55
+ $exception = false;
56
+ preg_match_all('/--DATA--(.*?)(?:--CONFIG--(.*?))?--EXPECT--(.*?)(?=\-\-DATA\-\-|$)/s', $test, $outputs, PREG_SET_ORDER);
57
+ } else {
58
+ throw new InvalidArgumentException(sprintf('Test "%s" is not valid.', str_replace($fixturesDir.'/', '', $file)));
59
+ }
60
+
61
+ $tests[] = array(str_replace($fixturesDir.'/', '', $file), $message, $condition, $templates, $exception, $outputs);
62
+ }
63
+
64
+ return $tests;
65
+ }
66
+
67
+ protected function doIntegrationTest($file, $message, $condition, $templates, $exception, $outputs)
68
+ {
69
+ if ($condition) {
70
+ eval('$ret = '.$condition.';');
71
+ if (!$ret) {
72
+ $this->markTestSkipped($condition);
73
+ }
74
+ }
75
+
76
+ $loader = new Twig_Loader_Array($templates);
77
+
78
+ foreach ($outputs as $match) {
79
+ $config = array_merge(array(
80
+ 'cache' => false,
81
+ 'strict_variables' => true,
82
+ ), $match[2] ? eval($match[2].';') : array());
83
+ $twig = new Twig_Environment($loader, $config);
84
+ $twig->addGlobal('global', 'global');
85
+ foreach ($this->getExtensions() as $extension) {
86
+ $twig->addExtension($extension);
87
+ }
88
+
89
+ try {
90
+ $template = $twig->loadTemplate('index.twig');
91
+ } catch (Exception $e) {
92
+ if (false !== $exception) {
93
+ $this->assertEquals(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage())));
94
+
95
+ return;
96
+ }
97
+
98
+ if ($e instanceof Twig_Error_Syntax) {
99
+ $e->setTemplateFile($file);
100
+
101
+ throw $e;
102
+ }
103
+
104
+ throw new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e);
105
+ }
106
+
107
+ try {
108
+ $output = trim($template->render(eval($match[1].';')), "\n ");
109
+ } catch (Exception $e) {
110
+ if (false !== $exception) {
111
+ $this->assertEquals(trim($exception), trim(sprintf('%s: %s', get_class($e), $e->getMessage())));
112
+
113
+ return;
114
+ }
115
+
116
+ if ($e instanceof Twig_Error_Syntax) {
117
+ $e->setTemplateFile($file);
118
+ } else {
119
+ $e = new Twig_Error(sprintf('%s: %s', get_class($e), $e->getMessage()), -1, $file, $e);
120
+ }
121
+
122
+ $output = trim(sprintf('%s: %s', get_class($e), $e->getMessage()));
123
+ }
124
+
125
+ if (false !== $exception) {
126
+ list($class, ) = explode(':', $exception);
127
+ $this->assertThat(NULL, new PHPUnit_Framework_Constraint_Exception($class));
128
+ }
129
+
130
+ $expected = trim($match[3], "\n ");
131
+
132
+ if ($expected != $output) {
133
+ echo 'Compiled template that failed:';
134
+
135
+ foreach (array_keys($templates) as $name) {
136
+ echo "Template: $name\n";
137
+ $source = $loader->getSource($name);
138
+ echo $twig->compile($twig->parse($twig->tokenize($source, $name)));
139
+ }
140
+ }
141
+ $this->assertEquals($expected, $output, $message.' (in '.$file.')');
142
+ }
143
+ }
144
+
145
+ protected static function parseTemplates($test)
146
+ {
147
+ $templates = array();
148
+ preg_match_all('/--TEMPLATE(?:\((.*?)\))?--(.*?)(?=\-\-TEMPLATE|$)/s', $test, $matches, PREG_SET_ORDER);
149
+ foreach ($matches as $match) {
150
+ $templates[($match[1] ? $match[1] : 'index.twig')] = $match[2];
151
+ }
152
+
153
+ return $templates;
154
+ }
155
+ }
Twig/lib/Twig/Test/Method.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ class Twig_Test_Method extends Twig_Test
20
+ {
21
+ protected $extension;
22
+ protected $method;
23
+
24
+ public function __construct(Twig_ExtensionInterface $extension, $method, array $options = array())
25
+ {
26
+ $options['callable'] = array($extension, $method);
27
+
28
+ parent::__construct($options);
29
+
30
+ $this->extension = $extension;
31
+ $this->method = $method;
32
+ }
33
+
34
+ public function compile()
35
+ {
36
+ return sprintf('$this->env->getExtension(\'%s\')->%s', $this->extension->getName(), $this->method);
37
+ }
38
+ }
Twig/lib/Twig/Test/Node.php ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ class Twig_Test_Node extends Twig_Test
20
+ {
21
+ protected $class;
22
+
23
+ public function __construct($class, array $options = array())
24
+ {
25
+ parent::__construct($options);
26
+
27
+ $this->class = $class;
28
+ }
29
+
30
+ public function getClass()
31
+ {
32
+ return $this->class;
33
+ }
34
+
35
+ public function compile()
36
+ {
37
+ }
38
+ }
Twig/lib/Twig/Test/NodeTestCase.php ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ abstract class Twig_Test_NodeTestCase extends PHPUnit_Framework_TestCase
12
+ {
13
+ abstract public function getTests();
14
+
15
+ /**
16
+ * @dataProvider getTests
17
+ */
18
+ public function testCompile($node, $source, $environment = null)
19
+ {
20
+ $this->assertNodeCompilation($source, $node, $environment);
21
+ }
22
+
23
+ public function assertNodeCompilation($source, Twig_Node $node, Twig_Environment $environment = null)
24
+ {
25
+ $compiler = $this->getCompiler($environment);
26
+ $compiler->compile($node);
27
+
28
+ $this->assertEquals($source, trim($compiler->getSource()));
29
+ }
30
+
31
+ protected function getCompiler(Twig_Environment $environment = null)
32
+ {
33
+ return new Twig_Compiler(null === $environment ? $this->getEnvironment() : $environment);
34
+ }
35
+
36
+ protected function getEnvironment()
37
+ {
38
+ return new Twig_Environment();
39
+ }
40
+
41
+ protected function getVariableGetter($name)
42
+ {
43
+ if (version_compare(phpversion(), '5.4.0RC1', '>=')) {
44
+ return sprintf('(isset($context["%s"]) ? $context["%s"] : null)', $name, $name);
45
+ }
46
+
47
+ return sprintf('$this->getContext($context, "%s")', $name);
48
+ }
49
+
50
+ protected function getAttributeGetter()
51
+ {
52
+ if (function_exists('twig_template_get_attributes')) {
53
+ return 'twig_template_get_attributes($this, ';
54
+ }
55
+
56
+ return '$this->getAttribute(';
57
+ }
58
+ }
Twig/lib/Twig/TestCallableInterface.php ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ interface Twig_TestCallableInterface
20
+ {
21
+ public function getCallable();
22
+ }
Twig/lib/Twig/TestInterface.php ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ * @deprecated since 1.12 (to be removed in 2.0)
18
+ */
19
+ interface Twig_TestInterface
20
+ {
21
+ /**
22
+ * Compiles a test.
23
+ *
24
+ * @return string The PHP code for the test
25
+ */
26
+ public function compile();
27
+ }
Twig/lib/Twig/Token.php ADDED
@@ -0,0 +1,219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_Token
20
+ {
21
+ protected $value;
22
+ protected $type;
23
+ protected $lineno;
24
+
25
+ const EOF_TYPE = -1;
26
+ const TEXT_TYPE = 0;
27
+ const BLOCK_START_TYPE = 1;
28
+ const VAR_START_TYPE = 2;
29
+ const BLOCK_END_TYPE = 3;
30
+ const VAR_END_TYPE = 4;
31
+ const NAME_TYPE = 5;
32
+ const NUMBER_TYPE = 6;
33
+ const STRING_TYPE = 7;
34
+ const OPERATOR_TYPE = 8;
35
+ const PUNCTUATION_TYPE = 9;
36
+ const INTERPOLATION_START_TYPE = 10;
37
+ const INTERPOLATION_END_TYPE = 11;
38
+
39
+ /**
40
+ * Constructor.
41
+ *
42
+ * @param integer $type The type of the token
43
+ * @param string $value The token value
44
+ * @param integer $lineno The line position in the source
45
+ */
46
+ public function __construct($type, $value, $lineno)
47
+ {
48
+ $this->type = $type;
49
+ $this->value = $value;
50
+ $this->lineno = $lineno;
51
+ }
52
+
53
+ /**
54
+ * Returns a string representation of the token.
55
+ *
56
+ * @return string A string representation of the token
57
+ */
58
+ public function __toString()
59
+ {
60
+ return sprintf('%s(%s)', self::typeToString($this->type, true, $this->lineno), $this->value);
61
+ }
62
+
63
+ /**
64
+ * Tests the current token for a type and/or a value.
65
+ *
66
+ * Parameters may be:
67
+ * * just type
68
+ * * type and value (or array of possible values)
69
+ * * just value (or array of possible values) (NAME_TYPE is used as type)
70
+ *
71
+ * @param array|integer $type The type to test
72
+ * @param array|string|null $values The token value
73
+ *
74
+ * @return Boolean
75
+ */
76
+ public function test($type, $values = null)
77
+ {
78
+ if (null === $values && !is_int($type)) {
79
+ $values = $type;
80
+ $type = self::NAME_TYPE;
81
+ }
82
+
83
+ return ($this->type === $type) && (
84
+ null === $values ||
85
+ (is_array($values) && in_array($this->value, $values)) ||
86
+ $this->value == $values
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Gets the line.
92
+ *
93
+ * @return integer The source line
94
+ */
95
+ public function getLine()
96
+ {
97
+ return $this->lineno;
98
+ }
99
+
100
+ /**
101
+ * Gets the token type.
102
+ *
103
+ * @return integer The token type
104
+ */
105
+ public function getType()
106
+ {
107
+ return $this->type;
108
+ }
109
+
110
+ /**
111
+ * Gets the token value.
112
+ *
113
+ * @return string The token value
114
+ */
115
+ public function getValue()
116
+ {
117
+ return $this->value;
118
+ }
119
+
120
+ /**
121
+ * Returns the constant representation (internal) of a given type.
122
+ *
123
+ * @param integer $type The type as an integer
124
+ * @param Boolean $short Whether to return a short representation or not
125
+ * @param integer $line The code line
126
+ *
127
+ * @return string The string representation
128
+ */
129
+ public static function typeToString($type, $short = false, $line = -1)
130
+ {
131
+ switch ($type) {
132
+ case self::EOF_TYPE:
133
+ $name = 'EOF_TYPE';
134
+ break;
135
+ case self::TEXT_TYPE:
136
+ $name = 'TEXT_TYPE';
137
+ break;
138
+ case self::BLOCK_START_TYPE:
139
+ $name = 'BLOCK_START_TYPE';
140
+ break;
141
+ case self::VAR_START_TYPE:
142
+ $name = 'VAR_START_TYPE';
143
+ break;
144
+ case self::BLOCK_END_TYPE:
145
+ $name = 'BLOCK_END_TYPE';
146
+ break;
147
+ case self::VAR_END_TYPE:
148
+ $name = 'VAR_END_TYPE';
149
+ break;
150
+ case self::NAME_TYPE:
151
+ $name = 'NAME_TYPE';
152
+ break;
153
+ case self::NUMBER_TYPE:
154
+ $name = 'NUMBER_TYPE';
155
+ break;
156
+ case self::STRING_TYPE:
157
+ $name = 'STRING_TYPE';
158
+ break;
159
+ case self::OPERATOR_TYPE:
160
+ $name = 'OPERATOR_TYPE';
161
+ break;
162
+ case self::PUNCTUATION_TYPE:
163
+ $name = 'PUNCTUATION_TYPE';
164
+ break;
165
+ case self::INTERPOLATION_START_TYPE:
166
+ $name = 'INTERPOLATION_START_TYPE';
167
+ break;
168
+ case self::INTERPOLATION_END_TYPE:
169
+ $name = 'INTERPOLATION_END_TYPE';
170
+ break;
171
+ default:
172
+ throw new LogicException(sprintf('Token of type "%s" does not exist.', $type));
173
+ }
174
+
175
+ return $short ? $name : 'Twig_Token::'.$name;
176
+ }
177
+
178
+ /**
179
+ * Returns the english representation of a given type.
180
+ *
181
+ * @param integer $type The type as an integer
182
+ * @param integer $line The code line
183
+ *
184
+ * @return string The string representation
185
+ */
186
+ public static function typeToEnglish($type, $line = -1)
187
+ {
188
+ switch ($type) {
189
+ case self::EOF_TYPE:
190
+ return 'end of template';
191
+ case self::TEXT_TYPE:
192
+ return 'text';
193
+ case self::BLOCK_START_TYPE:
194
+ return 'begin of statement block';
195
+ case self::VAR_START_TYPE:
196
+ return 'begin of print statement';
197
+ case self::BLOCK_END_TYPE:
198
+ return 'end of statement block';
199
+ case self::VAR_END_TYPE:
200
+ return 'end of print statement';
201
+ case self::NAME_TYPE:
202
+ return 'name';
203
+ case self::NUMBER_TYPE:
204
+ return 'number';
205
+ case self::STRING_TYPE:
206
+ return 'string';
207
+ case self::OPERATOR_TYPE:
208
+ return 'operator';
209
+ case self::PUNCTUATION_TYPE:
210
+ return 'punctuation';
211
+ case self::INTERPOLATION_START_TYPE:
212
+ return 'begin of string interpolation';
213
+ case self::INTERPOLATION_END_TYPE:
214
+ return 'end of string interpolation';
215
+ default:
216
+ throw new LogicException(sprintf('Token of type "%s" does not exist.', $type));
217
+ }
218
+ }
219
+ }
Twig/lib/Twig/TokenParser.php ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ abstract class Twig_TokenParser implements Twig_TokenParserInterface
19
+ {
20
+ /**
21
+ * @var Twig_Parser
22
+ */
23
+ protected $parser;
24
+
25
+ /**
26
+ * Sets the parser associated with this token parser
27
+ *
28
+ * @param $parser A Twig_Parser instance
29
+ */
30
+ public function setParser(Twig_Parser $parser)
31
+ {
32
+ $this->parser = $parser;
33
+ }
34
+ }
Twig/lib/Twig/TokenParser/AutoEscape.php ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Marks a section of a template to be escaped or not.
14
+ *
15
+ * <pre>
16
+ * {% autoescape true %}
17
+ * Everything will be automatically escaped in this block
18
+ * {% endautoescape %}
19
+ *
20
+ * {% autoescape false %}
21
+ * Everything will be outputed as is in this block
22
+ * {% endautoescape %}
23
+ *
24
+ * {% autoescape true js %}
25
+ * Everything will be automatically escaped in this block
26
+ * using the js escaping strategy
27
+ * {% endautoescape %}
28
+ * </pre>
29
+ */
30
+ class Twig_TokenParser_AutoEscape extends Twig_TokenParser
31
+ {
32
+ /**
33
+ * Parses a token and returns a node.
34
+ *
35
+ * @param Twig_Token $token A Twig_Token instance
36
+ *
37
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
38
+ */
39
+ public function parse(Twig_Token $token)
40
+ {
41
+ $lineno = $token->getLine();
42
+ $stream = $this->parser->getStream();
43
+
44
+ if ($stream->test(Twig_Token::BLOCK_END_TYPE)) {
45
+ $value = 'html';
46
+ } else {
47
+ $expr = $this->parser->getExpressionParser()->parseExpression();
48
+ if (!$expr instanceof Twig_Node_Expression_Constant) {
49
+ throw new Twig_Error_Syntax('An escaping strategy must be a string or a Boolean.', $stream->getCurrent()->getLine(), $stream->getFilename());
50
+ }
51
+ $value = $expr->getAttribute('value');
52
+
53
+ $compat = true === $value || false === $value;
54
+
55
+ if (true === $value) {
56
+ $value = 'html';
57
+ }
58
+
59
+ if ($compat && $stream->test(Twig_Token::NAME_TYPE)) {
60
+ if (false === $value) {
61
+ throw new Twig_Error_Syntax('Unexpected escaping strategy as you set autoescaping to false.', $stream->getCurrent()->getLine(), $stream->getFilename());
62
+ }
63
+
64
+ $value = $stream->next()->getValue();
65
+ }
66
+ }
67
+
68
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
69
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
70
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
71
+
72
+ return new Twig_Node_AutoEscape($value, $body, $lineno, $this->getTag());
73
+ }
74
+
75
+ public function decideBlockEnd(Twig_Token $token)
76
+ {
77
+ return $token->test('endautoescape');
78
+ }
79
+
80
+ /**
81
+ * Gets the tag name associated with this token parser.
82
+ *
83
+ * @return string The tag name
84
+ */
85
+ public function getTag()
86
+ {
87
+ return 'autoescape';
88
+ }
89
+ }
Twig/lib/Twig/TokenParser/Block.php ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ /**
14
+ * Marks a section of a template as being reusable.
15
+ *
16
+ * <pre>
17
+ * {% block head %}
18
+ * <link rel="stylesheet" href="style.css" />
19
+ * <title>{% block title %}{% endblock %} - My Webpage</title>
20
+ * {% endblock %}
21
+ * </pre>
22
+ */
23
+ class Twig_TokenParser_Block extends Twig_TokenParser
24
+ {
25
+ /**
26
+ * Parses a token and returns a node.
27
+ *
28
+ * @param Twig_Token $token A Twig_Token instance
29
+ *
30
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
31
+ */
32
+ public function parse(Twig_Token $token)
33
+ {
34
+ $lineno = $token->getLine();
35
+ $stream = $this->parser->getStream();
36
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
37
+ if ($this->parser->hasBlock($name)) {
38
+ throw new Twig_Error_Syntax(sprintf("The block '$name' has already been defined line %d", $this->parser->getBlock($name)->getLine()), $stream->getCurrent()->getLine(), $stream->getFilename());
39
+ }
40
+ $this->parser->setBlock($name, $block = new Twig_Node_Block($name, new Twig_Node(array()), $lineno));
41
+ $this->parser->pushLocalScope();
42
+ $this->parser->pushBlockStack($name);
43
+
44
+ if ($stream->test(Twig_Token::BLOCK_END_TYPE)) {
45
+ $stream->next();
46
+
47
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
48
+ if ($stream->test(Twig_Token::NAME_TYPE)) {
49
+ $value = $stream->next()->getValue();
50
+
51
+ if ($value != $name) {
52
+ throw new Twig_Error_Syntax(sprintf("Expected endblock for block '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename());
53
+ }
54
+ }
55
+ } else {
56
+ $body = new Twig_Node(array(
57
+ new Twig_Node_Print($this->parser->getExpressionParser()->parseExpression(), $lineno),
58
+ ));
59
+ }
60
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
61
+
62
+ $block->setNode('body', $body);
63
+ $this->parser->popBlockStack();
64
+ $this->parser->popLocalScope();
65
+
66
+ return new Twig_Node_BlockReference($name, $lineno, $this->getTag());
67
+ }
68
+
69
+ public function decideBlockEnd(Twig_Token $token)
70
+ {
71
+ return $token->test('endblock');
72
+ }
73
+
74
+ /**
75
+ * Gets the tag name associated with this token parser.
76
+ *
77
+ * @return string The tag name
78
+ */
79
+ public function getTag()
80
+ {
81
+ return 'block';
82
+ }
83
+ }
Twig/lib/Twig/TokenParser/Do.php ADDED
@@ -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, disgarding the returned value.
14
+ */
15
+ class Twig_TokenParser_Do extends Twig_TokenParser
16
+ {
17
+ /**
18
+ * Parses a token and returns a node.
19
+ *
20
+ * @param Twig_Token $token A Twig_Token instance
21
+ *
22
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
23
+ */
24
+ public function parse(Twig_Token $token)
25
+ {
26
+ $expr = $this->parser->getExpressionParser()->parseExpression();
27
+
28
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
29
+
30
+ return new Twig_Node_Do($expr, $token->getLine(), $this->getTag());
31
+ }
32
+
33
+ /**
34
+ * Gets the tag name associated with this token parser.
35
+ *
36
+ * @return string The tag name
37
+ */
38
+ public function getTag()
39
+ {
40
+ return 'do';
41
+ }
42
+ }
Twig/lib/Twig/TokenParser/Embed.php ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2012 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Embeds a template.
14
+ */
15
+ class Twig_TokenParser_Embed extends Twig_TokenParser_Include
16
+ {
17
+ /**
18
+ * Parses a token and returns a node.
19
+ *
20
+ * @param Twig_Token $token A Twig_Token instance
21
+ *
22
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
23
+ */
24
+ public function parse(Twig_Token $token)
25
+ {
26
+ $stream = $this->parser->getStream();
27
+
28
+ $parent = $this->parser->getExpressionParser()->parseExpression();
29
+
30
+ list($variables, $only, $ignoreMissing) = $this->parseArguments();
31
+
32
+ // inject a fake parent to make the parent() function work
33
+ $stream->injectTokens(array(
34
+ new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', $token->getLine()),
35
+ new Twig_Token(Twig_Token::NAME_TYPE, 'extends', $token->getLine()),
36
+ new Twig_Token(Twig_Token::STRING_TYPE, '__parent__', $token->getLine()),
37
+ new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', $token->getLine()),
38
+ ));
39
+
40
+ $module = $this->parser->parse($stream, array($this, 'decideBlockEnd'), true);
41
+
42
+ // override the parent with the correct one
43
+ $module->setNode('parent', $parent);
44
+
45
+ $this->parser->embedTemplate($module);
46
+
47
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
48
+
49
+ return new Twig_Node_Embed($module->getAttribute('filename'), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
50
+ }
51
+
52
+ public function decideBlockEnd(Twig_Token $token)
53
+ {
54
+ return $token->test('endembed');
55
+ }
56
+
57
+ /**
58
+ * Gets the tag name associated with this token parser.
59
+ *
60
+ * @return string The tag name
61
+ */
62
+ public function getTag()
63
+ {
64
+ return 'embed';
65
+ }
66
+ }
Twig/lib/Twig/TokenParser/Extends.php ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ /**
14
+ * Extends a template by another one.
15
+ *
16
+ * <pre>
17
+ * {% extends "base.html" %}
18
+ * </pre>
19
+ */
20
+ class Twig_TokenParser_Extends extends Twig_TokenParser
21
+ {
22
+ /**
23
+ * Parses a token and returns a node.
24
+ *
25
+ * @param Twig_Token $token A Twig_Token instance
26
+ *
27
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
28
+ */
29
+ public function parse(Twig_Token $token)
30
+ {
31
+ if (!$this->parser->isMainScope()) {
32
+ throw new Twig_Error_Syntax('Cannot extend from a block', $token->getLine(), $this->parser->getFilename());
33
+ }
34
+
35
+ if (null !== $this->parser->getParent()) {
36
+ throw new Twig_Error_Syntax('Multiple extends tags are forbidden', $token->getLine(), $this->parser->getFilename());
37
+ }
38
+ $this->parser->setParent($this->parser->getExpressionParser()->parseExpression());
39
+
40
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
41
+
42
+ return null;
43
+ }
44
+
45
+ /**
46
+ * Gets the tag name associated with this token parser.
47
+ *
48
+ * @return string The tag name
49
+ */
50
+ public function getTag()
51
+ {
52
+ return 'extends';
53
+ }
54
+ }
Twig/lib/Twig/TokenParser/Filter.php ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Filters a section of a template by applying filters.
14
+ *
15
+ * <pre>
16
+ * {% filter upper %}
17
+ * This text becomes uppercase
18
+ * {% endfilter %}
19
+ * </pre>
20
+ */
21
+ class Twig_TokenParser_Filter extends Twig_TokenParser
22
+ {
23
+ /**
24
+ * Parses a token and returns a node.
25
+ *
26
+ * @param Twig_Token $token A Twig_Token instance
27
+ *
28
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
29
+ */
30
+ public function parse(Twig_Token $token)
31
+ {
32
+ $name = $this->parser->getVarName();
33
+ $ref = new Twig_Node_Expression_BlockReference(new Twig_Node_Expression_Constant($name, $token->getLine()), true, $token->getLine(), $this->getTag());
34
+
35
+ $filter = $this->parser->getExpressionParser()->parseFilterExpressionRaw($ref, $this->getTag());
36
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
37
+
38
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
39
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
40
+
41
+ $block = new Twig_Node_Block($name, $body, $token->getLine());
42
+ $this->parser->setBlock($name, $block);
43
+
44
+ return new Twig_Node_Print($filter, $token->getLine(), $this->getTag());
45
+ }
46
+
47
+ public function decideBlockEnd(Twig_Token $token)
48
+ {
49
+ return $token->test('endfilter');
50
+ }
51
+
52
+ /**
53
+ * Gets the tag name associated with this token parser.
54
+ *
55
+ * @return string The tag name
56
+ */
57
+ public function getTag()
58
+ {
59
+ return 'filter';
60
+ }
61
+ }
Twig/lib/Twig/TokenParser/Flush.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2011 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Flushes the output to the client.
14
+ *
15
+ * @see flush()
16
+ */
17
+ class Twig_TokenParser_Flush extends Twig_TokenParser
18
+ {
19
+ /**
20
+ * Parses a token and returns a node.
21
+ *
22
+ * @param Twig_Token $token A Twig_Token instance
23
+ *
24
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
25
+ */
26
+ public function parse(Twig_Token $token)
27
+ {
28
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
29
+
30
+ return new Twig_Node_Flush($token->getLine(), $this->getTag());
31
+ }
32
+
33
+ /**
34
+ * Gets the tag name associated with this token parser.
35
+ *
36
+ * @return string The tag name
37
+ */
38
+ public function getTag()
39
+ {
40
+ return 'flush';
41
+ }
42
+ }
Twig/lib/Twig/TokenParser/For.php ADDED
@@ -0,0 +1,136 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ /**
14
+ * Loops over each item of a sequence.
15
+ *
16
+ * <pre>
17
+ * <ul>
18
+ * {% for user in users %}
19
+ * <li>{{ user.username|e }}</li>
20
+ * {% endfor %}
21
+ * </ul>
22
+ * </pre>
23
+ */
24
+ class Twig_TokenParser_For extends Twig_TokenParser
25
+ {
26
+ /**
27
+ * Parses a token and returns a node.
28
+ *
29
+ * @param Twig_Token $token A Twig_Token instance
30
+ *
31
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
32
+ */
33
+ public function parse(Twig_Token $token)
34
+ {
35
+ $lineno = $token->getLine();
36
+ $stream = $this->parser->getStream();
37
+ $targets = $this->parser->getExpressionParser()->parseAssignmentExpression();
38
+ $stream->expect(Twig_Token::OPERATOR_TYPE, 'in');
39
+ $seq = $this->parser->getExpressionParser()->parseExpression();
40
+
41
+ $ifexpr = null;
42
+ if ($stream->test(Twig_Token::NAME_TYPE, 'if')) {
43
+ $stream->next();
44
+ $ifexpr = $this->parser->getExpressionParser()->parseExpression();
45
+ }
46
+
47
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
48
+ $body = $this->parser->subparse(array($this, 'decideForFork'));
49
+ if ($stream->next()->getValue() == 'else') {
50
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
51
+ $else = $this->parser->subparse(array($this, 'decideForEnd'), true);
52
+ } else {
53
+ $else = null;
54
+ }
55
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
56
+
57
+ if (count($targets) > 1) {
58
+ $keyTarget = $targets->getNode(0);
59
+ $keyTarget = new Twig_Node_Expression_AssignName($keyTarget->getAttribute('name'), $keyTarget->getLine());
60
+ $valueTarget = $targets->getNode(1);
61
+ $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine());
62
+ } else {
63
+ $keyTarget = new Twig_Node_Expression_AssignName('_key', $lineno);
64
+ $valueTarget = $targets->getNode(0);
65
+ $valueTarget = new Twig_Node_Expression_AssignName($valueTarget->getAttribute('name'), $valueTarget->getLine());
66
+ }
67
+
68
+ if ($ifexpr) {
69
+ $this->checkLoopUsageCondition($stream, $ifexpr);
70
+ $this->checkLoopUsageBody($stream, $body);
71
+ }
72
+
73
+ return new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, $lineno, $this->getTag());
74
+ }
75
+
76
+ public function decideForFork(Twig_Token $token)
77
+ {
78
+ return $token->test(array('else', 'endfor'));
79
+ }
80
+
81
+ public function decideForEnd(Twig_Token $token)
82
+ {
83
+ return $token->test('endfor');
84
+ }
85
+
86
+ // the loop variable cannot be used in the condition
87
+ protected function checkLoopUsageCondition(Twig_TokenStream $stream, Twig_NodeInterface $node)
88
+ {
89
+ if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) {
90
+ throw new Twig_Error_Syntax('The "loop" variable cannot be used in a looping condition', $node->getLine(), $stream->getFilename());
91
+ }
92
+
93
+ foreach ($node as $n) {
94
+ if (!$n) {
95
+ continue;
96
+ }
97
+
98
+ $this->checkLoopUsageCondition($stream, $n);
99
+ }
100
+ }
101
+
102
+ // check usage of non-defined loop-items
103
+ // it does not catch all problems (for instance when a for is included into another or when the variable is used in an include)
104
+ protected function checkLoopUsageBody(Twig_TokenStream $stream, Twig_NodeInterface $node)
105
+ {
106
+ if ($node instanceof Twig_Node_Expression_GetAttr && $node->getNode('node') instanceof Twig_Node_Expression_Name && 'loop' == $node->getNode('node')->getAttribute('name')) {
107
+ $attribute = $node->getNode('attribute');
108
+ if ($attribute instanceof Twig_Node_Expression_Constant && in_array($attribute->getAttribute('value'), array('length', 'revindex0', 'revindex', 'last'))) {
109
+ throw new Twig_Error_Syntax(sprintf('The "loop.%s" variable is not defined when looping with a condition', $attribute->getAttribute('value')), $node->getLine(), $stream->getFilename());
110
+ }
111
+ }
112
+
113
+ // should check for parent.loop.XXX usage
114
+ if ($node instanceof Twig_Node_For) {
115
+ return;
116
+ }
117
+
118
+ foreach ($node as $n) {
119
+ if (!$n) {
120
+ continue;
121
+ }
122
+
123
+ $this->checkLoopUsageBody($stream, $n);
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Gets the tag name associated with this token parser.
129
+ *
130
+ * @return string The tag name
131
+ */
132
+ public function getTag()
133
+ {
134
+ return 'for';
135
+ }
136
+ }
Twig/lib/Twig/TokenParser/From.php ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Imports macros.
14
+ *
15
+ * <pre>
16
+ * {% from 'forms.html' import forms %}
17
+ * </pre>
18
+ */
19
+ class Twig_TokenParser_From extends Twig_TokenParser
20
+ {
21
+ /**
22
+ * Parses a token and returns a node.
23
+ *
24
+ * @param Twig_Token $token A Twig_Token instance
25
+ *
26
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
27
+ */
28
+ public function parse(Twig_Token $token)
29
+ {
30
+ $macro = $this->parser->getExpressionParser()->parseExpression();
31
+ $stream = $this->parser->getStream();
32
+ $stream->expect('import');
33
+
34
+ $targets = array();
35
+ do {
36
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
37
+
38
+ $alias = $name;
39
+ if ($stream->test('as')) {
40
+ $stream->next();
41
+
42
+ $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
43
+ }
44
+
45
+ $targets[$name] = $alias;
46
+
47
+ if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
48
+ break;
49
+ }
50
+
51
+ $stream->next();
52
+ } while (true);
53
+
54
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
55
+
56
+ $node = new Twig_Node_Import($macro, new Twig_Node_Expression_AssignName($this->parser->getVarName(), $token->getLine()), $token->getLine(), $this->getTag());
57
+
58
+ foreach ($targets as $name => $alias) {
59
+ $this->parser->addImportedSymbol('function', $alias, 'get'.$name, $node->getNode('var'));
60
+ }
61
+
62
+ return $node;
63
+ }
64
+
65
+ /**
66
+ * Gets the tag name associated with this token parser.
67
+ *
68
+ * @return string The tag name
69
+ */
70
+ public function getTag()
71
+ {
72
+ return 'from';
73
+ }
74
+ }
Twig/lib/Twig/TokenParser/If.php ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ /**
14
+ * Tests a condition.
15
+ *
16
+ * <pre>
17
+ * {% if users %}
18
+ * <ul>
19
+ * {% for user in users %}
20
+ * <li>{{ user.username|e }}</li>
21
+ * {% endfor %}
22
+ * </ul>
23
+ * {% endif %}
24
+ * </pre>
25
+ */
26
+ class Twig_TokenParser_If extends Twig_TokenParser
27
+ {
28
+ /**
29
+ * Parses a token and returns a node.
30
+ *
31
+ * @param Twig_Token $token A Twig_Token instance
32
+ *
33
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
34
+ */
35
+ public function parse(Twig_Token $token)
36
+ {
37
+ $lineno = $token->getLine();
38
+ $expr = $this->parser->getExpressionParser()->parseExpression();
39
+ $stream = $this->parser->getStream();
40
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
41
+ $body = $this->parser->subparse(array($this, 'decideIfFork'));
42
+ $tests = array($expr, $body);
43
+ $else = null;
44
+
45
+ $end = false;
46
+ while (!$end) {
47
+ switch ($stream->next()->getValue()) {
48
+ case 'else':
49
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
50
+ $else = $this->parser->subparse(array($this, 'decideIfEnd'));
51
+ break;
52
+
53
+ case 'elseif':
54
+ $expr = $this->parser->getExpressionParser()->parseExpression();
55
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
56
+ $body = $this->parser->subparse(array($this, 'decideIfFork'));
57
+ $tests[] = $expr;
58
+ $tests[] = $body;
59
+ break;
60
+
61
+ case 'endif':
62
+ $end = true;
63
+ break;
64
+
65
+ default:
66
+ throw new Twig_Error_Syntax(sprintf('Unexpected end of template. Twig was looking for the following tags "else", "elseif", or "endif" to close the "if" block started at line %d)', $lineno), $stream->getCurrent()->getLine(), $stream->getFilename());
67
+ }
68
+ }
69
+
70
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
71
+
72
+ return new Twig_Node_If(new Twig_Node($tests), $else, $lineno, $this->getTag());
73
+ }
74
+
75
+ public function decideIfFork(Twig_Token $token)
76
+ {
77
+ return $token->test(array('elseif', 'else', 'endif'));
78
+ }
79
+
80
+ public function decideIfEnd(Twig_Token $token)
81
+ {
82
+ return $token->test(array('endif'));
83
+ }
84
+
85
+ /**
86
+ * Gets the tag name associated with this token parser.
87
+ *
88
+ * @return string The tag name
89
+ */
90
+ public function getTag()
91
+ {
92
+ return 'if';
93
+ }
94
+ }
Twig/lib/Twig/TokenParser/Import.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Imports macros.
14
+ *
15
+ * <pre>
16
+ * {% import 'forms.html' as forms %}
17
+ * </pre>
18
+ */
19
+ class Twig_TokenParser_Import extends Twig_TokenParser
20
+ {
21
+ /**
22
+ * Parses a token and returns a node.
23
+ *
24
+ * @param Twig_Token $token A Twig_Token instance
25
+ *
26
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
27
+ */
28
+ public function parse(Twig_Token $token)
29
+ {
30
+ $macro = $this->parser->getExpressionParser()->parseExpression();
31
+ $this->parser->getStream()->expect('as');
32
+ $var = new Twig_Node_Expression_AssignName($this->parser->getStream()->expect(Twig_Token::NAME_TYPE)->getValue(), $token->getLine());
33
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
34
+
35
+ $this->parser->addImportedSymbol('template', $var->getAttribute('name'));
36
+
37
+ return new Twig_Node_Import($macro, $var, $token->getLine(), $this->getTag());
38
+ }
39
+
40
+ /**
41
+ * Gets the tag name associated with this token parser.
42
+ *
43
+ * @return string The tag name
44
+ */
45
+ public function getTag()
46
+ {
47
+ return 'import';
48
+ }
49
+ }
Twig/lib/Twig/TokenParser/Include.php ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ * (c) 2009 Armin Ronacher
8
+ *
9
+ * For the full copyright and license information, please view the LICENSE
10
+ * file that was distributed with this source code.
11
+ */
12
+
13
+ /**
14
+ * Includes a template.
15
+ *
16
+ * <pre>
17
+ * {% include 'header.html' %}
18
+ * Body
19
+ * {% include 'footer.html' %}
20
+ * </pre>
21
+ */
22
+ class Twig_TokenParser_Include extends Twig_TokenParser
23
+ {
24
+ /**
25
+ * Parses a token and returns a node.
26
+ *
27
+ * @param Twig_Token $token A Twig_Token instance
28
+ *
29
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
30
+ */
31
+ public function parse(Twig_Token $token)
32
+ {
33
+ $expr = $this->parser->getExpressionParser()->parseExpression();
34
+
35
+ list($variables, $only, $ignoreMissing) = $this->parseArguments();
36
+
37
+ return new Twig_Node_Include($expr, $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
38
+ }
39
+
40
+ protected function parseArguments()
41
+ {
42
+ $stream = $this->parser->getStream();
43
+
44
+ $ignoreMissing = false;
45
+ if ($stream->test(Twig_Token::NAME_TYPE, 'ignore')) {
46
+ $stream->next();
47
+ $stream->expect(Twig_Token::NAME_TYPE, 'missing');
48
+
49
+ $ignoreMissing = true;
50
+ }
51
+
52
+ $variables = null;
53
+ if ($stream->test(Twig_Token::NAME_TYPE, 'with')) {
54
+ $stream->next();
55
+
56
+ $variables = $this->parser->getExpressionParser()->parseExpression();
57
+ }
58
+
59
+ $only = false;
60
+ if ($stream->test(Twig_Token::NAME_TYPE, 'only')) {
61
+ $stream->next();
62
+
63
+ $only = true;
64
+ }
65
+
66
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
67
+
68
+ return array($variables, $only, $ignoreMissing);
69
+ }
70
+
71
+ /**
72
+ * Gets the tag name associated with this token parser.
73
+ *
74
+ * @return string The tag name
75
+ */
76
+ public function getTag()
77
+ {
78
+ return 'include';
79
+ }
80
+ }
Twig/lib/Twig/TokenParser/Macro.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Defines a macro.
14
+ *
15
+ * <pre>
16
+ * {% macro input(name, value, type, size) %}
17
+ * <input type="{{ type|default('text') }}" name="{{ name }}" value="{{ value|e }}" size="{{ size|default(20) }}" />
18
+ * {% endmacro %}
19
+ * </pre>
20
+ */
21
+ class Twig_TokenParser_Macro extends Twig_TokenParser
22
+ {
23
+ /**
24
+ * Parses a token and returns a node.
25
+ *
26
+ * @param Twig_Token $token A Twig_Token instance
27
+ *
28
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
29
+ */
30
+ public function parse(Twig_Token $token)
31
+ {
32
+ $lineno = $token->getLine();
33
+ $stream = $this->parser->getStream();
34
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
35
+
36
+ $arguments = $this->parser->getExpressionParser()->parseArguments(true, true);
37
+
38
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
39
+ $this->parser->pushLocalScope();
40
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
41
+ if ($stream->test(Twig_Token::NAME_TYPE)) {
42
+ $value = $stream->next()->getValue();
43
+
44
+ if ($value != $name) {
45
+ throw new Twig_Error_Syntax(sprintf("Expected endmacro for macro '$name' (but %s given)", $value), $stream->getCurrent()->getLine(), $stream->getFilename());
46
+ }
47
+ }
48
+ $this->parser->popLocalScope();
49
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
50
+
51
+ $this->parser->setMacro($name, new Twig_Node_Macro($name, new Twig_Node_Body(array($body)), $arguments, $lineno, $this->getTag()));
52
+
53
+ return null;
54
+ }
55
+
56
+ public function decideBlockEnd(Twig_Token $token)
57
+ {
58
+ return $token->test('endmacro');
59
+ }
60
+
61
+ /**
62
+ * Gets the tag name associated with this token parser.
63
+ *
64
+ * @return string The tag name
65
+ */
66
+ public function getTag()
67
+ {
68
+ return 'macro';
69
+ }
70
+ }
Twig/lib/Twig/TokenParser/Sandbox.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Marks a section of a template as untrusted code that must be evaluated in the sandbox mode.
14
+ *
15
+ * <pre>
16
+ * {% sandbox %}
17
+ * {% include 'user.html' %}
18
+ * {% endsandbox %}
19
+ * </pre>
20
+ *
21
+ * @see http://www.twig-project.org/doc/api.html#sandbox-extension for details
22
+ */
23
+ class Twig_TokenParser_Sandbox extends Twig_TokenParser
24
+ {
25
+ /**
26
+ * Parses a token and returns a node.
27
+ *
28
+ * @param Twig_Token $token A Twig_Token instance
29
+ *
30
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
31
+ */
32
+ public function parse(Twig_Token $token)
33
+ {
34
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
35
+ $body = $this->parser->subparse(array($this, 'decideBlockEnd'), true);
36
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
37
+
38
+ // in a sandbox tag, only include tags are allowed
39
+ if (!$body instanceof Twig_Node_Include) {
40
+ foreach ($body as $node) {
41
+ if ($node instanceof Twig_Node_Text && ctype_space($node->getAttribute('data'))) {
42
+ continue;
43
+ }
44
+
45
+ if (!$node instanceof Twig_Node_Include) {
46
+ throw new Twig_Error_Syntax('Only "include" tags are allowed within a "sandbox" section', $node->getLine(), $this->parser->getFilename());
47
+ }
48
+ }
49
+ }
50
+
51
+ return new Twig_Node_Sandbox($body, $token->getLine(), $this->getTag());
52
+ }
53
+
54
+ public function decideBlockEnd(Twig_Token $token)
55
+ {
56
+ return $token->test('endsandbox');
57
+ }
58
+
59
+ /**
60
+ * Gets the tag name associated with this token parser.
61
+ *
62
+ * @return string The tag name
63
+ */
64
+ public function getTag()
65
+ {
66
+ return 'sandbox';
67
+ }
68
+ }
Twig/lib/Twig/TokenParser/Set.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2009 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Defines a variable.
14
+ *
15
+ * <pre>
16
+ * {% set foo = 'foo' %}
17
+ *
18
+ * {% set foo = [1, 2] %}
19
+ *
20
+ * {% set foo = {'foo': 'bar'} %}
21
+ *
22
+ * {% set foo = 'foo' ~ 'bar' %}
23
+ *
24
+ * {% set foo, bar = 'foo', 'bar' %}
25
+ *
26
+ * {% set foo %}Some content{% endset %}
27
+ * </pre>
28
+ */
29
+ class Twig_TokenParser_Set extends Twig_TokenParser
30
+ {
31
+ /**
32
+ * Parses a token and returns a node.
33
+ *
34
+ * @param Twig_Token $token A Twig_Token instance
35
+ *
36
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
37
+ */
38
+ public function parse(Twig_Token $token)
39
+ {
40
+ $lineno = $token->getLine();
41
+ $stream = $this->parser->getStream();
42
+ $names = $this->parser->getExpressionParser()->parseAssignmentExpression();
43
+
44
+ $capture = false;
45
+ if ($stream->test(Twig_Token::OPERATOR_TYPE, '=')) {
46
+ $stream->next();
47
+ $values = $this->parser->getExpressionParser()->parseMultitargetExpression();
48
+
49
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
50
+
51
+ if (count($names) !== count($values)) {
52
+ throw new Twig_Error_Syntax("When using set, you must have the same number of variables and assignements.", $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
+ }
Twig/lib/Twig/TokenParser/Spaceless.php ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2010 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Remove whitespaces between HTML tags.
14
+ *
15
+ * <pre>
16
+ * {% spaceless %}
17
+ * <div>
18
+ * <strong>foo</strong>
19
+ * </div>
20
+ * {% endspaceless %}
21
+ *
22
+ * {# output will be <div><strong>foo</strong></div> #}
23
+ * </pre>
24
+ */
25
+ class Twig_TokenParser_Spaceless extends Twig_TokenParser
26
+ {
27
+ /**
28
+ * Parses a token and returns a node.
29
+ *
30
+ * @param Twig_Token $token A Twig_Token instance
31
+ *
32
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
33
+ */
34
+ public function parse(Twig_Token $token)
35
+ {
36
+ $lineno = $token->getLine();
37
+
38
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
39
+ $body = $this->parser->subparse(array($this, 'decideSpacelessEnd'), true);
40
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
41
+
42
+ return new Twig_Node_Spaceless($body, $lineno, $this->getTag());
43
+ }
44
+
45
+ public function decideSpacelessEnd(Twig_Token $token)
46
+ {
47
+ return $token->test('endspaceless');
48
+ }
49
+
50
+ /**
51
+ * Gets the tag name associated with this token parser.
52
+ *
53
+ * @return string The tag name
54
+ */
55
+ public function getTag()
56
+ {
57
+ return 'spaceless';
58
+ }
59
+ }
Twig/lib/Twig/TokenParser/Use.php ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) 2011 Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ /**
13
+ * Imports blocks defined in another template into the current template.
14
+ *
15
+ * <pre>
16
+ * {% extends "base.html" %}
17
+ *
18
+ * {% use "blocks.html" %}
19
+ *
20
+ * {% block title %}{% endblock %}
21
+ * {% block content %}{% endblock %}
22
+ * </pre>
23
+ *
24
+ * @see http://www.twig-project.org/doc/templates.html#horizontal-reuse for details.
25
+ */
26
+ class Twig_TokenParser_Use extends Twig_TokenParser
27
+ {
28
+ /**
29
+ * Parses a token and returns a node.
30
+ *
31
+ * @param Twig_Token $token A Twig_Token instance
32
+ *
33
+ * @return Twig_NodeInterface A Twig_NodeInterface instance
34
+ */
35
+ public function parse(Twig_Token $token)
36
+ {
37
+ $template = $this->parser->getExpressionParser()->parseExpression();
38
+ $stream = $this->parser->getStream();
39
+
40
+ if (!$template instanceof Twig_Node_Expression_Constant) {
41
+ throw new Twig_Error_Syntax('The template references in a "use" statement must be a string.', $stream->getCurrent()->getLine(), $stream->getFilename());
42
+ }
43
+
44
+ $targets = array();
45
+ if ($stream->test('with')) {
46
+ $stream->next();
47
+
48
+ do {
49
+ $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
50
+
51
+ $alias = $name;
52
+ if ($stream->test('as')) {
53
+ $stream->next();
54
+
55
+ $alias = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
56
+ }
57
+
58
+ $targets[$name] = new Twig_Node_Expression_Constant($alias, -1);
59
+
60
+ if (!$stream->test(Twig_Token::PUNCTUATION_TYPE, ',')) {
61
+ break;
62
+ }
63
+
64
+ $stream->next();
65
+ } while (true);
66
+ }
67
+
68
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
69
+
70
+ $this->parser->addTrait(new Twig_Node(array('template' => $template, 'targets' => new Twig_Node($targets))));
71
+
72
+ return null;
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 'use';
83
+ }
84
+ }
Twig/lib/Twig/TokenParserBroker.php ADDED
@@ -0,0 +1,139 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
18
+ * @deprecated since 1.12 (to be removed in 2.0)
19
+ */
20
+ class Twig_TokenParserBroker implements Twig_TokenParserBrokerInterface
21
+ {
22
+ protected $parser;
23
+ protected $parsers = array();
24
+ protected $brokers = array();
25
+
26
+ /**
27
+ * Constructor.
28
+ *
29
+ * @param array|Traversable $parsers A Traversable of Twig_TokenParserInterface instances
30
+ * @param array|Traversable $brokers A Traversable of Twig_TokenParserBrokerInterface instances
31
+ */
32
+ public function __construct($parsers = array(), $brokers = array())
33
+ {
34
+ foreach ($parsers as $parser) {
35
+ if (!$parser instanceof Twig_TokenParserInterface) {
36
+ throw new LogicException('$parsers must a an array of Twig_TokenParserInterface');
37
+ }
38
+ $this->parsers[$parser->getTag()] = $parser;
39
+ }
40
+ foreach ($brokers as $broker) {
41
+ if (!$broker instanceof Twig_TokenParserBrokerInterface) {
42
+ throw new LogicException('$brokers must a an array of Twig_TokenParserBrokerInterface');
43
+ }
44
+ $this->brokers[] = $broker;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Adds a TokenParser.
50
+ *
51
+ * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
52
+ */
53
+ public function addTokenParser(Twig_TokenParserInterface $parser)
54
+ {
55
+ $this->parsers[$parser->getTag()] = $parser;
56
+ }
57
+
58
+ /**
59
+ * Removes a TokenParser.
60
+ *
61
+ * @param Twig_TokenParserInterface $parser A Twig_TokenParserInterface instance
62
+ */
63
+ public function removeTokenParser(Twig_TokenParserInterface $parser)
64
+ {
65
+ $name = $parser->getTag();
66
+ if (isset($this->parsers[$name]) && $parser === $this->parsers[$name]) {
67
+ unset($this->parsers[$name]);
68
+ }
69
+ }
70
+
71
+ /**
72
+ * Adds a TokenParserBroker.
73
+ *
74
+ * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance
75
+ */
76
+ public function addTokenParserBroker(Twig_TokenParserBroker $broker)
77
+ {
78
+ $this->brokers[] = $broker;
79
+ }
80
+
81
+ /**
82
+ * Removes a TokenParserBroker.
83
+ *
84
+ * @param Twig_TokenParserBroker $broker A Twig_TokenParserBroker instance
85
+ */
86
+ public function removeTokenParserBroker(Twig_TokenParserBroker $broker)
87
+ {
88
+ if (false !== $pos = array_search($broker, $this->brokers)) {
89
+ unset($this->brokers[$pos]);
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Gets a suitable TokenParser for a tag.
95
+ *
96
+ * First looks in parsers, then in brokers.
97
+ *
98
+ * @param string $tag A tag name
99
+ *
100
+ * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found
101
+ */
102
+ public function getTokenParser($tag)
103
+ {
104
+ if (isset($this->parsers[$tag])) {
105
+ return $this->parsers[$tag];
106
+ }
107
+ $broker = end($this->brokers);
108
+ while (false !== $broker) {
109
+ $parser = $broker->getTokenParser($tag);
110
+ if (null !== $parser) {
111
+ return $parser;
112
+ }
113
+ $broker = prev($this->brokers);
114
+ }
115
+
116
+ return null;
117
+ }
118
+
119
+ public function getParsers()
120
+ {
121
+ return $this->parsers;
122
+ }
123
+
124
+ public function getParser()
125
+ {
126
+ return $this->parser;
127
+ }
128
+
129
+ public function setParser(Twig_ParserInterface $parser)
130
+ {
131
+ $this->parser = $parser;
132
+ foreach ($this->parsers as $tokenParser) {
133
+ $tokenParser->setParser($parser);
134
+ }
135
+ foreach ($this->brokers as $broker) {
136
+ $broker->setParser($parser);
137
+ }
138
+ }
139
+ }
Twig/lib/Twig/TokenParserBrokerInterface.php ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
19
+ * @author Arnaud Le Blanc <arnaud.lb@gmail.com>
20
+ * @deprecated since 1.12 (to be removed in 2.0)
21
+ */
22
+ interface Twig_TokenParserBrokerInterface
23
+ {
24
+ /**
25
+ * Gets a TokenParser suitable for a tag.
26
+ *
27
+ * @param string $tag A tag name
28
+ *
29
+ * @return null|Twig_TokenParserInterface A Twig_TokenParserInterface or null if no suitable TokenParser was found
30
+ */
31
+ public function getTokenParser($tag);
32
+
33
+ /**
34
+ * Calls Twig_TokenParserInterface::setParser on all parsers the implementation knows of.
35
+ *
36
+ * @param Twig_ParserInterface $parser A Twig_ParserInterface interface
37
+ */
38
+ public function setParser(Twig_ParserInterface $parser);
39
+
40
+ /**
41
+ * Gets the Twig_ParserInterface.
42
+ *
43
+ * @return null|Twig_ParserInterface A Twig_ParserInterface instance or null
44
+ */
45
+ public function getParser();
46
+ }
Twig/lib/Twig/TokenParserInterface.php ADDED
@@ -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
+ * Interface implemented by token parsers.
14
+ *
15
+ * @package twig
16
+ * @author Fabien Potencier <fabien@symfony.com>
17
+ */
18
+ interface Twig_TokenParserInterface
19
+ {
20
+ /**
21
+ * Sets the parser associated with this token parser
22
+ *
23
+ * @param $parser A Twig_Parser instance
24
+ */
25
+ public function setParser(Twig_Parser $parser);
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
+ /**
37
+ * Gets the tag name associated with this token parser.
38
+ *
39
+ * @return string The tag name
40
+ */
41
+ public function getTag();
42
+ }
Twig/lib/Twig/TokenStream.php ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ * @package twig
17
+ * @author Fabien Potencier <fabien@symfony.com>
18
+ */
19
+ class Twig_TokenStream
20
+ {
21
+ protected $tokens;
22
+ protected $current;
23
+ protected $filename;
24
+
25
+ /**
26
+ * Constructor.
27
+ *
28
+ * @param array $tokens An array of tokens
29
+ * @param string $filename The name of the filename which tokens are associated with
30
+ */
31
+ public function __construct(array $tokens, $filename = null)
32
+ {
33
+ $this->tokens = $tokens;
34
+ $this->current = 0;
35
+ $this->filename = $filename;
36
+ }
37
+
38
+ /**
39
+ * Returns a string representation of the token stream.
40
+ *
41
+ * @return string
42
+ */
43
+ public function __toString()
44
+ {
45
+ return implode("\n", $this->tokens);
46
+ }
47
+
48
+ public function injectTokens(array $tokens)
49
+ {
50
+ $this->tokens = array_merge(array_slice($this->tokens, 0, $this->current), $tokens, array_slice($this->tokens, $this->current));
51
+ }
52
+
53
+ /**
54
+ * Sets the pointer to the next token and returns the old one.
55
+ *
56
+ * @return Twig_Token
57
+ */
58
+ public function next()
59
+ {
60
+ if (!isset($this->tokens[++$this->current])) {
61
+ throw new Twig_Error_Syntax('Unexpected end of template', $this->tokens[$this->current - 1]->getLine(), $this->filename);
62
+ }
63
+
64
+ return $this->tokens[$this->current - 1];
65
+ }
66
+
67
+ /**
68
+ * Tests a token and returns it or throws a syntax error.
69
+ *
70
+ * @return Twig_Token
71
+ */
72
+ public function expect($type, $value = null, $message = null)
73
+ {
74
+ $token = $this->tokens[$this->current];
75
+ if (!$token->test($type, $value)) {
76
+ $line = $token->getLine();
77
+ throw new Twig_Error_Syntax(sprintf('%sUnexpected token "%s" of value "%s" ("%s" expected%s)',
78
+ $message ? $message.'. ' : '',
79
+ Twig_Token::typeToEnglish($token->getType(), $line), $token->getValue(),
80
+ Twig_Token::typeToEnglish($type, $line), $value ? sprintf(' with value "%s"', $value) : ''),
81
+ $line,
82
+ $this->filename
83
+ );
84
+ }
85
+ $this->next();
86
+
87
+ return $token;
88
+ }
89
+
90
+ /**
91
+ * Looks at the next token.
92
+ *
93
+ * @param integer $number
94
+ *
95
+ * @return Twig_Token
96
+ */
97
+ public function look($number = 1)
98
+ {
99
+ if (!isset($this->tokens[$this->current + $number])) {
100
+ throw new Twig_Error_Syntax('Unexpected end of template', $this->tokens[$this->current + $number - 1]->getLine(), $this->filename);
101
+ }
102
+
103
+ return $this->tokens[$this->current + $number];
104
+ }
105
+
106
+ /**
107
+ * Tests the current token
108
+ *
109
+ * @return bool
110
+ */
111
+ public function test($primary, $secondary = null)
112
+ {
113
+ return $this->tokens[$this->current]->test($primary, $secondary);
114
+ }
115
+
116
+ /**
117
+ * Checks if end of stream was reached
118
+ *
119
+ * @return bool
120
+ */
121
+ public function isEOF()
122
+ {
123
+ return $this->tokens[$this->current]->getType() === Twig_Token::EOF_TYPE;
124
+ }
125
+
126
+ /**
127
+ * Gets the current token
128
+ *
129
+ * @return Twig_Token
130
+ */
131
+ public function getCurrent()
132
+ {
133
+ return $this->tokens[$this->current];
134
+ }
135
+
136
+ /**
137
+ * Gets the filename associated with this stream
138
+ *
139
+ * @return string
140
+ */
141
+ public function getFilename()
142
+ {
143
+ return $this->filename;
144
+ }
145
+ }
Twig/phpunit.xml.dist ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+
3
+ <phpunit backupGlobals="false"
4
+ backupStaticAttributes="false"
5
+ colors="true"
6
+ convertErrorsToExceptions="true"
7
+ convertNoticesToExceptions="true"
8
+ convertWarningsToExceptions="true"
9
+ processIsolation="false"
10
+ stopOnFailure="false"
11
+ syntaxCheck="false"
12
+ bootstrap="test/bootstrap.php"
13
+ >
14
+ <testsuites>
15
+ <testsuite name="Twig Test Suite">
16
+ <directory>./test/Twig/</directory>
17
+ </testsuite>
18
+ </testsuites>
19
+
20
+ <filter>
21
+ <whitelist>
22
+ <directory suffix=".php">./lib/Twig/</directory>
23
+ </whitelist>
24
+ </filter>
25
+ </phpunit>
Twig/test/Twig/Tests/AutoloaderTest.php ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_AutoloaderTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ public function testAutoload()
15
+ {
16
+ $this->assertFalse(class_exists('FooBarFoo'), '->autoload() does not try to load classes that does not begin with Twig');
17
+
18
+ $autoloader = new Twig_Autoloader();
19
+ $this->assertNull($autoloader->autoload('Foo'), '->autoload() returns false if it is not able to load a class');
20
+ }
21
+ }
Twig/test/Twig/Tests/CompilerTest.php ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_CompilerTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ public function testReprNumericValueWithLocale()
15
+ {
16
+ $compiler = new Twig_Compiler(new Twig_Environment());
17
+
18
+ $locale = setlocale(LC_NUMERIC, 0);
19
+ if (false === $locale) {
20
+ $this->markTestSkipped('Your platform does not support locales.');
21
+ }
22
+
23
+ $required_locales = array('fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252');
24
+ if (false === setlocale(LC_ALL, $required_locales)) {
25
+ $this->markTestSkipped('Could not set any of required locales: ' . implode(", ", $required_locales));
26
+ }
27
+
28
+ $this->assertEquals('1.2', $compiler->repr(1.2)->getSource());
29
+ $this->assertContains('fr', strtolower(setlocale(LC_NUMERIC, 0)));
30
+
31
+ setlocale(LC_ALL, $locale);
32
+ }
33
+ }
Twig/test/Twig/Tests/EnvironmentTest.php ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_EnvironmentTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ public function testAutoescapeOption()
15
+ {
16
+ $loader = new Twig_Loader_Array(array(
17
+ 'html' => '{{ foo }} {{ foo }}',
18
+ 'js' => '{{ bar }} {{ bar }}',
19
+ ));
20
+
21
+ $twig = new Twig_Environment($loader, array(
22
+ 'debug' => true,
23
+ 'cache' => false,
24
+ 'autoescape' => array($this, 'escapingStrategyCallback'),
25
+ ));
26
+
27
+ $this->assertEquals('foo&lt;br/ &gt; foo&lt;br/ &gt;', $twig->render('html', array('foo' => 'foo<br/ >')));
28
+ $this->assertEquals('foo\x3Cbr\x2F\x20\x3E foo\x3Cbr\x2F\x20\x3E', $twig->render('js', array('bar' => 'foo<br/ >')));
29
+ }
30
+
31
+ public function escapingStrategyCallback($filename)
32
+ {
33
+ return $filename;
34
+ }
35
+
36
+ public function testGlobals()
37
+ {
38
+ // globals can be added after calling getGlobals
39
+ $twig = new Twig_Environment(new Twig_Loader_String());
40
+ $twig->addGlobal('foo', 'foo');
41
+ $globals = $twig->getGlobals();
42
+ $twig->addGlobal('foo', 'bar');
43
+ $globals = $twig->getGlobals();
44
+ $this->assertEquals('bar', $globals['foo']);
45
+
46
+ // globals can be modified after runtime init
47
+ $twig = new Twig_Environment(new Twig_Loader_String());
48
+ $twig->addGlobal('foo', 'foo');
49
+ $globals = $twig->getGlobals();
50
+ $twig->initRuntime();
51
+ $twig->addGlobal('foo', 'bar');
52
+ $globals = $twig->getGlobals();
53
+ $this->assertEquals('bar', $globals['foo']);
54
+
55
+ // globals can be modified after extensions init
56
+ $twig = new Twig_Environment(new Twig_Loader_String());
57
+ $twig->addGlobal('foo', 'foo');
58
+ $globals = $twig->getGlobals();
59
+ $twig->getFunctions();
60
+ $twig->addGlobal('foo', 'bar');
61
+ $globals = $twig->getGlobals();
62
+ $this->assertEquals('bar', $globals['foo']);
63
+
64
+ // globals can be modified after extensions and runtime init
65
+ $twig = new Twig_Environment(new Twig_Loader_String());
66
+ $twig->addGlobal('foo', 'foo');
67
+ $globals = $twig->getGlobals();
68
+ $twig->getFunctions();
69
+ $twig->initRuntime();
70
+ $twig->addGlobal('foo', 'bar');
71
+ $globals = $twig->getGlobals();
72
+ $this->assertEquals('bar', $globals['foo']);
73
+
74
+ /* to be uncomment in Twig 2.0
75
+ // globals cannot be added after runtime init
76
+ $twig = new Twig_Environment(new Twig_Loader_String());
77
+ $twig->addGlobal('foo', 'foo');
78
+ $globals = $twig->getGlobals();
79
+ $twig->initRuntime();
80
+ try {
81
+ $twig->addGlobal('bar', 'bar');
82
+ $this->fail();
83
+ } catch (LogicException $e) {
84
+ $this->assertFalse(array_key_exists('bar', $twig->getGlobals()));
85
+ }
86
+
87
+ // globals cannot be added after extensions init
88
+ $twig = new Twig_Environment(new Twig_Loader_String());
89
+ $twig->addGlobal('foo', 'foo');
90
+ $globals = $twig->getGlobals();
91
+ $twig->getFunctions();
92
+ try {
93
+ $twig->addGlobal('bar', 'bar');
94
+ $this->fail();
95
+ } catch (LogicException $e) {
96
+ $this->assertFalse(array_key_exists('bar', $twig->getGlobals()));
97
+ }
98
+
99
+ // globals cannot be added after extensions and runtime init
100
+ $twig = new Twig_Environment(new Twig_Loader_String());
101
+ $twig->addGlobal('foo', 'foo');
102
+ $globals = $twig->getGlobals();
103
+ $twig->getFunctions();
104
+ $twig->initRuntime();
105
+ try {
106
+ $twig->addGlobal('bar', 'bar');
107
+ $this->fail();
108
+ } catch (LogicException $e) {
109
+ $this->assertFalse(array_key_exists('bar', $twig->getGlobals()));
110
+ }
111
+
112
+ // test adding globals after initRuntime without call to getGlobals
113
+ $twig = new Twig_Environment(new Twig_Loader_String());
114
+ $twig->initRuntime();
115
+ try {
116
+ $twig->addGlobal('bar', 'bar');
117
+ $this->fail();
118
+ } catch (LogicException $e) {
119
+ $this->assertFalse(array_key_exists('bar', $twig->getGlobals()));
120
+ }
121
+ */
122
+ }
123
+
124
+ public function testExtensionsAreNotInitializedWhenRenderingACompiledTemplate()
125
+ {
126
+ $options = array('cache' => sys_get_temp_dir().'/twig', 'auto_reload' => false, 'debug' => false);
127
+
128
+ // force compilation
129
+ $twig = new Twig_Environment(new Twig_Loader_String(), $options);
130
+ $cache = $twig->getCacheFilename('{{ foo }}');
131
+ if (!is_dir(dirname($cache))) {
132
+ mkdir(dirname($cache), 0777, true);
133
+ }
134
+ file_put_contents($cache, $twig->compileSource('{{ foo }}', '{{ foo }}'));
135
+
136
+ // check that extensions won't be initialized when rendering a template that is already in the cache
137
+ $twig = $this
138
+ ->getMockBuilder('Twig_Environment')
139
+ ->setConstructorArgs(array(new Twig_Loader_String(), $options))
140
+ ->setMethods(array('initExtensions'))
141
+ ->getMock()
142
+ ;
143
+
144
+ $twig->expects($this->never())->method('initExtensions');
145
+
146
+ // render template
147
+ $output = $twig->render('{{ foo }}', array('foo' => 'bar'));
148
+ $this->assertEquals('bar', $output);
149
+
150
+ unlink($cache);
151
+ }
152
+
153
+ public function testAddExtension()
154
+ {
155
+ $twig = new Twig_Environment(new Twig_Loader_String());
156
+ $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension());
157
+
158
+ $this->assertArrayHasKey('test', $twig->getTags());
159
+ $this->assertArrayHasKey('foo_filter', $twig->getFilters());
160
+ $this->assertArrayHasKey('foo_function', $twig->getFunctions());
161
+ $this->assertArrayHasKey('foo_test', $twig->getTests());
162
+ $this->assertArrayHasKey('foo_unary', $twig->getUnaryOperators());
163
+ $this->assertArrayHasKey('foo_binary', $twig->getBinaryOperators());
164
+ $this->assertArrayHasKey('foo_global', $twig->getGlobals());
165
+ $visitors = $twig->getNodeVisitors();
166
+ $this->assertEquals('Twig_Tests_EnvironmentTest_NodeVisitor', get_class($visitors[2]));
167
+ }
168
+
169
+ public function testRemoveExtension()
170
+ {
171
+ $twig = new Twig_Environment(new Twig_Loader_String());
172
+ $twig->addExtension(new Twig_Tests_EnvironmentTest_Extension());
173
+ $twig->removeExtension('test');
174
+
175
+ $this->assertFalse(array_key_exists('test', $twig->getTags()));
176
+ $this->assertFalse(array_key_exists('foo_filter', $twig->getFilters()));
177
+ $this->assertFalse(array_key_exists('foo_function', $twig->getFunctions()));
178
+ $this->assertFalse(array_key_exists('foo_test', $twig->getTests()));
179
+ $this->assertFalse(array_key_exists('foo_unary', $twig->getUnaryOperators()));
180
+ $this->assertFalse(array_key_exists('foo_binary', $twig->getBinaryOperators()));
181
+ $this->assertFalse(array_key_exists('foo_global', $twig->getGlobals()));
182
+ $this->assertCount(2, $twig->getNodeVisitors());
183
+ }
184
+ }
185
+
186
+ class Twig_Tests_EnvironmentTest_Extension extends Twig_Extension
187
+ {
188
+ public function getTokenParsers()
189
+ {
190
+ return array(
191
+ new Twig_Tests_EnvironmentTest_TokenParser(),
192
+ );
193
+ }
194
+
195
+ public function getNodeVisitors()
196
+ {
197
+ return array(
198
+ new Twig_Tests_EnvironmentTest_NodeVisitor(),
199
+ );
200
+ }
201
+
202
+ public function getFilters()
203
+ {
204
+ return array(
205
+ 'foo_filter' => new Twig_Filter_Function('foo_filter'),
206
+ );
207
+ }
208
+
209
+ public function getTests()
210
+ {
211
+ return array(
212
+ 'foo_test' => new Twig_Test_Function('foo_test'),
213
+ );
214
+ }
215
+
216
+ public function getFunctions()
217
+ {
218
+ return array(
219
+ 'foo_function' => new Twig_Function_Function('foo_function'),
220
+ );
221
+ }
222
+
223
+ public function getOperators()
224
+ {
225
+ return array(
226
+ array('foo_unary' => array()),
227
+ array('foo_binary' => array()),
228
+ );
229
+ }
230
+
231
+ public function getGlobals()
232
+ {
233
+ return array(
234
+ 'foo_global' => 'foo_global',
235
+ );
236
+ }
237
+
238
+ public function getName()
239
+ {
240
+ return 'test';
241
+ }
242
+ }
243
+
244
+ class Twig_Tests_EnvironmentTest_TokenParser extends Twig_TokenParser
245
+ {
246
+ public function parse(Twig_Token $token)
247
+ {
248
+ }
249
+
250
+ public function getTag()
251
+ {
252
+ return 'test';
253
+ }
254
+ }
255
+
256
+ class Twig_Tests_EnvironmentTest_NodeVisitor implements Twig_NodeVisitorInterface
257
+ {
258
+ public function enterNode(Twig_NodeInterface $node, Twig_Environment $env)
259
+ {
260
+ return $node;
261
+ }
262
+
263
+ public function leaveNode(Twig_NodeInterface $node, Twig_Environment $env)
264
+ {
265
+ return $node;
266
+ }
267
+
268
+ public function getPriority()
269
+ {
270
+ return 0;
271
+ }
272
+ }
Twig/test/Twig/Tests/ErrorTest.php ADDED
@@ -0,0 +1,159 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_ErrorTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ public function testErrorWithObjectFilename()
15
+ {
16
+ $error = new Twig_Error('foo');
17
+ $error->setTemplateFile(new SplFileInfo(__FILE__));
18
+
19
+ $this->assertContains('test'.DIRECTORY_SEPARATOR.'Twig'.DIRECTORY_SEPARATOR.'Tests'.DIRECTORY_SEPARATOR.'ErrorTest.php', $error->getMessage());
20
+ }
21
+
22
+ public function testErrorWithArrayFilename()
23
+ {
24
+ $error = new Twig_Error('foo');
25
+ $error->setTemplateFile(array('foo' => 'bar'));
26
+
27
+ $this->assertEquals('foo in {"foo":"bar"}', $error->getMessage());
28
+ }
29
+
30
+ public function testTwigExceptionAddsFileAndLineWhenMissing()
31
+ {
32
+ $loader = new Twig_Loader_Array(array('index' => "\n\n{{ foo.bar }}\n\n\n{{ 'foo' }}"));
33
+ $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
34
+
35
+ $template = $twig->loadTemplate('index');
36
+
37
+ try {
38
+ $template->render(array());
39
+
40
+ $this->fail();
41
+ } catch (Twig_Error_Runtime $e) {
42
+ $this->assertEquals('Variable "foo" does not exist in "index" at line 3', $e->getMessage());
43
+ $this->assertEquals(3, $e->getTemplateLine());
44
+ $this->assertEquals('index', $e->getTemplateFile());
45
+ }
46
+ }
47
+
48
+ public function testRenderWrapsExceptions()
49
+ {
50
+ $loader = new Twig_Loader_Array(array('index' => "\n\n\n{{ foo.bar }}\n\n\n\n{{ 'foo' }}"));
51
+ $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
52
+
53
+ $template = $twig->loadTemplate('index');
54
+
55
+ try {
56
+ $template->render(array('foo' => new Twig_Tests_ErrorTest_Foo()));
57
+
58
+ $this->fail();
59
+ } catch (Twig_Error_Runtime $e) {
60
+ $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...") in "index" at line 4.', $e->getMessage());
61
+ $this->assertEquals(4, $e->getTemplateLine());
62
+ $this->assertEquals('index', $e->getTemplateFile());
63
+ }
64
+ }
65
+
66
+ public function testTwigExceptionAddsFileAndLineWhenMissingWithInheritance()
67
+ {
68
+ $loader = new Twig_Loader_Array(array(
69
+ 'index' => "{% extends 'base' %}
70
+ {% block content %}
71
+ {{ foo.bar }}
72
+ {% endblock %}
73
+ {% block foo %}
74
+ {{ foo.bar }}
75
+ {% endblock %}",
76
+ 'base' => '{% block content %}{% endblock %}'
77
+ ));
78
+ $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
79
+
80
+ $template = $twig->loadTemplate('index');
81
+ try {
82
+ $template->render(array());
83
+
84
+ $this->fail();
85
+ } catch (Twig_Error_Runtime $e) {
86
+ $this->assertEquals('Variable "foo" does not exist in "index" at line 3', $e->getMessage());
87
+ $this->assertEquals(3, $e->getTemplateLine());
88
+ $this->assertEquals('index', $e->getTemplateFile());
89
+ }
90
+
91
+ try {
92
+ $template->render(array('foo' => new Twig_Tests_ErrorTest_Foo()));
93
+
94
+ $this->fail();
95
+ } catch (Twig_Error_Runtime $e) {
96
+ $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...") in "index" at line 3.', $e->getMessage());
97
+ $this->assertEquals(3, $e->getTemplateLine());
98
+ $this->assertEquals('index', $e->getTemplateFile());
99
+ }
100
+ }
101
+
102
+ public function testTwigExceptionAddsFileAndLineWhenMissingWithInheritanceAgain()
103
+ {
104
+ $loader = new Twig_Loader_Array(array(
105
+ 'index' => "{% extends 'base' %}
106
+ {% block content %}
107
+ {{ parent() }}
108
+ {% endblock %}",
109
+ 'base' => '{% block content %}{{ foo }}{% endblock %}'
110
+ ));
111
+ $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
112
+
113
+ $template = $twig->loadTemplate('index');
114
+ try {
115
+ $template->render(array());
116
+
117
+ $this->fail();
118
+ } catch (Twig_Error_Runtime $e) {
119
+ $this->assertEquals('Variable "foo" does not exist in "base" at line 1', $e->getMessage());
120
+ $this->assertEquals(1, $e->getTemplateLine());
121
+ $this->assertEquals('base', $e->getTemplateFile());
122
+ }
123
+ }
124
+
125
+ public function testTwigExceptionAddsFileAndLineWhenMissingWithInheritanceOnDisk()
126
+ {
127
+ $loader = new Twig_Loader_Filesystem(dirname(__FILE__).'/Fixtures/errors');
128
+ $twig = new Twig_Environment($loader, array('strict_variables' => true, 'debug' => true, 'cache' => false));
129
+
130
+ $template = $twig->loadTemplate('index.html');
131
+ try {
132
+ $template->render(array());
133
+
134
+ $this->fail();
135
+ } catch (Twig_Error_Runtime $e) {
136
+ $this->assertEquals('Variable "foo" does not exist in "index.html" at line 3', $e->getMessage());
137
+ $this->assertEquals(3, $e->getTemplateLine());
138
+ $this->assertEquals('index.html', $e->getTemplateFile());
139
+ }
140
+
141
+ try {
142
+ $template->render(array('foo' => new Twig_Tests_ErrorTest_Foo()));
143
+
144
+ $this->fail();
145
+ } catch (Twig_Error_Runtime $e) {
146
+ $this->assertEquals('An exception has been thrown during the rendering of a template ("Runtime error...") in "index.html" at line 3.', $e->getMessage());
147
+ $this->assertEquals(3, $e->getTemplateLine());
148
+ $this->assertEquals('index.html', $e->getTemplateFile());
149
+ }
150
+ }
151
+ }
152
+
153
+ class Twig_Tests_ErrorTest_Foo
154
+ {
155
+ public function bar()
156
+ {
157
+ throw new Exception('Runtime error...');
158
+ }
159
+ }
Twig/test/Twig/Tests/ExpressionParserTest.php ADDED
@@ -0,0 +1,332 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_ExpressionParserTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ /**
15
+ * @expectedException Twig_Error_Syntax
16
+ * @dataProvider getFailingTestsForAssignment
17
+ */
18
+ public function testCanOnlyAssignToNames($template)
19
+ {
20
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
21
+ $parser = new Twig_Parser($env);
22
+
23
+ $parser->parse($env->tokenize($template, 'index'));
24
+ }
25
+
26
+ public function getFailingTestsForAssignment()
27
+ {
28
+ return array(
29
+ array('{% set false = "foo" %}'),
30
+ array('{% set true = "foo" %}'),
31
+ array('{% set none = "foo" %}'),
32
+ array('{% set 3 = "foo" %}'),
33
+ array('{% set 1 + 2 = "foo" %}'),
34
+ array('{% set "bar" = "foo" %}'),
35
+ array('{% set %}{% endset %}')
36
+ );
37
+ }
38
+
39
+ /**
40
+ * @dataProvider getTestsForArray
41
+ */
42
+ public function testArrayExpression($template, $expected)
43
+ {
44
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
45
+ $stream = $env->tokenize($template, 'index');
46
+ $parser = new Twig_Parser($env);
47
+
48
+ $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
49
+ }
50
+
51
+ /**
52
+ * @expectedException Twig_Error_Syntax
53
+ * @dataProvider getFailingTestsForArray
54
+ */
55
+ public function testArraySyntaxError($template)
56
+ {
57
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
58
+ $parser = new Twig_Parser($env);
59
+
60
+ $parser->parse($env->tokenize($template, 'index'));
61
+ }
62
+
63
+ public function getFailingTestsForArray()
64
+ {
65
+ return array(
66
+ array('{{ [1, "a": "b"] }}'),
67
+ array('{{ {"a": "b", 2} }}'),
68
+ );
69
+ }
70
+
71
+ public function getTestsForArray()
72
+ {
73
+ return array(
74
+ // simple array
75
+ array('{{ [1, 2] }}', new Twig_Node_Expression_Array(array(
76
+ new Twig_Node_Expression_Constant(0, 1),
77
+ new Twig_Node_Expression_Constant(1, 1),
78
+
79
+ new Twig_Node_Expression_Constant(1, 1),
80
+ new Twig_Node_Expression_Constant(2, 1),
81
+ ), 1),
82
+ ),
83
+
84
+ // array with trailing ,
85
+ array('{{ [1, 2, ] }}', new Twig_Node_Expression_Array(array(
86
+ new Twig_Node_Expression_Constant(0, 1),
87
+ new Twig_Node_Expression_Constant(1, 1),
88
+
89
+ new Twig_Node_Expression_Constant(1, 1),
90
+ new Twig_Node_Expression_Constant(2, 1),
91
+ ), 1),
92
+ ),
93
+
94
+ // simple hash
95
+ array('{{ {"a": "b", "b": "c"} }}', new Twig_Node_Expression_Array(array(
96
+ new Twig_Node_Expression_Constant('a', 1),
97
+ new Twig_Node_Expression_Constant('b', 1),
98
+
99
+ new Twig_Node_Expression_Constant('b', 1),
100
+ new Twig_Node_Expression_Constant('c', 1),
101
+ ), 1),
102
+ ),
103
+
104
+ // hash with trailing ,
105
+ array('{{ {"a": "b", "b": "c", } }}', new Twig_Node_Expression_Array(array(
106
+ new Twig_Node_Expression_Constant('a', 1),
107
+ new Twig_Node_Expression_Constant('b', 1),
108
+
109
+ new Twig_Node_Expression_Constant('b', 1),
110
+ new Twig_Node_Expression_Constant('c', 1),
111
+ ), 1),
112
+ ),
113
+
114
+ // hash in an array
115
+ array('{{ [1, {"a": "b", "b": "c"}] }}', new Twig_Node_Expression_Array(array(
116
+ new Twig_Node_Expression_Constant(0, 1),
117
+ new Twig_Node_Expression_Constant(1, 1),
118
+
119
+ new Twig_Node_Expression_Constant(1, 1),
120
+ new Twig_Node_Expression_Array(array(
121
+ new Twig_Node_Expression_Constant('a', 1),
122
+ new Twig_Node_Expression_Constant('b', 1),
123
+
124
+ new Twig_Node_Expression_Constant('b', 1),
125
+ new Twig_Node_Expression_Constant('c', 1),
126
+ ), 1),
127
+ ), 1),
128
+ ),
129
+
130
+ // array in a hash
131
+ array('{{ {"a": [1, 2], "b": "c"} }}', new Twig_Node_Expression_Array(array(
132
+ new Twig_Node_Expression_Constant('a', 1),
133
+ new Twig_Node_Expression_Array(array(
134
+ new Twig_Node_Expression_Constant(0, 1),
135
+ new Twig_Node_Expression_Constant(1, 1),
136
+
137
+ new Twig_Node_Expression_Constant(1, 1),
138
+ new Twig_Node_Expression_Constant(2, 1),
139
+ ), 1),
140
+ new Twig_Node_Expression_Constant('b', 1),
141
+ new Twig_Node_Expression_Constant('c', 1),
142
+ ), 1),
143
+ ),
144
+ );
145
+ }
146
+
147
+ /**
148
+ * @expectedException Twig_Error_Syntax
149
+ */
150
+ public function testStringExpressionDoesNotConcatenateTwoConsecutiveStrings()
151
+ {
152
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false, 'optimizations' => 0));
153
+ $stream = $env->tokenize('{{ "a" "b" }}', 'index');
154
+ $parser = new Twig_Parser($env);
155
+
156
+ $parser->parse($stream);
157
+ }
158
+
159
+ /**
160
+ * @dataProvider getTestsForString
161
+ */
162
+ public function testStringExpression($template, $expected)
163
+ {
164
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false, 'optimizations' => 0));
165
+ $stream = $env->tokenize($template, 'index');
166
+ $parser = new Twig_Parser($env);
167
+
168
+ $this->assertEquals($expected, $parser->parse($stream)->getNode('body')->getNode(0)->getNode('expr'));
169
+ }
170
+
171
+ public function getTestsForString()
172
+ {
173
+ return array(
174
+ array(
175
+ '{{ "foo" }}', new Twig_Node_Expression_Constant('foo', 1),
176
+ ),
177
+ array(
178
+ '{{ "foo #{bar}" }}', new Twig_Node_Expression_Binary_Concat(
179
+ new Twig_Node_Expression_Constant('foo ', 1),
180
+ new Twig_Node_Expression_Name('bar', 1),
181
+ 1
182
+ ),
183
+ ),
184
+ array(
185
+ '{{ "foo #{bar} baz" }}', new Twig_Node_Expression_Binary_Concat(
186
+ new Twig_Node_Expression_Binary_Concat(
187
+ new Twig_Node_Expression_Constant('foo ', 1),
188
+ new Twig_Node_Expression_Name('bar', 1),
189
+ 1
190
+ ),
191
+ new Twig_Node_Expression_Constant(' baz', 1),
192
+ 1
193
+ )
194
+ ),
195
+
196
+ array(
197
+ '{{ "foo #{"foo #{bar} baz"} baz" }}', new Twig_Node_Expression_Binary_Concat(
198
+ new Twig_Node_Expression_Binary_Concat(
199
+ new Twig_Node_Expression_Constant('foo ', 1),
200
+ new Twig_Node_Expression_Binary_Concat(
201
+ new Twig_Node_Expression_Binary_Concat(
202
+ new Twig_Node_Expression_Constant('foo ', 1),
203
+ new Twig_Node_Expression_Name('bar', 1),
204
+ 1
205
+ ),
206
+ new Twig_Node_Expression_Constant(' baz', 1),
207
+ 1
208
+ ),
209
+ 1
210
+ ),
211
+ new Twig_Node_Expression_Constant(' baz', 1),
212
+ 1
213
+ ),
214
+ ),
215
+ );
216
+ }
217
+
218
+ /**
219
+ * @expectedException Twig_Error_Syntax
220
+ */
221
+ public function testAttributeCallDoesNotSupportNamedArguments()
222
+ {
223
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
224
+ $parser = new Twig_Parser($env);
225
+
226
+ $parser->parse($env->tokenize('{{ foo.bar(name="Foo") }}', 'index'));
227
+ }
228
+
229
+ /**
230
+ * @expectedException Twig_Error_Syntax
231
+ */
232
+ public function testMacroCallDoesNotSupportNamedArguments()
233
+ {
234
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
235
+ $parser = new Twig_Parser($env);
236
+
237
+ $parser->parse($env->tokenize('{% from _self import foo %}{% macro foo() %}{% endmacro %}{{ foo(name="Foo") }}', 'index'));
238
+ }
239
+
240
+ /**
241
+ * @expectedException Twig_Error_Syntax
242
+ * @expectedExceptionMessage An argument must be a name. Unexpected token "string" of value "a" ("name" expected) in "index" at line 1
243
+ */
244
+ public function testMacroDefinitionDoesNotSupportNonNameVariableName()
245
+ {
246
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
247
+ $parser = new Twig_Parser($env);
248
+
249
+ $parser->parse($env->tokenize('{% macro foo("a") %}{% endmacro %}', 'index'));
250
+ }
251
+
252
+ /**
253
+ * @expectedException Twig_Error_Syntax
254
+ * @expectedExceptionMessage A default value for an argument must be a constant (a boolean, a string, a number, or an array) in "index" at line 1
255
+ * @dataProvider getMacroDefinitionDoesNotSupportNonConstantDefaultValues
256
+ */
257
+ public function testMacroDefinitionDoesNotSupportNonConstantDefaultValues($template)
258
+ {
259
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
260
+ $parser = new Twig_Parser($env);
261
+
262
+ $parser->parse($env->tokenize($template, 'index'));
263
+ }
264
+
265
+ public function getMacroDefinitionDoesNotSupportNonConstantDefaultValues()
266
+ {
267
+ return array(
268
+ array('{% macro foo(name = "a #{foo} a") %}{% endmacro %}'),
269
+ array('{% macro foo(name = [["b", "a #{foo} a"]]) %}{% endmacro %}'),
270
+ );
271
+ }
272
+
273
+ /**
274
+ * @dataProvider getMacroDefinitionSupportsConstantDefaultValues
275
+ */
276
+ public function testMacroDefinitionSupportsConstantDefaultValues($template)
277
+ {
278
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
279
+ $parser = new Twig_Parser($env);
280
+
281
+ $parser->parse($env->tokenize($template, 'index'));
282
+ }
283
+
284
+ public function getMacroDefinitionSupportsConstantDefaultValues()
285
+ {
286
+ return array(
287
+ array('{% macro foo(name = "aa") %}{% endmacro %}'),
288
+ array('{% macro foo(name = 12) %}{% endmacro %}'),
289
+ array('{% macro foo(name = true) %}{% endmacro %}'),
290
+ array('{% macro foo(name = ["a"]) %}{% endmacro %}'),
291
+ array('{% macro foo(name = [["a"]]) %}{% endmacro %}'),
292
+ array('{% macro foo(name = {a: "a"}) %}{% endmacro %}'),
293
+ array('{% macro foo(name = {a: {b: "a"}}) %}{% endmacro %}'),
294
+ );
295
+ }
296
+
297
+ /**
298
+ * @expectedException Twig_Error_Syntax
299
+ * @expectedExceptionMessage The function "cycl" does not exist. Did you mean "cycle" in "index" at line 1
300
+ */
301
+ public function testUnknownFunction()
302
+ {
303
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
304
+ $parser = new Twig_Parser($env);
305
+
306
+ $parser->parse($env->tokenize('{{ cycl() }}', 'index'));
307
+ }
308
+
309
+ /**
310
+ * @expectedException Twig_Error_Syntax
311
+ * @expectedExceptionMessage The filter "lowe" does not exist. Did you mean "lower" in "index" at line 1
312
+ */
313
+ public function testUnknownFilter()
314
+ {
315
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
316
+ $parser = new Twig_Parser($env);
317
+
318
+ $parser->parse($env->tokenize('{{ 1|lowe }}', 'index'));
319
+ }
320
+
321
+ /**
322
+ * @expectedException Twig_Error_Syntax
323
+ * @expectedExceptionMessage The test "nul" does not exist. Did you mean "null" in "index" at line 1
324
+ */
325
+ public function testUnknownTest()
326
+ {
327
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
328
+ $parser = new Twig_Parser($env);
329
+
330
+ $parser->parse($env->tokenize('{{ 1 is nul }}', 'index'));
331
+ }
332
+ }
Twig/test/Twig/Tests/Extension/CoreTest.php ADDED
@@ -0,0 +1,115 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Extension_CoreTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ /**
15
+ * @dataProvider getRandomFunctionTestData
16
+ */
17
+ public function testRandomFunction($value, $expectedInArray)
18
+ {
19
+ for ($i = 0; $i < 100; $i++) {
20
+ $this->assertTrue(in_array(twig_random(new Twig_Environment(), $value), $expectedInArray, true)); // assertContains() would not consider the type
21
+ }
22
+ }
23
+
24
+ public function getRandomFunctionTestData()
25
+ {
26
+ return array(
27
+ array( // array
28
+ array('apple', 'orange', 'citrus'),
29
+ array('apple', 'orange', 'citrus'),
30
+ ),
31
+ array( // Traversable
32
+ new ArrayObject(array('apple', 'orange', 'citrus')),
33
+ array('apple', 'orange', 'citrus'),
34
+ ),
35
+ array( // unicode string
36
+ 'Ä€é',
37
+ array('Ä', '€', 'é'),
38
+ ),
39
+ array( // numeric but string
40
+ '123',
41
+ array('1', '2', '3'),
42
+ ),
43
+ array( // integer
44
+ 5,
45
+ range(0, 5, 1),
46
+ ),
47
+ array( // float
48
+ 5.9,
49
+ range(0, 5, 1),
50
+ ),
51
+ array( // negative
52
+ -2,
53
+ array(0, -1, -2),
54
+ ),
55
+ );
56
+ }
57
+
58
+ public function testRandomFunctionWithoutParameter()
59
+ {
60
+ $max = mt_getrandmax();
61
+
62
+ for ($i = 0; $i < 100; $i++) {
63
+ $val = twig_random(new Twig_Environment());
64
+ $this->assertTrue(is_int($val) && $val >= 0 && $val <= $max);
65
+ }
66
+ }
67
+
68
+ public function testRandomFunctionReturnsAsIs()
69
+ {
70
+ $this->assertSame('', twig_random(new Twig_Environment(), ''));
71
+ $this->assertSame('', twig_random(new Twig_Environment(null, array('charset' => null)), ''));
72
+
73
+ $instance = new stdClass();
74
+ $this->assertSame($instance, twig_random(new Twig_Environment(), $instance));
75
+ }
76
+
77
+ /**
78
+ * @expectedException Twig_Error_Runtime
79
+ */
80
+ public function testRandomFunctionOfEmptyArrayThrowsException()
81
+ {
82
+ twig_random(new Twig_Environment(), array());
83
+ }
84
+
85
+ public function testRandomFunctionOnNonUTF8String()
86
+ {
87
+ if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) {
88
+ $this->markTestSkipped('needs iconv or mbstring');
89
+ }
90
+
91
+ $twig = new Twig_Environment();
92
+ $twig->setCharset('ISO-8859-1');
93
+
94
+ $text = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8');
95
+ for ($i = 0; $i < 30; $i++) {
96
+ $rand = twig_random($twig, $text);
97
+ $this->assertTrue(in_array(twig_convert_encoding($rand, 'UTF-8', 'ISO-8859-1'), array('Ä', 'é'), true));
98
+ }
99
+ }
100
+
101
+ public function testReverseFilterOnNonUTF8String()
102
+ {
103
+ if (!function_exists('iconv') && !function_exists('mb_convert_encoding')) {
104
+ $this->markTestSkipped('needs iconv or mbstring');
105
+ }
106
+
107
+ $twig = new Twig_Environment();
108
+ $twig->setCharset('ISO-8859-1');
109
+
110
+ $input = twig_convert_encoding('Äé', 'ISO-8859-1', 'UTF-8');
111
+ $output = twig_convert_encoding(twig_reverse_filter($twig, $input), 'UTF-8', 'ISO-8859-1');
112
+
113
+ $this->assertEquals($output, 'éÄ');
114
+ }
115
+ }
Twig/test/Twig/Tests/Extension/SandboxTest.php ADDED
@@ -0,0 +1,212 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Extension_SandboxTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ protected static $params, $templates;
15
+
16
+ public function setUp()
17
+ {
18
+ self::$params = array(
19
+ 'name' => 'Fabien',
20
+ 'obj' => new FooObject(),
21
+ 'arr' => array('obj' => new FooObject()),
22
+ );
23
+
24
+ self::$templates = array(
25
+ '1_basic1' => '{{ obj.foo }}',
26
+ '1_basic2' => '{{ name|upper }}',
27
+ '1_basic3' => '{% if name %}foo{% endif %}',
28
+ '1_basic4' => '{{ obj.bar }}',
29
+ '1_basic5' => '{{ obj }}',
30
+ '1_basic6' => '{{ arr.obj }}',
31
+ '1_basic7' => '{{ cycle(["foo","bar"], 1) }}',
32
+ '1_basic8' => '{{ obj.getfoobar }}{{ obj.getFooBar }}',
33
+ '1_basic' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
34
+ '1_layout' => '{% block content %}{% endblock %}',
35
+ '1_child' => '{% extends "1_layout" %}{% block content %}{{ "a"|json_encode }}{% endblock %}',
36
+ );
37
+ }
38
+
39
+ /**
40
+ * @expectedException Twig_Sandbox_SecurityError
41
+ * @expectedExceptionMessage Filter "json_encode" is not allowed in "1_child".
42
+ */
43
+ public function testSandboxWithInheritance()
44
+ {
45
+ $twig = $this->getEnvironment(true, array(), self::$templates, array('block'));
46
+ $twig->loadTemplate('1_child')->render(array());
47
+ }
48
+
49
+ public function testSandboxGloballySet()
50
+ {
51
+ $twig = $this->getEnvironment(false, array(), self::$templates);
52
+ $this->assertEquals('FOO', $twig->loadTemplate('1_basic')->render(self::$params), 'Sandbox does nothing if it is disabled globally');
53
+
54
+ $twig = $this->getEnvironment(true, array(), self::$templates);
55
+ try {
56
+ $twig->loadTemplate('1_basic1')->render(self::$params);
57
+ $this->fail('Sandbox throws a SecurityError exception if an unallowed method is called');
58
+ } catch (Twig_Sandbox_SecurityError $e) {
59
+ }
60
+
61
+ $twig = $this->getEnvironment(true, array(), self::$templates);
62
+ try {
63
+ $twig->loadTemplate('1_basic2')->render(self::$params);
64
+ $this->fail('Sandbox throws a SecurityError exception if an unallowed filter is called');
65
+ } catch (Twig_Sandbox_SecurityError $e) {
66
+ }
67
+
68
+ $twig = $this->getEnvironment(true, array(), self::$templates);
69
+ try {
70
+ $twig->loadTemplate('1_basic3')->render(self::$params);
71
+ $this->fail('Sandbox throws a SecurityError exception if an unallowed tag is used in the template');
72
+ } catch (Twig_Sandbox_SecurityError $e) {
73
+ }
74
+
75
+ $twig = $this->getEnvironment(true, array(), self::$templates);
76
+ try {
77
+ $twig->loadTemplate('1_basic4')->render(self::$params);
78
+ $this->fail('Sandbox throws a SecurityError exception if an unallowed property is called in the template');
79
+ } catch (Twig_Sandbox_SecurityError $e) {
80
+ }
81
+
82
+ $twig = $this->getEnvironment(true, array(), self::$templates);
83
+ try {
84
+ $twig->loadTemplate('1_basic5')->render(self::$params);
85
+ $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
86
+ } catch (Twig_Sandbox_SecurityError $e) {
87
+ }
88
+
89
+ $twig = $this->getEnvironment(true, array(), self::$templates);
90
+ try {
91
+ $twig->loadTemplate('1_basic6')->render(self::$params);
92
+ $this->fail('Sandbox throws a SecurityError exception if an unallowed method (__toString()) is called in the template');
93
+ } catch (Twig_Sandbox_SecurityError $e) {
94
+ }
95
+
96
+ $twig = $this->getEnvironment(true, array(), self::$templates);
97
+ try {
98
+ $twig->loadTemplate('1_basic7')->render(self::$params);
99
+ $this->fail('Sandbox throws a SecurityError exception if an unallowed function is called in the template');
100
+ } catch (Twig_Sandbox_SecurityError $e) {
101
+ }
102
+
103
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => 'foo'));
104
+ FooObject::reset();
105
+ $this->assertEquals('foo', $twig->loadTemplate('1_basic1')->render(self::$params), 'Sandbox allow some methods');
106
+ $this->assertEquals(1, FooObject::$called['foo'], 'Sandbox only calls method once');
107
+
108
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => '__toString'));
109
+ FooObject::reset();
110
+ $this->assertEquals('foo', $twig->loadTemplate('1_basic5')->render(self::$params), 'Sandbox allow some methods');
111
+ $this->assertEquals(1, FooObject::$called['__toString'], 'Sandbox only calls method once');
112
+
113
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array('upper'));
114
+ $this->assertEquals('FABIEN', $twig->loadTemplate('1_basic2')->render(self::$params), 'Sandbox allow some filters');
115
+
116
+ $twig = $this->getEnvironment(true, array(), self::$templates, array('if'));
117
+ $this->assertEquals('foo', $twig->loadTemplate('1_basic3')->render(self::$params), 'Sandbox allow some tags');
118
+
119
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array('FooObject' => 'bar'));
120
+ $this->assertEquals('bar', $twig->loadTemplate('1_basic4')->render(self::$params), 'Sandbox allow some properties');
121
+
122
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array(), array(), array('cycle'));
123
+ $this->assertEquals('bar', $twig->loadTemplate('1_basic7')->render(self::$params), 'Sandbox allow some functions');
124
+
125
+ foreach (array('getfoobar', 'getFoobar', 'getFooBar') as $name) {
126
+ $twig = $this->getEnvironment(true, array(), self::$templates, array(), array(), array('FooObject' => $name));
127
+ FooObject::reset();
128
+ $this->assertEquals('foobarfoobar', $twig->loadTemplate('1_basic8')->render(self::$params), 'Sandbox allow methods in a case-insensitive way');
129
+ $this->assertEquals(2, FooObject::$called['getFooBar'], 'Sandbox only calls method once');
130
+ }
131
+ }
132
+
133
+ public function testSandboxLocallySetForAnInclude()
134
+ {
135
+ self::$templates = array(
136
+ '2_basic' => '{{ obj.foo }}{% include "2_included" %}{{ obj.foo }}',
137
+ '2_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
138
+ );
139
+
140
+ $twig = $this->getEnvironment(false, array(), self::$templates);
141
+ $this->assertEquals('fooFOOfoo', $twig->loadTemplate('2_basic')->render(self::$params), 'Sandbox does nothing if disabled globally and sandboxed not used for the include');
142
+
143
+ self::$templates = array(
144
+ '3_basic' => '{{ obj.foo }}{% sandbox %}{% include "3_included" %}{% endsandbox %}{{ obj.foo }}',
145
+ '3_included' => '{% if obj.foo %}{{ obj.foo|upper }}{% endif %}',
146
+ );
147
+
148
+ $twig = $this->getEnvironment(true, array(), self::$templates);
149
+ try {
150
+ $twig->loadTemplate('3_basic')->render(self::$params);
151
+ $this->fail('Sandbox throws a SecurityError exception when the included file is sandboxed');
152
+ } catch (Twig_Sandbox_SecurityError $e) {
153
+ }
154
+ }
155
+
156
+ public function testMacrosInASandbox()
157
+ {
158
+ $twig = $this->getEnvironment(true, array('autoescape' => true), array('index' => <<<EOF
159
+ {%- import _self as macros %}
160
+
161
+ {%- macro test(text) %}<p>{{ text }}</p>{% endmacro %}
162
+
163
+ {{- macros.test('username') }}
164
+ EOF
165
+ ), array('macro', 'import'), array('escape'));
166
+
167
+ $this->assertEquals('<p>username</p>', $twig->loadTemplate('index')->render(array()));
168
+ }
169
+
170
+ protected function getEnvironment($sandboxed, $options, $templates, $tags = array(), $filters = array(), $methods = array(), $properties = array(), $functions = array())
171
+ {
172
+ $loader = new Twig_Loader_Array($templates);
173
+ $twig = new Twig_Environment($loader, array_merge(array('debug' => true, 'cache' => false, 'autoescape' => false), $options));
174
+ $policy = new Twig_Sandbox_SecurityPolicy($tags, $filters, $methods, $properties, $functions);
175
+ $twig->addExtension(new Twig_Extension_Sandbox($policy, $sandboxed));
176
+
177
+ return $twig;
178
+ }
179
+ }
180
+
181
+ class FooObject
182
+ {
183
+ public static $called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0);
184
+
185
+ public $bar = 'bar';
186
+
187
+ public static function reset()
188
+ {
189
+ self::$called = array('__toString' => 0, 'foo' => 0, 'getFooBar' => 0);
190
+ }
191
+
192
+ public function __toString()
193
+ {
194
+ ++self::$called['__toString'];
195
+
196
+ return 'foo';
197
+ }
198
+
199
+ public function foo()
200
+ {
201
+ ++self::$called['foo'];
202
+
203
+ return 'foo';
204
+ }
205
+
206
+ public function getFooBar()
207
+ {
208
+ ++self::$called['getFooBar'];
209
+
210
+ return 'foobar';
211
+ }
212
+ }
Twig/test/Twig/Tests/FileCachingTest.php ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ class Twig_Tests_FileCachingTest extends PHPUnit_Framework_TestCase
4
+ {
5
+ protected $fileName;
6
+ protected $env;
7
+ protected $tmpDir;
8
+
9
+ public function setUp()
10
+ {
11
+ $this->tmpDir = sys_get_temp_dir().'/TwigTests';
12
+ if (!file_exists($this->tmpDir)) {
13
+ @mkdir($this->tmpDir, 0777, true);
14
+ }
15
+
16
+ if (!is_writable($this->tmpDir)) {
17
+ $this->markTestSkipped(sprintf('Unable to run the tests as "%s" is not writable.', $this->tmpDir));
18
+ }
19
+
20
+ $this->env = new Twig_Environment(new Twig_Loader_String(), array('cache' => $this->tmpDir));
21
+ }
22
+
23
+ public function tearDown()
24
+ {
25
+ if ($this->fileName) {
26
+ unlink($this->fileName);
27
+ }
28
+
29
+ $this->removeDir($this->tmpDir);
30
+ }
31
+
32
+ public function testWritingCacheFiles()
33
+ {
34
+ $name = 'This is just text.';
35
+ $template = $this->env->loadTemplate($name);
36
+ $cacheFileName = $this->env->getCacheFilename($name);
37
+
38
+ $this->assertTrue(file_exists($cacheFileName), 'Cache file does not exist.');
39
+ $this->fileName = $cacheFileName;
40
+ }
41
+
42
+ public function testClearingCacheFiles()
43
+ {
44
+ $name = 'I will be deleted.';
45
+ $template = $this->env->loadTemplate($name);
46
+ $cacheFileName = $this->env->getCacheFilename($name);
47
+
48
+ $this->assertTrue(file_exists($cacheFileName), 'Cache file does not exist.');
49
+ $this->env->clearCacheFiles();
50
+ $this->assertFalse(file_exists($cacheFileName), 'Cache file was not cleared.');
51
+ }
52
+
53
+ private function removeDir($target)
54
+ {
55
+ $fp = opendir($target);
56
+ while (false !== $file = readdir($fp)) {
57
+ if (in_array($file, array('.', '..'))) {
58
+ continue;
59
+ }
60
+
61
+ if (is_dir($target.'/'.$file)) {
62
+ self::removeDir($target.'/'.$file);
63
+ } else {
64
+ unlink($target.'/'.$file);
65
+ }
66
+ }
67
+ closedir($fp);
68
+ rmdir($target);
69
+ }
70
+ }
Twig/test/Twig/Tests/Fixtures/errors/base.html ADDED
@@ -0,0 +1 @@
 
1
+ {% block content %}{% endblock %}
Twig/test/Twig/Tests/Fixtures/errors/index.html ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
1
+ {% extends 'base.html' %}
2
+ {% block content %}
3
+ {{ foo.bar }}
4
+ {% endblock %}
5
+ {% block foo %}
6
+ {{ foo.bar }}
7
+ {% endblock %}
Twig/test/Twig/Tests/Fixtures/exceptions/unclosed_tag.test ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Exception for an unclosed tag
3
+ --TEMPLATE--
4
+ {% block foo %}
5
+ {% if foo %}
6
+
7
+
8
+
9
+
10
+ {% for i in fo %}
11
+
12
+
13
+
14
+ {% endfor %}
15
+
16
+
17
+
18
+ {% endblock %}
19
+ --EXCEPTION--
20
+ Twig_Error_Syntax: Unexpected tag name "endblock" (expecting closing tag for the "if" tag defined near line 4) in "index.twig" at line 16
Twig/test/Twig/Tests/Fixtures/expressions/array.test ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports array notation
3
+ --TEMPLATE--
4
+ {# empty array #}
5
+ {{ []|join(',') }}
6
+
7
+ {{ [1, 2]|join(',') }}
8
+ {{ ['foo', "bar"]|join(',') }}
9
+ {{ {0: 1, 'foo': 'bar'}|join(',') }}
10
+ {{ {0: 1, 'foo': 'bar'}|keys|join(',') }}
11
+
12
+ {{ {0: 1, foo: 'bar'}|join(',') }}
13
+ {{ {0: 1, foo: 'bar'}|keys|join(',') }}
14
+
15
+ {# nested arrays #}
16
+ {% set a = [1, 2, [1, 2], {'foo': {'foo': 'bar'}}] %}
17
+ {{ a[2]|join(',') }}
18
+ {{ a[3]["foo"]|join(',') }}
19
+
20
+ {# works even if [] is used inside the array #}
21
+ {{ [foo[bar]]|join(',') }}
22
+
23
+ {# elements can be any expression #}
24
+ {{ ['foo'|upper, bar|upper, bar == foo]|join(',') }}
25
+
26
+ {# arrays can have a trailing , like in PHP #}
27
+ {{
28
+ [
29
+ 1,
30
+ 2,
31
+ ]|join(',')
32
+ }}
33
+
34
+ {# keys can be any expression #}
35
+ {% set a = 1 %}
36
+ {% set b = "foo" %}
37
+ {% set ary = { (a): 'a', (b): 'b', 'c': 'c', (a ~ b): 'd' } %}
38
+ {{ ary|keys|join(',') }}
39
+ {{ ary|join(',') }}
40
+ --DATA--
41
+ return array('bar' => 'bar', 'foo' => array('bar' => 'bar'))
42
+ --EXPECT--
43
+ 1,2
44
+ foo,bar
45
+ 1,bar
46
+ 0,foo
47
+
48
+ 1,bar
49
+ 0,foo
50
+
51
+ 1,2
52
+ bar
53
+
54
+ bar
55
+
56
+ FOO,BAR,
57
+
58
+ 1,2
59
+
60
+ 1,foo,c,1foo
61
+ a,b,c,d
Twig/test/Twig/Tests/Fixtures/expressions/array_call.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports method calls
3
+ --TEMPLATE--
4
+ {{ items.foo }}
5
+ {{ items['foo'] }}
6
+ {{ items[foo] }}
7
+ {{ items[items[foo]] }}
8
+ --DATA--
9
+ return array('foo' => 'bar', 'items' => array('foo' => 'bar', 'bar' => 'foo'))
10
+ --EXPECT--
11
+ bar
12
+ bar
13
+ foo
14
+ bar
Twig/test/Twig/Tests/Fixtures/expressions/binary.test ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports binary operations (+, -, *, /, ~, %, and, or)
3
+ --TEMPLATE--
4
+ {{ 1 + 1 }}
5
+ {{ 2 - 1 }}
6
+ {{ 2 * 2 }}
7
+ {{ 2 / 2 }}
8
+ {{ 3 % 2 }}
9
+ {{ 1 and 1 }}
10
+ {{ 1 and 0 }}
11
+ {{ 0 and 1 }}
12
+ {{ 0 and 0 }}
13
+ {{ 1 or 1 }}
14
+ {{ 1 or 0 }}
15
+ {{ 0 or 1 }}
16
+ {{ 0 or 0 }}
17
+ {{ 0 or 1 and 0 }}
18
+ {{ 1 or 0 and 1 }}
19
+ {{ "foo" ~ "bar" }}
20
+ {{ foo ~ "bar" }}
21
+ {{ "foo" ~ bar }}
22
+ {{ foo ~ bar }}
23
+ {{ 20 // 7 }}
24
+ --DATA--
25
+ return array('foo' => 'bar', 'bar' => 'foo')
26
+ --EXPECT--
27
+ 2
28
+ 1
29
+ 4
30
+ 1
31
+ 1
32
+ 1
33
+
34
+
35
+
36
+ 1
37
+ 1
38
+ 1
39
+
40
+
41
+ 1
42
+ foobar
43
+ barbar
44
+ foofoo
45
+ barfoo
46
+ 2
Twig/test/Twig/Tests/Fixtures/expressions/bitwise.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports bitwise operations
3
+ --TEMPLATE--
4
+ {{ 1 b-and 5 }}
5
+ {{ 1 b-or 5 }}
6
+ {{ 1 b-xor 5 }}
7
+ {{ (1 and 0 b-or 0) is sameas(1 and (0 b-or 0)) ? 'ok' : 'ko' }}
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
11
+ 1
12
+ 5
13
+ 4
14
+ ok
Twig/test/Twig/Tests/Fixtures/expressions/comparison.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports comparison operators (==, !=, <, >, >=, <=)
3
+ --TEMPLATE--
4
+ {{ 1 > 2 }}/{{ 1 > 1 }}/{{ 1 >= 2 }}/{{ 1 >= 1 }}
5
+ {{ 1 < 2 }}/{{ 1 < 1 }}/{{ 1 <= 2 }}/{{ 1 <= 1 }}
6
+ {{ 1 == 1 }}/{{ 1 == 2 }}
7
+ {{ 1 != 1 }}/{{ 1 != 2 }}
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
11
+ ///1
12
+ 1//1/1
13
+ 1/
14
+ /1
Twig/test/Twig/Tests/Fixtures/expressions/dotdot.test ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports the .. operator
3
+ --TEMPLATE--
4
+ {% for i in 0..10 %}{{ i }} {% endfor %}
5
+
6
+ {% for letter in 'a'..'z' %}{{ letter }} {% endfor %}
7
+
8
+ {% for letter in 'a'|upper..'z'|upper %}{{ letter }} {% endfor %}
9
+
10
+ {% for i in foo[0]..foo[1] %}{{ i }} {% endfor %}
11
+
12
+ {% for i in 0 + 1 .. 10 - 1 %}{{ i }} {% endfor %}
13
+ --DATA--
14
+ return array('foo' => array(1, 10))
15
+ --EXPECT--
16
+ 0 1 2 3 4 5 6 7 8 9 10
17
+ a b c d e f g h i j k l m n o p q r s t u v w x y z
18
+ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
19
+ 1 2 3 4 5 6 7 8 9 10
20
+ 1 2 3 4 5 6 7 8 9
Twig/test/Twig/Tests/Fixtures/expressions/grouping.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports grouping of expressions
3
+ --TEMPLATE--
4
+ {{ (2 + 2) / 2 }}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+ 2
Twig/test/Twig/Tests/Fixtures/expressions/literals.test ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports literals
3
+ --TEMPLATE--
4
+ 1 {{ true }}
5
+ 2 {{ TRUE }}
6
+ 3 {{ false }}
7
+ 4 {{ FALSE }}
8
+ 5 {{ none }}
9
+ 6 {{ NONE }}
10
+ 7 {{ null }}
11
+ 8 {{ NULL }}
12
+ --DATA--
13
+ return array()
14
+ --EXPECT--
15
+ 1 1
16
+ 2 1
17
+ 3
18
+ 4
19
+ 5
20
+ 6
21
+ 7
22
+ 8
Twig/test/Twig/Tests/Fixtures/expressions/magic_call.test ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports __call() for attributes
3
+ --TEMPLATE--
4
+ {{ foo.foo }}
5
+ {{ foo.bar }}
6
+ --DATA--
7
+ class TestClassForMagicCallAttributes
8
+ {
9
+ public function getBar()
10
+ {
11
+ return 'bar_from_getbar';
12
+ }
13
+
14
+ public function __call($method, $arguments)
15
+ {
16
+ if ('foo' === $method)
17
+ {
18
+ return 'foo_from_call';
19
+ }
20
+
21
+ return false;
22
+ }
23
+ }
24
+ return array('foo' => new TestClassForMagicCallAttributes())
25
+ --EXPECT--
26
+ foo_from_call
27
+ bar_from_getbar
Twig/test/Twig/Tests/Fixtures/expressions/method_call.test ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports method calls
3
+ --TEMPLATE--
4
+ {{ items.foo.foo }}
5
+ {{ items.foo.getFoo() }}
6
+ {{ items.foo.bar }}
7
+ {{ items.foo['bar'] }}
8
+ {{ items.foo.bar('a', 43) }}
9
+ {{ items.foo.bar(foo) }}
10
+ {{ items.foo.self.foo() }}
11
+ {{ items.foo.is }}
12
+ {{ items.foo.in }}
13
+ {{ items.foo.not }}
14
+ --DATA--
15
+ return array('foo' => 'bar', 'items' => array('foo' => new TwigTestFoo(), 'bar' => 'foo'))
16
+ --CONFIG--
17
+ return array('strict_variables' => false)
18
+ --EXPECT--
19
+ foo
20
+ foo
21
+ bar
22
+
23
+ bar_a-43
24
+ bar_bar
25
+ foo
26
+ is
27
+ in
28
+ not
Twig/test/Twig/Tests/Fixtures/expressions/postfix.test ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig parses postfix expressions
3
+ --TEMPLATE--
4
+ {% import _self as macros %}
5
+
6
+ {% macro foo() %}foo{% endmacro %}
7
+
8
+ {{ 'a' }}
9
+ {{ 'a'|upper }}
10
+ {{ ('a')|upper }}
11
+ {{ -1|upper }}
12
+ {{ macros.foo() }}
13
+ {{ (macros).foo() }}
14
+ --DATA--
15
+ return array();
16
+ --EXPECT--
17
+ a
18
+ A
19
+ A
20
+ -1
21
+ foo
22
+ foo
Twig/test/Twig/Tests/Fixtures/expressions/strings.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports string interpolation
3
+ --TEMPLATE--
4
+ {{ "foo #{"foo #{bar} baz"} baz" }}
5
+ {{ "foo #{bar}#{bar} baz" }}
6
+ --DATA--
7
+ return array('bar' => 'BAR');
8
+ --EXPECT--
9
+ foo foo BAR baz baz
10
+ foo BARBAR baz
Twig/test/Twig/Tests/Fixtures/expressions/ternary_operator.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports the ternary operator
3
+ --TEMPLATE--
4
+ {{ 1 ? 'YES' : 'NO' }}
5
+ {{ 0 ? 'YES' : 'NO' }}
6
+ {{ 0 ? 'YES' : (1 ? 'YES1' : 'NO1') }}
7
+ {{ 0 ? 'YES' : (0 ? 'YES1' : 'NO1') }}
8
+ {{ 1 == 1 ? 'foo<br />':'' }}
9
+ {{ foo ~ (bar ? ('-' ~ bar) : '') }}
10
+ --DATA--
11
+ return array('foo' => 'foo', 'bar' => 'bar')
12
+ --EXPECT--
13
+ YES
14
+ NO
15
+ YES1
16
+ NO1
17
+ foo<br />
18
+ foo-bar
Twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_noelse.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports the ternary operator
3
+ --TEMPLATE--
4
+ {{ 1 ? 'YES' }}
5
+ {{ 0 ? 'YES' }}
6
+ --DATA--
7
+ return array()
8
+ --EXPECT--
9
+ YES
10
+
Twig/test/Twig/Tests/Fixtures/expressions/ternary_operator_nothen.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports the ternary operator
3
+ --TEMPLATE--
4
+ {{ 'YES' ?: 'NO' }}
5
+ {{ 0 ?: 'NO' }}
6
+ --DATA--
7
+ return array()
8
+ --EXPECT--
9
+ YES
10
+ NO
Twig/test/Twig/Tests/Fixtures/expressions/unary.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports unary operators (not, -, +)
3
+ --TEMPLATE--
4
+ {{ not 1 }}/{{ not 0 }}
5
+ {{ +1 + 1 }}/{{ -1 - 1 }}
6
+ {{ not (false or true) }}
7
+ --DATA--
8
+ return array()
9
+ --EXPECT--
10
+ /1
11
+ 2/-2
12
+
Twig/test/Twig/Tests/Fixtures/expressions/unary_precedence.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig unary operators precedence
3
+ --TEMPLATE--
4
+ {{ -1 - 1 }}
5
+ {{ -1 - -1 }}
6
+ {{ -1 * -1 }}
7
+ {{ 4 / -1 * 5 }}
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
11
+ -2
12
+ 0
13
+ 1
14
+ -20
Twig/test/Twig/Tests/Fixtures/filters/abs.test ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "abs" filter
3
+ --TEMPLATE--
4
+ {{ (-5.5)|abs }}
5
+ {{ (-5)|abs }}
6
+ {{ (-0)|abs }}
7
+ {{ 0|abs }}
8
+ {{ 5|abs }}
9
+ {{ 5.5|abs }}
10
+ {{ number1|abs }}
11
+ {{ number2|abs }}
12
+ {{ number3|abs }}
13
+ {{ number4|abs }}
14
+ {{ number5|abs }}
15
+ {{ number6|abs }}
16
+ --DATA--
17
+ return array('number1' => -5.5, 'number2' => -5, 'number3' => -0, 'number4' => 0, 'number5' => 5, 'number6' => 5.5)
18
+ --EXPECT--
19
+ 5.5
20
+ 5
21
+ 0
22
+ 0
23
+ 5
24
+ 5.5
25
+ 5.5
26
+ 5
27
+ 0
28
+ 0
29
+ 5
30
+ 5.5
Twig/test/Twig/Tests/Fixtures/filters/convert_encoding.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "convert_encoding" filter
3
+ --CONDITION--
4
+ function_exists('iconv') || function_exists('mb_convert_encoding')
5
+ --TEMPLATE--
6
+ {{ "愛していますか?"|convert_encoding('ISO-2022-JP', 'UTF-8')|convert_encoding('UTF-8', 'ISO-2022-JP') }}
7
+ --DATA--
8
+ return array()
9
+ --EXPECT--
10
+ 愛していますか?
Twig/test/Twig/Tests/Fixtures/filters/date.test ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "date" filter
3
+ --TEMPLATE--
4
+ {{ date1|date }}
5
+ {{ date1|date('d/m/Y') }}
6
+ {{ date1|date('d/m/Y H:i:s', 'Europe/Paris') }}
7
+ {{ date1|date('d/m/Y H:i:s P', 'Europe/Paris') }}
8
+ {{ date1|date('d/m/Y H:i:s P', 'America/Chicago') }}
9
+ {{ date1|date('e') }}
10
+ {{ date1|date('d/m/Y H:i:s') }}
11
+ {{ date2|date }}
12
+ {{ date2|date('d/m/Y') }}
13
+ {{ date2|date('d/m/Y H:i:s', 'Europe/Paris') }}
14
+ {{ date2|date('d/m/Y H:i:s', timezone1) }}
15
+ {{ date2|date('d/m/Y H:i:s') }}
16
+ {{ date3|date }}
17
+ {{ date3|date('d/m/Y') }}
18
+ {{ date4|date }}
19
+ {{ date4|date('d/m/Y') }}
20
+ {{ date5|date }}
21
+ {{ date5|date('d/m/Y') }}
22
+ {{ date6|date('d/m/Y H:i:s P', 'Europe/Paris') }}
23
+ {{ date6|date('d/m/Y H:i:s P', 'Asia/Hong_Kong') }}
24
+ {{ date6|date('d/m/Y H:i:s P', false) }}
25
+ {{ date6|date('e', 'Europe/Paris') }}
26
+ {{ date6|date('e', false) }}
27
+ --DATA--
28
+ date_default_timezone_set('UTC');
29
+ return array(
30
+ 'date1' => mktime(13, 45, 0, 10, 4, 2010),
31
+ 'date2' => new DateTime('2010-10-04 13:45'),
32
+ 'date3' => '2010-10-04 13:45',
33
+ 'date4' => 1286199900,
34
+ 'date5' => -86410,
35
+ 'date6' => new DateTime('2010-10-04 13:45', new DateTimeZone('America/New_York')),
36
+ 'timezone1' => new DateTimeZone('America/New_York'),
37
+ )
38
+ --EXPECT--
39
+ October 4, 2010 13:45
40
+ 04/10/2010
41
+ 04/10/2010 15:45:00
42
+ 04/10/2010 15:45:00 +02:00
43
+ 04/10/2010 08:45:00 -05:00
44
+ UTC
45
+ 04/10/2010 13:45:00
46
+ October 4, 2010 13:45
47
+ 04/10/2010
48
+ 04/10/2010 15:45:00
49
+ 04/10/2010 09:45:00
50
+ 04/10/2010 13:45:00
51
+ October 4, 2010 13:45
52
+ 04/10/2010
53
+ October 4, 2010 13:45
54
+ 04/10/2010
55
+ December 30, 1969 23:59
56
+ 30/12/1969
57
+ 04/10/2010 19:45:00 +02:00
58
+ 05/10/2010 01:45:00 +08:00
59
+ 04/10/2010 13:45:00 -04:00
60
+ Europe/Paris
61
+ America/New_York
Twig/test/Twig/Tests/Fixtures/filters/date_default_format.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "date" filter
3
+ --TEMPLATE--
4
+ {{ date1|date }}
5
+ {{ date1|date('d/m/Y') }}
6
+ --DATA--
7
+ date_default_timezone_set('UTC');
8
+ $twig->getExtension('core')->setDateFormat('Y-m-d', '%d days %h hours');
9
+ return array(
10
+ 'date1' => mktime(13, 45, 0, 10, 4, 2010),
11
+ )
12
+ --EXPECT--
13
+ 2010-10-04
14
+ 04/10/2010
Twig/test/Twig/Tests/Fixtures/filters/date_default_format_interval.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "date" filter (interval support as of PHP 5.3)
3
+ --CONDITION--
4
+ version_compare(phpversion(), '5.3.0', '>=')
5
+ --TEMPLATE--
6
+ {{ date2|date }}
7
+ {{ date2|date('%d days') }}
8
+ --DATA--
9
+ date_default_timezone_set('UTC');
10
+ $twig->getExtension('core')->setDateFormat('Y-m-d', '%d days %h hours');
11
+ return array(
12
+ 'date2' => new DateInterval('P2D'),
13
+ )
14
+ --EXPECT--
15
+ 2 days 0 hours
16
+ 2 days
Twig/test/Twig/Tests/Fixtures/filters/date_interval.test ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "date" filter (interval support as of PHP 5.3)
3
+ --CONDITION--
4
+ version_compare(phpversion(), '5.3.0', '>=')
5
+ --TEMPLATE--
6
+ {{ date1|date }}
7
+ {{ date1|date('%d days %h hours') }}
8
+ {{ date1|date('%d days %h hours', timezone1) }}
9
+ --DATA--
10
+ date_default_timezone_set('UTC');
11
+ return array(
12
+ 'date1' => new DateInterval('P2D'),
13
+ // This should have no effect on DateInterval formatting
14
+ 'timezone1' => new DateTimeZone('America/New_York'),
15
+ )
16
+ --EXPECT--
17
+ 2 days
18
+ 2 days 0 hours
19
+ 2 days 0 hours
Twig/test/Twig/Tests/Fixtures/filters/date_modify.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "date_modify" filter
3
+ --TEMPLATE--
4
+ {{ date1|date_modify('-1day')|date('Y-m-d H:i:s') }}
5
+ {{ date2|date_modify('-1day')|date('Y-m-d H:i:s') }}
6
+ --DATA--
7
+ date_default_timezone_set('UTC');
8
+ return array(
9
+ 'date1' => '2010-10-04 13:45',
10
+ 'date2' => new DateTime('2010-10-04 13:45'),
11
+ )
12
+ --EXPECT--
13
+ 2010-10-03 13:45:00
14
+ 2010-10-03 13:45:00
Twig/test/Twig/Tests/Fixtures/filters/date_namedargs.test ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "date" filter
3
+ --TEMPLATE--
4
+ {{ date|date(format='d/m/Y H:i:s P', timezone='America/Chicago') }}
5
+ {{ date|date(timezone='America/Chicago', format='d/m/Y H:i:s P') }}
6
+ {{ date|date(timezone='America/Chicago', 'd/m/Y H:i:s P') }}
7
+ {{ date|date('d/m/Y H:i:s P', timezone='America/Chicago') }}
8
+ --DATA--
9
+ date_default_timezone_set('UTC');
10
+ return array('date' => mktime(13, 45, 0, 10, 4, 2010))
11
+ --EXPECT--
12
+ 04/10/2010 08:45:00 -05:00
13
+ 04/10/2010 08:45:00 -05:00
14
+ 04/10/2010 08:45:00 -05:00
15
+ 04/10/2010 08:45:00 -05:00
Twig/test/Twig/Tests/Fixtures/filters/default.test ADDED
@@ -0,0 +1,150 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "default" filter
3
+ --TEMPLATE--
4
+ Variable:
5
+ {{ definedVar |default('default') is sameas('default') ? 'ko' : 'ok' }}
6
+ {{ zeroVar |default('default') is sameas('default') ? 'ko' : 'ok' }}
7
+ {{ emptyVar |default('default') is sameas('default') ? 'ok' : 'ko' }}
8
+ {{ nullVar |default('default') is sameas('default') ? 'ok' : 'ko' }}
9
+ {{ undefinedVar |default('default') is sameas('default') ? 'ok' : 'ko' }}
10
+ Array access:
11
+ {{ nested.definedVar |default('default') is sameas('default') ? 'ko' : 'ok' }}
12
+ {{ nested['definedVar'] |default('default') is sameas('default') ? 'ko' : 'ok' }}
13
+ {{ nested.zeroVar |default('default') is sameas('default') ? 'ko' : 'ok' }}
14
+ {{ nested.emptyVar |default('default') is sameas('default') ? 'ok' : 'ko' }}
15
+ {{ nested.nullVar |default('default') is sameas('default') ? 'ok' : 'ko' }}
16
+ {{ nested.undefinedVar |default('default') is sameas('default') ? 'ok' : 'ko' }}
17
+ {{ nested['undefinedVar'] |default('default') is sameas('default') ? 'ok' : 'ko' }}
18
+ {{ undefinedVar.foo |default('default') is sameas('default') ? 'ok' : 'ko' }}
19
+ Plain values:
20
+ {{ 'defined' |default('default') is sameas('default') ? 'ko' : 'ok' }}
21
+ {{ 0 |default('default') is sameas('default') ? 'ko' : 'ok' }}
22
+ {{ '' |default('default') is sameas('default') ? 'ok' : 'ko' }}
23
+ {{ null |default('default') is sameas('default') ? 'ok' : 'ko' }}
24
+ Precedence:
25
+ {{ 'o' ~ nullVar |default('k') }}
26
+ {{ 'o' ~ nested.nullVar |default('k') }}
27
+ Object methods:
28
+ {{ object.foo |default('default') is sameas('default') ? 'ko' : 'ok' }}
29
+ {{ object.undefinedMethod |default('default') is sameas('default') ? 'ok' : 'ko' }}
30
+ {{ object.getFoo() |default('default') is sameas('default') ? 'ko' : 'ok' }}
31
+ {{ object.getFoo('a') |default('default') is sameas('default') ? 'ko' : 'ok' }}
32
+ {{ object.undefinedMethod() |default('default') is sameas('default') ? 'ok' : 'ko' }}
33
+ {{ object.undefinedMethod('a') |default('default') is sameas('default') ? 'ok' : 'ko' }}
34
+ Deep nested:
35
+ {{ nested.undefinedVar.foo.bar |default('default') is sameas('default') ? 'ok' : 'ko' }}
36
+ {{ nested.definedArray.0 |default('default') is sameas('default') ? 'ko' : 'ok' }}
37
+ {{ nested['definedArray'][0] |default('default') is sameas('default') ? 'ko' : 'ok' }}
38
+ {{ object.self.foo |default('default') is sameas('default') ? 'ko' : 'ok' }}
39
+ {{ object.self.undefinedMethod |default('default') is sameas('default') ? 'ok' : 'ko' }}
40
+ {{ object.undefinedMethod.self |default('default') is sameas('default') ? 'ok' : 'ko' }}
41
+ --DATA--
42
+ return array(
43
+ 'definedVar' => 'defined',
44
+ 'zeroVar' => 0,
45
+ 'emptyVar' => '',
46
+ 'nullVar' => null,
47
+ 'nested' => array(
48
+ 'definedVar' => 'defined',
49
+ 'zeroVar' => 0,
50
+ 'emptyVar' => '',
51
+ 'nullVar' => null,
52
+ 'definedArray' => array(0),
53
+ ),
54
+ 'object' => new TwigTestFoo(),
55
+ )
56
+ --CONFIG--
57
+ return array('strict_variables' => false)
58
+ --EXPECT--
59
+ Variable:
60
+ ok
61
+ ok
62
+ ok
63
+ ok
64
+ ok
65
+ Array access:
66
+ ok
67
+ ok
68
+ ok
69
+ ok
70
+ ok
71
+ ok
72
+ ok
73
+ ok
74
+ Plain values:
75
+ ok
76
+ ok
77
+ ok
78
+ ok
79
+ Precedence:
80
+ ok
81
+ ok
82
+ Object methods:
83
+ ok
84
+ ok
85
+ ok
86
+ ok
87
+ ok
88
+ ok
89
+ Deep nested:
90
+ ok
91
+ ok
92
+ ok
93
+ ok
94
+ ok
95
+ ok
96
+ --DATA--
97
+ return array(
98
+ 'definedVar' => 'defined',
99
+ 'zeroVar' => 0,
100
+ 'emptyVar' => '',
101
+ 'nullVar' => null,
102
+ 'nested' => array(
103
+ 'definedVar' => 'defined',
104
+ 'zeroVar' => 0,
105
+ 'emptyVar' => '',
106
+ 'nullVar' => null,
107
+ 'definedArray' => array(0),
108
+ ),
109
+ 'object' => new TwigTestFoo(),
110
+ )
111
+ --CONFIG--
112
+ return array('strict_variables' => true)
113
+ --EXPECT--
114
+ Variable:
115
+ ok
116
+ ok
117
+ ok
118
+ ok
119
+ ok
120
+ Array access:
121
+ ok
122
+ ok
123
+ ok
124
+ ok
125
+ ok
126
+ ok
127
+ ok
128
+ ok
129
+ Plain values:
130
+ ok
131
+ ok
132
+ ok
133
+ ok
134
+ Precedence:
135
+ ok
136
+ ok
137
+ Object methods:
138
+ ok
139
+ ok
140
+ ok
141
+ ok
142
+ ok
143
+ ok
144
+ Deep nested:
145
+ ok
146
+ ok
147
+ ok
148
+ ok
149
+ ok
150
+ ok
Twig/test/Twig/Tests/Fixtures/filters/dynamic_filter.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ dynamic filter
3
+ --TEMPLATE--
4
+ {{ 'bar'|foo_path }}
5
+ {{ 'bar'|a_foo_b_bar }}
6
+ --DATA--
7
+ return array()
8
+ --EXPECT--
9
+ foo/bar
10
+ a/b/bar
Twig/test/Twig/Tests/Fixtures/filters/escape.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "escape" filter
3
+ --TEMPLATE--
4
+ {{ "foo <br />"|e }}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+ foo &lt;br /&gt;
Twig/test/Twig/Tests/Fixtures/filters/escape_non_supported_charset.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "escape" filter
3
+ --TEMPLATE--
4
+ {{ "愛していますか? <br />"|e }}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+ 愛していますか? &lt;br /&gt;
Twig/test/Twig/Tests/Fixtures/filters/force_escape.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "escape" filter
3
+ --TEMPLATE--
4
+ {% set foo %}
5
+ foo<br />
6
+ {% endset %}
7
+
8
+ {{ foo|e('html') -}}
9
+ {{ foo|e('js') }}
10
+ {% autoescape true %}
11
+ {{ foo }}
12
+ {% endautoescape %}
13
+ --DATA--
14
+ return array()
15
+ --EXPECT--
16
+ foo&lt;br /&gt;
17
+ \x20\x20\x20\x20foo\x3Cbr\x20\x2F\x3E\x0A
18
+ foo<br />
Twig/test/Twig/Tests/Fixtures/filters/format.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "format" filter
3
+ --TEMPLATE--
4
+ {{ string|format(foo, 3) }}
5
+ --DATA--
6
+ return array('string' => '%s/%d', 'foo' => 'bar')
7
+ --EXPECT--
8
+ bar/3
Twig/test/Twig/Tests/Fixtures/filters/join.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "join" filter
3
+ --TEMPLATE--
4
+ {{ ["foo", "bar"]|join(', ') }}
5
+ {{ foo|join(', ') }}
6
+ {{ bar|join(', ') }}
7
+ --DATA--
8
+ return array('foo' => new TwigTestFoo(), 'bar' => new ArrayObject(array(3, 4)))
9
+ --EXPECT--
10
+ foo, bar
11
+ 1, 2
12
+ 3, 4
Twig/test/Twig/Tests/Fixtures/filters/json_encode.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "json_encode" filter
3
+ --TEMPLATE--
4
+ {{ "foo"|json_encode|raw }}
5
+ {{ foo|json_encode|raw }}
6
+ {{ [foo, "foo"]|json_encode|raw }}
7
+ --DATA--
8
+ return array('foo' => new Twig_Markup('foo', 'UTF-8'))
9
+ --EXPECT--
10
+ "foo"
11
+ "foo"
12
+ ["foo","foo"]
Twig/test/Twig/Tests/Fixtures/filters/length.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "length" filter
3
+ --TEMPLATE--
4
+ {{ array|length }}
5
+ {{ string|length }}
6
+ {{ number|length }}
7
+ {{ markup|length }}
8
+ --DATA--
9
+ return array('array' => array(1, 4), 'string' => 'foo', 'number' => 1000, 'markup' => new Twig_Markup('foo', 'UTF-8'))
10
+ --EXPECT--
11
+ 2
12
+ 3
13
+ 4
14
+ 3
Twig/test/Twig/Tests/Fixtures/filters/length_utf8.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "length" filter
3
+ --CONDITION--
4
+ function_exists('mb_get_info')
5
+ --TEMPLATE--
6
+ {{ string|length }}
7
+ {{ markup|length }}
8
+ --DATA--
9
+ return array('string' => 'été', 'markup' => new Twig_Markup('foo', 'UTF-8'))
10
+ --EXPECT--
11
+ 3
12
+ 3
Twig/test/Twig/Tests/Fixtures/filters/merge.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "merge" filter
3
+ --TEMPLATE--
4
+ {{ items|merge({'bar': 'foo'})|join }}
5
+ {{ items|merge({'bar': 'foo'})|keys|join }}
6
+ {{ {'bar': 'foo'}|merge(items)|join }}
7
+ {{ {'bar': 'foo'}|merge(items)|keys|join }}
8
+ {{ numerics|merge([4, 5, 6])|join }}
9
+ --DATA--
10
+ return array('items' => array('foo' => 'bar'), 'numerics' => array(1, 2, 3))
11
+ --EXPECT--
12
+ barfoo
13
+ foobar
14
+ foobar
15
+ barfoo
16
+ 123456
Twig/test/Twig/Tests/Fixtures/filters/nl2br.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "nl2br" filter
3
+ --TEMPLATE--
4
+ {{ "I like Twig.\nYou will like it too.\n\nEverybody like it!"|nl2br }}
5
+ {{ text|nl2br }}
6
+ --DATA--
7
+ return array('text' => "If you have some <strong>HTML</strong>\nit will be escaped.")
8
+ --EXPECT--
9
+ I like Twig.<br />
10
+ You will like it too.<br />
11
+ <br />
12
+ Everybody like it!
13
+ If you have some &lt;strong&gt;HTML&lt;/strong&gt;<br />
14
+ it will be escaped.
Twig/test/Twig/Tests/Fixtures/filters/number_format.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "number_format" filter
3
+ --TEMPLATE--
4
+ {{ 20|number_format }}
5
+ {{ 20.25|number_format }}
6
+ {{ 20.25|number_format(2) }}
7
+ {{ 20.25|number_format(2, ',') }}
8
+ {{ 1020.25|number_format(2, ',') }}
9
+ {{ 1020.25|number_format(2, ',', '.') }}
10
+ --DATA--
11
+ return array();
12
+ --EXPECT--
13
+ 20
14
+ 20
15
+ 20.25
16
+ 20,25
17
+ 1,020,25
18
+ 1.020,25
Twig/test/Twig/Tests/Fixtures/filters/number_format_default.test ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "number_format" filter with defaults.
3
+ --TEMPLATE--
4
+ {{ 20|number_format }}
5
+ {{ 20.25|number_format }}
6
+ {{ 20.25|number_format(1) }}
7
+ {{ 20.25|number_format(2, ',') }}
8
+ {{ 1020.25|number_format }}
9
+ {{ 1020.25|number_format(2, ',') }}
10
+ {{ 1020.25|number_format(2, ',', '.') }}
11
+ --DATA--
12
+ $twig->getExtension('core')->setNumberFormat(2, '!', '=');
13
+ return array();
14
+ --EXPECT--
15
+ 20!00
16
+ 20!25
17
+ 20!3
18
+ 20,25
19
+ 1=020!25
20
+ 1=020,25
21
+ 1.020,25
Twig/test/Twig/Tests/Fixtures/filters/replace.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "replace" filter
3
+ --TEMPLATE--
4
+ {{ "I like %this% and %that%."|replace({'%this%': "foo", '%that%': "bar"}) }}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+ I like foo and bar.
Twig/test/Twig/Tests/Fixtures/filters/reverse.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "reverse" filter
3
+ --TEMPLATE--
4
+ {{ [1, 2, 3, 4]|reverse|join('') }}
5
+ {{ '1234évènement'|reverse }}
6
+ {{ arr|reverse|join('') }}
7
+ {{ {'a': 'c', 'b': 'a'}|reverse()|join(',') }}
8
+ {{ {'a': 'c', 'b': 'a'}|reverse(preserveKeys=true)|join(glue=',') }}
9
+ {{ {'a': 'c', 'b': 'a'}|reverse(preserve_keys=true)|join(glue=',') }}
10
+ --DATA--
11
+ return array('arr' => new ArrayObject(array(1, 2, 3, 4)))
12
+ --EXPECT--
13
+ 4321
14
+ tnemenèvé4321
15
+ 4321
16
+ a,c
17
+ a,c
18
+ a,c
Twig/test/Twig/Tests/Fixtures/filters/slice.test ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "slice" filter
3
+ --TEMPLATE--
4
+ {{ [1, 2, 3, 4][1:2]|join('') }}
5
+ {{ {a: 1, b: 2, c: 3, d: 4}[1:2]|join('') }}
6
+ {{ [1, 2, 3, 4][start:length]|join('') }}
7
+ {{ [1, 2, 3, 4]|slice(1, 2)|join('') }}
8
+ {{ [1, 2, 3, 4]|slice(1, 2)|keys|join('') }}
9
+ {{ [1, 2, 3, 4]|slice(1, 2, true)|keys|join('') }}
10
+ {{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|join('') }}
11
+ {{ {a: 1, b: 2, c: 3, d: 4}|slice(1, 2)|keys|join('') }}
12
+ {{ '1234'|slice(1, 2) }}
13
+ {{ '1234'[1:2] }}
14
+ {{ arr|slice(1, 2)|join('') }}
15
+ {{ arr[1:2]|join('') }}
16
+
17
+ {{ [1, 2, 3, 4]|slice(1)|join('') }}
18
+ {{ [1, 2, 3, 4][1:]|join('') }}
19
+ {{ '1234'|slice(1) }}
20
+ {{ '1234'[1:] }}
21
+ {{ '1234'[:1] }}
22
+ --DATA--
23
+ return array('start' => 1, 'length' => 2, 'arr' => new ArrayObject(array(1, 2, 3, 4)))
24
+ --EXPECT--
25
+ 23
26
+ 23
27
+ 23
28
+ 23
29
+ 01
30
+ 12
31
+ 23
32
+ bc
33
+ 23
34
+ 23
35
+ 23
36
+ 23
37
+
38
+ 234
39
+ 234
40
+ 234
41
+ 234
42
+ 1
Twig/test/Twig/Tests/Fixtures/filters/sort.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "sort" filter
3
+ --TEMPLATE--
4
+ {{ array1|sort|join }}
5
+ {{ array2|sort|join }}
6
+ --DATA--
7
+ return array('array1' => array(4, 1), 'array2' => array('foo', 'bar'))
8
+ --EXPECT--
9
+ 14
10
+ barfoo
Twig/test/Twig/Tests/Fixtures/filters/special_chars.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "☃" custom filter
3
+ --TEMPLATE--
4
+ {{ 'foo'|☃ }}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+ ☃foo☃
Twig/test/Twig/Tests/Fixtures/filters/split.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "split" filter
3
+ --TEMPLATE--
4
+ {{ "one,two,three,four,five"|split(',')|join('-') }}
5
+ {{ foo|split(',')|join('-') }}
6
+ {{ foo|split(',', 3)|join('-') }}
7
+ {{ baz|split('')|join('-') }}
8
+ {{ baz|split('', 2)|join('-') }}
9
+ {{ foo|split(',', -2)|join('-') }}
10
+ --DATA--
11
+ return array('foo' => "one,two,three,four,five", 'baz' => '12345',)
12
+ --EXPECT--
13
+ one-two-three-four-five
14
+ one-two-three-four-five
15
+ one-two-three,four,five
16
+ 1-2-3-4-5
17
+ 12-34-5
18
+ one-two-three
Twig/test/Twig/Tests/Fixtures/filters/trim.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "trim" filter
3
+ --TEMPLATE--
4
+ {{ " I like Twig. "|trim }}
5
+ {{ text|trim }}
6
+ {{ " foo/"|trim("/") }}
7
+ --DATA--
8
+ return array('text' => " If you have some <strong>HTML</strong> it will be escaped. ")
9
+ --EXPECT--
10
+ I like Twig.
11
+ If you have some &lt;strong&gt;HTML&lt;/strong&gt; it will be escaped.
12
+ foo
Twig/test/Twig/Tests/Fixtures/functions/attribute.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "attribute" function
3
+ --TEMPLATE--
4
+ {{ attribute(obj, method) }}
5
+ {{ attribute(array, item) }}
6
+ {{ attribute(obj, "bar", ["a", "b"]) }}
7
+ --DATA--
8
+ return array('obj' => new TwigTestFoo(), 'method' => 'foo', 'array' => array('foo' => 'bar'), 'item' => 'foo')
9
+ --EXPECT--
10
+ foo
11
+ bar
12
+ bar_a-b
Twig/test/Twig/Tests/Fixtures/functions/block.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "block" function
3
+ --TEMPLATE--
4
+ {% extends 'base.twig' %}
5
+ {% block bar %}BAR{% endblock %}
6
+ --TEMPLATE(base.twig)--
7
+ {% block foo %}{{ block('bar') }}{% endblock %}
8
+ {% block bar %}BAR_BASE{% endblock %}
9
+ --DATA--
10
+ return array()
11
+ --EXPECT--
12
+ BARBAR
Twig/test/Twig/Tests/Fixtures/functions/constant.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "constant" function
3
+ --TEMPLATE--
4
+ {{ constant('DATE_W3C') == expect ? 'true' : 'false' }}
5
+ {{ constant('ARRAY_AS_PROPS', object) }}
6
+ --DATA--
7
+ return array('expect' => DATE_W3C, 'object' => new ArrayObject(array('hi')));
8
+ --EXPECT--
9
+ true
10
+ 2
Twig/test/Twig/Tests/Fixtures/functions/cycle.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "cycle" function
3
+ --TEMPLATE--
4
+ {% for i in 0..6 %}
5
+ {{ cycle(array1, i) }}-{{ cycle(array2, i) }}
6
+ {% endfor %}
7
+ --DATA--
8
+ return array('array1' => array('odd', 'even'), 'array2' => array('apple', 'orange', 'citrus'))
9
+ --EXPECT--
10
+ odd-apple
11
+ even-orange
12
+ odd-citrus
13
+ even-apple
14
+ odd-orange
15
+ even-citrus
16
+ odd-apple
Twig/test/Twig/Tests/Fixtures/functions/date.test ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "date" function
3
+ --TEMPLATE--
4
+ {{ date() == date('now') ? 'OK' : 'KO' }}
5
+ {{ date() > date('-1day') ? 'OK' : 'KO' }}
6
+ {{ date(date1) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}
7
+ {{ date(date2) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}
8
+ {{ date(date3) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}
9
+ {{ date(date4) == date('2010-10-04 13:45') ? 'OK' : 'KO' }}
10
+ --DATA--
11
+ date_default_timezone_set('UTC');
12
+ return array(
13
+ 'date1' => mktime(13, 45, 0, 10, 4, 2010),
14
+ 'date2' => new DateTime('2010-10-04 13:45'),
15
+ 'date3' => '2010-10-04 13:45',
16
+ 'date4' => 1286199900,
17
+ 'date5' => -86410,
18
+ )
19
+ --EXPECT--
20
+ OK
21
+ OK
22
+ OK
23
+ OK
24
+ OK
25
+ OK
Twig/test/Twig/Tests/Fixtures/functions/date_namedargs.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "date" function
3
+ --TEMPLATE--
4
+ {{ date(date, "America/New_York")|date('d/m/Y H:i:s P', false) }}
5
+ {{ date(timezone="America/New_York", date=date)|date('d/m/Y H:i:s P', false) }}
6
+ --DATA--
7
+ date_default_timezone_set('UTC');
8
+ return array('date' => mktime(13, 45, 0, 10, 4, 2010))
9
+ --EXPECT--
10
+ 04/10/2010 09:45:00 -04:00
11
+ 04/10/2010 09:45:00 -04:00
Twig/test/Twig/Tests/Fixtures/functions/dump.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "dump" function
3
+ --CONDITION--
4
+ !extension_loaded('xdebug')
5
+ --TEMPLATE--
6
+ {{ dump('foo') }}
7
+ {{ dump('foo', 'bar') }}
8
+ --DATA--
9
+ return array('foo' => 'foo', 'bar' => 'bar')
10
+ --CONFIG--
11
+ return array('debug' => true, 'autoescape' => false);
12
+ --EXPECT--
13
+ string(3) "foo"
14
+
15
+ string(3) "foo"
16
+ string(3) "bar"
Twig/test/Twig/Tests/Fixtures/functions/dump_array.test ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "dump" function, xdebug is not loaded or xdebug <2.2-dev is loaded
3
+ --CONDITION--
4
+ !extension_loaded('xdebug') || (($r = new ReflectionExtension('xdebug')) && version_compare($r->getVersion(), '2.2-dev', '<'))
5
+ --TEMPLATE--
6
+ {{ dump() }}
7
+ --DATA--
8
+ return array('foo' => 'foo', 'bar' => 'bar')
9
+ --CONFIG--
10
+ return array('debug' => true, 'autoescape' => false);
11
+ --EXPECT--
12
+ array(3) {
13
+ ["foo"]=>
14
+ string(3) "foo"
15
+ ["bar"]=>
16
+ string(3) "bar"
17
+ ["global"]=>
18
+ string(6) "global"
19
+ }
Twig/test/Twig/Tests/Fixtures/functions/dynamic_function.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ dynamic function
3
+ --TEMPLATE--
4
+ {{ foo_path('bar') }}
5
+ {{ a_foo_b_bar('bar') }}
6
+ --DATA--
7
+ return array()
8
+ --EXPECT--
9
+ foo/bar
10
+ a/b/bar
Twig/test/Twig/Tests/Fixtures/functions/include/basic.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function
3
+ --TEMPLATE--
4
+ FOO
5
+ {{ include("foo.twig") }}
6
+
7
+ BAR
8
+ --TEMPLATE(foo.twig)--
9
+ FOOBAR
10
+ --DATA--
11
+ return array()
12
+ --EXPECT--
13
+ FOO
14
+
15
+ FOOBAR
16
+
17
+ BAR
Twig/test/Twig/Tests/Fixtures/functions/include/expression.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function allows expressions for the template to include
3
+ --TEMPLATE--
4
+ FOO
5
+ {{ include(foo) }}
6
+
7
+ BAR
8
+ --TEMPLATE(foo.twig)--
9
+ FOOBAR
10
+ --DATA--
11
+ return array('foo' => 'foo.twig')
12
+ --EXPECT--
13
+ FOO
14
+
15
+ FOOBAR
16
+
17
+ BAR
Twig/test/Twig/Tests/Fixtures/functions/include/ignore_missing.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function
3
+ --TEMPLATE--
4
+ {{ include(["foo.twig", "bar.twig"], ignore_missing = true) }}
5
+ {{ include("foo.twig", ignore_missing = true) }}
6
+ {{ include("foo.twig", ignore_missing = true, variables = {}) }}
7
+ {{ include("foo.twig", ignore_missing = true, variables = {}, with_context = true) }}
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
Twig/test/Twig/Tests/Fixtures/functions/include/missing.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function
3
+ --TEMPLATE--
4
+ {{ include("foo.twig") }}
5
+ --DATA--
6
+ return array();
7
+ --EXCEPTION--
8
+ Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2.
Twig/test/Twig/Tests/Fixtures/functions/include/missing_nested.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function
3
+ --TEMPLATE--
4
+ {% extends "base.twig" %}
5
+
6
+ {% block content %}
7
+ {{ parent() }}
8
+ {% endblock %}
9
+ --TEMPLATE(base.twig)--
10
+ {% block content %}
11
+ {{ include("foo.twig") }}
12
+ {% endblock %}
13
+ --DATA--
14
+ return array();
15
+ --EXCEPTION--
16
+ Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3.
Twig/test/Twig/Tests/Fixtures/functions/include/sandbox.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag sandboxed
3
+ --TEMPLATE--
4
+ {{ include("foo.twig", sandboxed = true) }}
5
+ --TEMPLATE(foo.twig)--
6
+ {{ foo|e }}
7
+ --DATA--
8
+ return array()
9
+ --EXCEPTION--
10
+ Twig_Sandbox_SecurityError: Filter "e" is not allowed in "index.twig" at line 2.
Twig/test/Twig/Tests/Fixtures/functions/include/template_instance.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function accepts Twig_Template instance
3
+ --TEMPLATE--
4
+ {{ include(foo) }} FOO
5
+ --TEMPLATE(foo.twig)--
6
+ BAR
7
+ --DATA--
8
+ return array('foo' => $twig->loadTemplate('foo.twig'))
9
+ --EXPECT--
10
+ BAR FOO
Twig/test/Twig/Tests/Fixtures/functions/include/templates_as_array.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function
3
+ --TEMPLATE--
4
+ {{ include(["foo.twig", "bar.twig"]) }}
5
+ {{- include(["bar.twig", "foo.twig"]) }}
6
+ --TEMPLATE(foo.twig)--
7
+ foo
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
11
+ foo
12
+ foo
Twig/test/Twig/Tests/Fixtures/functions/include/with_context.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function accept variables and with_context
3
+ --TEMPLATE--
4
+ {{ include("foo.twig") }}
5
+ {{- include("foo.twig", with_context = false) }}
6
+ {{- include("foo.twig", {'foo1': 'bar'}) }}
7
+ {{- include("foo.twig", {'foo1': 'bar'}, with_context = false) }}
8
+ --TEMPLATE(foo.twig)--
9
+ {% for k, v in _context %}{{ k }},{% endfor %}
10
+ --DATA--
11
+ return array('foo' => 'bar')
12
+ --EXPECT--
13
+ foo,global,_parent,
14
+ global,_parent,
15
+ foo,global,foo1,_parent,
16
+ foo1,global,_parent,
Twig/test/Twig/Tests/Fixtures/functions/include/with_variables.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" function accept variables
3
+ --TEMPLATE--
4
+ {{ include("foo.twig", {'foo': 'bar'}) }}
5
+ {{- include("foo.twig", vars) }}
6
+ --TEMPLATE(foo.twig)--
7
+ {{ foo }}
8
+ --DATA--
9
+ return array('vars' => array('foo' => 'bar'))
10
+ --EXPECT--
11
+ bar
12
+ bar
Twig/test/Twig/Tests/Fixtures/functions/range.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "range" function
3
+ --TEMPLATE--
4
+ {{ range(low=0+1, high=10+0, step=2)|join(',') }}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+ 1,3,5,7,9
Twig/test/Twig/Tests/Fixtures/functions/special_chars.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "☃" custom function
3
+ --TEMPLATE--
4
+ {{ ☃('foo') }}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+ ☃foo☃
Twig/test/Twig/Tests/Fixtures/functions/template_from_string.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "template_from_string" function
3
+ --TEMPLATE--
4
+ {% include template_from_string(template) %}
5
+
6
+ {% include template_from_string("Hello {{ name }}") %}
7
+ --DATA--
8
+ return array('name' => 'Fabien', 'template' => "Hello {{ name }}")
9
+ --EXPECT--
10
+ Hello Fabien
11
+ Hello Fabien
Twig/test/Twig/Tests/Fixtures/macros/default_values.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ macro
3
+ --TEMPLATE--
4
+ {% from _self import test %}
5
+
6
+ {% macro test(a, b = 'bar') -%}
7
+ {{ a }}{{ b }}
8
+ {%- endmacro %}
9
+
10
+ {{ test('foo') }}
11
+ {{ test('bar', 'foo') }}
12
+ --DATA--
13
+ return array();
14
+ --EXPECT--
15
+ foobar
16
+ barfoo
Twig/test/Twig/Tests/Fixtures/macros/nested_calls.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ macro
3
+ --TEMPLATE--
4
+ {% import _self as macros %}
5
+
6
+ {% macro foo(data) %}
7
+ {{ data }}
8
+ {% endmacro %}
9
+
10
+ {% macro bar() %}
11
+ <br />
12
+ {% endmacro %}
13
+
14
+ {{ macros.foo(macros.bar()) }}
15
+ --DATA--
16
+ return array();
17
+ --EXPECT--
18
+ <br />
Twig/test/Twig/Tests/Fixtures/macros/reserved_variables.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ macro
3
+ --TEMPLATE--
4
+ {% from _self import test %}
5
+
6
+ {% macro test(this) -%}
7
+ {{ this }}
8
+ {%- endmacro %}
9
+
10
+ {{ test(this) }}
11
+ --DATA--
12
+ return array('this' => 'foo');
13
+ --EXPECT--
14
+ foo
Twig/test/Twig/Tests/Fixtures/macros/simple.test ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ macro
3
+ --TEMPLATE--
4
+ {% import _self as test %}
5
+ {% from _self import test %}
6
+
7
+ {% macro test(a, b) -%}
8
+ {{ a|default('a') }}<br />
9
+ {{- b|default('b') }}<br />
10
+ {%- endmacro %}
11
+
12
+ {{ test.test() }}
13
+ {{ test() }}
14
+ {{ test.test(1, "c") }}
15
+ {{ test(1, "c") }}
16
+ --DATA--
17
+ return array();
18
+ --EXPECT--
19
+ a<br />b<br />
20
+ a<br />b<br />
21
+ 1<br />c<br />
22
+ 1<br />c<br />
Twig/test/Twig/Tests/Fixtures/macros/with_filters.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ macro with a filter
3
+ --TEMPLATE--
4
+ {% import _self as test %}
5
+
6
+ {% macro test() %}
7
+ {% filter escape %}foo<br />{% endfilter %}
8
+ {% endmacro %}
9
+
10
+ {{ test.test() }}
11
+ --DATA--
12
+ return array();
13
+ --EXPECT--
14
+ foo&lt;br /&gt;
Twig/test/Twig/Tests/Fixtures/regression/empty_token.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig outputs 0 nodes correctly
3
+ --TEMPLATE--
4
+ {{ foo }}0{{ foo }}
5
+ --DATA--
6
+ return array('foo' => 'foo')
7
+ --EXPECT--
8
+ foo0foo
Twig/test/Twig/Tests/Fixtures/regression/simple_xml_element.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig is able to deal with SimpleXMLElement instances as variables
3
+ --CONDITION--
4
+ version_compare(phpversion(), '5.3.0', '>=')
5
+ --TEMPLATE--
6
+ Hello '{{ images.image.0.group }}'!
7
+ {{ images.children().count() }}
8
+ {% for image in images %}
9
+ - {{ image.group }}
10
+ {% endfor %}
11
+ --DATA--
12
+ return array('images' => new SimpleXMLElement('<images><image><group>foo</group></image><image><group>bar</group></image></images>'))
13
+ --EXPECT--
14
+ Hello 'foo'!
15
+ 2
16
+ - foo
17
+ - bar
Twig/test/Twig/Tests/Fixtures/regression/strings_like_numbers.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig does not confuse strings with integers in getAttribute()
3
+ --TEMPLATE--
4
+ {{ hash['2e2'] }}
5
+ --DATA--
6
+ return array('hash' => array('2e2' => 'works'))
7
+ --EXPECT--
8
+ works
Twig/test/Twig/Tests/Fixtures/tags/autoescape/basic.test ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag applies escaping on its children
3
+ --TEMPLATE--
4
+ {% autoescape %}
5
+ {{ var }}<br />
6
+ {% endautoescape %}
7
+ {% autoescape 'html' %}
8
+ {{ var }}<br />
9
+ {% endautoescape %}
10
+ {% autoescape false %}
11
+ {{ var }}<br />
12
+ {% endautoescape %}
13
+ {% autoescape true %}
14
+ {{ var }}<br />
15
+ {% endautoescape %}
16
+ {% autoescape false %}
17
+ {{ var }}<br />
18
+ {% endautoescape %}
19
+ --DATA--
20
+ return array('var' => '<br />')
21
+ --EXPECT--
22
+ &lt;br /&gt;<br />
23
+ &lt;br /&gt;<br />
24
+ <br /><br />
25
+ &lt;br /&gt;<br />
26
+ <br /><br />
Twig/test/Twig/Tests/Fixtures/tags/autoescape/blocks.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag applies escaping on embedded blocks
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+ {% block foo %}
6
+ {{ var }}
7
+ {% endblock %}
8
+ {% endautoescape %}
9
+ --DATA--
10
+ return array('var' => '<br />')
11
+ --EXPECT--
12
+ &lt;br /&gt;
Twig/test/Twig/Tests/Fixtures/tags/autoescape/double_escaping.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag does not double-escape
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+ {{ var|escape }}
6
+ {% endautoescape %}
7
+ --DATA--
8
+ return array('var' => '<br />')
9
+ --EXPECT--
10
+ &lt;br /&gt;
Twig/test/Twig/Tests/Fixtures/tags/autoescape/functions.test ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag applies escaping after calling functions
3
+ --TEMPLATE--
4
+
5
+ autoescape false
6
+ {% autoescape false %}
7
+
8
+ safe_br
9
+ {{ safe_br() }}
10
+
11
+ unsafe_br
12
+ {{ unsafe_br() }}
13
+
14
+ {% endautoescape %}
15
+
16
+ autoescape 'html'
17
+ {% autoescape 'html' %}
18
+
19
+ safe_br
20
+ {{ safe_br() }}
21
+
22
+ unsafe_br
23
+ {{ unsafe_br() }}
24
+
25
+ unsafe_br()|raw
26
+ {{ (unsafe_br())|raw }}
27
+
28
+ safe_br()|escape
29
+ {{ (safe_br())|escape }}
30
+
31
+ safe_br()|raw
32
+ {{ (safe_br())|raw }}
33
+
34
+ unsafe_br()|escape
35
+ {{ (unsafe_br())|escape }}
36
+
37
+ {% endautoescape %}
38
+
39
+ autoescape js
40
+ {% autoescape 'js' %}
41
+
42
+ safe_br
43
+ {{ safe_br() }}
44
+
45
+ {% endautoescape %}
46
+ --DATA--
47
+ return array()
48
+ --EXPECT--
49
+
50
+ autoescape false
51
+
52
+ safe_br
53
+ <br />
54
+
55
+ unsafe_br
56
+ <br />
57
+
58
+
59
+ autoescape 'html'
60
+
61
+ safe_br
62
+ <br />
63
+
64
+ unsafe_br
65
+ &lt;br /&gt;
66
+
67
+ unsafe_br()|raw
68
+ <br />
69
+
70
+ safe_br()|escape
71
+ &lt;br /&gt;
72
+
73
+ safe_br()|raw
74
+ <br />
75
+
76
+ unsafe_br()|escape
77
+ &lt;br /&gt;
78
+
79
+
80
+ autoescape js
81
+
82
+ safe_br
83
+ \x3Cbr\x20\x2F\x3E
Twig/test/Twig/Tests/Fixtures/tags/autoescape/literal.test ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag does not apply escaping on literals
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+
6
+ 1. Simple literal
7
+ {{ "<br />" }}
8
+
9
+ 2. Conditional expression with only literals
10
+ {{ true ? "<br />" : "<br>" }}
11
+
12
+ 3. Conditonal expression with a variable
13
+ {{ true ? "<br />" : someVar }}
14
+
15
+ 4. Nested conditionals with only literals
16
+ {{ true ? (true ? "<br />" : "<br>") : "\n" }}
17
+
18
+ 5. Nested conditionals with a variable
19
+ {{ true ? (true ? "<br />" : someVar) : "\n" }}
20
+
21
+ 6. Nested conditionals with a variable marked safe
22
+ {{ true ? (true ? "<br />" : someVar|raw) : "\n" }}
23
+
24
+ {% endautoescape %}
25
+ --DATA--
26
+ return array()
27
+ --EXPECT--
28
+
29
+ 1. Simple literal
30
+ <br />
31
+
32
+ 2. Conditional expression with only literals
33
+ <br />
34
+
35
+ 3. Conditonal expression with a variable
36
+ &lt;br /&gt;
37
+
38
+ 4. Nested conditionals with only literals
39
+ <br />
40
+
41
+ 5. Nested conditionals with a variable
42
+ &lt;br /&gt;
43
+
44
+ 6. Nested conditionals with a variable marked safe
45
+ <br />
Twig/test/Twig/Tests/Fixtures/tags/autoescape/nested.test ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tags can be nested at will
3
+ --TEMPLATE--
4
+ {{ var }}
5
+ {% autoescape 'html' %}
6
+ {{ var }}
7
+ {% autoescape false %}
8
+ {{ var }}
9
+ {% autoescape 'html' %}
10
+ {{ var }}
11
+ {% endautoescape %}
12
+ {{ var }}
13
+ {% endautoescape %}
14
+ {{ var }}
15
+ {% endautoescape %}
16
+ {{ var }}
17
+ --DATA--
18
+ return array('var' => '<br />')
19
+ --EXPECT--
20
+ &lt;br /&gt;
21
+ &lt;br /&gt;
22
+ <br />
23
+ &lt;br /&gt;
24
+ <br />
25
+ &lt;br /&gt;
26
+ &lt;br /&gt;
Twig/test/Twig/Tests/Fixtures/tags/autoescape/objects.test ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag applies escaping to object method calls
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+ {{ user.name }}
6
+ {{ user.name|lower }}
7
+ {{ user }}
8
+ {% endautoescape %}
9
+ --DATA--
10
+ class UserForAutoEscapeTest
11
+ {
12
+ public function getName()
13
+ {
14
+ return 'Fabien<br />';
15
+ }
16
+
17
+ public function __toString()
18
+ {
19
+ return 'Fabien<br />';
20
+ }
21
+ }
22
+ return array('user' => new UserForAutoEscapeTest())
23
+ --EXPECT--
24
+ Fabien&lt;br /&gt;
25
+ fabien&lt;br /&gt;
26
+ Fabien&lt;br /&gt;
Twig/test/Twig/Tests/Fixtures/tags/autoescape/raw.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag does not escape when raw is used as a filter
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+ {{ var|raw }}
6
+ {% endautoescape %}
7
+ --DATA--
8
+ return array('var' => '<br />')
9
+ --EXPECT--
10
+ <br />
Twig/test/Twig/Tests/Fixtures/tags/autoescape/strategy.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag accepts an escaping strategy
3
+ --TEMPLATE--
4
+ {% autoescape true js %}{{ var }}{% endautoescape %}
5
+
6
+ {% autoescape true html %}{{ var }}{% endautoescape %}
7
+
8
+ {% autoescape 'js' %}{{ var }}{% endautoescape %}
9
+
10
+ {% autoescape 'html' %}{{ var }}{% endautoescape %}
11
+ --DATA--
12
+ return array('var' => '<br />"')
13
+ --EXPECT--
14
+ \x3Cbr\x20\x2F\x3E\x22
15
+ &lt;br /&gt;&quot;
16
+ \x3Cbr\x20\x2F\x3E\x22
17
+ &lt;br /&gt;&quot;
Twig/test/Twig/Tests/Fixtures/tags/autoescape/type.test ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ escape types
3
+ --TEMPLATE--
4
+
5
+ 1. autoescape 'html' |escape('js')
6
+
7
+ {% autoescape 'html' %}
8
+ <a onclick="alert(&quot;{{ msg|escape('js') }}&quot;)"></a>
9
+ {% endautoescape %}
10
+
11
+ 2. autoescape 'html' |escape('js')
12
+
13
+ {% autoescape 'html' %}
14
+ <a onclick="alert(&quot;{{ msg|escape('js') }}&quot;)"></a>
15
+ {% endautoescape %}
16
+
17
+ 3. autoescape 'js' |escape('js')
18
+
19
+ {% autoescape 'js' %}
20
+ <a onclick="alert(&quot;{{ msg|escape('js') }}&quot;)"></a>
21
+ {% endautoescape %}
22
+
23
+ 4. no escape
24
+
25
+ {% autoescape false %}
26
+ <a onclick="alert(&quot;{{ msg }}&quot;)"></a>
27
+ {% endautoescape %}
28
+
29
+ 5. |escape('js')|escape('html')
30
+
31
+ {% autoescape false %}
32
+ <a onclick="alert(&quot;{{ msg|escape('js')|escape('html') }}&quot;)"></a>
33
+ {% endautoescape %}
34
+
35
+ 6. autoescape 'html' |escape('js')|escape('html')
36
+
37
+ {% autoescape 'html' %}
38
+ <a onclick="alert(&quot;{{ msg|escape('js')|escape('html') }}&quot;)"></a>
39
+ {% endautoescape %}
40
+
41
+ --DATA--
42
+ return array('msg' => "<>\n'\"")
43
+ --EXPECT--
44
+
45
+ 1. autoescape 'html' |escape('js')
46
+
47
+ <a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
48
+
49
+ 2. autoescape 'html' |escape('js')
50
+
51
+ <a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
52
+
53
+ 3. autoescape 'js' |escape('js')
54
+
55
+ <a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
56
+
57
+ 4. no escape
58
+
59
+ <a onclick="alert(&quot;<>
60
+ '"&quot;)"></a>
61
+
62
+ 5. |escape('js')|escape('html')
63
+
64
+ <a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
65
+
66
+ 6. autoescape 'html' |escape('js')|escape('html')
67
+
68
+ <a onclick="alert(&quot;\x3C\x3E\x0A\x27\x22&quot;)"></a>
69
+
Twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters.test ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag applies escaping after calling filters
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+
6
+ (escape_and_nl2br is an escaper filter)
7
+
8
+ 1. Don't escape escaper filter output
9
+ ( var is escaped by |escape_and_nl2br, line-breaks are added,
10
+ the output is not escaped )
11
+ {{ var|escape_and_nl2br }}
12
+
13
+ 2. Don't escape escaper filter output
14
+ ( var is escaped by |escape_and_nl2br, line-breaks are added,
15
+ the output is not escaped, |raw is redundant )
16
+ {{ var|escape_and_nl2br|raw }}
17
+
18
+ 3. Explicit escape
19
+ ( var is escaped by |escape_and_nl2br, line-breaks are added,
20
+ the output is explicitly escaped by |escape )
21
+ {{ var|escape_and_nl2br|escape }}
22
+
23
+ 4. Escape non-escaper filter output
24
+ ( var is upper-cased by |upper,
25
+ the output is auto-escaped )
26
+ {{ var|upper }}
27
+
28
+ 5. Escape if last filter is not an escaper
29
+ ( var is escaped by |escape_and_nl2br, line-breaks are added,
30
+ the output is upper-cased by |upper,
31
+ the output is auto-escaped as |upper is not an escaper )
32
+ {{ var|escape_and_nl2br|upper }}
33
+
34
+ 6. Don't escape escaper filter output
35
+ ( var is upper cased by upper,
36
+ the output is escaped by |escape_and_nl2br, line-breaks are added,
37
+ the output is not escaped as |escape_and_nl2br is an escaper )
38
+ {{ var|upper|escape_and_nl2br }}
39
+
40
+ 7. Escape if last filter is not an escaper
41
+ ( the output of |format is "<b>" ~ var ~ "</b>",
42
+ the output is auto-escaped )
43
+ {{ "<b>%s</b>"|format(var) }}
44
+
45
+ 8. Escape if last filter is not an escaper
46
+ ( the output of |format is "<b>" ~ var ~ "</b>",
47
+ |raw is redundant,
48
+ the output is auto-escaped )
49
+ {{ "<b>%s</b>"|raw|format(var) }}
50
+
51
+ 9. Don't escape escaper filter output
52
+ ( the output of |format is "<b>" ~ var ~ "</b>",
53
+ the output is not escaped due to |raw filter at the end )
54
+ {{ "<b>%s</b>"|format(var)|raw }}
55
+
56
+ 10. Don't escape escaper filter output
57
+ ( the output of |format is "<b>" ~ var ~ "</b>",
58
+ the output is not escaped due to |raw filter at the end,
59
+ the |raw filter on var is redundant )
60
+ {{ "<b>%s</b>"|format(var|raw)|raw }}
61
+
62
+ {% endautoescape %}
63
+ --DATA--
64
+ return array('var' => "<Fabien>\nTwig")
65
+ --EXPECT--
66
+
67
+ (escape_and_nl2br is an escaper filter)
68
+
69
+ 1. Don't escape escaper filter output
70
+ ( var is escaped by |escape_and_nl2br, line-breaks are added,
71
+ the output is not escaped )
72
+ &lt;Fabien&gt;<br />
73
+ Twig
74
+
75
+ 2. Don't escape escaper filter output
76
+ ( var is escaped by |escape_and_nl2br, line-breaks are added,
77
+ the output is not escaped, |raw is redundant )
78
+ &lt;Fabien&gt;<br />
79
+ Twig
80
+
81
+ 3. Explicit escape
82
+ ( var is escaped by |escape_and_nl2br, line-breaks are added,
83
+ the output is explicitly escaped by |escape )
84
+ &amp;lt;Fabien&amp;gt;&lt;br /&gt;
85
+ Twig
86
+
87
+ 4. Escape non-escaper filter output
88
+ ( var is upper-cased by |upper,
89
+ the output is auto-escaped )
90
+ &lt;FABIEN&gt;
91
+ TWIG
92
+
93
+ 5. Escape if last filter is not an escaper
94
+ ( var is escaped by |escape_and_nl2br, line-breaks are added,
95
+ the output is upper-cased by |upper,
96
+ the output is auto-escaped as |upper is not an escaper )
97
+ &amp;LT;FABIEN&amp;GT;&lt;BR /&gt;
98
+ TWIG
99
+
100
+ 6. Don't escape escaper filter output
101
+ ( var is upper cased by upper,
102
+ the output is escaped by |escape_and_nl2br, line-breaks are added,
103
+ the output is not escaped as |escape_and_nl2br is an escaper )
104
+ &lt;FABIEN&gt;<br />
105
+ TWIG
106
+
107
+ 7. Escape if last filter is not an escaper
108
+ ( the output of |format is "<b>" ~ var ~ "</b>",
109
+ the output is auto-escaped )
110
+ &lt;b&gt;&lt;Fabien&gt;
111
+ Twig&lt;/b&gt;
112
+
113
+ 8. Escape if last filter is not an escaper
114
+ ( the output of |format is "<b>" ~ var ~ "</b>",
115
+ |raw is redundant,
116
+ the output is auto-escaped )
117
+ &lt;b&gt;&lt;Fabien&gt;
118
+ Twig&lt;/b&gt;
119
+
120
+ 9. Don't escape escaper filter output
121
+ ( the output of |format is "<b>" ~ var ~ "</b>",
122
+ the output is not escaped due to |raw filter at the end )
123
+ <b><Fabien>
124
+ Twig</b>
125
+
126
+ 10. Don't escape escaper filter output
127
+ ( the output of |format is "<b>" ~ var ~ "</b>",
128
+ the output is not escaped due to |raw filter at the end,
129
+ the |raw filter on var is redundant )
130
+ <b><Fabien>
131
+ Twig</b>
Twig/test/Twig/Tests/Fixtures/tags/autoescape/with_filters_arguments.test ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag do not applies escaping on filter arguments
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+ {{ var|nl2br("<br />") }}
6
+ {{ var|nl2br("<br />"|escape) }}
7
+ {{ var|nl2br(sep) }}
8
+ {{ var|nl2br(sep|raw) }}
9
+ {{ var|nl2br(sep|escape) }}
10
+ {% endautoescape %}
11
+ --DATA--
12
+ return array('var' => "<Fabien>\nTwig", 'sep' => '<br />')
13
+ --EXPECT--
14
+ &lt;Fabien&gt;<br />
15
+ Twig
16
+ &lt;Fabien&gt;&lt;br /&gt;
17
+ Twig
18
+ &lt;Fabien&gt;<br />
19
+ Twig
20
+ &lt;Fabien&gt;<br />
21
+ Twig
22
+ &lt;Fabien&gt;&lt;br /&gt;
23
+ Twig
Twig/test/Twig/Tests/Fixtures/tags/autoescape/with_pre_escape_filters.test ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag applies escaping after calling filters, and before calling pre_escape filters
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+
6
+ (nl2br is pre_escaped for "html" and declared safe for "html")
7
+
8
+ 1. Pre-escape and don't post-escape
9
+ ( var|escape|nl2br )
10
+ {{ var|nl2br }}
11
+
12
+ 2. Don't double-pre-escape
13
+ ( var|escape|nl2br )
14
+ {{ var|escape|nl2br }}
15
+
16
+ 3. Don't escape safe values
17
+ ( var|raw|nl2br )
18
+ {{ var|raw|nl2br }}
19
+
20
+ 4. Don't escape safe values
21
+ ( var|escape|nl2br|nl2br )
22
+ {{ var|nl2br|nl2br }}
23
+
24
+ 5. Re-escape values that are escaped for an other contexts
25
+ ( var|escape_something|escape|nl2br )
26
+ {{ var|escape_something|nl2br }}
27
+
28
+ 6. Still escape when using filters not declared safe
29
+ ( var|escape|nl2br|upper|escape )
30
+ {{ var|nl2br|upper }}
31
+
32
+ {% endautoescape %}
33
+ --DATA--
34
+ return array('var' => "<Fabien>\nTwig")
35
+ --EXPECT--
36
+
37
+ (nl2br is pre_escaped for "html" and declared safe for "html")
38
+
39
+ 1. Pre-escape and don't post-escape
40
+ ( var|escape|nl2br )
41
+ &lt;Fabien&gt;<br />
42
+ Twig
43
+
44
+ 2. Don't double-pre-escape
45
+ ( var|escape|nl2br )
46
+ &lt;Fabien&gt;<br />
47
+ Twig
48
+
49
+ 3. Don't escape safe values
50
+ ( var|raw|nl2br )
51
+ <Fabien><br />
52
+ Twig
53
+
54
+ 4. Don't escape safe values
55
+ ( var|escape|nl2br|nl2br )
56
+ &lt;Fabien&gt;<br /><br />
57
+ Twig
58
+
59
+ 5. Re-escape values that are escaped for an other contexts
60
+ ( var|escape_something|escape|nl2br )
61
+ &lt;FABIEN&gt;<br />
62
+ TWIG
63
+
64
+ 6. Still escape when using filters not declared safe
65
+ ( var|escape|nl2br|upper|escape )
66
+ &amp;LT;FABIEN&amp;GT;&lt;BR /&gt;
67
+ TWIG
68
+
Twig/test/Twig/Tests/Fixtures/tags/autoescape/with_preserves_safety_filters.test ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "autoescape" tag handles filters preserving the safety
3
+ --TEMPLATE--
4
+ {% autoescape 'html' %}
5
+
6
+ (preserves_safety is preserving safety for "html")
7
+
8
+ 1. Unsafe values are still unsafe
9
+ ( var|preserves_safety|escape )
10
+ {{ var|preserves_safety }}
11
+
12
+ 2. Safe values are still safe
13
+ ( var|escape|preserves_safety )
14
+ {{ var|escape|preserves_safety }}
15
+
16
+ 3. Re-escape values that are escaped for an other contexts
17
+ ( var|escape_something|preserves_safety|escape )
18
+ {{ var|escape_something|preserves_safety }}
19
+
20
+ 4. Still escape when using filters not declared safe
21
+ ( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape )
22
+ {{ var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'}) }}
23
+
24
+ {% endautoescape %}
25
+ --DATA--
26
+ return array('var' => "<Fabien>\nTwig")
27
+ --EXPECT--
28
+
29
+ (preserves_safety is preserving safety for "html")
30
+
31
+ 1. Unsafe values are still unsafe
32
+ ( var|preserves_safety|escape )
33
+ &lt;FABIEN&gt;
34
+ TWIG
35
+
36
+ 2. Safe values are still safe
37
+ ( var|escape|preserves_safety )
38
+ &LT;FABIEN&GT;
39
+ TWIG
40
+
41
+ 3. Re-escape values that are escaped for an other contexts
42
+ ( var|escape_something|preserves_safety|escape )
43
+ &lt;FABIEN&gt;
44
+ TWIG
45
+
46
+ 4. Still escape when using filters not declared safe
47
+ ( var|escape|preserves_safety|replace({'FABIEN': 'FABPOT'})|escape )
48
+ &amp;LT;FABPOT&amp;GT;
49
+ TWIG
50
+
Twig/test/Twig/Tests/Fixtures/tags/block/basic.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "block" tag
3
+ --TEMPLATE--
4
+ {% block title1 %}FOO{% endblock %}
5
+ {% block title2 foo|lower %}
6
+ --TEMPLATE(foo.twig)--
7
+ {% block content %}{% endblock %}
8
+ --DATA--
9
+ return array('foo' => 'bar')
10
+ --EXPECT--
11
+ FOObar
Twig/test/Twig/Tests/Fixtures/tags/block/block_unique_name.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "block" tag
3
+ --TEMPLATE--
4
+ {% block content %}
5
+ {% block content %}
6
+ {% endblock %}
7
+ {% endblock %}
8
+ --DATA--
9
+ return array()
10
+ --EXCEPTION--
11
+ Twig_Error_Syntax: The block 'content' has already been defined line 2 in "index.twig" at line 3
Twig/test/Twig/Tests/Fixtures/tags/block/special_chars.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "☃" special chars in a block name
3
+ --TEMPLATE--
4
+ {% block ☃ %}
5
+
6
+ {% endblock ☃ %}
7
+ --DATA--
8
+ return array()
9
+ --EXPECT--
10
+
Twig/test/Twig/Tests/Fixtures/tags/embed/basic.test ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "embed" tag
3
+ --TEMPLATE--
4
+ FOO
5
+ {% embed "foo.twig" %}
6
+ {% block c1 %}
7
+ {{ parent() }}
8
+ block1extended
9
+ {% endblock %}
10
+ {% endembed %}
11
+
12
+ BAR
13
+ --TEMPLATE(foo.twig)--
14
+ A
15
+ {% block c1 %}
16
+ block1
17
+ {% endblock %}
18
+ B
19
+ {% block c2 %}
20
+ block2
21
+ {% endblock %}
22
+ C
23
+ --DATA--
24
+ return array()
25
+ --EXPECT--
26
+ FOO
27
+
28
+ A
29
+ block1
30
+
31
+ block1extended
32
+ B
33
+ block2
34
+ C
35
+ BAR
Twig/test/Twig/Tests/Fixtures/tags/embed/multiple.test ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "embed" tag
3
+ --TEMPLATE--
4
+ FOO
5
+ {% embed "foo.twig" %}
6
+ {% block c1 %}
7
+ {{ parent() }}
8
+ block1extended
9
+ {% endblock %}
10
+ {% endembed %}
11
+
12
+ {% embed "foo.twig" %}
13
+ {% block c1 %}
14
+ {{ parent() }}
15
+ block1extended
16
+ {% endblock %}
17
+ {% endembed %}
18
+
19
+ BAR
20
+ --TEMPLATE(foo.twig)--
21
+ A
22
+ {% block c1 %}
23
+ block1
24
+ {% endblock %}
25
+ B
26
+ {% block c2 %}
27
+ block2
28
+ {% endblock %}
29
+ C
30
+ --DATA--
31
+ return array()
32
+ --EXPECT--
33
+ FOO
34
+
35
+ A
36
+ block1
37
+
38
+ block1extended
39
+ B
40
+ block2
41
+ C
42
+
43
+ A
44
+ block1
45
+
46
+ block1extended
47
+ B
48
+ block2
49
+ C
50
+ BAR
Twig/test/Twig/Tests/Fixtures/tags/embed/nested.test ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "embed" tag
3
+ --TEMPLATE--
4
+ {% embed "foo.twig" %}
5
+ {% block c1 %}
6
+ {{ parent() }}
7
+ {% embed "foo.twig" %}
8
+ {% block c1 %}
9
+ {{ parent() }}
10
+ block1extended
11
+ {% endblock %}
12
+ {% endembed %}
13
+
14
+ {% endblock %}
15
+ {% endembed %}
16
+ --TEMPLATE(foo.twig)--
17
+ A
18
+ {% block c1 %}
19
+ block1
20
+ {% endblock %}
21
+ B
22
+ {% block c2 %}
23
+ block2
24
+ {% endblock %}
25
+ C
26
+ --DATA--
27
+ return array()
28
+ --EXPECT--
29
+ A
30
+ block1
31
+
32
+
33
+ A
34
+ block1
35
+
36
+ block1extended
37
+ B
38
+ block2
39
+ C
40
+ B
41
+ block2
42
+ C
Twig/test/Twig/Tests/Fixtures/tags/embed/with_extends.test ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "embed" tag
3
+ --TEMPLATE--
4
+ {% extends "base.twig" %}
5
+
6
+ {% block c1 %}
7
+ {{ parent() }}
8
+ blockc1baseextended
9
+ {% endblock %}
10
+
11
+ {% block c2 %}
12
+ {{ parent() }}
13
+
14
+ {% embed "foo.twig" %}
15
+ {% block c1 %}
16
+ {{ parent() }}
17
+ block1extended
18
+ {% endblock %}
19
+ {% endembed %}
20
+ {% endblock %}
21
+ --TEMPLATE(base.twig)--
22
+ A
23
+ {% block c1 %}
24
+ blockc1base
25
+ {% endblock %}
26
+ {% block c2 %}
27
+ blockc2base
28
+ {% endblock %}
29
+ B
30
+ --TEMPLATE(foo.twig)--
31
+ A
32
+ {% block c1 %}
33
+ block1
34
+ {% endblock %}
35
+ B
36
+ {% block c2 %}
37
+ block2
38
+ {% endblock %}
39
+ C
40
+ --DATA--
41
+ return array()
42
+ --EXPECT--
43
+ A
44
+ blockc1base
45
+
46
+ blockc1baseextended
47
+ blockc2base
48
+
49
+
50
+
51
+ A
52
+ block1
53
+
54
+ block1extended
55
+ B
56
+ block2
57
+ CB
Twig/test/Twig/Tests/Fixtures/tags/filter/basic.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "filter" tag applies a filter on its children
3
+ --TEMPLATE--
4
+ {% filter upper %}
5
+ Some text with a {{ var }}
6
+ {% endfilter %}
7
+ --DATA--
8
+ return array('var' => 'var')
9
+ --EXPECT--
10
+ SOME TEXT WITH A VAR
Twig/test/Twig/Tests/Fixtures/tags/filter/json_encode.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "filter" tag applies a filter on its children
3
+ --TEMPLATE--
4
+ {% filter json_encode|raw %}test{% endfilter %}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+ "test"
Twig/test/Twig/Tests/Fixtures/tags/filter/multiple.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "filter" tags accept multiple chained filters
3
+ --TEMPLATE--
4
+ {% filter lower|title %}
5
+ {{ var }}
6
+ {% endfilter %}
7
+ --DATA--
8
+ return array('var' => 'VAR')
9
+ --EXPECT--
10
+ Var
Twig/test/Twig/Tests/Fixtures/tags/filter/nested.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "filter" tags can be nested at will
3
+ --TEMPLATE--
4
+ {% filter lower|title %}
5
+ {{ var }}
6
+ {% filter upper %}
7
+ {{ var }}
8
+ {% endfilter %}
9
+ {{ var }}
10
+ {% endfilter %}
11
+ --DATA--
12
+ return array('var' => 'var')
13
+ --EXPECT--
14
+ Var
15
+ Var
16
+ Var
Twig/test/Twig/Tests/Fixtures/tags/filter/with_for_tag.test ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "filter" tag applies the filter on "for" tags
3
+ --TEMPLATE--
4
+ {% filter upper %}
5
+ {% for item in items %}
6
+ {{ item }}
7
+ {% endfor %}
8
+ {% endfilter %}
9
+ --DATA--
10
+ return array('items' => array('a', 'b'))
11
+ --EXPECT--
12
+ A
13
+ B
Twig/test/Twig/Tests/Fixtures/tags/filter/with_if_tag.test ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "filter" tag applies the filter on "if" tags
3
+ --TEMPLATE--
4
+ {% filter upper %}
5
+ {% if items %}
6
+ {{ items|join(', ') }}
7
+ {% endif %}
8
+
9
+ {% if items.3 is defined %}
10
+ FOO
11
+ {% else %}
12
+ {{ items.1 }}
13
+ {% endif %}
14
+
15
+ {% if items.3 is defined %}
16
+ FOO
17
+ {% elseif items.1 %}
18
+ {{ items.0 }}
19
+ {% endif %}
20
+
21
+ {% endfilter %}
22
+ --DATA--
23
+ return array('items' => array('a', 'b'))
24
+ --EXPECT--
25
+ A, B
26
+
27
+ B
28
+
29
+ A
Twig/test/Twig/Tests/Fixtures/tags/for/condition.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag takes a condition
3
+ --TEMPLATE--
4
+ {% for i in 1..5 if i is odd -%}
5
+ {{ loop.index }}.{{ i }}{{ foo.bar }}
6
+ {% endfor %}
7
+ --DATA--
8
+ return array('foo' => array('bar' => 'X'))
9
+ --CONFIG--
10
+ return array('strict_variables' => false)
11
+ --EXPECT--
12
+ 1.1X
13
+ 2.3X
14
+ 3.5X
Twig/test/Twig/Tests/Fixtures/tags/for/context.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag keeps the context safe
3
+ --TEMPLATE--
4
+ {% for item in items %}
5
+ {% for item in items %}
6
+ * {{ item }}
7
+ {% endfor %}
8
+ * {{ item }}
9
+ {% endfor %}
10
+ --DATA--
11
+ return array('items' => array('a', 'b'))
12
+ --EXPECT--
13
+ * a
14
+ * b
15
+ * a
16
+ * a
17
+ * b
18
+ * b
Twig/test/Twig/Tests/Fixtures/tags/for/else.test ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag can use an "else" clause
3
+ --TEMPLATE--
4
+ {% for item in items %}
5
+ * {{ item }}
6
+ {% else %}
7
+ no item
8
+ {% endfor %}
9
+ --DATA--
10
+ return array('items' => array('a', 'b'))
11
+ --EXPECT--
12
+ * a
13
+ * b
14
+ --DATA--
15
+ return array('items' => array())
16
+ --EXPECT--
17
+ no item
18
+ --DATA--
19
+ return array()
20
+ --CONFIG--
21
+ return array('strict_variables' => false)
22
+ --EXPECT--
23
+ no item
Twig/test/Twig/Tests/Fixtures/tags/for/inner_variables.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag does not reset inner variables
3
+ --TEMPLATE--
4
+ {% for i in 1..2 %}
5
+ {% for j in 0..2 %}
6
+ {{k}}{% set k = k+1 %} {{ loop.parent.loop.index }}
7
+ {% endfor %}
8
+ {% endfor %}
9
+ --DATA--
10
+ return array('k' => 0)
11
+ --EXPECT--
12
+ 0 1
13
+ 1 1
14
+ 2 1
15
+ 3 2
16
+ 4 2
17
+ 5 2
Twig/test/Twig/Tests/Fixtures/tags/for/keys.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag can iterate over keys
3
+ --TEMPLATE--
4
+ {% for key in items|keys %}
5
+ * {{ key }}
6
+ {% endfor %}
7
+ --DATA--
8
+ return array('items' => array('a', 'b'))
9
+ --EXPECT--
10
+ * 0
11
+ * 1
Twig/test/Twig/Tests/Fixtures/tags/for/keys_and_values.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag can iterate over keys and values
3
+ --TEMPLATE--
4
+ {% for key, item in items %}
5
+ * {{ key }}/{{ item }}
6
+ {% endfor %}
7
+ --DATA--
8
+ return array('items' => array('a', 'b'))
9
+ --EXPECT--
10
+ * 0/a
11
+ * 1/b
Twig/test/Twig/Tests/Fixtures/tags/for/loop_context.test ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag adds a loop variable to the context
3
+ --TEMPLATE--
4
+ {% for item in items %}
5
+ * {{ loop.index }}/{{ loop.index0 }}
6
+ * {{ loop.revindex }}/{{ loop.revindex0 }}
7
+ * {{ loop.first }}/{{ loop.last }}/{{ loop.length }}
8
+
9
+ {% endfor %}
10
+ --DATA--
11
+ return array('items' => array('a', 'b'))
12
+ --EXPECT--
13
+ * 1/0
14
+ * 2/1
15
+ * 1//2
16
+
17
+ * 2/1
18
+ * 1/0
19
+ * /1/2
Twig/test/Twig/Tests/Fixtures/tags/for/loop_context_local.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag adds a loop variable to the context locally
3
+ --TEMPLATE--
4
+ {% for item in items %}
5
+ {% endfor %}
6
+ {% if loop is not defined %}WORKS{% endif %}
7
+ --DATA--
8
+ return array('items' => array())
9
+ --EXPECT--
10
+ WORKS
Twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag
3
+ --TEMPLATE--
4
+ {% for i, item in items if i > 0 %}
5
+ {{ loop.last }}
6
+ {% endfor %}
7
+ --DATA--
8
+ return array('items' => array('a', 'b'))
9
+ --EXCEPTION--
10
+ Twig_Error_Syntax: The "loop.last" variable is not defined when looping with a condition in "index.twig" at line 3
Twig/test/Twig/Tests/Fixtures/tags/for/loop_not_defined_cond.test ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag
3
+ --TEMPLATE--
4
+ {% for i, item in items if loop.last > 0 %}
5
+ {% endfor %}
6
+ --DATA--
7
+ return array('items' => array('a', 'b'))
8
+ --EXCEPTION--
9
+ Twig_Error_Syntax: The "loop" variable cannot be used in a looping condition in "index.twig" at line 2
Twig/test/Twig/Tests/Fixtures/tags/for/nested_else.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag can use an "else" clause
3
+ --TEMPLATE--
4
+ {% for item in items %}
5
+ {% for item in items1 %}
6
+ * {{ item }}
7
+ {% else %}
8
+ no {{ item }}
9
+ {% endfor %}
10
+ {% else %}
11
+ no item1
12
+ {% endfor %}
13
+ --DATA--
14
+ return array('items' => array('a', 'b'), 'items1' => array())
15
+ --EXPECT--
16
+ no a
17
+ no b
Twig/test/Twig/Tests/Fixtures/tags/for/objects.test ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag iterates over iterable objects
3
+ --TEMPLATE--
4
+ {% for item in items %}
5
+ * {{ item }}
6
+ * {{ loop.index }}/{{ loop.index0 }}
7
+ * {{ loop.first }}
8
+
9
+ {% endfor %}
10
+
11
+ {% for key, value in items %}
12
+ * {{ key }}/{{ value }}
13
+ {% endfor %}
14
+
15
+ {% for key in items|keys %}
16
+ * {{ key }}
17
+ {% endfor %}
18
+ --DATA--
19
+ class ItemsIterator implements Iterator
20
+ {
21
+ protected $values = array('foo' => 'bar', 'bar' => 'foo');
22
+ public function current() { return current($this->values); }
23
+ public function key() { return key($this->values); }
24
+ public function next() { return next($this->values); }
25
+ public function rewind() { return reset($this->values); }
26
+ public function valid() { return false !== current($this->values); }
27
+ }
28
+ return array('items' => new ItemsIterator())
29
+ --EXPECT--
30
+ * bar
31
+ * 1/0
32
+ * 1
33
+
34
+ * foo
35
+ * 2/1
36
+ *
37
+
38
+
39
+ * foo/bar
40
+ * bar/foo
41
+
42
+ * foo
43
+ * bar
Twig/test/Twig/Tests/Fixtures/tags/for/objects_countable.test ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag iterates over iterable and countable objects
3
+ --TEMPLATE--
4
+ {% for item in items %}
5
+ * {{ item }}
6
+ * {{ loop.index }}/{{ loop.index0 }}
7
+ * {{ loop.revindex }}/{{ loop.revindex0 }}
8
+ * {{ loop.first }}/{{ loop.last }}/{{ loop.length }}
9
+
10
+ {% endfor %}
11
+
12
+ {% for key, value in items %}
13
+ * {{ key }}/{{ value }}
14
+ {% endfor %}
15
+
16
+ {% for key in items|keys %}
17
+ * {{ key }}
18
+ {% endfor %}
19
+ --DATA--
20
+ class ItemsIteratorCountable implements Iterator, Countable
21
+ {
22
+ protected $values = array('foo' => 'bar', 'bar' => 'foo');
23
+ public function current() { return current($this->values); }
24
+ public function key() { return key($this->values); }
25
+ public function next() { return next($this->values); }
26
+ public function rewind() { return reset($this->values); }
27
+ public function valid() { return false !== current($this->values); }
28
+ public function count() { return count($this->values); }
29
+ }
30
+ return array('items' => new ItemsIteratorCountable())
31
+ --EXPECT--
32
+ * bar
33
+ * 1/0
34
+ * 2/1
35
+ * 1//2
36
+
37
+ * foo
38
+ * 2/1
39
+ * 1/0
40
+ * /1/2
41
+
42
+
43
+ * foo/bar
44
+ * bar/foo
45
+
46
+ * foo
47
+ * bar
Twig/test/Twig/Tests/Fixtures/tags/for/recursive.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tags can be nested
3
+ --TEMPLATE--
4
+ {% for key, item in items %}
5
+ * {{ key }} ({{ loop.length }}):
6
+ {% for value in item %}
7
+ * {{ value }} ({{ loop.length }})
8
+ {% endfor %}
9
+ {% endfor %}
10
+ --DATA--
11
+ return array('items' => array('a' => array('a1', 'a2', 'a3'), 'b' => array('b1')))
12
+ --EXPECT--
13
+ * a (2):
14
+ * a1 (3)
15
+ * a2 (3)
16
+ * a3 (3)
17
+ * b (2):
18
+ * b1 (1)
Twig/test/Twig/Tests/Fixtures/tags/for/values.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "for" tag iterates over item values
3
+ --TEMPLATE--
4
+ {% for item in items %}
5
+ * {{ item }}
6
+ {% endfor %}
7
+ --DATA--
8
+ return array('items' => array('a', 'b'))
9
+ --EXPECT--
10
+ * a
11
+ * b
Twig/test/Twig/Tests/Fixtures/tags/from.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ global variables
3
+ --TEMPLATE--
4
+ {% include "included.twig" %}
5
+ {% from "included.twig" import foobar %}
6
+ {{ foobar() }}
7
+ --TEMPLATE(included.twig)--
8
+ {% macro foobar() %}
9
+ called foobar
10
+ {% endmacro %}
11
+ --DATA--
12
+ return array();
13
+ --EXPECT--
14
+ called foobar
Twig/test/Twig/Tests/Fixtures/tags/if/basic.test ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "if" creates a condition
3
+ --TEMPLATE--
4
+ {% if a is defined %}
5
+ {{ a }}
6
+ {% elseif b is defined %}
7
+ {{ b }}
8
+ {% else %}
9
+ NOTHING
10
+ {% endif %}
11
+ --DATA--
12
+ return array('a' => 'a')
13
+ --EXPECT--
14
+ a
15
+ --DATA--
16
+ return array('b' => 'b')
17
+ --EXPECT--
18
+ b
19
+ --DATA--
20
+ return array()
21
+ --EXPECT--
22
+ NOTHING
Twig/test/Twig/Tests/Fixtures/tags/if/expression.test ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "if" takes an expression as a test
3
+ --TEMPLATE--
4
+ {% if a < 2 %}
5
+ A1
6
+ {% elseif a > 10 %}
7
+ A2
8
+ {% else %}
9
+ A3
10
+ {% endif %}
11
+ --DATA--
12
+ return array('a' => 1)
13
+ --EXPECT--
14
+ A1
15
+ --DATA--
16
+ return array('a' => 12)
17
+ --EXPECT--
18
+ A2
19
+ --DATA--
20
+ return array('a' => 7)
21
+ --EXPECT--
22
+ A3
Twig/test/Twig/Tests/Fixtures/tags/include/basic.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag
3
+ --TEMPLATE--
4
+ FOO
5
+ {% include "foo.twig" %}
6
+
7
+ BAR
8
+ --TEMPLATE(foo.twig)--
9
+ FOOBAR
10
+ --DATA--
11
+ return array()
12
+ --EXPECT--
13
+ FOO
14
+
15
+ FOOBAR
16
+ BAR
Twig/test/Twig/Tests/Fixtures/tags/include/expression.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag allows expressions for the template to include
3
+ --TEMPLATE--
4
+ FOO
5
+ {% include foo %}
6
+
7
+ BAR
8
+ --TEMPLATE(foo.twig)--
9
+ FOOBAR
10
+ --DATA--
11
+ return array('foo' => 'foo.twig')
12
+ --EXPECT--
13
+ FOO
14
+
15
+ FOOBAR
16
+ BAR
Twig/test/Twig/Tests/Fixtures/tags/include/ignore_missing.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag
3
+ --TEMPLATE--
4
+ {% include ["foo.twig", "bar.twig"] ignore missing %}
5
+ {% include "foo.twig" ignore missing %}
6
+ {% include "foo.twig" ignore missing with {} %}
7
+ {% include "foo.twig" ignore missing with {} only %}
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
Twig/test/Twig/Tests/Fixtures/tags/include/missing.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag
3
+ --TEMPLATE--
4
+ {% include "foo.twig" %}
5
+ --DATA--
6
+ return array();
7
+ --EXCEPTION--
8
+ Twig_Error_Loader: Template "foo.twig" is not defined in "index.twig" at line 2.
Twig/test/Twig/Tests/Fixtures/tags/include/missing_nested.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag
3
+ --TEMPLATE--
4
+ {% extends "base.twig" %}
5
+
6
+ {% block content %}
7
+ {{ parent() }}
8
+ {% endblock %}
9
+ --TEMPLATE(base.twig)--
10
+ {% block content %}
11
+ {% include "foo.twig" %}
12
+ {% endblock %}
13
+ --DATA--
14
+ return array();
15
+ --EXCEPTION--
16
+ Twig_Error_Loader: Template "foo.twig" is not defined in "base.twig" at line 3.
Twig/test/Twig/Tests/Fixtures/tags/include/only.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag accept variables and only
3
+ --TEMPLATE--
4
+ {% include "foo.twig" %}
5
+ {% include "foo.twig" only %}
6
+ {% include "foo.twig" with {'foo1': 'bar'} %}
7
+ {% include "foo.twig" with {'foo1': 'bar'} only %}
8
+ --TEMPLATE(foo.twig)--
9
+ {% for k, v in _context %}{{ k }},{% endfor %}
10
+ --DATA--
11
+ return array('foo' => 'bar')
12
+ --EXPECT--
13
+ foo,global,_parent,
14
+ global,_parent,
15
+ foo,global,foo1,_parent,
16
+ foo1,global,_parent,
Twig/test/Twig/Tests/Fixtures/tags/include/template_instance.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag accepts Twig_Template instance
3
+ --TEMPLATE--
4
+ {% include foo %} FOO
5
+ --TEMPLATE(foo.twig)--
6
+ BAR
7
+ --DATA--
8
+ return array('foo' => $twig->loadTemplate('foo.twig'))
9
+ --EXPECT--
10
+ BAR FOO
Twig/test/Twig/Tests/Fixtures/tags/include/templates_as_array.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag
3
+ --TEMPLATE--
4
+ {% include ["foo.twig", "bar.twig"] %}
5
+ {% include ["bar.twig", "foo.twig"] %}
6
+ --TEMPLATE(foo.twig)--
7
+ foo
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
11
+ foo
12
+ foo
Twig/test/Twig/Tests/Fixtures/tags/include/with_variables.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "include" tag accept variables
3
+ --TEMPLATE--
4
+ {% include "foo.twig" with {'foo': 'bar'} %}
5
+ {% include "foo.twig" with vars %}
6
+ --TEMPLATE(foo.twig)--
7
+ {{ foo }}
8
+ --DATA--
9
+ return array('vars' => array('foo' => 'bar'))
10
+ --EXPECT--
11
+ bar
12
+ bar
Twig/test/Twig/Tests/Fixtures/tags/inheritance/basic.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends "foo.twig" %}
5
+
6
+ {% block content %}
7
+ FOO
8
+ {% endblock %}
9
+ --TEMPLATE(foo.twig)--
10
+ {% block content %}{% endblock %}
11
+ --DATA--
12
+ return array()
13
+ --EXPECT--
14
+ FOO
Twig/test/Twig/Tests/Fixtures/tags/inheritance/conditional.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends standalone ? foo : 'bar.twig' %}
5
+
6
+ {% block content %}{{ parent() }}FOO{% endblock %}
7
+ --TEMPLATE(foo.twig)--
8
+ {% block content %}FOO{% endblock %}
9
+ --TEMPLATE(bar.twig)--
10
+ {% block content %}BAR{% endblock %}
11
+ --DATA--
12
+ return array('foo' => 'foo.twig', 'standalone' => true)
13
+ --EXPECT--
14
+ FOOFOO
Twig/test/Twig/Tests/Fixtures/tags/inheritance/dynamic.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends foo %}
5
+
6
+ {% block content %}
7
+ FOO
8
+ {% endblock %}
9
+ --TEMPLATE(foo.twig)--
10
+ {% block content %}{% endblock %}
11
+ --DATA--
12
+ return array('foo' => 'foo.twig')
13
+ --EXPECT--
14
+ FOO
Twig/test/Twig/Tests/Fixtures/tags/inheritance/empty.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends "foo.twig" %}
5
+ --TEMPLATE(foo.twig)--
6
+ {% block content %}FOO{% endblock %}
7
+ --DATA--
8
+ return array()
9
+ --EXPECT--
10
+ FOO
Twig/test/Twig/Tests/Fixtures/tags/inheritance/extends_as_array.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends ["foo.twig", "bar.twig"] %}
5
+ --TEMPLATE(bar.twig)--
6
+ {% block content %}
7
+ foo
8
+ {% endblock %}
9
+ --DATA--
10
+ return array()
11
+ --EXPECT--
12
+ foo
Twig/test/Twig/Tests/Fixtures/tags/inheritance/multiple.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends "layout.twig" %}{% block content %}{{ parent() }}index {% endblock %}
5
+ --TEMPLATE(layout.twig)--
6
+ {% extends "base.twig" %}{% block content %}{{ parent() }}layout {% endblock %}
7
+ --TEMPLATE(base.twig)--
8
+ {% block content %}base {% endblock %}
9
+ --DATA--
10
+ return array()
11
+ --EXPECT--
12
+ base layout index
Twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks.test ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "block" tag
3
+ --TEMPLATE--
4
+ {% extends "foo.twig" %}
5
+
6
+ {% block content %}
7
+ {% block subcontent %}
8
+ {% block subsubcontent %}
9
+ SUBSUBCONTENT
10
+ {% endblock %}
11
+ {% endblock %}
12
+ {% endblock %}
13
+ --TEMPLATE(foo.twig)--
14
+ {% block content %}
15
+ {% block subcontent %}
16
+ SUBCONTENT
17
+ {% endblock %}
18
+ {% endblock %}
19
+ --DATA--
20
+ return array()
21
+ --EXPECT--
22
+ SUBSUBCONTENT
Twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_blocks_parent_only.test ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "block" tag
3
+ --TEMPLATE--
4
+ {% block content %}
5
+ CONTENT
6
+ {%- block subcontent -%}
7
+ SUBCONTENT
8
+ {%- endblock -%}
9
+ ENDCONTENT
10
+ {% endblock %}
11
+ --TEMPLATE(foo.twig)--
12
+ --DATA--
13
+ return array()
14
+ --EXPECT--
15
+ CONTENTSUBCONTENTENDCONTENT
Twig/test/Twig/Tests/Fixtures/tags/inheritance/nested_inheritance.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends "layout.twig" %}
5
+ {% block inside %}INSIDE{% endblock inside %}
6
+ --TEMPLATE(layout.twig)--
7
+ {% extends "base.twig" %}
8
+ {% block body %}
9
+ {% block inside '' %}
10
+ {% endblock body %}
11
+ --TEMPLATE(base.twig)--
12
+ {% block body '' %}
13
+ --DATA--
14
+ return array()
15
+ --EXPECT--
16
+ INSIDE
Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends "foo.twig" %}
5
+
6
+ {% block content %}{{ parent() }}FOO{{ parent() }}{% endblock %}
7
+ --TEMPLATE(foo.twig)--
8
+ {% block content %}BAR{% endblock %}
9
+ --DATA--
10
+ return array()
11
+ --EXPECT--
12
+ BARFOOBAR
Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_change.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends foo ? 'foo.twig' : 'bar.twig' %}
5
+ --TEMPLATE(foo.twig)--
6
+ FOO
7
+ --TEMPLATE(bar.twig)--
8
+ BAR
9
+ --DATA--
10
+ return array('foo' => true)
11
+ --EXPECT--
12
+ FOO
13
+ --DATA--
14
+ return array('foo' => false)
15
+ --EXPECT--
16
+ BAR
Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_in_a_block.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% block content %}
5
+ {% extends "foo.twig" %}
6
+ {% endblock %}
7
+ --EXCEPTION--
8
+ Twig_Error_Syntax: Cannot extend from a block in "index.twig" at line 3
Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_isolation.test ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends "base.twig" %}
5
+ {% block content %}{% include "included.twig" %}{% endblock %}
6
+
7
+ {% block footer %}Footer{% endblock %}
8
+ --TEMPLATE(included.twig)--
9
+ {% extends "base.twig" %}
10
+ {% block content %}Included Content{% endblock %}
11
+ --TEMPLATE(base.twig)--
12
+ {% block content %}Default Content{% endblock %}
13
+
14
+ {% block footer %}Default Footer{% endblock %}
15
+ --DATA--
16
+ return array()
17
+ --EXPECT--
18
+ Included Content
19
+ Default Footer
20
+ Footer
Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_nested.test ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag
3
+ --TEMPLATE--
4
+ {% extends "foo.twig" %}
5
+
6
+ {% block content %}
7
+ {% block inside %}
8
+ INSIDE OVERRIDDEN
9
+ {% endblock %}
10
+
11
+ BEFORE
12
+ {{ parent() }}
13
+ AFTER
14
+ {% endblock %}
15
+ --TEMPLATE(foo.twig)--
16
+ {% block content %}
17
+ BAR
18
+ {% endblock %}
19
+ --DATA--
20
+ return array()
21
+ --EXPECT--
22
+
23
+ INSIDE OVERRIDDEN
24
+
25
+ BEFORE
26
+ BAR
27
+
28
+ AFTER
Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "parent" tag
3
+ --TEMPLATE--
4
+ {% block content %}
5
+ {{ parent() }}
6
+ {% endblock %}
7
+ --EXCEPTION--
8
+ Twig_Error_Syntax: Calling "parent" on a template that does not extend nor "use" another template is forbidden in "index.twig" at line 3
Twig/test/Twig/Tests/Fixtures/tags/inheritance/parent_without_extends_but_traits.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "parent" tag
3
+ --TEMPLATE--
4
+ {% use 'foo.twig' %}
5
+
6
+ {% block content %}
7
+ {{ parent() }}
8
+ {% endblock %}
9
+ --TEMPLATE(foo.twig)--
10
+ {% block content %}BAR{% endblock %}
11
+ --DATA--
12
+ return array()
13
+ --EXPECT--
14
+ BAR
Twig/test/Twig/Tests/Fixtures/tags/inheritance/template_instance.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "extends" tag accepts Twig_Template instance
3
+ --TEMPLATE--
4
+ {% extends foo %}
5
+
6
+ {% block content %}
7
+ {{ parent() }}FOO
8
+ {% endblock %}
9
+ --TEMPLATE(foo.twig)--
10
+ {% block content %}BAR{% endblock %}
11
+ --DATA--
12
+ return array('foo' => $twig->loadTemplate('foo.twig'))
13
+ --EXPECT--
14
+ BARFOO
Twig/test/Twig/Tests/Fixtures/tags/inheritance/use.test ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "parent" function
3
+ --TEMPLATE--
4
+ {% extends "parent.twig" %}
5
+
6
+ {% use "use1.twig" %}
7
+ {% use "use2.twig" %}
8
+
9
+ {% block content_parent %}
10
+ {{ parent() }}
11
+ {% endblock %}
12
+
13
+ {% block content_use1 %}
14
+ {{ parent() }}
15
+ {% endblock %}
16
+
17
+ {% block content_use2 %}
18
+ {{ parent() }}
19
+ {% endblock %}
20
+
21
+ {% block content %}
22
+ {{ block('content_use1_only') }}
23
+ {{ block('content_use2_only') }}
24
+ {% endblock %}
25
+ --TEMPLATE(parent.twig)--
26
+ {% block content_parent 'content_parent' %}
27
+ {% block content_use1 'content_parent' %}
28
+ {% block content_use2 'content_parent' %}
29
+ {% block content '' %}
30
+ --TEMPLATE(use1.twig)--
31
+ {% block content_use1 'content_use1' %}
32
+ {% block content_use2 'content_use1' %}
33
+ {% block content_use1_only 'content_use1_only' %}
34
+ --TEMPLATE(use2.twig)--
35
+ {% block content_use2 'content_use2' %}
36
+ {% block content_use2_only 'content_use2_only' %}
37
+ --DATA--
38
+ return array()
39
+ --EXPECT--
40
+ content_parent
41
+ content_use1
42
+ content_use2
43
+ content_use1_only
44
+ content_use2_only
Twig/test/Twig/Tests/Fixtures/tags/macro/basic.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "macro" tag
3
+ --TEMPLATE--
4
+ {% import _self as macros %}
5
+
6
+ {{ macros.input('username') }}
7
+ {{ macros.input('password', null, 'password', 1) }}
8
+
9
+ {% macro input(name, value, type, size) %}
10
+ <input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
11
+ {% endmacro %}
12
+ --DATA--
13
+ return array()
14
+ --EXPECT--
15
+ <input type="text" name="username" value="" size="20">
16
+
17
+ <input type="password" name="password" value="" size="1">
Twig/test/Twig/Tests/Fixtures/tags/macro/endmacro_name.test ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "macro" tag supports name for endmacro
3
+ --TEMPLATE--
4
+ {% import _self as macros %}
5
+
6
+ {{ macros.foo() }}
7
+ {{ macros.bar() }}
8
+
9
+ {% macro foo() %}foo{% endmacro %}
10
+ {% macro bar() %}bar{% endmacro bar %}
11
+ --DATA--
12
+ return array()
13
+ --EXPECT--
14
+ foo
15
+ bar
16
+
Twig/test/Twig/Tests/Fixtures/tags/macro/external.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "macro" tag
3
+ --TEMPLATE--
4
+ {% import 'forms.twig' as forms %}
5
+
6
+ {{ forms.input('username') }}
7
+ {{ forms.input('password', null, 'password', 1) }}
8
+ --TEMPLATE(forms.twig)--
9
+ {% macro input(name, value, type, size) %}
10
+ <input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
11
+ {% endmacro %}
12
+ --DATA--
13
+ return array()
14
+ --EXPECT--
15
+ <input type="text" name="username" value="" size="20">
16
+
17
+ <input type="password" name="password" value="" size="1">
Twig/test/Twig/Tests/Fixtures/tags/macro/from.test ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "macro" tag
3
+ --TEMPLATE--
4
+ {% from 'forms.twig' import foo %}
5
+ {% from 'forms.twig' import foo as foobar, bar %}
6
+
7
+ {{ foo('foo') }}
8
+ {{ foobar('foo') }}
9
+ {{ bar('foo') }}
10
+ --TEMPLATE(forms.twig)--
11
+ {% macro foo(name) %}foo{{ name }}{% endmacro %}
12
+ {% macro bar(name) %}bar{{ name }}{% endmacro %}
13
+ --DATA--
14
+ return array()
15
+ --EXPECT--
16
+ foofoo
17
+ foofoo
18
+ barfoo
Twig/test/Twig/Tests/Fixtures/tags/macro/global.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "macro" tag
3
+ --TEMPLATE--
4
+ {% from 'forms.twig' import foo %}
5
+
6
+ {{ foo('foo') }}
7
+ {{ foo() }}
8
+ --TEMPLATE(forms.twig)--
9
+ {% macro foo(name) %}{{ name|default('foo') }}{{ global }}{% endmacro %}
10
+ --DATA--
11
+ return array()
12
+ --EXPECT--
13
+ fooglobal
14
+ fooglobal
Twig/test/Twig/Tests/Fixtures/tags/macro/self_import.test ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "macro" tag
3
+ --TEMPLATE--
4
+ {% import _self as forms %}
5
+
6
+ {{ forms.input('username') }}
7
+ {{ forms.input('password', null, 'password', 1) }}
8
+
9
+ {% macro input(name, value, type, size) %}
10
+ <input type="{{ type|default("text") }}" name="{{ name }}" value="{{ value|e|default('') }}" size="{{ size|default(20) }}">
11
+ {% endmacro %}
12
+ --DATA--
13
+ return array()
14
+ --EXPECT--
15
+ <input type="text" name="username" value="" size="20">
16
+
17
+ <input type="password" name="password" value="" size="1">
Twig/test/Twig/Tests/Fixtures/tags/macro/special_chars.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "☃" as a macro name
3
+ --TEMPLATE--
4
+ {% import _self as macros %}
5
+
6
+ {{ macros.☃('foo') }}
7
+
8
+ {% macro ☃(foo) %}
9
+ ☃{{ foo }}☃
10
+ {% endmacro %}
11
+ --DATA--
12
+ return array()
13
+ --EXPECT--
14
+ ☃foo☃
Twig/test/Twig/Tests/Fixtures/tags/raw/basic.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "raw" tag
3
+ --TEMPLATE--
4
+ {% raw %}
5
+ {{ foo }}
6
+ {% endraw %}
7
+ --DATA--
8
+ return array()
9
+ --EXPECT--
10
+ {{ foo }}
Twig/test/Twig/Tests/Fixtures/tags/raw/mixed_usage_with_raw.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "raw" tag
3
+ --TEMPLATE--
4
+ {% raw %}
5
+ {{ foo }}
6
+ {% endverbatim %}
7
+ --DATA--
8
+ return array()
9
+ --EXCEPTION--
10
+ Twig_Error_Syntax: Unexpected end of file: Unclosed "raw" block in "index.twig" at line 2
Twig/test/Twig/Tests/Fixtures/tags/raw/whitespace_control.test ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "raw" tag
3
+ --TEMPLATE--
4
+ 1***
5
+
6
+ {%- raw %}
7
+ {{ 'bla' }}
8
+ {% endraw %}
9
+
10
+ 1***
11
+ 2***
12
+
13
+ {%- raw -%}
14
+ {{ 'bla' }}
15
+ {% endraw %}
16
+
17
+ 2***
18
+ 3***
19
+
20
+ {%- raw -%}
21
+ {{ 'bla' }}
22
+ {% endraw -%}
23
+
24
+ 3***
25
+ 4***
26
+
27
+ {%- raw -%}
28
+ {{ 'bla' }}
29
+ {%- endraw %}
30
+
31
+ 4***
32
+ 5***
33
+
34
+ {%- raw -%}
35
+ {{ 'bla' }}
36
+ {%- endraw -%}
37
+
38
+ 5***
39
+ --DATA--
40
+ return array()
41
+ --EXPECT--
42
+ 1***
43
+ {{ 'bla' }}
44
+
45
+
46
+ 1***
47
+ 2***{{ 'bla' }}
48
+
49
+
50
+ 2***
51
+ 3***{{ 'bla' }}
52
+ 3***
53
+ 4***{{ 'bla' }}
54
+
55
+ 4***
56
+ 5***{{ 'bla' }}5***
Twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid1.test ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ sandbox tag
3
+ --TEMPLATE--
4
+ {%- sandbox %}
5
+ {%- include "foo.twig" %}
6
+ a
7
+ {%- endsandbox %}
8
+ --TEMPLATE(foo.twig)--
9
+ foo
10
+ --EXCEPTION--
11
+ Twig_Error_Syntax: Only "include" tags are allowed within a "sandbox" section in "index.twig" at line 4
Twig/test/Twig/Tests/Fixtures/tags/sandbox/not_valid2.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ sandbox tag
3
+ --TEMPLATE--
4
+ {%- sandbox %}
5
+ {%- include "foo.twig" %}
6
+
7
+ {% if 1 %}
8
+ {%- include "foo.twig" %}
9
+ {% endif %}
10
+ {%- endsandbox %}
11
+ --TEMPLATE(foo.twig)--
12
+ foo
13
+ --EXCEPTION--
14
+ Twig_Error_Syntax: Only "include" tags are allowed within a "sandbox" section in "index.twig" at line 5
Twig/test/Twig/Tests/Fixtures/tags/sandbox/simple.test ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ sandbox tag
3
+ --TEMPLATE--
4
+ {%- sandbox %}
5
+ {%- include "foo.twig" %}
6
+ {%- endsandbox %}
7
+
8
+ {%- sandbox %}
9
+ {%- include "foo.twig" %}
10
+ {%- include "foo.twig" %}
11
+ {%- endsandbox %}
12
+
13
+ {%- sandbox %}{% include "foo.twig" %}{% endsandbox %}
14
+ --TEMPLATE(foo.twig)--
15
+ foo
16
+ --DATA--
17
+ return array()
18
+ --EXPECT--
19
+ foo
20
+ foo
21
+ foo
22
+ foo
Twig/test/Twig/Tests/Fixtures/tags/set/basic.test ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "set" tag
3
+ --TEMPLATE--
4
+ {% set foo = 'foo' %}
5
+ {% set bar = 'foo<br />' %}
6
+
7
+ {{ foo }}
8
+ {{ bar }}
9
+
10
+ {% set foo, bar = 'foo', 'bar' %}
11
+
12
+ {{ foo }}{{ bar }}
13
+ --DATA--
14
+ return array()
15
+ --EXPECT--
16
+ foo
17
+ foo&lt;br /&gt;
18
+
19
+
20
+ foobar
Twig/test/Twig/Tests/Fixtures/tags/set/capture-empty.test ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "set" tag block empty capture
3
+ --TEMPLATE--
4
+ {% set foo %}{% endset %}
5
+
6
+ {% if foo %}FAIL{% endif %}
7
+ --DATA--
8
+ return array()
9
+ --EXPECT--
Twig/test/Twig/Tests/Fixtures/tags/set/capture.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "set" tag block capture
3
+ --TEMPLATE--
4
+ {% set foo %}f<br />o<br />o{% endset %}
5
+
6
+ {{ foo }}
7
+ --DATA--
8
+ return array()
9
+ --EXPECT--
10
+ f<br />o<br />o
Twig/test/Twig/Tests/Fixtures/tags/set/expression.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "set" tag
3
+ --TEMPLATE--
4
+ {% set foo, bar = 'foo' ~ 'bar', 'bar' ~ 'foo' %}
5
+
6
+ {{ foo }}
7
+ {{ bar }}
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
11
+ foobar
12
+ barfoo
Twig/test/Twig/Tests/Fixtures/tags/spaceless/simple.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "spaceless" tag removes whites between HTML tags
3
+ --TEMPLATE--
4
+ {% spaceless %}
5
+
6
+ <div> <div> foo </div> </div>
7
+
8
+ {% endspaceless %}
9
+ --DATA--
10
+ return array()
11
+ --EXPECT--
12
+ <div><div> foo </div></div>
Twig/test/Twig/Tests/Fixtures/tags/special_chars.test ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "☃" custom tag
3
+ --TEMPLATE--
4
+ {% ☃ %}
5
+ --DATA--
6
+ return array()
7
+ --EXPECT--
8
+
Twig/test/Twig/Tests/Fixtures/tags/trim_block.test ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Whitespace trimming on tags.
3
+ --TEMPLATE--
4
+ {{ 5 * '{#-'|length }}
5
+ {{ '{{-'|length * 5 + '{%-'|length }}
6
+
7
+ Trim on control tag:
8
+ {% for i in range(1, 9) -%}
9
+ {{ i }}
10
+ {%- endfor %}
11
+
12
+
13
+ Trim on output tag:
14
+ {% for i in range(1, 9) %}
15
+ {{- i -}}
16
+ {% endfor %}
17
+
18
+
19
+ Trim comments:
20
+
21
+ {#- Invisible -#}
22
+
23
+ After the comment.
24
+
25
+ Trim leading space:
26
+ {% if leading %}
27
+
28
+ {{- leading }}
29
+ {% endif %}
30
+
31
+ {%- if leading %}
32
+ {{- leading }}
33
+
34
+ {%- endif %}
35
+
36
+
37
+ Trim trailing space:
38
+ {% if trailing -%}
39
+ {{ trailing -}}
40
+
41
+ {% endif -%}
42
+
43
+ Combined:
44
+
45
+ {%- if both -%}
46
+ <ul>
47
+ <li> {{- both -}} </li>
48
+ </ul>
49
+
50
+ {%- endif -%}
51
+
52
+ end
53
+ --DATA--
54
+ return array('leading' => 'leading space', 'trailing' => 'trailing space', 'both' => 'both')
55
+ --EXPECT--
56
+ 15
57
+ 18
58
+
59
+ Trim on control tag:
60
+ 123456789
61
+
62
+ Trim on output tag:
63
+ 123456789
64
+
65
+ Trim comments:After the comment.
66
+
67
+ Trim leading space:
68
+ leading space
69
+ leading space
70
+
71
+ Trim trailing space:
72
+ trailing spaceCombined:<ul>
73
+ <li>both</li>
74
+ </ul>end
Twig/test/Twig/Tests/Fixtures/tags/use/aliases.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "use" tag
3
+ --TEMPLATE--
4
+ {% use "blocks.twig" with content as foo %}
5
+
6
+ {{ block('foo') }}
7
+ --TEMPLATE(blocks.twig)--
8
+ {% block content 'foo' %}
9
+ --DATA--
10
+ return array()
11
+ --EXPECT--
12
+ foo
Twig/test/Twig/Tests/Fixtures/tags/use/basic.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "use" tag
3
+ --TEMPLATE--
4
+ {% use "blocks.twig" %}
5
+
6
+ {{ block('content') }}
7
+ --TEMPLATE(blocks.twig)--
8
+ {% block content 'foo' %}
9
+ --DATA--
10
+ return array()
11
+ --EXPECT--
12
+ foo
Twig/test/Twig/Tests/Fixtures/tags/use/deep.test ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "use" tag
3
+ --TEMPLATE--
4
+ {% use "foo.twig" %}
5
+
6
+ {{ block('content') }}
7
+ {{ block('foo') }}
8
+ {{ block('bar') }}
9
+ --TEMPLATE(foo.twig)--
10
+ {% use "bar.twig" %}
11
+
12
+ {% block content 'foo' %}
13
+ {% block foo 'foo' %}
14
+ --TEMPLATE(bar.twig)--
15
+ {% block content 'bar' %}
16
+ {% block bar 'bar' %}
17
+ --DATA--
18
+ return array()
19
+ --EXPECT--
20
+ foo
21
+ foo
22
+ bar
Twig/test/Twig/Tests/Fixtures/tags/use/deep_empty.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "use" tag
3
+ --TEMPLATE--
4
+ {% use "foo.twig" %}
5
+ --TEMPLATE(foo.twig)--
6
+ {% use "bar.twig" %}
7
+ --TEMPLATE(bar.twig)--
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
Twig/test/Twig/Tests/Fixtures/tags/use/multiple.test ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "use" tag
3
+ --TEMPLATE--
4
+ {% use "foo.twig" %}
5
+ {% use "bar.twig" %}
6
+
7
+ {{ block('content') }}
8
+ {{ block('foo') }}
9
+ {{ block('bar') }}
10
+ --TEMPLATE(foo.twig)--
11
+ {% block content 'foo' %}
12
+ {% block foo 'foo' %}
13
+ --TEMPLATE(bar.twig)--
14
+ {% block content 'bar' %}
15
+ {% block bar 'bar' %}
16
+ --DATA--
17
+ return array()
18
+ --EXPECT--
19
+ bar
20
+ foo
21
+ bar
Twig/test/Twig/Tests/Fixtures/tags/use/multiple_aliases.test ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "use" tag
3
+ --TEMPLATE--
4
+ {% use "foo.twig" with content as foo_content %}
5
+ {% use "bar.twig" %}
6
+
7
+ {{ block('content') }}
8
+ {{ block('foo') }}
9
+ {{ block('bar') }}
10
+ {{ block('foo_content') }}
11
+ --TEMPLATE(foo.twig)--
12
+ {% block content 'foo' %}
13
+ {% block foo 'foo' %}
14
+ --TEMPLATE(bar.twig)--
15
+ {% block content 'bar' %}
16
+ {% block bar 'bar' %}
17
+ --DATA--
18
+ return array()
19
+ --EXPECT--
20
+ bar
21
+ foo
22
+ bar
23
+ foo
Twig/test/Twig/Tests/Fixtures/tags/verbatim/basic.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "verbatim" tag
3
+ --TEMPLATE--
4
+ {% verbatim %}
5
+ {{ foo }}
6
+ {% endverbatim %}
7
+ --DATA--
8
+ return array()
9
+ --EXPECT--
10
+ {{ foo }}
Twig/test/Twig/Tests/Fixtures/tags/verbatim/mixed_usage_with_raw.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "verbatim" tag
3
+ --TEMPLATE--
4
+ {% verbatim %}
5
+ {{ foo }}
6
+ {% endraw %}
7
+ --DATA--
8
+ return array()
9
+ --EXCEPTION--
10
+ Twig_Error_Syntax: Unexpected end of file: Unclosed "verbatim" block in "index.twig" at line 2
Twig/test/Twig/Tests/Fixtures/tags/verbatim/whitespace_control.test ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "verbatim" tag
3
+ --TEMPLATE--
4
+ 1***
5
+
6
+ {%- verbatim %}
7
+ {{ 'bla' }}
8
+ {% endverbatim %}
9
+
10
+ 1***
11
+ 2***
12
+
13
+ {%- verbatim -%}
14
+ {{ 'bla' }}
15
+ {% endverbatim %}
16
+
17
+ 2***
18
+ 3***
19
+
20
+ {%- verbatim -%}
21
+ {{ 'bla' }}
22
+ {% endverbatim -%}
23
+
24
+ 3***
25
+ 4***
26
+
27
+ {%- verbatim -%}
28
+ {{ 'bla' }}
29
+ {%- endverbatim %}
30
+
31
+ 4***
32
+ 5***
33
+
34
+ {%- verbatim -%}
35
+ {{ 'bla' }}
36
+ {%- endverbatim -%}
37
+
38
+ 5***
39
+ --DATA--
40
+ return array()
41
+ --EXPECT--
42
+ 1***
43
+ {{ 'bla' }}
44
+
45
+
46
+ 1***
47
+ 2***{{ 'bla' }}
48
+
49
+
50
+ 2***
51
+ 3***{{ 'bla' }}
52
+ 3***
53
+ 4***{{ 'bla' }}
54
+
55
+ 4***
56
+ 5***{{ 'bla' }}5***
Twig/test/Twig/Tests/Fixtures/tests/array.test ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ array index test
3
+ --TEMPLATE--
4
+ {% for key, value in days %}
5
+ {{ key }}
6
+ {% endfor %}
7
+ --DATA--
8
+ return array('days' => array(
9
+ 1 => array('money' => 9),
10
+ 2 => array('money' => 21),
11
+ 3 => array('money' => 38),
12
+ 4 => array('money' => 6),
13
+ 18 => array('money' => 6),
14
+ 19 => array('money' => 3),
15
+ 31 => array('money' => 11),
16
+ ));
17
+ --EXPECT--
18
+ 1
19
+ 2
20
+ 3
21
+ 4
22
+ 18
23
+ 19
24
+ 31
Twig/test/Twig/Tests/Fixtures/tests/constant.test ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "const" test
3
+ --TEMPLATE--
4
+ {{ 8 is constant('E_NOTICE') ? 'ok' : 'no' }}
5
+ {{ 'bar' is constant('TwigTestFoo::BAR_NAME') ? 'ok' : 'no' }}
6
+ {{ value is constant('TwigTestFoo::BAR_NAME') ? 'ok' : 'no' }}
7
+ --DATA--
8
+ return array('value' => 'bar');
9
+ --EXPECT--
10
+ ok
11
+ ok
12
+ ok
Twig/test/Twig/Tests/Fixtures/tests/defined.test ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "defined" test
3
+ --TEMPLATE--
4
+ {{ definedVar is defined ? 'ok' : 'ko' }}
5
+ {{ definedVar is not defined ? 'ko' : 'ok' }}
6
+ {{ undefinedVar is defined ? 'ko' : 'ok' }}
7
+ {{ undefinedVar is not defined ? 'ok' : 'ko' }}
8
+ {{ zeroVar is defined ? 'ok' : 'ko' }}
9
+ {{ nullVar is defined ? 'ok' : 'ko' }}
10
+ {{ nested.definedVar is defined ? 'ok' : 'ko' }}
11
+ {{ nested['definedVar'] is defined ? 'ok' : 'ko' }}
12
+ {{ nested.definedVar is not defined ? 'ko' : 'ok' }}
13
+ {{ nested.undefinedVar is defined ? 'ko' : 'ok' }}
14
+ {{ nested['undefinedVar'] is defined ? 'ko' : 'ok' }}
15
+ {{ nested.undefinedVar is not defined ? 'ok' : 'ko' }}
16
+ {{ nested.zeroVar is defined ? 'ok' : 'ko' }}
17
+ {{ nested.nullVar is defined ? 'ok' : 'ko' }}
18
+ {{ nested.definedArray.0 is defined ? 'ok' : 'ko' }}
19
+ {{ nested['definedArray'][0] is defined ? 'ok' : 'ko' }}
20
+ {{ object.foo is defined ? 'ok' : 'ko' }}
21
+ {{ object.undefinedMethod is defined ? 'ko' : 'ok' }}
22
+ {{ object.getFoo() is defined ? 'ok' : 'ko' }}
23
+ {{ object.getFoo('a') is defined ? 'ok' : 'ko' }}
24
+ {{ object.undefinedMethod() is defined ? 'ko' : 'ok' }}
25
+ {{ object.undefinedMethod('a') is defined ? 'ko' : 'ok' }}
26
+ {{ object.self.foo is defined ? 'ok' : 'ko' }}
27
+ {{ object.self.undefinedMethod is defined ? 'ko' : 'ok' }}
28
+ {{ object.undefinedMethod.self is defined ? 'ko' : 'ok' }}
29
+ --DATA--
30
+ return array(
31
+ 'definedVar' => 'defined',
32
+ 'zeroVar' => 0,
33
+ 'nullVar' => null,
34
+ 'nested' => array(
35
+ 'definedVar' => 'defined',
36
+ 'zeroVar' => 0,
37
+ 'nullVar' => null,
38
+ 'definedArray' => array(0),
39
+ ),
40
+ 'object' => new TwigTestFoo(),
41
+ );
42
+ --EXPECT--
43
+ ok
44
+ ok
45
+ ok
46
+ ok
47
+ ok
48
+ ok
49
+ ok
50
+ ok
51
+ ok
52
+ ok
53
+ ok
54
+ ok
55
+ ok
56
+ ok
57
+ ok
58
+ ok
59
+ ok
60
+ ok
61
+ ok
62
+ ok
63
+ ok
64
+ ok
65
+ ok
66
+ ok
67
+ ok
68
+ --DATA--
69
+ return array(
70
+ 'definedVar' => 'defined',
71
+ 'zeroVar' => 0,
72
+ 'nullVar' => null,
73
+ 'nested' => array(
74
+ 'definedVar' => 'defined',
75
+ 'zeroVar' => 0,
76
+ 'nullVar' => null,
77
+ 'definedArray' => array(0),
78
+ ),
79
+ 'object' => new TwigTestFoo(),
80
+ );
81
+ --CONFIG--
82
+ return array('strict_variables' => false)
83
+ --EXPECT--
84
+ ok
85
+ ok
86
+ ok
87
+ ok
88
+ ok
89
+ ok
90
+ ok
91
+ ok
92
+ ok
93
+ ok
94
+ ok
95
+ ok
96
+ ok
97
+ ok
98
+ ok
99
+ ok
100
+ ok
101
+ ok
102
+ ok
103
+ ok
104
+ ok
105
+ ok
106
+ ok
107
+ ok
108
+ ok
Twig/test/Twig/Tests/Fixtures/tests/empty.test ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "empty" test
3
+ --TEMPLATE--
4
+ {{ foo is empty ? 'ok' : 'ko' }}
5
+ {{ bar is empty ? 'ok' : 'ko' }}
6
+ {{ foobar is empty ? 'ok' : 'ko' }}
7
+ {{ array is empty ? 'ok' : 'ko' }}
8
+ {{ zero is empty ? 'ok' : 'ko' }}
9
+ {{ string is empty ? 'ok' : 'ko' }}
10
+ {{ countable_empty is empty ? 'ok' : 'ko' }}
11
+ {{ countable_not_empty is empty ? 'ok' : 'ko' }}
12
+ {{ markup_empty is empty ? 'ok' : 'ko' }}
13
+ {{ markup_not_empty is empty ? 'ok' : 'ko' }}
14
+ --DATA--
15
+
16
+ class CountableStub implements Countable
17
+ {
18
+ private $items;
19
+
20
+ public function __construct(array $items)
21
+ {
22
+ $this->items = $items;
23
+ }
24
+
25
+ public function count()
26
+ {
27
+ return count($this->items);
28
+ }
29
+ }
30
+ return array(
31
+ 'foo' => '', 'bar' => null, 'foobar' => false, 'array' => array(), 'zero' => 0, 'string' => '0',
32
+ 'countable_empty' => new CountableStub(array()), 'countable_not_empty' => new CountableStub(array(1, 2)),
33
+ 'markup_empty' => new Twig_Markup('', 'UTF-8'), 'markup_not_empty' => new Twig_Markup('test', 'UTF-8'),
34
+ );
35
+ --EXPECT--
36
+ ok
37
+ ok
38
+ ok
39
+ ok
40
+ ko
41
+ ko
42
+ ok
43
+ ko
44
+ ok
45
+ ko
Twig/test/Twig/Tests/Fixtures/tests/even.test ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "even" test
3
+ --TEMPLATE--
4
+ {{ 1 is even ? 'ko' : 'ok' }}
5
+ {{ 2 is even ? 'ok' : 'ko' }}
6
+ {{ 1 is not even ? 'ok' : 'ko' }}
7
+ {{ 2 is not even ? 'ko' : 'ok' }}
8
+ --DATA--
9
+ return array()
10
+ --EXPECT--
11
+ ok
12
+ ok
13
+ ok
14
+ ok
Twig/test/Twig/Tests/Fixtures/tests/in.test ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports the in operator
3
+ --TEMPLATE--
4
+ {% if bar in foo %}
5
+ TRUE
6
+ {% endif %}
7
+ {% if not (bar in foo) %}
8
+ {% else %}
9
+ TRUE
10
+ {% endif %}
11
+ {% if bar not in foo %}
12
+ {% else %}
13
+ TRUE
14
+ {% endif %}
15
+ {% if 'a' in bar %}
16
+ TRUE
17
+ {% endif %}
18
+ {% if 'c' not in bar %}
19
+ TRUE
20
+ {% endif %}
21
+ {% if '' not in bar %}
22
+ TRUE
23
+ {% endif %}
24
+ {% if '' in '' %}
25
+ TRUE
26
+ {% endif %}
27
+ {% if '0' not in '' %}
28
+ TRUE
29
+ {% endif %}
30
+ {% if 'a' not in '0' %}
31
+ TRUE
32
+ {% endif %}
33
+ {% if '0' in '0' %}
34
+ TRUE
35
+ {% endif %}
36
+ --DATA--
37
+ return array('bar' => 'bar', 'foo' => array('bar' => 'bar'))
38
+ --EXPECT--
39
+ TRUE
40
+ TRUE
41
+ TRUE
42
+ TRUE
43
+ TRUE
44
+ TRUE
45
+ TRUE
46
+ TRUE
47
+ TRUE
48
+ TRUE
Twig/test/Twig/Tests/Fixtures/tests/in_with_objects.test ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ Twig supports the in operator when using objects
3
+ --TEMPLATE--
4
+ {% if object in object_list %}
5
+ TRUE
6
+ {% endif %}
7
+ --DATA--
8
+ $foo = new TwigTestFoo();
9
+ $foo1 = new TwigTestFoo();
10
+
11
+ $foo->position = $foo1;
12
+ $foo1->position = $foo;
13
+
14
+ return array(
15
+ 'object' => $foo,
16
+ 'object_list' => array($foo1, $foo),
17
+ );
18
+ --EXPECT--
19
+ TRUE
Twig/test/Twig/Tests/Fixtures/tests/iterable.test ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "iterable" test
3
+ --TEMPLATE--
4
+ {{ foo is iterable ? 'ok' : 'ko' }}
5
+ {{ traversable is iterable ? 'ok' : 'ko' }}
6
+ {{ obj is iterable ? 'ok' : 'ko' }}
7
+ {{ val is iterable ? 'ok' : 'ko' }}
8
+ --DATA--
9
+ return array(
10
+ 'foo' => array(),
11
+ 'traversable' => new ArrayIterator(array()),
12
+ 'obj' => new stdClass(),
13
+ 'val' => 'test',
14
+ );
15
+ --EXPECT--
16
+ ok
17
+ ok
18
+ ko
19
+ ko
Twig/test/Twig/Tests/Fixtures/tests/odd.test ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
1
+ --TEST--
2
+ "odd" test
3
+ --TEMPLATE--
4
+ {{ 1 is odd ? 'ok' : 'ko' }}
5
+ {{ 2 is odd ? 'ko' : 'ok' }}
6
+ --DATA--
7
+ return array()
8
+ --EXPECT--
9
+ ok
10
+ ok
Twig/test/Twig/Tests/IntegrationTest.php ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ // This function is defined to check that escaping strategies
13
+ // like html works even if a function with the same name is defined.
14
+ function html()
15
+ {
16
+ return 'foo';
17
+ }
18
+
19
+ class Twig_Tests_IntegrationTest extends Twig_Test_IntegrationTestCase
20
+ {
21
+ public function getExtensions()
22
+ {
23
+ $policy = new Twig_Sandbox_SecurityPolicy(array(), array(), array(), array(), array());
24
+
25
+ return array(
26
+ new Twig_Extension_Debug(),
27
+ new Twig_Extension_Sandbox($policy, false),
28
+ new Twig_Extension_StringLoader(),
29
+ new TwigTestExtension(),
30
+ );
31
+ }
32
+
33
+ public function getFixturesDir()
34
+ {
35
+ return dirname(__FILE__).'/Fixtures/';
36
+ }
37
+ }
38
+
39
+ function test_foo($value = 'foo')
40
+ {
41
+ return $value;
42
+ }
43
+
44
+ class TwigTestFoo implements Iterator
45
+ {
46
+ const BAR_NAME = 'bar';
47
+
48
+ public $position = 0;
49
+ public $array = array(1, 2);
50
+
51
+ public function bar($param1 = null, $param2 = null)
52
+ {
53
+ return 'bar'.($param1 ? '_'.$param1 : '').($param2 ? '-'.$param2 : '');
54
+ }
55
+
56
+ public function getFoo()
57
+ {
58
+ return 'foo';
59
+ }
60
+
61
+ public function getSelf()
62
+ {
63
+ return $this;
64
+ }
65
+
66
+ public function is()
67
+ {
68
+ return 'is';
69
+ }
70
+
71
+ public function in()
72
+ {
73
+ return 'in';
74
+ }
75
+
76
+ public function not()
77
+ {
78
+ return 'not';
79
+ }
80
+
81
+ public function strToLower($value)
82
+ {
83
+ return strtolower($value);
84
+ }
85
+
86
+ public function rewind()
87
+ {
88
+ $this->position = 0;
89
+ }
90
+
91
+ public function current()
92
+ {
93
+ return $this->array[$this->position];
94
+ }
95
+
96
+ public function key()
97
+ {
98
+ return 'a';
99
+ }
100
+
101
+ public function next()
102
+ {
103
+ ++$this->position;
104
+ }
105
+
106
+ public function valid()
107
+ {
108
+ return isset($this->array[$this->position]);
109
+ }
110
+ }
111
+
112
+ class TwigTestTokenParser_☃ extends Twig_TokenParser
113
+ {
114
+ public function parse(Twig_Token $token)
115
+ {
116
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
117
+
118
+ return new Twig_Node_Print(new Twig_Node_Expression_Constant('☃', -1), -1);
119
+ }
120
+
121
+ public function getTag()
122
+ {
123
+ return '☃';
124
+ }
125
+ }
126
+
127
+ class TwigTestExtension extends Twig_Extension
128
+ {
129
+ public function getTokenParsers()
130
+ {
131
+ return array(
132
+ new TwigTestTokenParser_☃(),
133
+ );
134
+ }
135
+
136
+ public function getFilters()
137
+ {
138
+ return array(
139
+ '☃' => new Twig_Filter_Method($this, '☃Filter'),
140
+ 'escape_and_nl2br' => new Twig_Filter_Method($this, 'escape_and_nl2br', array('needs_environment' => true, 'is_safe' => array('html'))),
141
+ 'nl2br' => new Twig_Filter_Method($this, 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
142
+ 'escape_something' => new Twig_Filter_Method($this, 'escape_something', array('is_safe' => array('something'))),
143
+ 'preserves_safety' => new Twig_Filter_Method($this, 'preserves_safety', array('preserves_safety' => array('html'))),
144
+ '*_path' => new Twig_Filter_Method($this, 'dynamic_path'),
145
+ '*_foo_*_bar' => new Twig_Filter_Method($this, 'dynamic_foo'),
146
+ );
147
+ }
148
+
149
+ public function getFunctions()
150
+ {
151
+ return array(
152
+ '☃' => new Twig_Function_Method($this, '☃Function'),
153
+ 'safe_br' => new Twig_Function_Method($this, 'br', array('is_safe' => array('html'))),
154
+ 'unsafe_br' => new Twig_Function_Method($this, 'br'),
155
+ '*_path' => new Twig_Function_Method($this, 'dynamic_path'),
156
+ '*_foo_*_bar' => new Twig_Function_Method($this, 'dynamic_foo'),
157
+ );
158
+ }
159
+
160
+ public function ☃Filter($value)
161
+ {
162
+ return "☃{$value}☃";
163
+ }
164
+
165
+ public function ☃Function($value)
166
+ {
167
+ return "☃{$value}☃";
168
+ }
169
+
170
+ /**
171
+ * nl2br which also escapes, for testing escaper filters
172
+ */
173
+ public function escape_and_nl2br($env, $value, $sep = '<br />')
174
+ {
175
+ return $this->nl2br(twig_escape_filter($env, $value, 'html'), $sep);
176
+ }
177
+
178
+ /**
179
+ * nl2br only, for testing filters with pre_escape
180
+ */
181
+ public function nl2br($value, $sep = '<br />')
182
+ {
183
+ // not secure if $value contains html tags (not only entities)
184
+ // don't use
185
+ return str_replace("\n", "$sep\n", $value);
186
+ }
187
+
188
+ public function dynamic_path($element, $item)
189
+ {
190
+ return $element.'/'.$item;
191
+ }
192
+
193
+ public function dynamic_foo($foo, $bar, $item)
194
+ {
195
+ return $foo.'/'.$bar.'/'.$item;
196
+ }
197
+
198
+ public function escape_something($value)
199
+ {
200
+ return strtoupper($value);
201
+ }
202
+
203
+ public function preserves_safety($value)
204
+ {
205
+ return strtoupper($value);
206
+ }
207
+
208
+ public function br()
209
+ {
210
+ return '<br />';
211
+ }
212
+
213
+ public function getName()
214
+ {
215
+ return 'test';
216
+ }
217
+ }
Twig/test/Twig/Tests/LexerTest.php ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Tests_LexerTest extends PHPUnit_Framework_TestCase
12
+ {
13
+ public function testNameLabelForTag()
14
+ {
15
+ $template = '{% ☃ %}';
16
+
17
+ $lexer = new Twig_Lexer(new Twig_Environment());
18
+ $stream = $lexer->tokenize($template);
19
+
20
+ $stream->expect(Twig_Token::BLOCK_START_TYPE);
21
+ $this->assertSame('☃', $stream->expect(Twig_Token::NAME_TYPE)->getValue());
22
+ }
23
+
24
+ public function testNameLabelForFunction()
25
+ {
26
+ $template = '{{ ☃() }}';
27
+
28
+ $lexer = new Twig_Lexer(new Twig_Environment());
29
+ $stream = $lexer->tokenize($template);
30
+
31
+ $stream->expect(Twig_Token::VAR_START_TYPE);
32
+ $this->assertSame('☃', $stream->expect(Twig_Token::NAME_TYPE)->getValue());
33
+ }
34
+
35
+ public function testBracketsNesting()
36
+ {
37
+ $template = '{{ {"a":{"b":"c"}} }}';
38
+
39
+ $this->assertEquals(2, $this->countToken($template, Twig_Token::PUNCTUATION_TYPE, '{'));
40
+ $this->assertEquals(2, $this->countToken($template, Twig_Token::PUNCTUATION_TYPE, '}'));
41
+ }
42
+
43
+ protected function countToken($template, $type, $value = null)
44
+ {
45
+ $lexer = new Twig_Lexer(new Twig_Environment());
46
+ $stream = $lexer->tokenize($template);
47
+
48
+ $count = 0;
49
+ $tokens = array();
50
+ while (!$stream->isEOF()) {
51
+ $token = $stream->next();
52
+ if ($type === $token->getType()) {
53
+ if (null === $value || $value === $token->getValue()) {
54
+ ++$count;
55
+ }
56
+ }
57
+ }
58
+
59
+ return $count;
60
+ }
61
+
62
+ public function testLineDirective()
63
+ {
64
+ $template = "foo\n"
65
+ . "bar\n"
66
+ . "{% line 10 %}\n"
67
+ . "{{\n"
68
+ . "baz\n"
69
+ . "}}\n";
70
+
71
+ $lexer = new Twig_Lexer(new Twig_Environment());
72
+ $stream = $lexer->tokenize($template);
73
+
74
+ // foo\nbar\n
75
+ $this->assertSame(1, $stream->expect(Twig_Token::TEXT_TYPE)->getLine());
76
+ // \n (after {% line %})
77
+ $this->assertSame(10, $stream->expect(Twig_Token::TEXT_TYPE)->getLine());
78
+ // {{
79
+ $this->assertSame(11, $stream->expect(Twig_Token::VAR_START_TYPE)->getLine());
80
+ // baz
81
+ $this->assertSame(12, $stream->expect(Twig_Token::NAME_TYPE)->getLine());
82
+ }
83
+
84
+ public function testLineDirectiveInline()
85
+ {
86
+ $template = "foo\n"
87
+ . "bar{% line 10 %}{{\n"
88
+ . "baz\n"
89
+ . "}}\n";
90
+
91
+ $lexer = new Twig_Lexer(new Twig_Environment());
92
+ $stream = $lexer->tokenize($template);
93
+
94
+ // foo\nbar
95
+ $this->assertSame(1, $stream->expect(Twig_Token::TEXT_TYPE)->getLine());
96
+ // {{
97
+ $this->assertSame(10, $stream->expect(Twig_Token::VAR_START_TYPE)->getLine());
98
+ // baz
99
+ $this->assertSame(11, $stream->expect(Twig_Token::NAME_TYPE)->getLine());
100
+ }
101
+
102
+ public function testLongComments()
103
+ {
104
+ $template = '{# '.str_repeat('*', 100000).' #}';
105
+
106
+ $lexer = new Twig_Lexer(new Twig_Environment());
107
+ $lexer->tokenize($template);
108
+
109
+ // should not throw an exception
110
+ }
111
+
112
+ public function testLongRaw()
113
+ {
114
+ $template = '{% raw %}'.str_repeat('*', 100000).'{% endraw %}';
115
+
116
+ $lexer = new Twig_Lexer(new Twig_Environment());
117
+ $stream = $lexer->tokenize($template);
118
+
119
+ // should not throw an exception
120
+ }
121
+
122
+ public function testLongVar()
123
+ {
124
+ $template = '{{ '.str_repeat('x', 100000).' }}';
125
+
126
+ $lexer = new Twig_Lexer(new Twig_Environment());
127
+ $stream = $lexer->tokenize($template);
128
+
129
+ // should not throw an exception
130
+ }
131
+
132
+ public function testLongBlock()
133
+ {
134
+ $template = '{% '.str_repeat('x', 100000).' %}';
135
+
136
+ $lexer = new Twig_Lexer(new Twig_Environment());
137
+ $stream = $lexer->tokenize($template);
138
+
139
+ // should not throw an exception
140
+ }
141
+
142
+ public function testBigNumbers()
143
+ {
144
+ $template = '{{ 922337203685477580700 }}';
145
+
146
+ $lexer = new Twig_Lexer(new Twig_Environment());
147
+ $stream = $lexer->tokenize($template);
148
+ $node = $stream->next();
149
+ $node = $stream->next();
150
+ $this->assertEquals(922337203685477580700, $node->getValue());
151
+ }
152
+
153
+ public function testString()
154
+ {
155
+ $template = 'foo {{ "bar #{ baz + 1 }" }}';
156
+
157
+ $lexer = new Twig_Lexer(new Twig_Environment());
158
+ $stream = $lexer->tokenize($template);
159
+ $stream->expect(Twig_Token::TEXT_TYPE, 'foo ');
160
+ $stream->expect(Twig_Token::VAR_START_TYPE);
161
+ $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
162
+ $stream->expect(Twig_Token::INTERPOLATION_START_TYPE);
163
+ $stream->expect(Twig_Token::NAME_TYPE, 'baz');
164
+ $stream->expect(Twig_Token::OPERATOR_TYPE, '+');
165
+ $stream->expect(Twig_Token::NUMBER_TYPE, '1');
166
+ $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
167
+ $stream->expect(Twig_Token::VAR_END_TYPE);
168
+ }
169
+
170
+ public function testStringWithEscapedInterpolation()
171
+ {
172
+ $template = '{{ "bar \#{baz+1}" }}';
173
+
174
+ $lexer = new Twig_Lexer(new Twig_Environment());
175
+ $stream = $lexer->tokenize($template);
176
+ $stream->expect(Twig_Token::VAR_START_TYPE);
177
+ $stream->expect(Twig_Token::STRING_TYPE, 'bar #{baz+1}');
178
+ $stream->expect(Twig_Token::VAR_END_TYPE);
179
+ }
180
+
181
+ public function testStringWithHash()
182
+ {
183
+ $template = '{{ "bar # baz" }}';
184
+
185
+ $lexer = new Twig_Lexer(new Twig_Environment());
186
+ $stream = $lexer->tokenize($template);
187
+ $stream->expect(Twig_Token::VAR_START_TYPE);
188
+ $stream->expect(Twig_Token::STRING_TYPE, 'bar # baz');
189
+ $stream->expect(Twig_Token::VAR_END_TYPE);
190
+ }
191
+
192
+ /**
193
+ * @expectedException Twig_Error_Syntax
194
+ * @expectedExceptionMessage Unclosed """
195
+ */
196
+ public function testStringWithUnterminatedInterpolation()
197
+ {
198
+ $template = '{{ "bar #{x" }}';
199
+
200
+ $lexer = new Twig_Lexer(new Twig_Environment());
201
+ $stream = $lexer->tokenize($template);
202
+ }
203
+
204
+ public function testStringWithNestedInterpolations()
205
+ {
206
+ $template = '{{ "bar #{ "foo#{bar}" }" }}';
207
+
208
+ $lexer = new Twig_Lexer(new Twig_Environment());
209
+ $stream = $lexer->tokenize($template);
210
+ $stream->expect(Twig_Token::VAR_START_TYPE);
211
+ $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
212
+ $stream->expect(Twig_Token::INTERPOLATION_START_TYPE);
213
+ $stream->expect(Twig_Token::STRING_TYPE, 'foo');
214
+ $stream->expect(Twig_Token::INTERPOLATION_START_TYPE);
215
+ $stream->expect(Twig_Token::NAME_TYPE, 'bar');
216
+ $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
217
+ $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
218
+ $stream->expect(Twig_Token::VAR_END_TYPE);
219
+ }
220
+
221
+ public function testStringWithNestedInterpolationsInBlock()
222
+ {
223
+ $template = '{% foo "bar #{ "foo#{bar}" }" %}';
224
+
225
+ $lexer = new Twig_Lexer(new Twig_Environment());
226
+ $stream = $lexer->tokenize($template);
227
+ $stream->expect(Twig_Token::BLOCK_START_TYPE);
228
+ $stream->expect(Twig_Token::NAME_TYPE, 'foo');
229
+ $stream->expect(Twig_Token::STRING_TYPE, 'bar ');
230
+ $stream->expect(Twig_Token::INTERPOLATION_START_TYPE);
231
+ $stream->expect(Twig_Token::STRING_TYPE, 'foo');
232
+ $stream->expect(Twig_Token::INTERPOLATION_START_TYPE);
233
+ $stream->expect(Twig_Token::NAME_TYPE, 'bar');
234
+ $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
235
+ $stream->expect(Twig_Token::INTERPOLATION_END_TYPE);
236
+ $stream->expect(Twig_Token::BLOCK_END_TYPE);
237
+ }
238
+
239
+ public function testOperatorEndingWithALetterAtTheEndOfALine()
240
+ {
241
+ $template = "{{ 1 and\n0}}";
242
+
243
+ $lexer = new Twig_Lexer(new Twig_Environment());
244
+ $stream = $lexer->tokenize($template);
245
+ $stream->expect(Twig_Token::VAR_START_TYPE);
246
+ $stream->expect(Twig_Token::NUMBER_TYPE, 1);
247
+ $stream->expect(Twig_Token::OPERATOR_TYPE, 'and');
248
+ }
249
+ }
Twig/test/Twig/Tests/Loader/ArrayTest.php ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Loader_ArrayTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ public function testGetSource()
15
+ {
16
+ $loader = new Twig_Loader_Array(array('foo' => 'bar'));
17
+
18
+ $this->assertEquals('bar', $loader->getSource('foo'));
19
+ }
20
+
21
+ /**
22
+ * @expectedException Twig_Error_Loader
23
+ */
24
+ public function testGetSourceWhenTemplateDoesNotExist()
25
+ {
26
+ $loader = new Twig_Loader_Array(array());
27
+
28
+ $loader->getSource('foo');
29
+ }
30
+
31
+ public function testGetCacheKey()
32
+ {
33
+ $loader = new Twig_Loader_Array(array('foo' => 'bar'));
34
+
35
+ $this->assertEquals('bar', $loader->getCacheKey('foo'));
36
+ }
37
+
38
+ /**
39
+ * @expectedException Twig_Error_Loader
40
+ */
41
+ public function testGetCacheKeyWhenTemplateDoesNotExist()
42
+ {
43
+ $loader = new Twig_Loader_Array(array());
44
+
45
+ $loader->getCacheKey('foo');
46
+ }
47
+
48
+ public function testSetTemplate()
49
+ {
50
+ $loader = new Twig_Loader_Array(array());
51
+ $loader->setTemplate('foo', 'bar');
52
+
53
+ $this->assertEquals('bar', $loader->getSource('foo'));
54
+ }
55
+
56
+ public function testIsFresh()
57
+ {
58
+ $loader = new Twig_Loader_Array(array('foo' => 'bar'));
59
+ $this->assertTrue($loader->isFresh('foo', time()));
60
+ }
61
+
62
+ /**
63
+ * @expectedException Twig_Error_Loader
64
+ */
65
+ public function testIsFreshWhenTemplateDoesNotExist()
66
+ {
67
+ $loader = new Twig_Loader_Array(array());
68
+
69
+ $loader->isFresh('foo', time());
70
+ }
71
+
72
+ public function testTemplateReference()
73
+ {
74
+ $name = new Twig_Test_Loader_TemplateReference('foo');
75
+ $loader = new Twig_Loader_Array(array('foo' => 'bar'));
76
+
77
+ $loader->getCacheKey($name);
78
+ $loader->getSource($name);
79
+ $loader->isFresh($name, time());
80
+ $loader->setTemplate($name, 'foobar');
81
+ }
82
+ }
83
+
84
+ class Twig_Test_Loader_TemplateReference
85
+ {
86
+ private $name;
87
+
88
+ public function __construct($name)
89
+ {
90
+ $this->name = $name;
91
+ }
92
+
93
+ public function __toString()
94
+ {
95
+ return $this->name;
96
+ }
97
+ }
Twig/test/Twig/Tests/Loader/ChainTest.php ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Loader_ChainTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ public function testGetSource()
15
+ {
16
+ $loader = new Twig_Loader_Chain(array(
17
+ new Twig_Loader_Array(array('foo' => 'bar')),
18
+ new Twig_Loader_Array(array('foo' => 'foobar', 'bar' => 'foo')),
19
+ ));
20
+
21
+ $this->assertEquals('bar', $loader->getSource('foo'));
22
+ $this->assertEquals('foo', $loader->getSource('bar'));
23
+ }
24
+
25
+ /**
26
+ * @expectedException Twig_Error_Loader
27
+ */
28
+ public function testGetSourceWhenTemplateDoesNotExist()
29
+ {
30
+ $loader = new Twig_Loader_Chain(array());
31
+
32
+ $loader->getSource('foo');
33
+ }
34
+
35
+ public function testGetCacheKey()
36
+ {
37
+ $loader = new Twig_Loader_Chain(array(
38
+ new Twig_Loader_Array(array('foo' => 'bar')),
39
+ new Twig_Loader_Array(array('foo' => 'foobar', 'bar' => 'foo')),
40
+ ));
41
+
42
+ $this->assertEquals('bar', $loader->getCacheKey('foo'));
43
+ $this->assertEquals('foo', $loader->getCacheKey('bar'));
44
+ }
45
+
46
+ /**
47
+ * @expectedException Twig_Error_Loader
48
+ */
49
+ public function testGetCacheKeyWhenTemplateDoesNotExist()
50
+ {
51
+ $loader = new Twig_Loader_Chain(array());
52
+
53
+ $loader->getCacheKey('foo');
54
+ }
55
+
56
+ public function testAddLoader()
57
+ {
58
+ $loader = new Twig_Loader_Chain();
59
+ $loader->addLoader(new Twig_Loader_Array(array('foo' => 'bar')));
60
+
61
+ $this->assertEquals('bar', $loader->getSource('foo'));
62
+ }
63
+ }
Twig/test/Twig/Tests/Loader/FilesystemTest.php ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Loader_FilesystemTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ /**
15
+ * @dataProvider getSecurityTests
16
+ */
17
+ public function testSecurity($template)
18
+ {
19
+ $loader = new Twig_Loader_Filesystem(array(dirname(__FILE__).'/../Fixtures'));
20
+
21
+ try {
22
+ $loader->getCacheKey($template);
23
+ $this->fail();
24
+ } catch (Twig_Error_Loader $e) {
25
+ $this->assertNotContains('Unable to find template', $e->getMessage());
26
+ }
27
+ }
28
+
29
+ public function getSecurityTests()
30
+ {
31
+ return array(
32
+ array("AutoloaderTest\0.php"),
33
+ array('..\\AutoloaderTest.php'),
34
+ array('..\\\\\\AutoloaderTest.php'),
35
+ array('../AutoloaderTest.php'),
36
+ array('..////AutoloaderTest.php'),
37
+ array('./../AutoloaderTest.php'),
38
+ array('.\\..\\AutoloaderTest.php'),
39
+ array('././././././../AutoloaderTest.php'),
40
+ array('.\\./.\\./.\\./../AutoloaderTest.php'),
41
+ array('foo/../../AutoloaderTest.php'),
42
+ array('foo\\..\\..\\AutoloaderTest.php'),
43
+ array('foo/../bar/../../AutoloaderTest.php'),
44
+ array('foo/bar/../../../AutoloaderTest.php'),
45
+ array('filters/../../AutoloaderTest.php'),
46
+ array('filters//..//..//AutoloaderTest.php'),
47
+ array('filters\\..\\..\\AutoloaderTest.php'),
48
+ array('filters\\\\..\\\\..\\\\AutoloaderTest.php'),
49
+ array('filters\\//../\\/\\..\\AutoloaderTest.php'),
50
+ );
51
+ }
52
+
53
+ public function testPaths()
54
+ {
55
+ $basePath = dirname(__FILE__).'/Fixtures';
56
+
57
+ $loader = new Twig_Loader_Filesystem(array($basePath.'/normal', $basePath.'/normal_bis'));
58
+ $loader->setPaths(array($basePath.'/named', $basePath.'/named_bis'), 'named');
59
+ $loader->addPath($basePath.'/named_ter', 'named');
60
+ $loader->addPath($basePath.'/normal_ter');
61
+ $loader->prependPath($basePath.'/normal_final');
62
+ $loader->prependPath($basePath.'/named_final', 'named');
63
+
64
+ $this->assertEquals(array(
65
+ $basePath.'/normal_final',
66
+ $basePath.'/normal',
67
+ $basePath.'/normal_bis',
68
+ $basePath.'/normal_ter',
69
+ ), $loader->getPaths());
70
+ $this->assertEquals(array(
71
+ $basePath.'/named_final',
72
+ $basePath.'/named',
73
+ $basePath.'/named_bis',
74
+ $basePath.'/named_ter',
75
+ ), $loader->getPaths('named'));
76
+
77
+ $this->assertEquals("path (final)\n", $loader->getSource('index.html'));
78
+ $this->assertEquals("path (final)\n", $loader->getSource('@__main__/index.html'));
79
+ $this->assertEquals("named path (final)\n", $loader->getSource('@named/index.html'));
80
+ }
81
+
82
+ public function testGetNamespaces()
83
+ {
84
+ $loader = new Twig_Loader_Filesystem(sys_get_temp_dir());
85
+ $this->assertEquals(array('__main__'), $loader->getNamespaces());
86
+
87
+ $loader->addPath(sys_get_temp_dir(), 'named');
88
+ $this->assertEquals(array('__main__', 'named'), $loader->getNamespaces());
89
+ }
90
+ }
Twig/test/Twig/Tests/Loader/Fixtures/named/index.html ADDED
@@ -0,0 +1 @@
 
1
+ named path
Twig/test/Twig/Tests/Loader/Fixtures/named_bis/index.html ADDED
@@ -0,0 +1 @@
 
1
+ named path (bis)
Twig/test/Twig/Tests/Loader/Fixtures/named_final/index.html ADDED
@@ -0,0 +1 @@
 
1
+ named path (final)
Twig/test/Twig/Tests/Loader/Fixtures/named_ter/index.html ADDED
@@ -0,0 +1 @@
 
1
+ named path (ter)
Twig/test/Twig/Tests/Loader/Fixtures/normal/index.html ADDED
@@ -0,0 +1 @@
 
1
+ path
Twig/test/Twig/Tests/Loader/Fixtures/normal_bis/index.html ADDED
@@ -0,0 +1 @@
 
1
+ path (bis)
Twig/test/Twig/Tests/Loader/Fixtures/normal_final/index.html ADDED
@@ -0,0 +1 @@
 
1
+ path (final)
Twig/test/Twig/Tests/Loader/Fixtures/normal_ter/index.html ADDED
@@ -0,0 +1 @@
 
1
+ path (ter)
Twig/test/Twig/Tests/NativeExtensionTest.php ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_NativeExtensionTest extends PHPUnit_Framework_TestCase
13
+ {
14
+ public function testGetProperties()
15
+ {
16
+ $twig = new Twig_Environment(new Twig_Loader_String(), array(
17
+ 'debug' => true,
18
+ 'cache' => false,
19
+ 'autoescape' => false
20
+ ));
21
+
22
+ $d1 = new DateTime();
23
+ $d2 = new DateTime();
24
+ $output = $twig->render('{{ d1.date }}{{ d2.date }}', compact('d1', 'd2'));
25
+
26
+ // If it fails, PHP will crash.
27
+ $this->assertEquals($output, $d1->date . $d2->date);
28
+ }
29
+ }
Twig/test/Twig/Tests/Node/AutoEscapeTest.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_AutoEscapeTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_AutoEscape::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $body = new Twig_Node(array(new Twig_Node_Text('foo', 1)));
20
+ $node = new Twig_Node_AutoEscape(true, $body, 1);
21
+
22
+ $this->assertEquals($body, $node->getNode('body'));
23
+ $this->assertEquals(true, $node->getAttribute('value'));
24
+ }
25
+
26
+ /**
27
+ * @covers Twig_Node_AutoEscape::compile
28
+ * @dataProvider getTests
29
+ */
30
+ public function testCompile($node, $source, $environment = null)
31
+ {
32
+ parent::testCompile($node, $source, $environment);
33
+ }
34
+
35
+ public function getTests()
36
+ {
37
+ $body = new Twig_Node(array(new Twig_Node_Text('foo', 1)));
38
+ $node = new Twig_Node_AutoEscape(true, $body, 1);
39
+
40
+ return array(
41
+ array($node, "// line 1\necho \"foo\";"),
42
+ );
43
+ }
44
+ }
Twig/test/Twig/Tests/Node/BlockReferenceTest.php ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_BlockReferenceTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_BlockReference::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $node = new Twig_Node_BlockReference('foo', 1);
20
+
21
+ $this->assertEquals('foo', $node->getAttribute('name'));
22
+ }
23
+
24
+ /**
25
+ * @covers Twig_Node_BlockReference::compile
26
+ * @dataProvider getTests
27
+ */
28
+ public function testCompile($node, $source, $environment = null)
29
+ {
30
+ parent::testCompile($node, $source, $environment);
31
+ }
32
+
33
+ public function getTests()
34
+ {
35
+ return array(
36
+ array(new Twig_Node_BlockReference('foo', 1), <<<EOF
37
+ // line 1
38
+ \$this->displayBlock('foo', \$context, \$blocks);
39
+ EOF
40
+ ),
41
+ );
42
+ }
43
+ }
Twig/test/Twig/Tests/Node/BlockTest.php ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_BlockTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Block::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $body = new Twig_Node_Text('foo', 1);
20
+ $node = new Twig_Node_Block('foo', $body, 1);
21
+
22
+ $this->assertEquals($body, $node->getNode('body'));
23
+ $this->assertEquals('foo', $node->getAttribute('name'));
24
+ }
25
+
26
+ /**
27
+ * @covers Twig_Node_Block::compile
28
+ * @dataProvider getTests
29
+ */
30
+ public function testCompile($node, $source, $environment = null)
31
+ {
32
+ parent::testCompile($node, $source, $environment);
33
+ }
34
+
35
+ public function getTests()
36
+ {
37
+ $body = new Twig_Node_Text('foo', 1);
38
+ $node = new Twig_Node_Block('foo', $body, 1);
39
+
40
+ return array(
41
+ array($node, <<<EOF
42
+ // line 1
43
+ public function block_foo(\$context, array \$blocks = array())
44
+ {
45
+ echo "foo";
46
+ }
47
+ EOF
48
+ ),
49
+ );
50
+ }
51
+ }
Twig/test/Twig/Tests/Node/DoTest.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_DoTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Do::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Constant('foo', 1);
20
+ $node = new Twig_Node_Do($expr, 1);
21
+
22
+ $this->assertEquals($expr, $node->getNode('expr'));
23
+ }
24
+
25
+ /**
26
+ * @covers Twig_Node_Do::compile
27
+ * @dataProvider getTests
28
+ */
29
+ public function testCompile($node, $source, $environment = null)
30
+ {
31
+ parent::testCompile($node, $source, $environment);
32
+ }
33
+
34
+ public function getTests()
35
+ {
36
+ $tests = array();
37
+
38
+ $expr = new Twig_Node_Expression_Constant('foo', 1);
39
+ $node = new Twig_Node_Do($expr, 1);
40
+ $tests[] = array($node, "// line 1\n\"foo\";");
41
+
42
+ return $tests;
43
+ }
44
+ }
Twig/test/Twig/Tests/Node/Expression/ArrayTest.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_ArrayTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Array::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $elements = array(new Twig_Node_Expression_Constant('foo', 1), $foo = new Twig_Node_Expression_Constant('bar', 1));
20
+ $node = new Twig_Node_Expression_Array($elements, 1);
21
+
22
+ $this->assertEquals($foo, $node->getNode(1));
23
+ }
24
+
25
+ /**
26
+ * @covers Twig_Node_Expression_Array::compile
27
+ * @dataProvider getTests
28
+ */
29
+ public function testCompile($node, $source, $environment = null)
30
+ {
31
+ parent::testCompile($node, $source, $environment);
32
+ }
33
+
34
+ public function getTests()
35
+ {
36
+ $elements = array(
37
+ new Twig_Node_Expression_Constant('foo', 1),
38
+ new Twig_Node_Expression_Constant('bar', 1),
39
+
40
+ new Twig_Node_Expression_Constant('bar', 1),
41
+ new Twig_Node_Expression_Constant('foo', 1),
42
+ );
43
+ $node = new Twig_Node_Expression_Array($elements, 1);
44
+
45
+ return array(
46
+ array($node, 'array("foo" => "bar", "bar" => "foo")'),
47
+ );
48
+ }
49
+ }
Twig/test/Twig/Tests/Node/Expression/AssignNameTest.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_AssignNameTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_AssignName::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $node = new Twig_Node_Expression_AssignName('foo', 1);
20
+
21
+ $this->assertEquals('foo', $node->getAttribute('name'));
22
+ }
23
+
24
+ /**
25
+ * @covers Twig_Node_Expression_AssignName::compile
26
+ * @dataProvider getTests
27
+ */
28
+ public function testCompile($node, $source, $environment = null)
29
+ {
30
+ parent::testCompile($node, $source, $environment);
31
+ }
32
+
33
+ public function getTests()
34
+ {
35
+ $node = new Twig_Node_Expression_AssignName('foo', 1);
36
+
37
+ return array(
38
+ array($node, '$context["foo"]'),
39
+ );
40
+ }
41
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/AddTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_AddTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_Add::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_Add($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_Add::compile
29
+ * @covers Twig_Node_Expression_Binary_Add::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_Add($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, '(1 + 2)'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/AndTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_AndTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_And::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_And($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_And::compile
29
+ * @covers Twig_Node_Expression_Binary_And::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_And($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, '(1 && 2)'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/ConcatTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_ConcatTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_Concat::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_Concat($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_Concat::compile
29
+ * @covers Twig_Node_Expression_Binary_Concat::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_Concat($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, '(1 . 2)'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/DivTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_DivTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_Div::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_Div($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_Div::compile
29
+ * @covers Twig_Node_Expression_Binary_Div::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_Div($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, '(1 / 2)'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/FloorDivTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_FloorDivTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_FloorDiv::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_FloorDiv($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_FloorDiv::compile
29
+ * @covers Twig_Node_Expression_Binary_FloorDiv::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_FloorDiv($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, 'intval(floor((1 / 2)))'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/ModTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_ModTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_Mod::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_Mod($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_Mod::compile
29
+ * @covers Twig_Node_Expression_Binary_Mod::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_Mod($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, '(1 % 2)'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/MulTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_MulTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_Mul::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_Mul($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_Mul::compile
29
+ * @covers Twig_Node_Expression_Binary_Mul::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_Mul($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, '(1 * 2)'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/OrTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_OrTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_Or::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_Or($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_Or::compile
29
+ * @covers Twig_Node_Expression_Binary_Or::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_Or($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, '(1 || 2)'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/Binary/SubTest.php ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Binary_SubTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Binary_Sub::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $left = new Twig_Node_Expression_Constant(1, 1);
20
+ $right = new Twig_Node_Expression_Constant(2, 1);
21
+ $node = new Twig_Node_Expression_Binary_Sub($left, $right, 1);
22
+
23
+ $this->assertEquals($left, $node->getNode('left'));
24
+ $this->assertEquals($right, $node->getNode('right'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Binary_Sub::compile
29
+ * @covers Twig_Node_Expression_Binary_Sub::operator
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $left = new Twig_Node_Expression_Constant(1, 1);
40
+ $right = new Twig_Node_Expression_Constant(2, 1);
41
+ $node = new Twig_Node_Expression_Binary_Sub($left, $right, 1);
42
+
43
+ return array(
44
+ array($node, '(1 - 2)'),
45
+ );
46
+ }
47
+ }
Twig/test/Twig/Tests/Node/Expression/ConditionalTest.php ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_ConditionalTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Conditional::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr1 = new Twig_Node_Expression_Constant(1, 1);
20
+ $expr2 = new Twig_Node_Expression_Constant(2, 1);
21
+ $expr3 = new Twig_Node_Expression_Constant(3, 1);
22
+ $node = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, 1);
23
+
24
+ $this->assertEquals($expr1, $node->getNode('expr1'));
25
+ $this->assertEquals($expr2, $node->getNode('expr2'));
26
+ $this->assertEquals($expr3, $node->getNode('expr3'));
27
+ }
28
+
29
+ /**
30
+ * @covers Twig_Node_Expression_Conditional::compile
31
+ * @dataProvider getTests
32
+ */
33
+ public function testCompile($node, $source, $environment = null)
34
+ {
35
+ parent::testCompile($node, $source, $environment);
36
+ }
37
+
38
+ public function getTests()
39
+ {
40
+ $tests = array();
41
+
42
+ $expr1 = new Twig_Node_Expression_Constant(1, 1);
43
+ $expr2 = new Twig_Node_Expression_Constant(2, 1);
44
+ $expr3 = new Twig_Node_Expression_Constant(3, 1);
45
+ $node = new Twig_Node_Expression_Conditional($expr1, $expr2, $expr3, 1);
46
+ $tests[] = array($node, '((1) ? (2) : (3))');
47
+
48
+ return $tests;
49
+ }
50
+ }
Twig/test/Twig/Tests/Node/Expression/ConstantTest.php ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_ConstantTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Constant::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $node = new Twig_Node_Expression_Constant('foo', 1);
20
+
21
+ $this->assertEquals('foo', $node->getAttribute('value'));
22
+ }
23
+
24
+ /**
25
+ * @covers Twig_Node_Expression_Constant::compile
26
+ * @dataProvider getTests
27
+ */
28
+ public function testCompile($node, $source, $environment = null)
29
+ {
30
+ parent::testCompile($node, $source, $environment);
31
+ }
32
+
33
+ public function getTests()
34
+ {
35
+ $tests = array();
36
+
37
+ $node = new Twig_Node_Expression_Constant('foo', 1);
38
+ $tests[] = array($node, '"foo"');
39
+
40
+ return $tests;
41
+ }
42
+ }
Twig/test/Twig/Tests/Node/Expression/FilterTest.php ADDED
@@ -0,0 +1,133 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_FilterTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Filter::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Constant('foo', 1);
20
+ $name = new Twig_Node_Expression_Constant('upper', 1);
21
+ $args = new Twig_Node();
22
+ $node = new Twig_Node_Expression_Filter($expr, $name, $args, 1);
23
+
24
+ $this->assertEquals($expr, $node->getNode('node'));
25
+ $this->assertEquals($name, $node->getNode('filter'));
26
+ $this->assertEquals($args, $node->getNode('arguments'));
27
+ }
28
+
29
+ /**
30
+ * @covers Twig_Node_Expression_Filter::compile
31
+ * @dataProvider getTests
32
+ */
33
+ public function testCompile($node, $source, $environment = null)
34
+ {
35
+ parent::testCompile($node, $source, $environment);
36
+ }
37
+
38
+ public function getTests()
39
+ {
40
+ $tests = array();
41
+
42
+ $expr = new Twig_Node_Expression_Constant('foo', 1);
43
+ $node = $this->createFilter($expr, 'upper');
44
+ $node = $this->createFilter($node, 'number_format', array(new Twig_Node_Expression_Constant(2, 1), new Twig_Node_Expression_Constant('.', 1), new Twig_Node_Expression_Constant(',', 1)));
45
+
46
+ if (function_exists('mb_get_info')) {
47
+ $tests[] = array($node, 'twig_number_format_filter($this->env, twig_upper_filter($this->env, "foo"), 2, ".", ",")');
48
+ } else {
49
+ $tests[] = array($node, 'twig_number_format_filter($this->env, strtoupper("foo"), 2, ".", ",")');
50
+ }
51
+
52
+ // named arguments
53
+ $date = new Twig_Node_Expression_Constant(0, 1);
54
+ $node = $this->createFilter($date, 'date', array(
55
+ 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
56
+ 'format' => new Twig_Node_Expression_Constant('d/m/Y H:i:s P', 1),
57
+ ));
58
+ $tests[] = array($node, 'twig_date_format_filter($this->env, 0, "d/m/Y H:i:s P", "America/Chicago")');
59
+
60
+ // skip an optional argument
61
+ $date = new Twig_Node_Expression_Constant(0, 1);
62
+ $node = $this->createFilter($date, 'date', array(
63
+ 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
64
+ ));
65
+ $tests[] = array($node, 'twig_date_format_filter($this->env, 0, null, "America/Chicago")');
66
+
67
+ // underscores vs camelCase for named arguments
68
+ $string = new Twig_Node_Expression_Constant('abc', 1);
69
+ $node = $this->createFilter($string, 'reverse', array(
70
+ 'preserve_keys' => new Twig_Node_Expression_Constant(true, 1),
71
+ ));
72
+ $tests[] = array($node, 'twig_reverse_filter($this->env, "abc", true)');
73
+ $node = $this->createFilter($string, 'reverse', array(
74
+ 'preserveKeys' => new Twig_Node_Expression_Constant(true, 1),
75
+ ));
76
+ $tests[] = array($node, 'twig_reverse_filter($this->env, "abc", true)');
77
+
78
+ // filter as an anonymous function
79
+ if (version_compare(phpversion(), '5.3.0', '>=')) {
80
+ $node = $this->createFilter(new Twig_Node_Expression_Constant('foo', 1), 'anonymous');
81
+ $tests[] = array($node, 'call_user_func_array($this->env->getFilter(\'anonymous\')->getCallable(), array("foo"))');
82
+ }
83
+
84
+ return $tests;
85
+ }
86
+
87
+ /**
88
+ * @expectedException Twig_Error_Syntax
89
+ * @expectedExceptionMessage Unknown argument "foobar" for filter "date".
90
+ */
91
+ public function testCompileWithWrongNamedArgumentName()
92
+ {
93
+ $date = new Twig_Node_Expression_Constant(0, 1);
94
+ $node = $this->createFilter($date, 'date', array(
95
+ 'foobar' => new Twig_Node_Expression_Constant('America/Chicago', 1),
96
+ ));
97
+
98
+ $compiler = $this->getCompiler();
99
+ $compiler->compile($node);
100
+ }
101
+
102
+ /**
103
+ * @expectedException Twig_Error_Syntax
104
+ * @expectedExceptionMessage Value for argument "from" is required for filter "replace".
105
+ */
106
+ public function testCompileWithMissingNamedArgument()
107
+ {
108
+ $value = new Twig_Node_Expression_Constant(0, 1);
109
+ $node = $this->createFilter($value, 'replace', array(
110
+ 'to' => new Twig_Node_Expression_Constant('foo', 1),
111
+ ));
112
+
113
+ $compiler = $this->getCompiler();
114
+ $compiler->compile($node);
115
+ }
116
+
117
+ protected function createFilter($node, $name, array $arguments = array())
118
+ {
119
+ $name = new Twig_Node_Expression_Constant($name, 1);
120
+ $arguments = new Twig_Node($arguments);
121
+
122
+ return new Twig_Node_Expression_Filter($node, $name, $arguments, 1);
123
+ }
124
+
125
+ protected function getEnvironment()
126
+ {
127
+ if (version_compare(phpversion(), '5.3.0', '>=')) {
128
+ return include 'PHP53/FilterInclude.php';
129
+ }
130
+
131
+ return parent::getEnvironment();
132
+ }
133
+ }
Twig/test/Twig/Tests/Node/Expression/FunctionTest.php ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_FunctionTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Function::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $name = 'function';
20
+ $args = new Twig_Node();
21
+ $node = new Twig_Node_Expression_Function($name, $args, 1);
22
+
23
+ $this->assertEquals($name, $node->getAttribute('name'));
24
+ $this->assertEquals($args, $node->getNode('arguments'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Expression_Function::compile
29
+ * @dataProvider getTests
30
+ */
31
+ public function testCompile($node, $source, $environment = null)
32
+ {
33
+ parent::testCompile($node, $source, $environment);
34
+ }
35
+
36
+ public function getTests()
37
+ {
38
+ $environment = new Twig_Environment();
39
+ $environment->addFunction('foo', new Twig_Function_Function('foo', array()));
40
+ $environment->addFunction('bar', new Twig_Function_Function('bar', array('needs_environment' => true)));
41
+ $environment->addFunction('foofoo', new Twig_Function_Function('foofoo', array('needs_context' => true)));
42
+ $environment->addFunction('foobar', new Twig_Function_Function('foobar', array('needs_environment' => true, 'needs_context' => true)));
43
+
44
+ $tests = array();
45
+
46
+ $node = $this->createFunction('foo');
47
+ $tests[] = array($node, 'foo()', $environment);
48
+
49
+ $node = $this->createFunction('foo', array(new Twig_Node_Expression_Constant('bar', 1), new Twig_Node_Expression_Constant('foobar', 1)));
50
+ $tests[] = array($node, 'foo("bar", "foobar")', $environment);
51
+
52
+ $node = $this->createFunction('bar');
53
+ $tests[] = array($node, 'bar($this->env)', $environment);
54
+
55
+ $node = $this->createFunction('bar', array(new Twig_Node_Expression_Constant('bar', 1)));
56
+ $tests[] = array($node, 'bar($this->env, "bar")', $environment);
57
+
58
+ $node = $this->createFunction('foofoo');
59
+ $tests[] = array($node, 'foofoo($context)', $environment);
60
+
61
+ $node = $this->createFunction('foofoo', array(new Twig_Node_Expression_Constant('bar', 1)));
62
+ $tests[] = array($node, 'foofoo($context, "bar")', $environment);
63
+
64
+ $node = $this->createFunction('foobar');
65
+ $tests[] = array($node, 'foobar($this->env, $context)', $environment);
66
+
67
+ $node = $this->createFunction('foobar', array(new Twig_Node_Expression_Constant('bar', 1)));
68
+ $tests[] = array($node, 'foobar($this->env, $context, "bar")', $environment);
69
+
70
+ // named arguments
71
+ $node = $this->createFunction('date', array(
72
+ 'timezone' => new Twig_Node_Expression_Constant('America/Chicago', 1),
73
+ 'date' => new Twig_Node_Expression_Constant(0, 1),
74
+ ));
75
+ $tests[] = array($node, 'twig_date_converter($this->env, 0, "America/Chicago")');
76
+
77
+ // function as an anonymous function
78
+ if (version_compare(phpversion(), '5.3.0', '>=')) {
79
+ $node = $this->createFunction('anonymous', array(new Twig_Node_Expression_Constant('foo', 1)));
80
+ $tests[] = array($node, 'call_user_func_array($this->env->getFunction(\'anonymous\')->getCallable(), array("foo"))');
81
+ }
82
+
83
+ return $tests;
84
+ }
85
+
86
+ protected function createFunction($name, array $arguments = array())
87
+ {
88
+ return new Twig_Node_Expression_Function($name, new Twig_Node($arguments), 1);
89
+ }
90
+
91
+ protected function getEnvironment()
92
+ {
93
+ if (version_compare(phpversion(), '5.3.0', '>=')) {
94
+ return include 'PHP53/FunctionInclude.php';
95
+ }
96
+
97
+ return parent::getEnvironment();
98
+ }
99
+ }
Twig/test/Twig/Tests/Node/Expression/GetAttrTest.php ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_GetAttrTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_GetAttr::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Name('foo', 1);
20
+ $attr = new Twig_Node_Expression_Constant('bar', 1);
21
+ $args = new Twig_Node_Expression_Array(array(), 1);
22
+ $args->addElement(new Twig_Node_Expression_Name('foo', 1));
23
+ $args->addElement(new Twig_Node_Expression_Constant('bar', 1));
24
+ $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_TemplateInterface::ARRAY_CALL, 1);
25
+
26
+ $this->assertEquals($expr, $node->getNode('node'));
27
+ $this->assertEquals($attr, $node->getNode('attribute'));
28
+ $this->assertEquals($args, $node->getNode('arguments'));
29
+ $this->assertEquals(Twig_TemplateInterface::ARRAY_CALL, $node->getAttribute('type'));
30
+ }
31
+
32
+ /**
33
+ * @covers Twig_Node_Expression_GetAttr::compile
34
+ * @dataProvider getTests
35
+ */
36
+ public function testCompile($node, $source, $environment = null)
37
+ {
38
+ parent::testCompile($node, $source, $environment);
39
+ }
40
+
41
+ public function getTests()
42
+ {
43
+ $tests = array();
44
+
45
+ $expr = new Twig_Node_Expression_Name('foo', 1);
46
+ $attr = new Twig_Node_Expression_Constant('bar', 1);
47
+ $args = new Twig_Node_Expression_Array(array(), 1);
48
+ $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_TemplateInterface::ANY_CALL, 1);
49
+ $tests[] = array($node, sprintf('%s%s, "bar")', $this->getAttributeGetter(), $this->getVariableGetter('foo')));
50
+
51
+ $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_TemplateInterface::ARRAY_CALL, 1);
52
+ $tests[] = array($node, sprintf('%s%s, "bar", array(), "array")', $this->getAttributeGetter(), $this->getVariableGetter('foo')));
53
+
54
+ $args = new Twig_Node_Expression_Array(array(), 1);
55
+ $args->addElement(new Twig_Node_Expression_Name('foo', 1));
56
+ $args->addElement(new Twig_Node_Expression_Constant('bar', 1));
57
+ $node = new Twig_Node_Expression_GetAttr($expr, $attr, $args, Twig_TemplateInterface::METHOD_CALL, 1);
58
+ $tests[] = array($node, sprintf('%s%s, "bar", array(0 => %s, 1 => "bar"), "method")', $this->getAttributeGetter(), $this->getVariableGetter('foo'), $this->getVariableGetter('foo')));
59
+
60
+ return $tests;
61
+ }
62
+ }
Twig/test/Twig/Tests/Node/Expression/NameTest.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_NameTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Name::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $node = new Twig_Node_Expression_Name('foo', 1);
20
+
21
+ $this->assertEquals('foo', $node->getAttribute('name'));
22
+ }
23
+
24
+ /**
25
+ * @covers Twig_Node_Expression_Name::compile
26
+ * @dataProvider getTests
27
+ */
28
+ public function testCompile($node, $source, $environment = null)
29
+ {
30
+ parent::testCompile($node, $source, $environment);
31
+ }
32
+
33
+ public function getTests()
34
+ {
35
+ $node = new Twig_Node_Expression_Name('foo', 1);
36
+ $self = new Twig_Node_Expression_Name('_self', 1);
37
+ $context = new Twig_Node_Expression_Name('_context', 1);
38
+
39
+ $env = new Twig_Environment(null, array('strict_variables' => true));
40
+ $env1 = new Twig_Environment(null, array('strict_variables' => false));
41
+
42
+ return array(
43
+ version_compare(PHP_VERSION, '5.4.0') >= 0 ? array($node, '(isset($context["foo"]) ? $context["foo"] : $this->getContext($context, "foo"))', $env) : array($node, '$this->getContext($context, "foo")', $env),
44
+ array($node, $this->getVariableGetter('foo'), $env1),
45
+ array($self, '$this'),
46
+ array($context, '$context'),
47
+ );
48
+ }
49
+ }
Twig/test/Twig/Tests/Node/Expression/PHP53/FilterInclude.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $env = new Twig_Environment();
4
+ $env->addFilter(new Twig_SimpleFilter('anonymous', function () {}));
5
+
6
+ return $env;
Twig/test/Twig/Tests/Node/Expression/PHP53/FunctionInclude.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $env = new Twig_Environment();
4
+ $env->addFunction(new Twig_SimpleFunction('anonymous', function () {}));
5
+
6
+ return $env;
Twig/test/Twig/Tests/Node/Expression/PHP53/TestInclude.php ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
1
+ <?php
2
+
3
+ $env = new Twig_Environment();
4
+ $env->addTest(new Twig_SimpleTest('anonymous', function () {}));
5
+
6
+ return $env;
Twig/test/Twig/Tests/Node/Expression/ParentTest.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_ParentTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Parent::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $node = new Twig_Node_Expression_Parent('foo', 1);
20
+
21
+ $this->assertEquals('foo', $node->getAttribute('name'));
22
+ }
23
+
24
+ /**
25
+ * @covers Twig_Node_Expression_Parent::compile
26
+ * @dataProvider getTests
27
+ */
28
+ public function testCompile($node, $source, $environment = null)
29
+ {
30
+ parent::testCompile($node, $source, $environment);
31
+ }
32
+
33
+ public function getTests()
34
+ {
35
+ $tests = array();
36
+ $tests[] = array(new Twig_Node_Expression_Parent('foo', 1), '$this->renderParentBlock("foo", $context, $blocks)');
37
+
38
+ return $tests;
39
+ }
40
+ }
Twig/test/Twig/Tests/Node/Expression/TestTest.php ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_TestTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Test::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Constant('foo', 1);
20
+ $name = new Twig_Node_Expression_Constant('null', 1);
21
+ $args = new Twig_Node();
22
+ $node = new Twig_Node_Expression_Test($expr, $name, $args, 1);
23
+
24
+ $this->assertEquals($expr, $node->getNode('node'));
25
+ $this->assertEquals($args, $node->getNode('arguments'));
26
+ $this->assertEquals($name, $node->getAttribute('name'));
27
+ }
28
+
29
+ /**
30
+ * @covers Twig_Node_Expression_Test::compile
31
+ * @dataProvider getTests
32
+ */
33
+ public function testCompile($node, $source, $environment = null)
34
+ {
35
+ parent::testCompile($node, $source, $environment);
36
+ }
37
+
38
+ public function getTests()
39
+ {
40
+ $tests = array();
41
+
42
+ $expr = new Twig_Node_Expression_Constant('foo', 1);
43
+ $node = new Twig_Node_Expression_Test_Null($expr, 'null', new Twig_Node(array()), 1);
44
+ $tests[] = array($node, '(null === "foo")');
45
+
46
+ // test as an anonymous function
47
+ if (version_compare(phpversion(), '5.3.0', '>=')) {
48
+ $node = $this->createTest(new Twig_Node_Expression_Constant('foo', 1), 'anonymous', array(new Twig_Node_Expression_Constant('foo', 1)));
49
+ $tests[] = array($node, 'call_user_func_array($this->env->getTest(\'anonymous\')->getCallable(), array("foo", "foo"))');
50
+ }
51
+
52
+ return $tests;
53
+ }
54
+
55
+ protected function createTest($node, $name, array $arguments = array())
56
+ {
57
+ return new Twig_Node_Expression_Test($node, $name, new Twig_Node($arguments), 1);
58
+ }
59
+
60
+ protected function getEnvironment()
61
+ {
62
+ if (version_compare(phpversion(), '5.3.0', '>=')) {
63
+ return include 'PHP53/TestInclude.php';
64
+ }
65
+
66
+ return parent::getEnvironment();
67
+ }
68
+ }
Twig/test/Twig/Tests/Node/Expression/Unary/NegTest.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Unary_NegTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Unary_Neg::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Constant(1, 1);
20
+ $node = new Twig_Node_Expression_Unary_Neg($expr, 1);
21
+
22
+ $this->assertEquals($expr, $node->getNode('node'));
23
+ }
24
+
25
+ /**
26
+ * @covers Twig_Node_Expression_Unary_Neg::compile
27
+ * @covers Twig_Node_Expression_Unary_Neg::operator
28
+ * @dataProvider getTests
29
+ */
30
+ public function testCompile($node, $source, $environment = null)
31
+ {
32
+ parent::testCompile($node, $source, $environment);
33
+ }
34
+
35
+ public function getTests()
36
+ {
37
+ $node = new Twig_Node_Expression_Constant(1, 1);
38
+ $node = new Twig_Node_Expression_Unary_Neg($node, 1);
39
+
40
+ return array(
41
+ array($node, '(-1)'),
42
+ );
43
+ }
44
+ }
Twig/test/Twig/Tests/Node/Expression/Unary/NotTest.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Unary_NotTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Unary_Not::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Constant(1, 1);
20
+ $node = new Twig_Node_Expression_Unary_Not($expr, 1);
21
+
22
+ $this->assertEquals($expr, $node->getNode('node'));
23
+ }
24
+
25
+ /**
26
+ * @covers Twig_Node_Expression_Unary_Not::compile
27
+ * @covers Twig_Node_Expression_Unary_Not::operator
28
+ * @dataProvider getTests
29
+ */
30
+ public function testCompile($node, $source, $environment = null)
31
+ {
32
+ parent::testCompile($node, $source, $environment);
33
+ }
34
+
35
+ public function getTests()
36
+ {
37
+ $node = new Twig_Node_Expression_Constant(1, 1);
38
+ $node = new Twig_Node_Expression_Unary_Not($node, 1);
39
+
40
+ return array(
41
+ array($node, '(!1)'),
42
+ );
43
+ }
44
+ }
Twig/test/Twig/Tests/Node/Expression/Unary/PosTest.php ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_Expression_Unary_PosTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Expression_Unary_Pos::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Constant(1, 1);
20
+ $node = new Twig_Node_Expression_Unary_Pos($expr, 1);
21
+
22
+ $this->assertEquals($expr, $node->getNode('node'));
23
+ }
24
+
25
+ /**
26
+ * @covers Twig_Node_Expression_Unary_Pos::compile
27
+ * @covers Twig_Node_Expression_Unary_Pos::operator
28
+ * @dataProvider getTests
29
+ */
30
+ public function testCompile($node, $source, $environment = null)
31
+ {
32
+ parent::testCompile($node, $source, $environment);
33
+ }
34
+
35
+ public function getTests()
36
+ {
37
+ $node = new Twig_Node_Expression_Constant(1, 1);
38
+ $node = new Twig_Node_Expression_Unary_Pos($node, 1);
39
+
40
+ return array(
41
+ array($node, '(+1)'),
42
+ );
43
+ }
44
+ }
Twig/test/Twig/Tests/Node/ForTest.php ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_ForTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_For::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $keyTarget = new Twig_Node_Expression_AssignName('key', 1);
20
+ $valueTarget = new Twig_Node_Expression_AssignName('item', 1);
21
+ $seq = new Twig_Node_Expression_Name('items', 1);
22
+ $ifexpr = new Twig_Node_Expression_Constant(true, 1);
23
+ $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1);
24
+ $else = null;
25
+ $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1);
26
+ $node->setAttribute('with_loop', false);
27
+
28
+ $this->assertEquals($keyTarget, $node->getNode('key_target'));
29
+ $this->assertEquals($valueTarget, $node->getNode('value_target'));
30
+ $this->assertEquals($seq, $node->getNode('seq'));
31
+ $this->assertTrue($node->getAttribute('ifexpr'));
32
+ $this->assertEquals('Twig_Node_If', get_class($node->getNode('body')));
33
+ $this->assertEquals($body, $node->getNode('body')->getNode('tests')->getNode(1)->getNode(0));
34
+ $this->assertEquals(null, $node->getNode('else'));
35
+
36
+ $else = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1);
37
+ $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1);
38
+ $node->setAttribute('with_loop', false);
39
+ $this->assertEquals($else, $node->getNode('else'));
40
+ }
41
+
42
+ /**
43
+ * @covers Twig_Node_For::compile
44
+ * @dataProvider getTests
45
+ */
46
+ public function testCompile($node, $source, $environment = null)
47
+ {
48
+ parent::testCompile($node, $source, $environment);
49
+ }
50
+
51
+ public function getTests()
52
+ {
53
+ $tests = array();
54
+
55
+ $keyTarget = new Twig_Node_Expression_AssignName('key', 1);
56
+ $valueTarget = new Twig_Node_Expression_AssignName('item', 1);
57
+ $seq = new Twig_Node_Expression_Name('items', 1);
58
+ $ifexpr = null;
59
+ $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1);
60
+ $else = null;
61
+ $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1);
62
+ $node->setAttribute('with_loop', false);
63
+
64
+ $tests[] = array($node, <<<EOF
65
+ // line 1
66
+ \$context['_parent'] = (array) \$context;
67
+ \$context['_seq'] = twig_ensure_traversable({$this->getVariableGetter('items')});
68
+ foreach (\$context['_seq'] as \$context["key"] => \$context["item"]) {
69
+ echo {$this->getVariableGetter('foo')};
70
+ }
71
+ \$_parent = \$context['_parent'];
72
+ unset(\$context['_seq'], \$context['_iterated'], \$context['key'], \$context['item'], \$context['_parent'], \$context['loop']);
73
+ \$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));
74
+ EOF
75
+ );
76
+
77
+ $keyTarget = new Twig_Node_Expression_AssignName('k', 1);
78
+ $valueTarget = new Twig_Node_Expression_AssignName('v', 1);
79
+ $seq = new Twig_Node_Expression_Name('values', 1);
80
+ $ifexpr = null;
81
+ $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1);
82
+ $else = null;
83
+ $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1);
84
+ $node->setAttribute('with_loop', true);
85
+
86
+ $tests[] = array($node, <<<EOF
87
+ // line 1
88
+ \$context['_parent'] = (array) \$context;
89
+ \$context['_seq'] = twig_ensure_traversable({$this->getVariableGetter('values')});
90
+ \$context['loop'] = array(
91
+ 'parent' => \$context['_parent'],
92
+ 'index0' => 0,
93
+ 'index' => 1,
94
+ 'first' => true,
95
+ );
96
+ if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {
97
+ \$length = count(\$context['_seq']);
98
+ \$context['loop']['revindex0'] = \$length - 1;
99
+ \$context['loop']['revindex'] = \$length;
100
+ \$context['loop']['length'] = \$length;
101
+ \$context['loop']['last'] = 1 === \$length;
102
+ }
103
+ foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) {
104
+ echo {$this->getVariableGetter('foo')};
105
+ ++\$context['loop']['index0'];
106
+ ++\$context['loop']['index'];
107
+ \$context['loop']['first'] = false;
108
+ if (isset(\$context['loop']['length'])) {
109
+ --\$context['loop']['revindex0'];
110
+ --\$context['loop']['revindex'];
111
+ \$context['loop']['last'] = 0 === \$context['loop']['revindex0'];
112
+ }
113
+ }
114
+ \$_parent = \$context['_parent'];
115
+ unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']);
116
+ \$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));
117
+ EOF
118
+ );
119
+
120
+ $keyTarget = new Twig_Node_Expression_AssignName('k', 1);
121
+ $valueTarget = new Twig_Node_Expression_AssignName('v', 1);
122
+ $seq = new Twig_Node_Expression_Name('values', 1);
123
+ $ifexpr = new Twig_Node_Expression_Constant(true, 1);
124
+ $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1);
125
+ $else = null;
126
+ $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1);
127
+ $node->setAttribute('with_loop', true);
128
+
129
+ $tests[] = array($node, <<<EOF
130
+ // line 1
131
+ \$context['_parent'] = (array) \$context;
132
+ \$context['_seq'] = twig_ensure_traversable({$this->getVariableGetter('values')});
133
+ \$context['loop'] = array(
134
+ 'parent' => \$context['_parent'],
135
+ 'index0' => 0,
136
+ 'index' => 1,
137
+ 'first' => true,
138
+ );
139
+ foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) {
140
+ if (true) {
141
+ echo {$this->getVariableGetter('foo')};
142
+ ++\$context['loop']['index0'];
143
+ ++\$context['loop']['index'];
144
+ \$context['loop']['first'] = false;
145
+ }
146
+ }
147
+ \$_parent = \$context['_parent'];
148
+ unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']);
149
+ \$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));
150
+ EOF
151
+ );
152
+
153
+ $keyTarget = new Twig_Node_Expression_AssignName('k', 1);
154
+ $valueTarget = new Twig_Node_Expression_AssignName('v', 1);
155
+ $seq = new Twig_Node_Expression_Name('values', 1);
156
+ $ifexpr = null;
157
+ $body = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1)), array(), 1);
158
+ $else = new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1);
159
+ $node = new Twig_Node_For($keyTarget, $valueTarget, $seq, $ifexpr, $body, $else, 1);
160
+ $node->setAttribute('with_loop', true);
161
+
162
+ $tests[] = array($node, <<<EOF
163
+ // line 1
164
+ \$context['_parent'] = (array) \$context;
165
+ \$context['_seq'] = twig_ensure_traversable({$this->getVariableGetter('values')});
166
+ \$context['_iterated'] = false;
167
+ \$context['loop'] = array(
168
+ 'parent' => \$context['_parent'],
169
+ 'index0' => 0,
170
+ 'index' => 1,
171
+ 'first' => true,
172
+ );
173
+ if (is_array(\$context['_seq']) || (is_object(\$context['_seq']) && \$context['_seq'] instanceof Countable)) {
174
+ \$length = count(\$context['_seq']);
175
+ \$context['loop']['revindex0'] = \$length - 1;
176
+ \$context['loop']['revindex'] = \$length;
177
+ \$context['loop']['length'] = \$length;
178
+ \$context['loop']['last'] = 1 === \$length;
179
+ }
180
+ foreach (\$context['_seq'] as \$context["k"] => \$context["v"]) {
181
+ echo {$this->getVariableGetter('foo')};
182
+ \$context['_iterated'] = true;
183
+ ++\$context['loop']['index0'];
184
+ ++\$context['loop']['index'];
185
+ \$context['loop']['first'] = false;
186
+ if (isset(\$context['loop']['length'])) {
187
+ --\$context['loop']['revindex0'];
188
+ --\$context['loop']['revindex'];
189
+ \$context['loop']['last'] = 0 === \$context['loop']['revindex0'];
190
+ }
191
+ }
192
+ if (!\$context['_iterated']) {
193
+ echo {$this->getVariableGetter('foo')};
194
+ }
195
+ \$_parent = \$context['_parent'];
196
+ unset(\$context['_seq'], \$context['_iterated'], \$context['k'], \$context['v'], \$context['_parent'], \$context['loop']);
197
+ \$context = array_merge(\$_parent, array_intersect_key(\$context, \$_parent));
198
+ EOF
199
+ );
200
+
201
+ return $tests;
202
+ }
203
+ }
Twig/test/Twig/Tests/Node/IfTest.php ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_IfTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_If::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $t = new Twig_Node(array(
20
+ new Twig_Node_Expression_Constant(true, 1),
21
+ new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1),
22
+ ), array(), 1);
23
+ $else = null;
24
+ $node = new Twig_Node_If($t, $else, 1);
25
+
26
+ $this->assertEquals($t, $node->getNode('tests'));
27
+ $this->assertEquals(null, $node->getNode('else'));
28
+
29
+ $else = new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 1), 1);
30
+ $node = new Twig_Node_If($t, $else, 1);
31
+ $this->assertEquals($else, $node->getNode('else'));
32
+ }
33
+
34
+ /**
35
+ * @covers Twig_Node_If::compile
36
+ * @dataProvider getTests
37
+ */
38
+ public function testCompile($node, $source, $environment = null)
39
+ {
40
+ parent::testCompile($node, $source, $environment);
41
+ }
42
+
43
+ public function getTests()
44
+ {
45
+ $tests = array();
46
+
47
+ $t = new Twig_Node(array(
48
+ new Twig_Node_Expression_Constant(true, 1),
49
+ new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1),
50
+ ), array(), 1);
51
+ $else = null;
52
+ $node = new Twig_Node_If($t, $else, 1);
53
+
54
+ $tests[] = array($node, <<<EOF
55
+ // line 1
56
+ if (true) {
57
+ echo {$this->getVariableGetter('foo')};
58
+ }
59
+ EOF
60
+ );
61
+
62
+ $t = new Twig_Node(array(
63
+ new Twig_Node_Expression_Constant(true, 1),
64
+ new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1),
65
+ new Twig_Node_Expression_Constant(false, 1),
66
+ new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 1), 1),
67
+ ), array(), 1);
68
+ $else = null;
69
+ $node = new Twig_Node_If($t, $else, 1);
70
+
71
+ $tests[] = array($node, <<<EOF
72
+ // line 1
73
+ if (true) {
74
+ echo {$this->getVariableGetter('foo')};
75
+ } elseif (false) {
76
+ echo {$this->getVariableGetter('bar')};
77
+ }
78
+ EOF
79
+ );
80
+
81
+ $t = new Twig_Node(array(
82
+ new Twig_Node_Expression_Constant(true, 1),
83
+ new Twig_Node_Print(new Twig_Node_Expression_Name('foo', 1), 1),
84
+ ), array(), 1);
85
+ $else = new Twig_Node_Print(new Twig_Node_Expression_Name('bar', 1), 1);
86
+ $node = new Twig_Node_If($t, $else, 1);
87
+
88
+ $tests[] = array($node, <<<EOF
89
+ // line 1
90
+ if (true) {
91
+ echo {$this->getVariableGetter('foo')};
92
+ } else {
93
+ echo {$this->getVariableGetter('bar')};
94
+ }
95
+ EOF
96
+ );
97
+
98
+ return $tests;
99
+ }
100
+ }
Twig/test/Twig/Tests/Node/ImportTest.php ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_ImportTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Import::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $macro = new Twig_Node_Expression_Constant('foo.twig', 1);
20
+ $var = new Twig_Node_Expression_AssignName('macro', 1);
21
+ $node = new Twig_Node_Import($macro, $var, 1);
22
+
23
+ $this->assertEquals($macro, $node->getNode('expr'));
24
+ $this->assertEquals($var, $node->getNode('var'));
25
+ }
26
+
27
+ /**
28
+ * @covers Twig_Node_Import::compile
29
+ * @dataProvider getTests
30
+ */
31
+ public function testCompile($node, $source, $environment = null)
32
+ {
33
+ parent::testCompile($node, $source, $environment);
34
+ }
35
+
36
+ public function getTests()
37
+ {
38
+ $tests = array();
39
+
40
+ $macro = new Twig_Node_Expression_Constant('foo.twig', 1);
41
+ $var = new Twig_Node_Expression_AssignName('macro', 1);
42
+ $node = new Twig_Node_Import($macro, $var, 1);
43
+
44
+ $tests[] = array($node, <<<EOF
45
+ // line 1
46
+ \$context["macro"] = \$this->env->loadTemplate("foo.twig");
47
+ EOF
48
+ );
49
+
50
+ return $tests;
51
+ }
52
+ }
Twig/test/Twig/Tests/Node/IncludeTest.php ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_IncludeTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Include::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Constant('foo.twig', 1);
20
+ $node = new Twig_Node_Include($expr, null, false, false, 1);
21
+
22
+ $this->assertEquals(null, $node->getNode('variables'));
23
+ $this->assertEquals($expr, $node->getNode('expr'));
24
+ $this->assertFalse($node->getAttribute('only'));
25
+
26
+ $vars = new Twig_Node_Expression_Array(array(new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant(true, 1)), 1);
27
+ $node = new Twig_Node_Include($expr, $vars, true, false, 1);
28
+ $this->assertEquals($vars, $node->getNode('variables'));
29
+ $this->assertTrue($node->getAttribute('only'));
30
+ }
31
+
32
+ /**
33
+ * @covers Twig_Node_Include::compile
34
+ * @dataProvider getTests
35
+ */
36
+ public function testCompile($node, $source, $environment = null)
37
+ {
38
+ parent::testCompile($node, $source, $environment);
39
+ }
40
+
41
+ public function getTests()
42
+ {
43
+ $tests = array();
44
+
45
+ $expr = new Twig_Node_Expression_Constant('foo.twig', 1);
46
+ $node = new Twig_Node_Include($expr, null, false, false, 1);
47
+ $tests[] = array($node, <<<EOF
48
+ // line 1
49
+ \$this->env->loadTemplate("foo.twig")->display(\$context);
50
+ EOF
51
+ );
52
+
53
+ $expr = new Twig_Node_Expression_Conditional(
54
+ new Twig_Node_Expression_Constant(true, 1),
55
+ new Twig_Node_Expression_Constant('foo', 1),
56
+ new Twig_Node_Expression_Constant('foo', 1),
57
+ 0
58
+ );
59
+ $node = new Twig_Node_Include($expr, null, false, false, 1);
60
+ $tests[] = array($node, <<<EOF
61
+ // line 1
62
+ \$template = \$this->env->resolveTemplate(((true) ? ("foo") : ("foo")));
63
+ \$template->display(\$context);
64
+ EOF
65
+ );
66
+
67
+ $expr = new Twig_Node_Expression_Constant('foo.twig', 1);
68
+ $vars = new Twig_Node_Expression_Array(array(new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Constant(true, 1)), 1);
69
+ $node = new Twig_Node_Include($expr, $vars, false, false, 1);
70
+ $tests[] = array($node, <<<EOF
71
+ // line 1
72
+ \$this->env->loadTemplate("foo.twig")->display(array_merge(\$context, array("foo" => true)));
73
+ EOF
74
+ );
75
+
76
+ $node = new Twig_Node_Include($expr, $vars, true, false, 1);
77
+ $tests[] = array($node, <<<EOF
78
+ // line 1
79
+ \$this->env->loadTemplate("foo.twig")->display(array("foo" => true));
80
+ EOF
81
+ );
82
+
83
+ $node = new Twig_Node_Include($expr, $vars, true, true, 1);
84
+ $tests[] = array($node, <<<EOF
85
+ // line 1
86
+ try {
87
+ \$this->env->loadTemplate("foo.twig")->display(array("foo" => true));
88
+ } catch (Twig_Error_Loader \$e) {
89
+ // ignore missing template
90
+ }
91
+ EOF
92
+ );
93
+
94
+ return $tests;
95
+ }
96
+ }
Twig/test/Twig/Tests/Node/MacroTest.php ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_MacroTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Macro::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $body = new Twig_Node_Text('foo', 1);
20
+ $arguments = new Twig_Node(array(new Twig_Node_Expression_Name('foo', 1)), array(), 1);
21
+ $node = new Twig_Node_Macro('foo', $body, $arguments, 1);
22
+
23
+ $this->assertEquals($body, $node->getNode('body'));
24
+ $this->assertEquals($arguments, $node->getNode('arguments'));
25
+ $this->assertEquals('foo', $node->getAttribute('name'));
26
+ }
27
+
28
+ /**
29
+ * @covers Twig_Node_Macro::compile
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $body = new Twig_Node_Text('foo', 1);
40
+ $arguments = new Twig_Node(array(
41
+ 'foo' => new Twig_Node_Expression_Constant(null, 1),
42
+ 'bar' => new Twig_Node_Expression_Constant('Foo', 1),
43
+ ), array(), 1);
44
+ $node = new Twig_Node_Macro('foo', $body, $arguments, 1);
45
+
46
+ return array(
47
+ array($node, <<<EOF
48
+ // line 1
49
+ public function getfoo(\$_foo = null, \$_bar = "Foo")
50
+ {
51
+ \$context = \$this->env->mergeGlobals(array(
52
+ "foo" => \$_foo,
53
+ "bar" => \$_bar,
54
+ ));
55
+
56
+ \$blocks = array();
57
+
58
+ ob_start();
59
+ try {
60
+ echo "foo";
61
+ } catch (Exception \$e) {
62
+ ob_end_clean();
63
+
64
+ throw \$e;
65
+ }
66
+
67
+ return ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset());
68
+ }
69
+ EOF
70
+ ),
71
+ );
72
+ }
73
+ }
Twig/test/Twig/Tests/Node/ModuleTest.php ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_ModuleTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Module::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $body = new Twig_Node_Text('foo', 1);
20
+ $parent = new Twig_Node_Expression_Constant('layout.twig', 1);
21
+ $blocks = new Twig_Node();
22
+ $macros = new Twig_Node();
23
+ $traits = new Twig_Node();
24
+ $filename = 'foo.twig';
25
+ $node = new Twig_Node_Module($body, $parent, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
26
+
27
+ $this->assertEquals($body, $node->getNode('body'));
28
+ $this->assertEquals($blocks, $node->getNode('blocks'));
29
+ $this->assertEquals($macros, $node->getNode('macros'));
30
+ $this->assertEquals($parent, $node->getNode('parent'));
31
+ $this->assertEquals($filename, $node->getAttribute('filename'));
32
+ }
33
+
34
+ /**
35
+ * @covers Twig_Node_Module::compile
36
+ * @covers Twig_Node_Module::compileTemplate
37
+ * @covers Twig_Node_Module::compileMacros
38
+ * @covers Twig_Node_Module::compileClassHeader
39
+ * @covers Twig_Node_Module::compileDisplayHeader
40
+ * @covers Twig_Node_Module::compileDisplayBody
41
+ * @covers Twig_Node_Module::compileDisplayFooter
42
+ * @covers Twig_Node_Module::compileClassFooter
43
+ * @dataProvider getTests
44
+ */
45
+ public function testCompile($node, $source, $environment = null)
46
+ {
47
+ parent::testCompile($node, $source, $environment);
48
+ }
49
+
50
+ public function getTests()
51
+ {
52
+ $twig = new Twig_Environment(new Twig_Loader_String());
53
+
54
+ $tests = array();
55
+
56
+ $body = new Twig_Node_Text('foo', 1);
57
+ $extends = null;
58
+ $blocks = new Twig_Node();
59
+ $macros = new Twig_Node();
60
+ $traits = new Twig_Node();
61
+ $filename = 'foo.twig';
62
+
63
+ $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
64
+ $tests[] = array($node, <<<EOF
65
+ <?php
66
+
67
+ /* foo.twig */
68
+ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
69
+ {
70
+ public function __construct(Twig_Environment \$env)
71
+ {
72
+ parent::__construct(\$env);
73
+
74
+ \$this->parent = false;
75
+
76
+ \$this->blocks = array(
77
+ );
78
+ }
79
+
80
+ protected function doDisplay(array \$context, array \$blocks = array())
81
+ {
82
+ // line 1
83
+ echo "foo";
84
+ }
85
+
86
+ public function getTemplateName()
87
+ {
88
+ return "foo.twig";
89
+ }
90
+
91
+ public function getDebugInfo()
92
+ {
93
+ return array ( 19 => 1,);
94
+ }
95
+ }
96
+ EOF
97
+ , $twig);
98
+
99
+ $import = new Twig_Node_Import(new Twig_Node_Expression_Constant('foo.twig', 1), new Twig_Node_Expression_AssignName('macro', 1), 1);
100
+
101
+ $body = new Twig_Node(array($import));
102
+ $extends = new Twig_Node_Expression_Constant('layout.twig', 1);
103
+
104
+ $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
105
+ $tests[] = array($node, <<<EOF
106
+ <?php
107
+
108
+ /* foo.twig */
109
+ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
110
+ {
111
+ public function __construct(Twig_Environment \$env)
112
+ {
113
+ parent::__construct(\$env);
114
+
115
+ \$this->parent = \$this->env->loadTemplate("layout.twig");
116
+
117
+ \$this->blocks = array(
118
+ );
119
+ }
120
+
121
+ protected function doGetParent(array \$context)
122
+ {
123
+ return "layout.twig";
124
+ }
125
+
126
+ protected function doDisplay(array \$context, array \$blocks = array())
127
+ {
128
+ // line 1
129
+ \$context["macro"] = \$this->env->loadTemplate("foo.twig");
130
+ \$this->parent->display(\$context, array_merge(\$this->blocks, \$blocks));
131
+ }
132
+
133
+ public function getTemplateName()
134
+ {
135
+ return "foo.twig";
136
+ }
137
+
138
+ public function isTraitable()
139
+ {
140
+ return false;
141
+ }
142
+
143
+ public function getDebugInfo()
144
+ {
145
+ return array ( 24 => 1,);
146
+ }
147
+ }
148
+ EOF
149
+ , $twig);
150
+
151
+ $body = new Twig_Node();
152
+ $extends = new Twig_Node_Expression_Conditional(
153
+ new Twig_Node_Expression_Constant(true, 1),
154
+ new Twig_Node_Expression_Constant('foo', 1),
155
+ new Twig_Node_Expression_Constant('foo', 1),
156
+ 0
157
+ );
158
+
159
+ $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
160
+ $tests[] = array($node, <<<EOF
161
+ <?php
162
+
163
+ /* foo.twig */
164
+ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
165
+ {
166
+ protected function doGetParent(array \$context)
167
+ {
168
+ return \$this->env->resolveTemplate(((true) ? ("foo") : ("foo")));
169
+ }
170
+
171
+ protected function doDisplay(array \$context, array \$blocks = array())
172
+ {
173
+ \$this->getParent(\$context)->display(\$context, array_merge(\$this->blocks, \$blocks));
174
+ }
175
+
176
+ public function getTemplateName()
177
+ {
178
+ return "foo.twig";
179
+ }
180
+
181
+ public function isTraitable()
182
+ {
183
+ return false;
184
+ }
185
+
186
+ public function getDebugInfo()
187
+ {
188
+ return array ();
189
+ }
190
+ }
191
+ EOF
192
+ , $twig);
193
+
194
+ return $tests;
195
+ }
196
+ }
Twig/test/Twig/Tests/Node/PrintTest.php ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_PrintTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Print::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $expr = new Twig_Node_Expression_Constant('foo', 1);
20
+ $node = new Twig_Node_Print($expr, 1);
21
+
22
+ $this->assertEquals($expr, $node->getNode('expr'));
23
+ }
24
+
25
+ /**
26
+ * @covers Twig_Node_Print::compile
27
+ * @dataProvider getTests
28
+ */
29
+ public function testCompile($node, $source, $environment = null)
30
+ {
31
+ parent::testCompile($node, $source, $environment);
32
+ }
33
+
34
+ public function getTests()
35
+ {
36
+ $tests = array();
37
+ $tests[] = array(new Twig_Node_Print(new Twig_Node_Expression_Constant('foo', 1), 1), "// line 1\necho \"foo\";");
38
+
39
+ return $tests;
40
+ }
41
+ }
Twig/test/Twig/Tests/Node/SandboxTest.php ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_SandboxTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Sandbox::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $body = new Twig_Node_Text('foo', 1);
20
+ $node = new Twig_Node_Sandbox($body, 1);
21
+
22
+ $this->assertEquals($body, $node->getNode('body'));
23
+ }
24
+
25
+ /**
26
+ * @covers Twig_Node_Sandbox::compile
27
+ * @dataProvider getTests
28
+ */
29
+ public function testCompile($node, $source, $environment = null)
30
+ {
31
+ parent::testCompile($node, $source, $environment);
32
+ }
33
+
34
+ public function getTests()
35
+ {
36
+ $tests = array();
37
+
38
+ $body = new Twig_Node_Text('foo', 1);
39
+ $node = new Twig_Node_Sandbox($body, 1);
40
+
41
+ $tests[] = array($node, <<<EOF
42
+ // line 1
43
+ \$sandbox = \$this->env->getExtension('sandbox');
44
+ if (!\$alreadySandboxed = \$sandbox->isSandboxed()) {
45
+ \$sandbox->enableSandbox();
46
+ }
47
+ echo "foo";
48
+ if (!\$alreadySandboxed) {
49
+ \$sandbox->disableSandbox();
50
+ }
51
+ EOF
52
+ );
53
+
54
+ return $tests;
55
+ }
56
+ }
Twig/test/Twig/Tests/Node/SandboxedModuleTest.php ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_SandboxedModuleTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_SandboxedModule::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $body = new Twig_Node_Text('foo', 1);
20
+ $parent = new Twig_Node_Expression_Constant('layout.twig', 1);
21
+ $blocks = new Twig_Node();
22
+ $macros = new Twig_Node();
23
+ $traits = new Twig_Node();
24
+ $filename = 'foo.twig';
25
+ $node = new Twig_Node_Module($body, $parent, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
26
+ $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle'));
27
+
28
+ $this->assertEquals($body, $node->getNode('body'));
29
+ $this->assertEquals($blocks, $node->getNode('blocks'));
30
+ $this->assertEquals($macros, $node->getNode('macros'));
31
+ $this->assertEquals($parent, $node->getNode('parent'));
32
+ $this->assertEquals($filename, $node->getAttribute('filename'));
33
+ }
34
+
35
+ /**
36
+ * @covers Twig_Node_SandboxedModule::compile
37
+ * @covers Twig_Node_SandboxedModule::compileDisplayBody
38
+ * @covers Twig_Node_SandboxedModule::compileDisplayFooter
39
+ * @dataProvider getTests
40
+ */
41
+ public function testCompile($node, $source, $environment = null)
42
+ {
43
+ parent::testCompile($node, $source, $environment);
44
+ }
45
+
46
+ public function getTests()
47
+ {
48
+ $twig = new Twig_Environment(new Twig_Loader_String());
49
+
50
+ $tests = array();
51
+
52
+ $body = new Twig_Node_Text('foo', 1);
53
+ $extends = null;
54
+ $blocks = new Twig_Node();
55
+ $macros = new Twig_Node();
56
+ $traits = new Twig_Node();
57
+ $filename = 'foo.twig';
58
+
59
+ $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
60
+ $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle'));
61
+
62
+ $tests[] = array($node, <<<EOF
63
+ <?php
64
+
65
+ /* foo.twig */
66
+ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
67
+ {
68
+ public function __construct(Twig_Environment \$env)
69
+ {
70
+ parent::__construct(\$env);
71
+
72
+ \$this->parent = false;
73
+
74
+ \$this->blocks = array(
75
+ );
76
+ }
77
+
78
+ protected function doDisplay(array \$context, array \$blocks = array())
79
+ {
80
+ \$this->checkSecurity();
81
+ // line 1
82
+ echo "foo";
83
+ }
84
+
85
+ protected function checkSecurity()
86
+ {
87
+ \$this->env->getExtension('sandbox')->checkSecurity(
88
+ array('upper'),
89
+ array('for'),
90
+ array('cycle')
91
+ );
92
+ }
93
+
94
+ public function getTemplateName()
95
+ {
96
+ return "foo.twig";
97
+ }
98
+
99
+ public function getDebugInfo()
100
+ {
101
+ return array ( 20 => 1,);
102
+ }
103
+ }
104
+ EOF
105
+ , $twig);
106
+
107
+ $body = new Twig_Node();
108
+ $extends = new Twig_Node_Expression_Constant('layout.twig', 1);
109
+ $blocks = new Twig_Node();
110
+ $macros = new Twig_Node();
111
+ $traits = new Twig_Node();
112
+ $filename = 'foo.twig';
113
+
114
+ $node = new Twig_Node_Module($body, $extends, $blocks, $macros, $traits, new Twig_Node(array()), $filename);
115
+ $node = new Twig_Node_SandboxedModule($node, array('for'), array('upper'), array('cycle'));
116
+
117
+ $tests[] = array($node, <<<EOF
118
+ <?php
119
+
120
+ /* foo.twig */
121
+ class __TwigTemplate_be925a7b06dda0dfdbd18a1509f7eb34 extends Twig_Template
122
+ {
123
+ public function __construct(Twig_Environment \$env)
124
+ {
125
+ parent::__construct(\$env);
126
+
127
+ \$this->parent = \$this->env->loadTemplate("layout.twig");
128
+
129
+ \$this->blocks = array(
130
+ );
131
+ }
132
+
133
+ protected function doGetParent(array \$context)
134
+ {
135
+ return "layout.twig";
136
+ }
137
+
138
+ protected function doDisplay(array \$context, array \$blocks = array())
139
+ {
140
+ \$this->checkSecurity();
141
+ \$this->parent->display(\$context, array_merge(\$this->blocks, \$blocks));
142
+ }
143
+
144
+ protected function checkSecurity()
145
+ {
146
+ \$this->env->getExtension('sandbox')->checkSecurity(
147
+ array('upper'),
148
+ array('for'),
149
+ array('cycle')
150
+ );
151
+ }
152
+
153
+ public function getTemplateName()
154
+ {
155
+ return "foo.twig";
156
+ }
157
+
158
+ public function isTraitable()
159
+ {
160
+ return false;
161
+ }
162
+
163
+ public function getDebugInfo()
164
+ {
165
+ return array ();
166
+ }
167
+ }
168
+ EOF
169
+ , $twig);
170
+
171
+ return $tests;
172
+ }
173
+ }
Twig/test/Twig/Tests/Node/SandboxedPrintTest.php ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_SandboxedPrintTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_SandboxedPrint::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $node = new Twig_Node_SandboxedPrint($expr = new Twig_Node_Expression_Constant('foo', 1), 1);
20
+
21
+ $this->assertEquals($expr, $node->getNode('expr'));
22
+ }
23
+
24
+ /**
25
+ * @covers Twig_Node_SandboxedPrint::compile
26
+ * @dataProvider getTests
27
+ */
28
+ public function testCompile($node, $source, $environment = null)
29
+ {
30
+ parent::testCompile($node, $source, $environment);
31
+ }
32
+
33
+ public function getTests()
34
+ {
35
+ $tests = array();
36
+
37
+ $tests[] = array(new Twig_Node_SandboxedPrint(new Twig_Node_Expression_Constant('foo', 1), 1), <<<EOF
38
+ // line 1
39
+ echo \$this->env->getExtension('sandbox')->ensureToStringAllowed("foo");
40
+ EOF
41
+ );
42
+
43
+ return $tests;
44
+ }
45
+ }
Twig/test/Twig/Tests/Node/SetTest.php ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_SetTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Set::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 1)), array(), 1);
20
+ $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 1)), array(), 1);
21
+ $node = new Twig_Node_Set(false, $names, $values, 1);
22
+
23
+ $this->assertEquals($names, $node->getNode('names'));
24
+ $this->assertEquals($values, $node->getNode('values'));
25
+ $this->assertEquals(false, $node->getAttribute('capture'));
26
+ }
27
+
28
+ /**
29
+ * @covers Twig_Node_Set::compile
30
+ * @dataProvider getTests
31
+ */
32
+ public function testCompile($node, $source, $environment = null)
33
+ {
34
+ parent::testCompile($node, $source, $environment);
35
+ }
36
+
37
+ public function getTests()
38
+ {
39
+ $tests = array();
40
+
41
+ $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 1)), array(), 1);
42
+ $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 1)), array(), 1);
43
+ $node = new Twig_Node_Set(false, $names, $values, 1);
44
+ $tests[] = array($node, <<<EOF
45
+ // line 1
46
+ \$context["foo"] = "foo";
47
+ EOF
48
+ );
49
+
50
+ $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 1)), array(), 1);
51
+ $values = new Twig_Node(array(new Twig_Node_Print(new Twig_Node_Expression_Constant('foo', 1), 1)), array(), 1);
52
+ $node = new Twig_Node_Set(true, $names, $values, 1);
53
+ $tests[] = array($node, <<<EOF
54
+ // line 1
55
+ ob_start();
56
+ echo "foo";
57
+ \$context["foo"] = ('' === \$tmp = ob_get_clean()) ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset());
58
+ EOF
59
+ );
60
+
61
+ $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 1)), array(), 1);
62
+ $values = new Twig_Node_Text('foo', 1);
63
+ $node = new Twig_Node_Set(true, $names, $values, 1);
64
+ $tests[] = array($node, <<<EOF
65
+ // line 1
66
+ \$context["foo"] = ('' === \$tmp = "foo") ? '' : new Twig_Markup(\$tmp, \$this->env->getCharset());
67
+ EOF
68
+ );
69
+
70
+ $names = new Twig_Node(array(new Twig_Node_Expression_AssignName('foo', 1), new Twig_Node_Expression_AssignName('bar', 1)), array(), 1);
71
+ $values = new Twig_Node(array(new Twig_Node_Expression_Constant('foo', 1), new Twig_Node_Expression_Name('bar', 1)), array(), 1);
72
+ $node = new Twig_Node_Set(false, $names, $values, 1);
73
+ $tests[] = array($node, <<<EOF
74
+ // line 1
75
+ list(\$context["foo"], \$context["bar"]) = array("foo", {$this->getVariableGetter('bar')});
76
+ EOF
77
+ );
78
+
79
+ return $tests;
80
+ }
81
+ }
Twig/test/Twig/Tests/Node/SpacelessTest.php ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_SpacelessTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Spaceless::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $body = new Twig_Node(array(new Twig_Node_Text('<div> <div> foo </div> </div>', 1)));
20
+ $node = new Twig_Node_Spaceless($body, 1);
21
+
22
+ $this->assertEquals($body, $node->getNode('body'));
23
+ }
24
+
25
+ /**
26
+ * @covers Twig_Node_Spaceless::compile
27
+ * @dataProvider getTests
28
+ */
29
+ public function testCompile($node, $source, $environment = null)
30
+ {
31
+ parent::testCompile($node, $source, $environment);
32
+ }
33
+
34
+ public function getTests()
35
+ {
36
+ $body = new Twig_Node(array(new Twig_Node_Text('<div> <div> foo </div> </div>', 1)));
37
+ $node = new Twig_Node_Spaceless($body, 1);
38
+
39
+ return array(
40
+ array($node, <<<EOF
41
+ // line 1
42
+ ob_start();
43
+ echo "<div> <div> foo </div> </div>";
44
+ echo trim(preg_replace('/>\s+</', '><', ob_get_clean()));
45
+ EOF
46
+ ),
47
+ );
48
+ }
49
+ }
Twig/test/Twig/Tests/Node/TextTest.php ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+
12
+ class Twig_Tests_Node_TextTest extends Twig_Test_NodeTestCase
13
+ {
14
+ /**
15
+ * @covers Twig_Node_Text::__construct
16
+ */
17
+ public function testConstructor()
18
+ {
19
+ $node = new Twig_Node_Text('foo', 1);
20
+
21
+ $this->assertEquals('foo', $node->getAttribute('data'));
22
+ }
23
+
24
+ /**
25
+ * @covers Twig_Node_Text::compile
26
+ * @dataProvider getTests
27
+ */
28
+ public function testCompile($node, $source, $environment = null)
29
+ {
30
+ parent::testCompile($node, $source, $environment);
31
+ }
32
+
33
+ public function getTests()
34
+ {
35
+ $tests = array();
36
+ $tests[] = array(new Twig_Node_Text('foo', 1), "// line 1\necho \"foo\";");
37
+
38
+ return $tests;
39
+ }
40
+ }
Twig/test/Twig/Tests/NodeVisitor/OptimizerTest.php ADDED
@@ -0,0 +1,114 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Tests_NodeVisitor_OptimizerTest extends PHPUnit_Framework_TestCase
12
+ {
13
+ public function testRenderBlockOptimizer()
14
+ {
15
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
16
+
17
+ $stream = $env->parse($env->tokenize('{{ block("foo") }}', 'index'));
18
+
19
+ $node = $stream->getNode('body')->getNode(0);
20
+
21
+ $this->assertEquals('Twig_Node_Expression_BlockReference', get_class($node));
22
+ $this->assertTrue($node->getAttribute('output'));
23
+ }
24
+
25
+ public function testRenderParentBlockOptimizer()
26
+ {
27
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
28
+
29
+ $stream = $env->parse($env->tokenize('{% extends "foo" %}{% block content %}{{ parent() }}{% endblock %}', 'index'));
30
+
31
+ $node = $stream->getNode('blocks')->getNode('content')->getNode(0)->getNode('body');
32
+
33
+ $this->assertEquals('Twig_Node_Expression_Parent', get_class($node));
34
+ $this->assertTrue($node->getAttribute('output'));
35
+ }
36
+
37
+ public function testRenderVariableBlockOptimizer()
38
+ {
39
+ if (version_compare(phpversion(), '5.4.0RC1', '>=')) {
40
+ return;
41
+ }
42
+
43
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false, 'autoescape' => false));
44
+ $stream = $env->parse($env->tokenize('{{ block(name|lower) }}', 'index'));
45
+
46
+ $node = $stream->getNode('body')->getNode(0)->getNode(1);
47
+
48
+ $this->assertEquals('Twig_Node_Expression_BlockReference', get_class($node));
49
+ $this->assertTrue($node->getAttribute('output'));
50
+ }
51
+
52
+ /**
53
+ * @dataProvider getTestsForForOptimizer
54
+ */
55
+ public function testForOptimizer($template, $expected)
56
+ {
57
+ $env = new Twig_Environment(new Twig_Loader_String(), array('cache' => false));
58
+
59
+ $stream = $env->parse($env->tokenize($template, 'index'));
60
+
61
+ foreach ($expected as $target => $withLoop) {
62
+ $this->assertTrue($this->checkForConfiguration($stream, $target, $withLoop), sprintf('variable %s is %soptimized', $target, $withLoop ? 'not ' : ''));
63
+ }
64
+ }
65
+
66
+ public function getTestsForForOptimizer()
67
+ {
68
+ return array(
69
+ array('{% for i in foo %}{% endfor %}', array('i' => false)),
70
+
71
+ array('{% for i in foo %}{{ loop.index }}{% endfor %}', array('i' => true)),
72
+
73
+ array('{% for i in foo %}{% for j in foo %}{% endfor %}{% endfor %}', array('i' => false, 'j' => false)),
74
+
75
+ array('{% for i in foo %}{% include "foo" %}{% endfor %}', array('i' => true)),
76
+
77
+ array('{% for i in foo %}{% include "foo" only %}{% endfor %}', array('i' => false)),
78
+
79
+ array('{% for i in foo %}{% include "foo" with { "foo": "bar" } only %}{% endfor %}', array('i' => false)),
80
+
81
+ array('{% for i in foo %}{% include "foo" with { "foo": loop.index } only %}{% endfor %}', array('i' => true)),
82
+
83
+ array('{% for i in foo %}{% for j in foo %}{{ loop.index }}{% endfor %}{% endfor %}', array('i' => false, 'j' => true)),
84
+
85
+ array('{% for i in foo %}{% for j in foo %}{{ loop.parent.loop.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => true)),
86
+
87
+ array('{% for i in foo %}{% set l = loop %}{% for j in foo %}{{ l.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => false)),
88
+
89
+ array('{% for i in foo %}{% for j in foo %}{{ foo.parent.loop.index }}{% endfor %}{% endfor %}', array('i' => false, 'j' => false)),
90
+
91
+ array('{% for i in foo %}{% for j in foo %}{{ loop["parent"].loop.index }}{% endfor %}{% endfor %}', array('i' => true, 'j' => true)),
92
+ );
93
+ }
94
+
95
+ public function checkForConfiguration(Twig_NodeInterface $node = null, $target, $withLoop)
96
+ {
97
+ if (null === $node) {
98
+ return;
99
+ }
100
+
101
+ foreach ($node as $n) {
102
+ if ($n instanceof Twig_Node_For) {
103
+ if ($target === $n->getNode('value_target')->getAttribute('name')) {
104
+ return $withLoop == $n->getAttribute('with_loop');
105
+ }
106
+ }
107
+
108
+ $ret = $this->checkForConfiguration($n, $target, $withLoop);
109
+ if (null !== $ret) {
110
+ return $ret;
111
+ }
112
+ }
113
+ }
114
+ }
Twig/test/Twig/Tests/ParserTest.php ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Tests_ParserTest extends PHPUnit_Framework_TestCase
12
+ {
13
+ /**
14
+ * @expectedException Twig_Error_Syntax
15
+ */
16
+ public function testSetMacroThrowsExceptionOnReservedMethods()
17
+ {
18
+ $parser = $this->getParser();
19
+ $parser->setMacro('display', $this->getMock('Twig_Node_Macro', array(), array(), '', null));
20
+ }
21
+
22
+ /**
23
+ * @expectedException Twig_Error_Syntax
24
+ * @expectedExceptionMessage Unknown tag name "foo". Did you mean "for" at line 1
25
+ */
26
+ public function testUnkownTag()
27
+ {
28
+ $stream = new Twig_TokenStream(array(
29
+ new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', 1),
30
+ new Twig_Token(Twig_Token::NAME_TYPE, 'foo', 1),
31
+ new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', 1),
32
+ new Twig_Token(Twig_Token::EOF_TYPE, '', 1),
33
+ ));
34
+ $parser = new Twig_Parser(new Twig_Environment());
35
+ $parser->parse($stream);
36
+ }
37
+
38
+ /**
39
+ * @dataProvider getFilterBodyNodesData
40
+ */
41
+ public function testFilterBodyNodes($input, $expected)
42
+ {
43
+ $parser = $this->getParser();
44
+
45
+ $this->assertEquals($expected, $parser->filterBodyNodes($input));
46
+ }
47
+
48
+ public function getFilterBodyNodesData()
49
+ {
50
+ return array(
51
+ array(
52
+ new Twig_Node(array(new Twig_Node_Text(' ', 1))),
53
+ new Twig_Node(array()),
54
+ ),
55
+ array(
56
+ $input = new Twig_Node(array(new Twig_Node_Set(false, new Twig_Node(), new Twig_Node(), 1))),
57
+ $input,
58
+ ),
59
+ array(
60
+ $input = new Twig_Node(array(new Twig_Node_Set(true, new Twig_Node(), new Twig_Node(array(new Twig_Node(array(new Twig_Node_Text('foo', 1))))), 1))),
61
+ $input,
62
+ ),
63
+ );
64
+ }
65
+
66
+ /**
67
+ * @dataProvider getFilterBodyNodesDataThrowsException
68
+ * @expectedException Twig_Error_Syntax
69
+ */
70
+ public function testFilterBodyNodesThrowsException($input)
71
+ {
72
+ $parser = $this->getParser();
73
+
74
+ $parser->filterBodyNodes($input);
75
+ }
76
+
77
+ public function getFilterBodyNodesDataThrowsException()
78
+ {
79
+ return array(
80
+ array(new Twig_Node_Text('foo', 1)),
81
+ array(new Twig_Node(array(new Twig_Node(array(new Twig_Node_Text('foo', 1)))))),
82
+ );
83
+ }
84
+
85
+ /**
86
+ * @expectedException Twig_Error_Syntax
87
+ * @expectedExceptionMessage A template that extends another one cannot have a body but a byte order mark (BOM) has been detected; it must be removed at line 1.
88
+ */
89
+ public function testFilterBodyNodesWithBOM()
90
+ {
91
+ $parser = $this->getParser();
92
+ $parser->filterBodyNodes(new Twig_Node_Text(chr(0xEF).chr(0xBB).chr(0xBF), 1));
93
+ }
94
+
95
+ public function testParseIsReentrant()
96
+ {
97
+ $twig = new Twig_Environment(null, array(
98
+ 'autoescape' => false,
99
+ 'optimizations' => 0,
100
+ ));
101
+ $twig->addTokenParser(new TestTokenParser());
102
+
103
+ $parser = new Twig_Parser($twig);
104
+
105
+ $parser->parse(new Twig_TokenStream(array(
106
+ new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', 1),
107
+ new Twig_Token(Twig_Token::NAME_TYPE, 'test', 1),
108
+ new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', 1),
109
+ new Twig_Token(Twig_Token::VAR_START_TYPE, '', 1),
110
+ new Twig_Token(Twig_Token::NAME_TYPE, 'foo', 1),
111
+ new Twig_Token(Twig_Token::VAR_END_TYPE, '', 1),
112
+ new Twig_Token(Twig_Token::EOF_TYPE, '', 1),
113
+ )));
114
+
115
+ $this->assertEquals(null, $parser->getParent());
116
+ }
117
+
118
+ // The getVarName() must not depend on the template loaders,
119
+ // If this test does not throw any exception, that's good.
120
+ // see https://github.com/symfony/symfony/issues/4218
121
+ public function testGetVarName()
122
+ {
123
+ $twig = new Twig_Environment(null, array(
124
+ 'autoescape' => false,
125
+ 'optimizations' => 0,
126
+ ));
127
+
128
+ $twig->parse($twig->tokenize(<<<EOF
129
+ {% from _self import foo %}
130
+
131
+ {% macro foo() %}
132
+ {{ foo }}
133
+ {% endmacro %}
134
+ EOF
135
+ ));
136
+ }
137
+
138
+ protected function getParser()
139
+ {
140
+ $parser = new TestParser(new Twig_Environment());
141
+ $parser->setParent(new Twig_Node());
142
+ $parser->stream = $this->getMockBuilder('Twig_TokenStream')->disableOriginalConstructor()->getMock();
143
+
144
+ return $parser;
145
+ }
146
+ }
147
+
148
+ class TestParser extends Twig_Parser
149
+ {
150
+ public $stream;
151
+
152
+ public function filterBodyNodes(Twig_NodeInterface $node)
153
+ {
154
+ return parent::filterBodyNodes($node);
155
+ }
156
+ }
157
+
158
+ class TestTokenParser extends Twig_TokenParser
159
+ {
160
+ public function parse(Twig_Token $token)
161
+ {
162
+ // simulate the parsing of another template right in the middle of the parsing of the current template
163
+ $this->parser->parse(new Twig_TokenStream(array(
164
+ new Twig_Token(Twig_Token::BLOCK_START_TYPE, '', 1),
165
+ new Twig_Token(Twig_Token::NAME_TYPE, 'extends', 1),
166
+ new Twig_Token(Twig_Token::STRING_TYPE, 'base', 1),
167
+ new Twig_Token(Twig_Token::BLOCK_END_TYPE, '', 1),
168
+ new Twig_Token(Twig_Token::EOF_TYPE, '', 1),
169
+ )));
170
+
171
+ $this->parser->getStream()->expect(Twig_Token::BLOCK_END_TYPE);
172
+
173
+ return new Twig_Node(array());
174
+ }
175
+
176
+ public function getTag()
177
+ {
178
+ return 'test';
179
+ }
180
+ }
Twig/test/Twig/Tests/TemplateTest.php ADDED
@@ -0,0 +1,556 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <?php
2
+
3
+ /*
4
+ * This file is part of Twig.
5
+ *
6
+ * (c) Fabien Potencier
7
+ *
8
+ * For the full copyright and license information, please view the LICENSE
9
+ * file that was distributed with this source code.
10
+ */
11
+ class Twig_Tests_TemplateTest extends PHPUnit_Framework_TestCase
12
+ {
13
+ /**
14
+ * @dataProvider getAttributeExceptions
15
+ */
16
+ public function testGetAttributeExceptions($template, $message, $useExt)
17
+ {
18
+ $name = 'index_'.($useExt ? 1 : 0);
19
+ $templates = array(
20
+ $name => $template.$useExt, // appending $useExt makes the template content unique
21
+ );
22
+
23
+ $env = new Twig_Environment(new Twig_Loader_Array($templates), array('strict_variables' => true));
24
+ if (!$useExt) {
25
+ $env->addNodeVisitor(new CExtDisablingNodeVisitor());
26
+ }
27
+ $template = $env->loadTemplate($name);
28
+
29
+ $context = array(
30
+ 'string' => 'foo',
31
+ 'array' => array('foo' => 'foo'),
32
+ 'array_access' => new Twig_TemplateArrayAccessObject(),
33
+ );
34
+
35
+ try {
36
+ $template->render($context);
37
+ } catch (Twig_Error_Runtime $e) {
38
+ $this->assertEquals(sprintf($message, $name), $e->getMessage());
39
+ }
40
+ }
41
+
42
+ public function getAttributeExceptions()
43
+ {
44
+ $tests = array(
45
+ array('{{ string["a"] }}', 'Impossible to access a key ("a") on a "string" variable in "%s" at line 1', false),
46
+ array('{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1', false),
47
+ array('{{ array_access["a"] }}', 'Key "a" in object (with ArrayAccess) of type "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
48
+ array('{{ string.a }}', 'Item "a" for "foo" does not exist in "%s" at line 1', false),
49
+ array('{{ array.a }}', 'Item "a" for "Array" does not exist in "%s" at line 1', false),
50
+ array('{{ array_access.a }}', 'Method "a" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
51
+ array('{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ _self.foo(array_access) }}', 'Method "missing_method" for object "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1', false),
52
+ );
53
+
54
+ if (function_exists('twig_template_get_attributes')) {
55
+ foreach (array_slice($tests, 0) as $test) {
56
+ $test[2] = true;
57
+ $tests[] = $test;
58
+ }
59
+ }
60
+
61
+ return $tests;
62
+ }
63
+
64
+ /**
65
+ * @dataProvider getGetAttributeWithSandbox
66
+ */
67
+ public function testGetAttributeWithSandbox($object, $item, $allowed, $useExt)
68
+ {
69
+ $twig = new Twig_Environment();
70
+ $policy = new Twig_Sandbox_SecurityPolicy(array(), array(), array(/*methid*/), array(/*peop*/), array());
71
+ $twig->addExtension(new Twig_Extension_Sandbox($policy, !$allowed));
72
+ $template = new Twig_TemplateTest($twig, $useExt);
73
+
74
+ try {
75
+ $template->getAttribute($object, $item, array(), 'any');
76
+
77
+ if (!$allowed) {
78
+ $this->fail();
79
+ }
80
+ } catch (Twig_Sandbox_SecurityError $e) {
81
+ if ($allowed) {
82
+ $this->fail();
83
+ }
84
+
85
+ $this->assertContains('is not allowed', $e->getMessage());
86
+ }
87
+ }
88
+
89
+ public function getGetAttributeWithSandbox()
90
+ {
91
+ $tests = array(
92
+ array(new Twig_TemplatePropertyObject(), 'defined', false, false),
93
+ array(new Twig_TemplatePropertyObject(), 'defined', true, false),
94
+ array(new Twig_TemplateMethodObject(), 'defined', false, false),
95
+ array(new Twig_TemplateMethodObject(), 'defined', true, false),
96
+ );
97
+
98
+ if (function_exists('twig_template_get_attributes')) {
99
+ foreach (array_slice($tests, 0) as $test) {
100
+ $test[3] = true;
101
+ $tests[] = $test;
102
+ }
103
+ }
104
+
105
+ return $tests;
106
+ }
107
+
108
+ /**
109
+ * @dataProvider getGetAttributeWithTemplateAsObject
110
+ */
111
+ public function testGetAttributeWithTemplateAsObject($useExt)
112
+ {
113
+ $template = new Twig_TemplateTest(new Twig_Environment(), $useExt);
114
+ $template1 = new Twig_TemplateTest(new Twig_Environment(), false);
115
+
116
+ $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'string'));
117
+ $this->assertEquals('some_string', $template->getAttribute($template1, 'string'));
118
+
119
+ $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'true'));
120
+ $this->assertEquals('1', $template->getAttribute($template1, 'true'));
121
+
122
+ $this->assertInstanceof('Twig_Markup', $template->getAttribute($template1, 'zero'));
123
+ $this->assertEquals('0', $template->getAttribute($template1, 'zero'));
124
+
125
+ $this->assertNotInstanceof('Twig_Markup', $template->getAttribute($template1, 'empty'));
126
+ $this->assertSame('', $template->getAttribute($template1, 'empty'));
127
+ }
128
+
129
+ public function getGetAttributeWithTemplateAsObject()
130
+ {
131
+ $bools = array(
132
+ array(false),
133
+ );
134
+
135
+ if (function_exists('twig_template_get_attributes')) {
136
+ $bools[] = array(true);
137
+ }
138
+
139
+ return $bools;
140
+ }
141
+
142
+ /**
143
+ * @dataProvider getGetAttributeTests
144
+ */
145
+ public function testGetAttribute($defined, $value, $object, $item, $arguments, $type, $useExt = false)
146
+ {
147
+ $template = new Twig_TemplateTest(new Twig_Environment(), $useExt);
148
+
149
+ $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
150
+ }
151
+
152
+ /**
153
+ * @dataProvider getGetAttributeTests
154
+ */
155
+ public function testGetAttributeStrict($defined, $value, $object, $item, $arguments, $type, $useExt = false, $exceptionMessage = null)
156
+ {
157
+ $template = new Twig_TemplateTest(new Twig_Environment(null, array('strict_variables' => true)), $useExt);
158
+
159
+ if ($defined) {
160
+ $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
161
+ } else {
162
+ try {
163
+ $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
164
+
165
+ throw new Exception('Expected Twig_Error_Runtime exception.');
166
+ } catch (Twig_Error_Runtime $e) {
167
+ if (null !== $exceptionMessage) {
168
+ $this->assertSame($exceptionMessage, $e->getMessage());
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * @dataProvider getGetAttributeTests
176
+ */
177
+ public function testGetAttributeDefined($defined, $value, $object, $item, $arguments, $type, $useExt = false)
178
+ {
179
+ $template = new Twig_TemplateTest(new Twig_Environment(), $useExt);
180
+
181
+ $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true));
182
+ }
183
+
184
+ /**
185
+ * @dataProvider getGetAttributeTests
186
+ */
187
+ public function testGetAttributeDefinedStrict($defined, $value, $object, $item, $arguments, $type, $useExt = false)
188
+ {
189
+ $template = new Twig_TemplateTest(new Twig_Environment(null, array('strict_variables' => true)), $useExt);
190
+
191
+ $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true));
192
+ }
193
+
194
+ public function getGetAttributeTests()
195
+ {
196
+ $array = array(
197
+ 'defined' => 'defined',
198
+ 'zero' => 0,
199
+ 'null' => null,
200
+ '1' => 1,
201
+ 'bar' => true,
202
+ );
203
+
204
+ $objectArray = new Twig_TemplateArrayAccessObject();
205
+ $stdObject = (object) $array;
206
+ $magicPropertyObject = new Twig_TemplateMagicPropertyObject();
207
+ $propertyObject = new Twig_TemplatePropertyObject();
208
+ $propertyObject1 = new Twig_TemplatePropertyObjectAndIterator();
209
+ $propertyObject2 = new Twig_TemplatePropertyObjectAndArrayAccess();
210
+ $methodObject = new Twig_TemplateMethodObject();
211
+ $magicMethodObject = new Twig_TemplateMagicMethodObject();
212
+
213
+ $anyType = Twig_TemplateInterface::ANY_CALL;
214
+ $methodType = Twig_TemplateInterface::METHOD_CALL;
215
+ $arrayType = Twig_TemplateInterface::ARRAY_CALL;
216
+
217
+ $basicTests = array(
218
+ // array(defined, value, property to fetch)
219
+ array(true, 'defined', 'defined'),
220
+ array(false, null, 'undefined'),
221
+ array(false, null, 'protected'),
222
+ array(true, 0, 'zero'),
223
+ array(true, 1, 1),
224
+ array(true, 1, 1.0),
225
+ array(true, null, 'null'),
226
+ array(true, true, 'bar'),
227
+ );
228
+ $testObjects = array(
229
+ // array(object, type of fetch)
230
+ array($array, $arrayType),
231
+ array($objectArray, $arrayType),
232
+ array($stdObject, $anyType),
233
+ array($magicPropertyObject, $anyType),
234
+ array($methodObject, $methodType),
235
+ array($methodObject, $anyType),
236
+ array($propertyObject, $anyType),
237
+ array($propertyObject1, $anyType),
238
+ array($propertyObject2, $anyType),
239
+ );
240
+
241
+ $tests = array();
242
+ foreach ($testObjects as $testObject) {
243
+ foreach ($basicTests as $test) {
244
+ // properties cannot be numbers
245
+ if (($testObject[0] instanceof stdClass || $testObject[0] instanceof Twig_TemplatePropertyObject) && is_numeric($test[2])) {
246
+ continue;
247
+ }
248
+
249
+ $tests[] = array($test[0], $test[1], $testObject[0], $test[2], array(), $testObject[1]);
250
+ }
251
+ }
252
+
253
+ // additional method tests
254
+ $tests = array_merge($tests, array(
255
+ array(true, 'defined', $methodObject, 'defined', array(), $methodType),
256
+ array(true, 'defined', $methodObject, 'DEFINED', array(), $methodType),
257
+ array(true, 'defined', $methodObject, 'getDefined', array(), $methodType),
258
+ array(true, 'defined', $methodObject, 'GETDEFINED', array(), $methodType),
259
+ array(true, 'static', $methodObject, 'static', array(), $methodType),
260
+ array(true, 'static', $methodObject, 'getStatic', array(), $methodType),
261
+
262
+ array(true, '__call_undefined', $magicMethodObject, 'undefined', array(), $methodType),
263
+ array(true, '__call_UNDEFINED', $magicMethodObject, 'UNDEFINED', array(), $methodType),
264
+ ));
265
+
266
+ // add the same tests for the any type
267
+ foreach ($tests as $test) {
268
+ if ($anyType !== $test[5]) {
269
+ $test[5] = $anyType;
270
+ $tests[] = $test;
271
+ }
272
+ }
273
+
274
+ $methodAndPropObject = new Twig_TemplateMethodAndPropObject;
275
+
276
+ // additional method tests
277
+ $tests = array_merge($tests, array(
278
+ array(true, 'a', $methodAndPropObject, 'a', array(), $anyType),
279
+ array(true, 'a', $methodAndPropObject, 'a', array(), $methodType),
280
+ array(false, null, $methodAndPropObject, 'a', array(), $arrayType),
281
+
282
+ array(true, 'b_prop', $methodAndPropObject, 'b', array(), $anyType),
283
+ array(true, 'b', $methodAndPropObject, 'B', array(), $anyType),
284
+ array(true, 'b', $methodAndPropObject, 'b', array(), $methodType),
285
+ array(true, 'b', $methodAndPropObject, 'B', array(), $methodType),
286
+ array(false, null, $methodAndPropObject, 'b', array(), $arrayType),
287
+
288
+ array(false, null, $methodAndPropObject, 'c', array(), $anyType),
289
+ array(false, null, $methodAndPropObject, 'c', array(), $methodType),
290
+ array(false, null, $methodAndPropObject, 'c', array(), $arrayType),
291
+
292
+ ));
293
+
294
+ // tests when input is not an array or object
295
+ $tests = array_merge($tests, array(
296
+ array(false, null, 42, 'a', array(), $anyType, false, 'Item "a" for "42" does not exist'),
297
+ array(false, null, "string", 'a', array(), $anyType, false, 'Item "a" for "string" does not exist'),
298
+ array(false, null, array(), 'a', array(), $anyType, false, 'Item "a" for "Array" does not exist'),
299
+ ));
300
+
301
+ // add twig_template_get_attributes tests
302
+
303
+ if (function_exists('twig_template_get_attributes')) {
304
+ foreach (array_slice($tests, 0) as $test) {
305
+ $test = array_pad($test, 7, null);
306
+ $test[6] = true;
307
+ $tests[] = $test;
308
+ }
309
+ }
310
+
311
+ return $tests;
312
+ }
313
+ }
314
+
315
+ class Twig_TemplateTest extends Twig_Template
316
+ {
317
+ protected $useExtGetAttribute = false;
318
+
319
+ public function __construct(Twig_Environment $env, $useExtGetAttribute = false)
320
+ {
321
+ parent::__construct($env);
322
+ $this->useExtGetAttribute = $useExtGetAttribute;
323
+ Twig_Template::clearCache();
324
+ }
325
+
326
+ public function getZero()
327
+ {
328
+ return 0;
329
+ }
330
+
331
+ public function getEmpty()
332
+ {
333
+ return '';
334
+ }
335
+
336
+ public function getString()
337
+ {
338
+ return 'some_string';
339
+ }
340
+
341
+ public function getTrue()
342
+ {
343
+ return true;
344
+ }
345
+
346
+ public function getTemplateName()
347
+ {
348
+ }
349
+
350
+ public function getDebugInfo()
351
+ {
352
+ return array();
353
+ }
354
+
355
+ protected function doGetParent(array $context)
356
+ {
357
+ }
358
+
359
+ protected function doDisplay(array $context, array $blocks = array())
360
+ {
361
+ }
362
+
363
+ public function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
364
+ {
365
+ if ($this->useExtGetAttribute) {
366
+ return twig_template_get_attributes($this, $object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck);
367
+ } else {
368
+ return parent::getAttribute($object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck);
369
+ }
370
+ }
371
+ }
372
+
373
+ class Twig_TemplateArrayAccessObject implements ArrayAccess
374
+ {
375
+ protected $protected = 'protected';
376
+
377
+ public $attributes = array(
378
+ 'defined' => 'defined',
379
+ 'zero' => 0,
380
+ 'null' => null,
381
+ '1' => 1,
382
+ 'bar' => true,
383
+ );
384
+
385
+ public function offsetExists($name)
386
+ {
387
+ return array_key_exists($name, $this->attributes);
388
+ }
389
+
390
+ public function offsetGet($name)
391
+ {
392
+ return array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;
393
+ }
394
+
395
+ public function of