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\LoaderError;
14use Twig\Loader\FilesystemLoader;
15
16class Twig_Tests_Loader_FilesystemTest extends \PHPUnit\Framework\TestCase
17{
18    public function testGetSourceContext()
19    {
20        $path = __DIR__.'/../Fixtures';
21        $loader = new FilesystemLoader([$path]);
22        $this->assertEquals('errors/index.html', $loader->getSourceContext('errors/index.html')->getName());
23        $this->assertEquals(realpath($path.'/errors/index.html'), realpath($loader->getSourceContext('errors/index.html')->getPath()));
24    }
25
26    /**
27     * @dataProvider getSecurityTests
28     */
29    public function testSecurity($template)
30    {
31        $loader = new FilesystemLoader([__DIR__.'/../Fixtures']);
32
33        try {
34            $loader->getCacheKey($template);
35            $this->fail();
36        } catch (LoaderError $e) {
37            $this->assertNotContains('Unable to find template', $e->getMessage());
38        }
39    }
40
41    public function getSecurityTests()
42    {
43        return [
44            ["AutoloaderTest\0.php"],
45            ['..\\AutoloaderTest.php'],
46            ['..\\\\\\AutoloaderTest.php'],
47            ['../AutoloaderTest.php'],
48            ['..////AutoloaderTest.php'],
49            ['./../AutoloaderTest.php'],
50            ['.\\..\\AutoloaderTest.php'],
51            ['././././././../AutoloaderTest.php'],
52            ['.\\./.\\./.\\./../AutoloaderTest.php'],
53            ['foo/../../AutoloaderTest.php'],
54            ['foo\\..\\..\\AutoloaderTest.php'],
55            ['foo/../bar/../../AutoloaderTest.php'],
56            ['foo/bar/../../../AutoloaderTest.php'],
57            ['filters/../../AutoloaderTest.php'],
58            ['filters//..//..//AutoloaderTest.php'],
59            ['filters\\..\\..\\AutoloaderTest.php'],
60            ['filters\\\\..\\\\..\\\\AutoloaderTest.php'],
61            ['filters\\//../\\/\\..\\AutoloaderTest.php'],
62            ['/../AutoloaderTest.php'],
63        ];
64    }
65
66    /**
67     * @dataProvider getBasePaths
68     */
69    public function testPaths($basePath, $cacheKey, $rootPath)
70    {
71        $loader = new FilesystemLoader([$basePath.'/normal', $basePath.'/normal_bis'], $rootPath);
72        $loader->setPaths([$basePath.'/named', $basePath.'/named_bis'], 'named');
73        $loader->addPath($basePath.'/named_ter', 'named');
74        $loader->addPath($basePath.'/normal_ter');
75        $loader->prependPath($basePath.'/normal_final');
76        $loader->prependPath($basePath.'/named/../named_quater', 'named');
77        $loader->prependPath($basePath.'/named_final', 'named');
78
79        $this->assertEquals([
80            $basePath.'/normal_final',
81            $basePath.'/normal',
82            $basePath.'/normal_bis',
83            $basePath.'/normal_ter',
84        ], $loader->getPaths());
85        $this->assertEquals([
86            $basePath.'/named_final',
87            $basePath.'/named/../named_quater',
88            $basePath.'/named',
89            $basePath.'/named_bis',
90            $basePath.'/named_ter',
91        ], $loader->getPaths('named'));
92
93        // do not use realpath here as it would make the test unuseful
94        $this->assertEquals($cacheKey, str_replace('\\', '/', $loader->getCacheKey('@named/named_absolute.html')));
95        $this->assertEquals("path (final)\n", $loader->getSourceContext('index.html')->getCode());
96        $this->assertEquals("path (final)\n", $loader->getSourceContext('@__main__/index.html')->getCode());
97        $this->assertEquals("named path (final)\n", $loader->getSourceContext('@named/index.html')->getCode());
98    }
99
100    public function getBasePaths()
101    {
102        return [
103            [
104                __DIR__.'/Fixtures',
105                'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html',
106                null,
107            ],
108            [
109                __DIR__.'/Fixtures/../Fixtures',
110                'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html',
111                null,
112            ],
113            [
114                'test/Twig/Tests/Loader/Fixtures',
115                'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html',
116                getcwd(),
117            ],
118            [
119                'Fixtures',
120                'Fixtures/named_quater/named_absolute.html',
121                getcwd().'/test/Twig/Tests/Loader',
122            ],
123            [
124                'Fixtures',
125                'Fixtures/named_quater/named_absolute.html',
126                getcwd().'/test/../test/Twig/Tests/Loader',
127            ],
128        ];
129    }
130
131    public function testEmptyConstructor()
132    {
133        $loader = new FilesystemLoader();
134        $this->assertEquals([], $loader->getPaths());
135    }
136
137    public function testGetNamespaces()
138    {
139        $loader = new FilesystemLoader(sys_get_temp_dir());
140        $this->assertEquals([FilesystemLoader::MAIN_NAMESPACE], $loader->getNamespaces());
141
142        $loader->addPath(sys_get_temp_dir(), 'named');
143        $this->assertEquals([FilesystemLoader::MAIN_NAMESPACE, 'named'], $loader->getNamespaces());
144    }
145
146    public function testFindTemplateExceptionNamespace()
147    {
148        $basePath = __DIR__.'/Fixtures';
149
150        $loader = new FilesystemLoader([$basePath.'/normal']);
151        $loader->addPath($basePath.'/named', 'named');
152
153        try {
154            $loader->getSourceContext('@named/nowhere.html');
155        } catch (\Exception $e) {
156            $this->assertInstanceOf('\Twig\Error\LoaderError', $e);
157            $this->assertContains('Unable to find template "@named/nowhere.html"', $e->getMessage());
158        }
159    }
160
161    public function testFindTemplateWithCache()
162    {
163        $basePath = __DIR__.'/Fixtures';
164
165        $loader = new FilesystemLoader([$basePath.'/normal']);
166        $loader->addPath($basePath.'/named', 'named');
167
168        // prime the cache for index.html in the named namespace
169        $namedSource = $loader->getSourceContext('@named/index.html')->getCode();
170        $this->assertEquals("named path\n", $namedSource);
171
172        // get index.html from the main namespace
173        $this->assertEquals("path\n", $loader->getSourceContext('index.html')->getCode());
174    }
175
176    public function testLoadTemplateAndRenderBlockWithCache()
177    {
178        $loader = new FilesystemLoader([]);
179        $loader->addPath(__DIR__.'/Fixtures/themes/theme2');
180        $loader->addPath(__DIR__.'/Fixtures/themes/theme1');
181        $loader->addPath(__DIR__.'/Fixtures/themes/theme1', 'default_theme');
182
183        $twig = new Environment($loader);
184
185        $template = $twig->load('blocks.html.twig');
186        $this->assertSame('block from theme 1', $template->renderBlock('b1', []));
187
188        $template = $twig->load('blocks.html.twig');
189        $this->assertSame('block from theme 2', $template->renderBlock('b2', []));
190    }
191
192    public function getArrayInheritanceTests()
193    {
194        return [
195            'valid array inheritance' => ['array_inheritance_valid_parent.html.twig'],
196            'array inheritance with null first template' => ['array_inheritance_null_parent.html.twig'],
197            'array inheritance with empty first template' => ['array_inheritance_empty_parent.html.twig'],
198            'array inheritance with non-existent first template' => ['array_inheritance_nonexistent_parent.html.twig'],
199        ];
200    }
201
202    /**
203     * @dataProvider getArrayInheritanceTests
204     *
205     * @param $templateName string Template name with array inheritance
206     */
207    public function testArrayInheritance($templateName)
208    {
209        $loader = new FilesystemLoader([]);
210        $loader->addPath(__DIR__.'/Fixtures/inheritance');
211
212        $twig = new Environment($loader);
213
214        $template = $twig->load($templateName);
215        $this->assertSame('VALID Child', $template->renderBlock('body', []));
216    }
217
218    /**
219     * @requires PHP 5.3
220     */
221    public function testLoadTemplateFromPhar()
222    {
223        $loader = new FilesystemLoader([]);
224        // phar-sample.phar was created with the following script:
225        // $f = new Phar('phar-test.phar');
226        // $f->addFromString('hello.twig', 'hello from phar');
227        $loader->addPath('phar://'.__DIR__.'/Fixtures/phar/phar-sample.phar');
228        $this->assertSame('hello from phar', $loader->getSourceContext('hello.twig')->getCode());
229    }
230
231    public function testTemplateExistsAlwaysReturnsBool()
232    {
233        $loader = new FilesystemLoader([]);
234        $this->assertFalse($loader->exists("foo\0.twig"));
235        $this->assertFalse($loader->exists('../foo.twig'));
236        $this->assertFalse($loader->exists('@foo'));
237        $this->assertFalse($loader->exists('foo'));
238        $this->assertFalse($loader->exists('@foo/bar.twig'));
239
240        $loader->addPath(__DIR__.'/Fixtures/normal');
241        $this->assertTrue($loader->exists('index.html'));
242        $loader->addPath(__DIR__.'/Fixtures/normal', 'foo');
243        $this->assertTrue($loader->exists('@foo/index.html'));
244    }
245}
246