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\Environment;
13use Twig\Error\RuntimeError;
14use Twig\Extension\SandboxExtension;
15use Twig\Loader\ArrayLoader;
16use Twig\Loader\LoaderInterface;
17use Twig\Loader\SourceContextLoaderInterface;
18use Twig\Node\Expression\GetAttrExpression;
19use Twig\NodeVisitor\NodeVisitorInterface;
20use Twig\Sandbox\SecurityError;
21use Twig\Sandbox\SecurityPolicy;
22use Twig\Template;
23
24class Twig_Tests_TemplateTest extends \PHPUnit\Framework\TestCase
25{
26    /**
27     * @expectedException \LogicException
28     */
29    public function testDisplayBlocksAcceptTemplateOnlyAsBlocks()
30    {
31        $template = $this->getMockForAbstractClass('\Twig\Template', [], '', false);
32        $template->displayBlock('foo', [], ['foo' => [new \stdClass(), 'foo']]);
33    }
34
35    /**
36     * @dataProvider getAttributeExceptions
37     */
38    public function testGetAttributeExceptions($template, $message)
39    {
40        $templates = ['index' => $template];
41        $env = new Environment(new ArrayLoader($templates), ['strict_variables' => true]);
42        $template = $env->load('index');
43
44        $context = [
45            'string' => 'foo',
46            'null' => null,
47            'empty_array' => [],
48            'array' => ['foo' => 'foo'],
49            'array_access' => new Twig_TemplateArrayAccessObject(),
50            'magic_exception' => new Twig_TemplateMagicPropertyObjectWithException(),
51            'object' => new \stdClass(),
52        ];
53
54        try {
55            $template->render($context);
56            $this->fail('Accessing an invalid attribute should throw an exception.');
57        } catch (RuntimeError $e) {
58            $this->assertSame(sprintf($message, 'index'), $e->getMessage());
59        }
60    }
61
62    public function getAttributeExceptions()
63    {
64        return [
65            ['{{ string["a"] }}', 'Impossible to access a key ("a") on a string variable ("foo") in "%s" at line 1.'],
66            ['{{ null["a"] }}', 'Impossible to access a key ("a") on a null variable in "%s" at line 1.'],
67            ['{{ empty_array["a"] }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'],
68            ['{{ array["a"] }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.'],
69            ['{{ array_access["a"] }}', 'Key "a" in object with ArrayAccess of class "Twig_TemplateArrayAccessObject" does not exist in "%s" at line 1.'],
70            ['{{ string.a }}', 'Impossible to access an attribute ("a") on a string variable ("foo") in "%s" at line 1.'],
71            ['{{ string.a() }}', 'Impossible to invoke a method ("a") on a string variable ("foo") in "%s" at line 1.'],
72            ['{{ null.a }}', 'Impossible to access an attribute ("a") on a null variable in "%s" at line 1.'],
73            ['{{ null.a() }}', 'Impossible to invoke a method ("a") on a null variable in "%s" at line 1.'],
74            ['{{ array.a() }}', 'Impossible to invoke a method ("a") on an array in "%s" at line 1.'],
75            ['{{ empty_array.a }}', 'Key "a" does not exist as the array is empty in "%s" at line 1.'],
76            ['{{ array.a }}', 'Key "a" for array with keys "foo" does not exist in "%s" at line 1.'],
77            ['{{ attribute(array, -10) }}', 'Key "-10" for array with keys "foo" does not exist in "%s" at line 1.'],
78            ['{{ array_access.a }}', 'Neither the property "a" nor one of the methods "a()", "geta()"/"isa()" or "__call()" exist and have public access in class "Twig_TemplateArrayAccessObject" in "%s" at line 1.'],
79            ['{% from _self import foo %}{% macro foo(obj) %}{{ obj.missing_method() }}{% endmacro %}{{ foo(array_access) }}', 'Neither the property "missing_method" nor one of the methods "missing_method()", "getmissing_method()"/"ismissing_method()" or "__call()" exist and have public access in class "Twig_TemplateArrayAccessObject" in "%s" at line 1.'],
80            ['{{ magic_exception.test }}', 'An exception has been thrown during the rendering of a template ("Hey! Don\'t try to isset me!") in "%s" at line 1.'],
81            ['{{ object["a"] }}', 'Impossible to access a key "a" on an object of class "stdClass" that does not implement ArrayAccess interface in "%s" at line 1.'],
82        ];
83    }
84
85    /**
86     * @dataProvider getGetAttributeWithSandbox
87     */
88    public function testGetAttributeWithSandbox($object, $item, $allowed)
89    {
90        $twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock());
91        $policy = new SecurityPolicy([], [], [/*method*/], [/*prop*/], []);
92        $twig->addExtension(new SandboxExtension($policy, !$allowed));
93        $template = new Twig_TemplateTest($twig);
94
95        try {
96            $template->getAttribute($object, $item, [], 'any');
97
98            if (!$allowed) {
99                $this->fail();
100            } else {
101                $this->addToAssertionCount(1);
102            }
103        } catch (SecurityError $e) {
104            if ($allowed) {
105                $this->fail();
106            } else {
107                $this->addToAssertionCount(1);
108            }
109
110            $this->assertContains('is not allowed', $e->getMessage());
111        }
112    }
113
114    public function getGetAttributeWithSandbox()
115    {
116        return [
117            [new Twig_TemplatePropertyObject(), 'defined', false],
118            [new Twig_TemplatePropertyObject(), 'defined', true],
119            [new Twig_TemplateMethodObject(), 'defined', false],
120            [new Twig_TemplateMethodObject(), 'defined', true],
121        ];
122    }
123
124    /**
125     * @group legacy
126     */
127    public function testGetAttributeWithTemplateAsObject()
128    {
129        // to be removed in 2.0
130        $twig = new Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock());
131        //$twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface', '\Twig\Loader\SourceContextLoaderInterface')->getMock());
132
133        $template = new Twig_TemplateTest($twig, 'index.twig');
134        $template1 = new Twig_TemplateTest($twig, 'index1.twig');
135
136        $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'string'));
137        $this->assertEquals('some_string', $template->getAttribute($template1, 'string'));
138
139        $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'true'));
140        $this->assertEquals('1', $template->getAttribute($template1, 'true'));
141
142        $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'zero'));
143        $this->assertEquals('0', $template->getAttribute($template1, 'zero'));
144
145        $this->assertNotInstanceof('\Twig\Markup', $template->getAttribute($template1, 'empty'));
146        $this->assertSame('', $template->getAttribute($template1, 'empty'));
147
148        $this->assertFalse($template->getAttribute($template1, 'env', [], Template::ANY_CALL, true));
149        $this->assertFalse($template->getAttribute($template1, 'environment', [], Template::ANY_CALL, true));
150        $this->assertFalse($template->getAttribute($template1, 'getEnvironment', [], Template::METHOD_CALL, true));
151        $this->assertFalse($template->getAttribute($template1, 'displayWithErrorHandling', [], Template::METHOD_CALL, true));
152    }
153
154    /**
155     * @group legacy
156     * @expectedDeprecation Calling "string" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0.
157     * @expectedDeprecation Calling "string" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0.
158     * @expectedDeprecation Calling "true" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0.
159     * @expectedDeprecation Calling "true" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0.
160     * @expectedDeprecation Calling "zero" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0.
161     * @expectedDeprecation Calling "zero" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0.
162     * @expectedDeprecation Calling "empty" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0.
163     * @expectedDeprecation Calling "empty" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0.
164     * @expectedDeprecation Calling "renderBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name") instead).
165     * @expectedDeprecation Calling "displayBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name") instead).
166     * @expectedDeprecation Calling "hasBlock" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use "block("name") is defined" instead).
167     * @expectedDeprecation Calling "render" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index.twig") instead).
168     * @expectedDeprecation Calling "display" on template "index.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index.twig") instead).
169     * @expectedDeprecation Calling "renderBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name", template) instead).
170     * @expectedDeprecation Calling "displayBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use block("name", template) instead).
171     * @expectedDeprecation Calling "hasBlock" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use "block("name", template) is defined" instead).
172     * @expectedDeprecation Calling "render" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index1.twig") instead).
173     * @expectedDeprecation Calling "display" on template "index1.twig" from template "index.twig" is deprecated since version 1.28 and won't be supported anymore in 2.0. Use include("index1.twig") instead).
174     */
175    public function testGetAttributeWithTemplateAsObjectForDeprecations()
176    {
177        // to be removed in 2.0
178        $twig = new Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock());
179        //$twig = new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface', '\Twig\Loader\SourceContextLoaderInterface')->getMock());
180
181        $template = new Twig_TemplateTest($twig, 'index.twig');
182        $template1 = new Twig_TemplateTest($twig, 'index1.twig');
183
184        $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'string'));
185        $this->assertEquals('some_string', $template->getAttribute($template1, 'string'));
186
187        $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'true'));
188        $this->assertEquals('1', $template->getAttribute($template1, 'true'));
189
190        $this->assertInstanceOf('\Twig\Markup', $template->getAttribute($template1, 'zero'));
191        $this->assertEquals('0', $template->getAttribute($template1, 'zero'));
192
193        $this->assertNotInstanceof('\Twig\Markup', $template->getAttribute($template1, 'empty'));
194        $this->assertSame('', $template->getAttribute($template1, 'empty'));
195
196        $blocks = ['name' => [$template1, 'block_name']];
197
198        // trigger some deprecation notice messages to check them with @expectedDeprecation
199        $template->getAttribute($template, 'renderBlock', ['name', [], $blocks]);
200        $template->getAttribute($template, 'displayBlock', ['name', [], $blocks]);
201        $template->getAttribute($template, 'hasBlock', ['name', []]);
202        $template->getAttribute($template, 'render', [[]]);
203        $template->getAttribute($template, 'display', [[]]);
204
205        $template->getAttribute($template1, 'renderBlock', ['name', [], $blocks]);
206        $template->getAttribute($template1, 'displayBlock', ['name', [], $blocks]);
207        $template->getAttribute($template1, 'hasBlock', ['name', []]);
208        $template->getAttribute($template1, 'render', [[]]);
209        $template->getAttribute($template1, 'display', [[]]);
210
211        $this->assertFalse($template->getAttribute($template1, 'env', [], Template::ANY_CALL, true));
212        $this->assertFalse($template->getAttribute($template1, 'environment', [], Template::ANY_CALL, true));
213        $this->assertFalse($template->getAttribute($template1, 'getEnvironment', [], Template::METHOD_CALL, true));
214        $this->assertFalse($template->getAttribute($template1, 'displayWithErrorHandling', [], Template::METHOD_CALL, true));
215    }
216
217    /**
218     * @group legacy
219     * @expectedDeprecation Silent display of undefined block "unknown" in template "index.twig" is deprecated since version 1.29 and will throw an exception in 2.0. Use the "block('unknown') is defined" expression to test for block existence.
220     * @expectedDeprecation Silent display of undefined block "unknown" in template "index.twig" is deprecated since version 1.29 and will throw an exception in 2.0. Use the "block('unknown') is defined" expression to test for block existence.
221     */
222    public function testRenderBlockWithUndefinedBlock()
223    {
224        $twig = new Environment($this->getMockBuilder('Twig_TemplateTestLoaderInterface')->getMock());
225
226        $template = new Twig_TemplateTest($twig, 'index.twig');
227        $template->renderBlock('unknown', []);
228        $template->displayBlock('unknown', []);
229    }
230
231    public function testGetAttributeOnArrayWithConfusableKey()
232    {
233        $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
234
235        $array = ['Zero', 'One', -1 => 'MinusOne', '' => 'EmptyString', '1.5' => 'FloatButString', '01' => 'IntegerButStringWithLeadingZeros'];
236
237        $this->assertSame('Zero', $array[false]);
238        $this->assertSame('One', $array[true]);
239        $this->assertSame('One', $array[1.5]);
240        $this->assertSame('One', $array['1']);
241        $this->assertSame('MinusOne', $array[-1.5]);
242        $this->assertSame('FloatButString', $array['1.5']);
243        $this->assertSame('IntegerButStringWithLeadingZeros', $array['01']);
244        $this->assertSame('EmptyString', $array[null]);
245
246        $this->assertSame('Zero', $template->getAttribute($array, false), 'false is treated as 0 when accessing an array (equals PHP behavior)');
247        $this->assertSame('One', $template->getAttribute($array, true), 'true is treated as 1 when accessing an array (equals PHP behavior)');
248        $this->assertSame('One', $template->getAttribute($array, 1.5), 'float is casted to int when accessing an array (equals PHP behavior)');
249        $this->assertSame('One', $template->getAttribute($array, '1'), '"1" is treated as integer 1 when accessing an array (equals PHP behavior)');
250        $this->assertSame('MinusOne', $template->getAttribute($array, -1.5), 'negative float is casted to int when accessing an array (equals PHP behavior)');
251        $this->assertSame('FloatButString', $template->getAttribute($array, '1.5'), '"1.5" is treated as-is when accessing an array (equals PHP behavior)');
252        $this->assertSame('IntegerButStringWithLeadingZeros', $template->getAttribute($array, '01'), '"01" is treated as-is when accessing an array (equals PHP behavior)');
253        $this->assertSame('EmptyString', $template->getAttribute($array, null), 'null is treated as "" when accessing an array (equals PHP behavior)');
254    }
255
256    /**
257     * @dataProvider getGetAttributeTests
258     */
259    public function testGetAttribute($defined, $value, $object, $item, $arguments, $type)
260    {
261        $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
262
263        $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
264    }
265
266    /**
267     * @dataProvider getGetAttributeTests
268     */
269    public function testGetAttributeStrict($defined, $value, $object, $item, $arguments, $type, $exceptionMessage = null)
270    {
271        $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), ['strict_variables' => true]));
272
273        if ($defined) {
274            $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
275        } else {
276            if (method_exists($this, 'expectException')) {
277                $this->expectException('\Twig\Error\RuntimeError');
278                if (null !== $exceptionMessage) {
279                    $this->expectExceptionMessage($exceptionMessage);
280                }
281            } else {
282                $this->setExpectedException('\Twig\Error\RuntimeError', $exceptionMessage);
283            }
284            $this->assertEquals($value, $template->getAttribute($object, $item, $arguments, $type));
285        }
286    }
287
288    /**
289     * @dataProvider getGetAttributeTests
290     */
291    public function testGetAttributeDefined($defined, $value, $object, $item, $arguments, $type)
292    {
293        $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
294
295        $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true));
296    }
297
298    /**
299     * @dataProvider getGetAttributeTests
300     */
301    public function testGetAttributeDefinedStrict($defined, $value, $object, $item, $arguments, $type)
302    {
303        $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), ['strict_variables' => true]));
304
305        $this->assertEquals($defined, $template->getAttribute($object, $item, $arguments, $type, true));
306    }
307
308    public function testGetAttributeCallExceptions()
309    {
310        $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock()));
311
312        $object = new Twig_TemplateMagicMethodExceptionObject();
313
314        $this->assertNull($template->getAttribute($object, 'foo'));
315    }
316
317    public function getGetAttributeTests()
318    {
319        $array = [
320            'defined' => 'defined',
321            'zero' => 0,
322            'null' => null,
323            '1' => 1,
324            'bar' => true,
325            'baz' => 'baz',
326            '09' => '09',
327            '+4' => '+4',
328        ];
329
330        $objectArray = new Twig_TemplateArrayAccessObject();
331        $arrayObject = new \ArrayObject($array);
332        $stdObject = (object) $array;
333        $magicPropertyObject = new Twig_TemplateMagicPropertyObject();
334        $propertyObject = new Twig_TemplatePropertyObject();
335        $propertyObject1 = new Twig_TemplatePropertyObjectAndIterator();
336        $propertyObject2 = new Twig_TemplatePropertyObjectAndArrayAccess();
337        $propertyObject3 = new Twig_TemplatePropertyObjectDefinedWithUndefinedValue();
338        $methodObject = new Twig_TemplateMethodObject();
339        $magicMethodObject = new Twig_TemplateMagicMethodObject();
340
341        $anyType = Template::ANY_CALL;
342        $methodType = Template::METHOD_CALL;
343        $arrayType = Template::ARRAY_CALL;
344
345        $basicTests = [
346            // array(defined, value, property to fetch)
347            [true,  'defined', 'defined'],
348            [false, null,      'undefined'],
349            [false, null,      'protected'],
350            [true,  0,         'zero'],
351            [true,  1,         1],
352            [true,  1,         1.0],
353            [true,  null,      'null'],
354            [true,  true,      'bar'],
355            [true,  'baz',     'baz'],
356            [true,  '09',      '09'],
357            [true,  '+4',      '+4'],
358        ];
359        $testObjects = [
360            // array(object, type of fetch)
361            [$array,               $arrayType],
362            [$objectArray,         $arrayType],
363            [$arrayObject,         $anyType],
364            [$stdObject,           $anyType],
365            [$magicPropertyObject, $anyType],
366            [$methodObject,        $methodType],
367            [$methodObject,        $anyType],
368            [$propertyObject,      $anyType],
369            [$propertyObject1,     $anyType],
370            [$propertyObject2,     $anyType],
371        ];
372
373        $tests = [];
374        foreach ($testObjects as $testObject) {
375            foreach ($basicTests as $test) {
376                // properties cannot be numbers
377                if (($testObject[0] instanceof \stdClass || $testObject[0] instanceof Twig_TemplatePropertyObject) && is_numeric($test[2])) {
378                    continue;
379                }
380
381                if ('+4' === $test[2] && $methodObject === $testObject[0]) {
382                    continue;
383                }
384
385                $tests[] = [$test[0], $test[1], $testObject[0], $test[2], [], $testObject[1]];
386            }
387        }
388
389        // additional properties tests
390        $tests = array_merge($tests, [
391            [true, null, $propertyObject3, 'foo', [], $anyType],
392        ]);
393
394        // additional method tests
395        $tests = array_merge($tests, [
396            [true, 'defined', $methodObject, 'defined',    [], $methodType],
397            [true, 'defined', $methodObject, 'DEFINED',    [], $methodType],
398            [true, 'defined', $methodObject, 'getDefined', [], $methodType],
399            [true, 'defined', $methodObject, 'GETDEFINED', [], $methodType],
400            [true, 'static',  $methodObject, 'static',     [], $methodType],
401            [true, 'static',  $methodObject, 'getStatic',  [], $methodType],
402
403            [true, '__call_undefined', $magicMethodObject, 'undefined', [], $methodType],
404            [true, '__call_UNDEFINED', $magicMethodObject, 'UNDEFINED', [], $methodType],
405        ]);
406
407        // add the same tests for the any type
408        foreach ($tests as $test) {
409            if ($anyType !== $test[5]) {
410                $test[5] = $anyType;
411                $tests[] = $test;
412            }
413        }
414
415        $methodAndPropObject = new Twig_TemplateMethodAndPropObject();
416
417        // additional method tests
418        $tests = array_merge($tests, [
419            [true, 'a', $methodAndPropObject, 'a', [], $anyType],
420            [true, 'a', $methodAndPropObject, 'a', [], $methodType],
421            [false, null, $methodAndPropObject, 'a', [], $arrayType],
422
423            [true, 'b_prop', $methodAndPropObject, 'b', [], $anyType],
424            [true, 'b', $methodAndPropObject, 'B', [], $anyType],
425            [true, 'b', $methodAndPropObject, 'b', [], $methodType],
426            [true, 'b', $methodAndPropObject, 'B', [], $methodType],
427            [false, null, $methodAndPropObject, 'b', [], $arrayType],
428
429            [false, null, $methodAndPropObject, 'c', [], $anyType],
430            [false, null, $methodAndPropObject, 'c', [], $methodType],
431            [false, null, $methodAndPropObject, 'c', [], $arrayType],
432        ]);
433
434        $arrayAccess = new Twig_TemplateArrayAccess();
435        $tests = array_merge($tests, [
436            [true, ['foo' => 'bar'], $arrayAccess, 'vars', [], $anyType],
437        ]);
438
439        // tests when input is not an array or object
440        $tests = array_merge($tests, [
441            [false, null, 42, 'a', [], $anyType, 'Impossible to access an attribute ("a") on a integer variable ("42") in "index.twig".'],
442            [false, null, 'string', 'a', [], $anyType, 'Impossible to access an attribute ("a") on a string variable ("string") in "index.twig".'],
443            [false, null, [], 'a', [], $anyType, 'Key "a" does not exist as the array is empty in "index.twig".'],
444        ]);
445
446        return $tests;
447    }
448
449    /**
450     * @expectedException \Twig\Error\RuntimeError
451     */
452    public function testGetIsMethods()
453    {
454        $getIsObject = new Twig_TemplateGetIsMethods();
455        $template = new Twig_TemplateTest(new Environment($this->getMockBuilder('\Twig\Loader\LoaderInterface')->getMock(), ['strict_variables' => true]));
456        // first time should not create a cache for "get"
457        $this->assertNull($template->getAttribute($getIsObject, 'get'));
458        // 0 should be in the method cache now, so this should fail
459        $this->assertNull($template->getAttribute($getIsObject, 0));
460    }
461}
462
463class Twig_TemplateTest extends Template
464{
465    private $name;
466
467    public function __construct(Environment $env, $name = 'index.twig')
468    {
469        parent::__construct($env);
470        self::$cache = [];
471        $this->name = $name;
472    }
473
474    public function getZero()
475    {
476        return 0;
477    }
478
479    public function getEmpty()
480    {
481        return '';
482    }
483
484    public function getString()
485    {
486        return 'some_string';
487    }
488
489    public function getTrue()
490    {
491        return true;
492    }
493
494    public function getTemplateName()
495    {
496        return $this->name;
497    }
498
499    public function getDebugInfo()
500    {
501        return [];
502    }
503
504    protected function doGetParent(array $context)
505    {
506        return false;
507    }
508
509    protected function doDisplay(array $context, array $blocks = [])
510    {
511    }
512
513    public function getAttribute($object, $item, array $arguments = [], $type = Template::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false)
514    {
515        if (\function_exists('twig_template_get_attributes')) {
516            return twig_template_get_attributes($this, $object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck);
517        } else {
518            return parent::getAttribute($object, $item, $arguments, $type, $isDefinedTest, $ignoreStrictCheck);
519        }
520    }
521
522    public function block_name($context, array $blocks = [])
523    {
524    }
525}
526
527class Twig_TemplateArrayAccessObject implements \ArrayAccess
528{
529    protected $protected = 'protected';
530
531    public $attributes = [
532        'defined' => 'defined',
533        'zero' => 0,
534        'null' => null,
535        '1' => 1,
536        'bar' => true,
537        'baz' => 'baz',
538        '09' => '09',
539        '+4' => '+4',
540    ];
541
542    public function offsetExists($name)
543    {
544        return \array_key_exists($name, $this->attributes);
545    }
546
547    public function offsetGet($name)
548    {
549        return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;
550    }
551
552    public function offsetSet($name, $value)
553    {
554    }
555
556    public function offsetUnset($name)
557    {
558    }
559}
560
561class Twig_TemplateMagicPropertyObject
562{
563    public $defined = 'defined';
564
565    public $attributes = [
566        'zero' => 0,
567        'null' => null,
568        '1' => 1,
569        'bar' => true,
570        'baz' => 'baz',
571        '09' => '09',
572        '+4' => '+4',
573    ];
574
575    protected $protected = 'protected';
576
577    public function __isset($name)
578    {
579        return \array_key_exists($name, $this->attributes);
580    }
581
582    public function __get($name)
583    {
584        return \array_key_exists($name, $this->attributes) ? $this->attributes[$name] : null;
585    }
586}
587
588class Twig_TemplateMagicPropertyObjectWithException
589{
590    public function __isset($key)
591    {
592        throw new \Exception('Hey! Don\'t try to isset me!');
593    }
594}
595
596class Twig_TemplatePropertyObject
597{
598    public $defined = 'defined';
599    public $zero = 0;
600    public $null = null;
601    public $bar = true;
602    public $baz = 'baz';
603
604    protected $protected = 'protected';
605}
606
607class Twig_TemplatePropertyObjectAndIterator extends Twig_TemplatePropertyObject implements \IteratorAggregate
608{
609    public function getIterator()
610    {
611        return new \ArrayIterator(['foo', 'bar']);
612    }
613}
614
615class Twig_TemplatePropertyObjectAndArrayAccess extends Twig_TemplatePropertyObject implements \ArrayAccess
616{
617    private $data = [
618        'defined' => 'defined',
619        'zero' => 0,
620        'null' => null,
621        'bar' => true,
622        'foo' => true,
623        'baz' => 'baz',
624        'baf' => 'baf',
625    ];
626
627    public function offsetExists($offset)
628    {
629        return \array_key_exists($offset, $this->data);
630    }
631
632    public function offsetGet($offset)
633    {
634        return $this->offsetExists($offset) ? $this->data[$offset] : 'n/a';
635    }
636
637    public function offsetSet($offset, $value)
638    {
639    }
640
641    public function offsetUnset($offset)
642    {
643    }
644}
645
646class Twig_TemplatePropertyObjectDefinedWithUndefinedValue
647{
648    public $foo;
649
650    public function __construct()
651    {
652        $this->foo = @$notExist;
653    }
654}
655
656class Twig_TemplateMethodObject
657{
658    public function getDefined()
659    {
660        return 'defined';
661    }
662
663    public function get1()
664    {
665        return 1;
666    }
667
668    public function get09()
669    {
670        return '09';
671    }
672
673    public function getZero()
674    {
675        return 0;
676    }
677
678    public function getNull()
679    {
680    }
681
682    public function isBar()
683    {
684        return true;
685    }
686
687    public function isBaz()
688    {
689        return 'should never be returned';
690    }
691
692    public function getBaz()
693    {
694        return 'baz';
695    }
696
697    protected function getProtected()
698    {
699        return 'protected';
700    }
701
702    public static function getStatic()
703    {
704        return 'static';
705    }
706}
707
708class Twig_TemplateGetIsMethods
709{
710    public function get()
711    {
712    }
713
714    public function is()
715    {
716    }
717}
718
719class Twig_TemplateMethodAndPropObject
720{
721    private $a = 'a_prop';
722
723    public function getA()
724    {
725        return 'a';
726    }
727
728    public $b = 'b_prop';
729
730    public function getB()
731    {
732        return 'b';
733    }
734
735    private $c = 'c_prop';
736
737    private function getC()
738    {
739        return 'c';
740    }
741}
742
743class Twig_TemplateArrayAccess implements \ArrayAccess
744{
745    public $vars = [
746        'foo' => 'bar',
747    ];
748    private $children = [];
749
750    public function offsetExists($offset)
751    {
752        return \array_key_exists($offset, $this->children);
753    }
754
755    public function offsetGet($offset)
756    {
757        return $this->children[$offset];
758    }
759
760    public function offsetSet($offset, $value)
761    {
762        $this->children[$offset] = $value;
763    }
764
765    public function offsetUnset($offset)
766    {
767        unset($this->children[$offset]);
768    }
769}
770
771class Twig_TemplateMagicMethodObject
772{
773    public function __call($method, $arguments)
774    {
775        return '__call_'.$method;
776    }
777}
778
779class Twig_TemplateMagicMethodExceptionObject
780{
781    public function __call($method, $arguments)
782    {
783        throw new \BadMethodCallException(sprintf('Unknown method "%s".', $method));
784    }
785}
786
787class CExtDisablingNodeVisitor implements NodeVisitorInterface
788{
789    public function enterNode(Twig_NodeInterface $node, Environment $env)
790    {
791        if ($node instanceof GetAttrExpression) {
792            $node->setAttribute('disable_c_ext', true);
793        }
794
795        return $node;
796    }
797
798    public function leaveNode(Twig_NodeInterface $node, Environment $env)
799    {
800        return $node;
801    }
802
803    public function getPriority()
804    {
805        return 0;
806    }
807}
808
809// to be removed in 2.0
810interface Twig_TemplateTestLoaderInterface extends LoaderInterface, SourceContextLoaderInterface
811{
812}
813