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 12use Twig\Extension\AbstractExtension; 13use Twig\Extension\DebugExtension; 14use Twig\Extension\SandboxExtension; 15use Twig\Extension\StringLoaderExtension; 16use Twig\Node\Expression\ConstantExpression; 17use Twig\Node\PrintNode; 18use Twig\Sandbox\SecurityPolicy; 19use Twig\Test\IntegrationTestCase; 20use Twig\Token; 21use Twig\TokenParser\AbstractTokenParser; 22use Twig\TwigFilter; 23use Twig\TwigFunction; 24use Twig\TwigTest; 25 26// This function is defined to check that escaping strategies 27// like html works even if a function with the same name is defined. 28function html() 29{ 30 return 'foo'; 31} 32 33class Twig_Tests_IntegrationTest extends IntegrationTestCase 34{ 35 public function getExtensions() 36 { 37 $policy = new SecurityPolicy([], [], [], [], ['dump']); 38 39 return [ 40 new DebugExtension(), 41 new SandboxExtension($policy, false), 42 new StringLoaderExtension(), 43 new TwigTestExtension(), 44 ]; 45 } 46 47 public function getFixturesDir() 48 { 49 return __DIR__.'/Fixtures/'; 50 } 51} 52 53function test_foo($value = 'foo') 54{ 55 return $value; 56} 57 58class TwigTestFoo implements Iterator 59{ 60 const BAR_NAME = 'bar'; 61 62 public $position = 0; 63 public $array = [1, 2]; 64 65 public function bar($param1 = null, $param2 = null) 66 { 67 return 'bar'.($param1 ? '_'.$param1 : '').($param2 ? '-'.$param2 : ''); 68 } 69 70 public function getFoo() 71 { 72 return 'foo'; 73 } 74 75 public function getSelf() 76 { 77 return $this; 78 } 79 80 public function is() 81 { 82 return 'is'; 83 } 84 85 public function in() 86 { 87 return 'in'; 88 } 89 90 public function not() 91 { 92 return 'not'; 93 } 94 95 public function strToLower($value) 96 { 97 return strtolower($value); 98 } 99 100 public function rewind() 101 { 102 $this->position = 0; 103 } 104 105 public function current() 106 { 107 return $this->array[$this->position]; 108 } 109 110 public function key() 111 { 112 return 'a'; 113 } 114 115 public function next() 116 { 117 ++$this->position; 118 } 119 120 public function valid() 121 { 122 return isset($this->array[$this->position]); 123 } 124} 125 126class TwigTestTokenParser_§ extends AbstractTokenParser 127{ 128 public function parse(Token $token) 129 { 130 $this->parser->getStream()->expect(Token::BLOCK_END_TYPE); 131 132 return new PrintNode(new ConstantExpression('§', -1), -1); 133 } 134 135 public function getTag() 136 { 137 return '§'; 138 } 139} 140 141class TwigTestExtension extends AbstractExtension 142{ 143 public function getTokenParsers() 144 { 145 return [ 146 new TwigTestTokenParser_§(), 147 ]; 148 } 149 150 public function getFilters() 151 { 152 return [ 153 new TwigFilter('§', [$this, '§Filter']), 154 new TwigFilter('escape_and_nl2br', [$this, 'escape_and_nl2br'], ['needs_environment' => true, 'is_safe' => ['html']]), 155 new TwigFilter('nl2br', [$this, 'nl2br'], ['pre_escape' => 'html', 'is_safe' => ['html']]), 156 new TwigFilter('escape_something', [$this, 'escape_something'], ['is_safe' => ['something']]), 157 new TwigFilter('preserves_safety', [$this, 'preserves_safety'], ['preserves_safety' => ['html']]), 158 new TwigFilter('static_call_string', 'TwigTestExtension::staticCall'), 159 new TwigFilter('static_call_array', ['TwigTestExtension', 'staticCall']), 160 new TwigFilter('magic_call', [$this, 'magicCall']), 161 new TwigFilter('magic_call_string', 'TwigTestExtension::magicStaticCall'), 162 new TwigFilter('magic_call_array', ['TwigTestExtension', 'magicStaticCall']), 163 new TwigFilter('*_path', [$this, 'dynamic_path']), 164 new TwigFilter('*_foo_*_bar', [$this, 'dynamic_foo']), 165 ]; 166 } 167 168 public function getFunctions() 169 { 170 return [ 171 new TwigFunction('§', [$this, '§Function']), 172 new TwigFunction('safe_br', [$this, 'br'], ['is_safe' => ['html']]), 173 new TwigFunction('unsafe_br', [$this, 'br']), 174 new TwigFunction('static_call_string', 'TwigTestExtension::staticCall'), 175 new TwigFunction('static_call_array', ['TwigTestExtension', 'staticCall']), 176 new TwigFunction('*_path', [$this, 'dynamic_path']), 177 new TwigFunction('*_foo_*_bar', [$this, 'dynamic_foo']), 178 ]; 179 } 180 181 public function getTests() 182 { 183 return [ 184 new TwigTest('multi word', [$this, 'is_multi_word']), 185 new TwigTest('test_*', [$this, 'dynamic_test']), 186 ]; 187 } 188 189 public function §Filter($value) 190 { 191 return "§{$value}§"; 192 } 193 194 public function §Function($value) 195 { 196 return "§{$value}§"; 197 } 198 199 /** 200 * nl2br which also escapes, for testing escaper filters. 201 */ 202 public function escape_and_nl2br($env, $value, $sep = '<br />') 203 { 204 return $this->nl2br(twig_escape_filter($env, $value, 'html'), $sep); 205 } 206 207 /** 208 * nl2br only, for testing filters with pre_escape. 209 */ 210 public function nl2br($value, $sep = '<br />') 211 { 212 // not secure if $value contains html tags (not only entities) 213 // don't use 214 return str_replace("\n", "$sep\n", $value); 215 } 216 217 public function dynamic_path($element, $item) 218 { 219 return $element.'/'.$item; 220 } 221 222 public function dynamic_foo($foo, $bar, $item) 223 { 224 return $foo.'/'.$bar.'/'.$item; 225 } 226 227 public function dynamic_test($element, $item) 228 { 229 return $element === $item; 230 } 231 232 public function escape_something($value) 233 { 234 return strtoupper($value); 235 } 236 237 public function preserves_safety($value) 238 { 239 return strtoupper($value); 240 } 241 242 public static function staticCall($value) 243 { 244 return "*$value*"; 245 } 246 247 public function br() 248 { 249 return '<br />'; 250 } 251 252 public function is_multi_word($value) 253 { 254 return false !== strpos($value, ' '); 255 } 256 257 public function __call($method, $arguments) 258 { 259 if ('magicCall' !== $method) { 260 throw new \BadMethodCallException('Unexpected call to __call'); 261 } 262 263 return 'magic_'.$arguments[0]; 264 } 265 266 public static function __callStatic($method, $arguments) 267 { 268 if ('magicStaticCall' !== $method) { 269 throw new \BadMethodCallException('Unexpected call to __callStatic'); 270 } 271 272 return 'static_magic_'.$arguments[0]; 273 } 274} 275 276/** 277 * This class is used in tests for the "length" filter and "empty" test. It asserts that __call is not 278 * used to convert such objects to strings. 279 */ 280class MagicCallStub 281{ 282 public function __call($name, $args) 283 { 284 throw new \Exception('__call shall not be called'); 285 } 286} 287 288class ToStringStub 289{ 290 /** 291 * @var string 292 */ 293 private $string; 294 295 public function __construct($string) 296 { 297 $this->string = $string; 298 } 299 300 public function __toString() 301 { 302 return $this->string; 303 } 304} 305 306/** 307 * This class is used in tests for the length filter and empty test to show 308 * that when \Countable is implemented, it is preferred over the __toString() 309 * method. 310 */ 311class CountableStub implements \Countable 312{ 313 private $count; 314 315 public function __construct($count) 316 { 317 $this->count = $count; 318 } 319 320 public function count() 321 { 322 return $this->count; 323 } 324 325 public function __toString() 326 { 327 throw new \Exception('__toString shall not be called on \Countables'); 328 } 329} 330 331/** 332 * This class is used in tests for the length filter. 333 */ 334class IteratorAggregateStub implements \IteratorAggregate 335{ 336 private $data; 337 338 public function __construct(array $data) 339 { 340 $this->data = $data; 341 } 342 343 public function getIterator() 344 { 345 return new \ArrayIterator($this->data); 346 } 347} 348