Project

General

Profile

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
}
(3-3/4)