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
 * @author Fabien Potencier <fabien@symfony.com>
16
 */
17
class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
18
{
19
    /** Identifier of the main namespace. */
20
    const MAIN_NAMESPACE = '__main__';
21

    
22
    protected $paths = array();
23
    protected $cache = array();
24
    protected $errorCache = array();
25

    
26
    /**
27
     * Constructor.
28
     *
29
     * @param string|array $paths A path or an array of paths where to look for templates
30
     */
31
    public function __construct($paths = array())
32
    {
33
        if ($paths) {
34
            $this->setPaths($paths);
35
        }
36
    }
37

    
38
    /**
39
     * Returns the paths to the templates.
40
     *
41
     * @param string $namespace A path namespace
42
     *
43
     * @return array The array of paths where to look for templates
44
     */
45
    public function getPaths($namespace = self::MAIN_NAMESPACE)
46
    {
47
        return isset($this->paths[$namespace]) ? $this->paths[$namespace] : array();
48
    }
49

    
50
    /**
51
     * Returns the path namespaces.
52
     *
53
     * The main namespace is always defined.
54
     *
55
     * @return array The array of defined namespaces
56
     */
57
    public function getNamespaces()
58
    {
59
        return array_keys($this->paths);
60
    }
61

    
62
    /**
63
     * Sets the paths where templates are stored.
64
     *
65
     * @param string|array $paths     A path or an array of paths where to look for templates
66
     * @param string       $namespace A path namespace
67
     */
68
    public function setPaths($paths, $namespace = self::MAIN_NAMESPACE)
69
    {
70
        if (!is_array($paths)) {
71
            $paths = array($paths);
72
        }
73

    
74
        $this->paths[$namespace] = array();
75
        foreach ($paths as $path) {
76
            $this->addPath($path, $namespace);
77
        }
78
    }
79

    
80
    /**
81
     * Adds a path where templates are stored.
82
     *
83
     * @param string $path      A path where to look for templates
84
     * @param string $namespace A path name
85
     *
86
     * @throws Twig_Error_Loader
87
     */
88
    public function addPath($path, $namespace = self::MAIN_NAMESPACE)
89
    {
90
        // invalidate the cache
91
        $this->cache = $this->errorCache = array();
92

    
93
        if (!is_dir($path)) {
94
            throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
95
        }
96

    
97
        $this->paths[$namespace][] = rtrim($path, '/\\');
98
    }
99

    
100
    /**
101
     * Prepends a path where templates are stored.
102
     *
103
     * @param string $path      A path where to look for templates
104
     * @param string $namespace A path name
105
     *
106
     * @throws Twig_Error_Loader
107
     */
108
    public function prependPath($path, $namespace = self::MAIN_NAMESPACE)
109
    {
110
        // invalidate the cache
111
        $this->cache = $this->errorCache = array();
112

    
113
        if (!is_dir($path)) {
114
            throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
115
        }
116

    
117
        $path = rtrim($path, '/\\');
118

    
119
        if (!isset($this->paths[$namespace])) {
120
            $this->paths[$namespace][] = $path;
121
        } else {
122
            array_unshift($this->paths[$namespace], $path);
123
        }
124
    }
125

    
126
    /**
127
     * {@inheritdoc}
128
     */
129
    public function getSource($name)
130
    {
131
        return file_get_contents($this->findTemplate($name));
132
    }
133

    
134
    /**
135
     * {@inheritdoc}
136
     */
137
    public function getCacheKey($name)
138
    {
139
        return $this->findTemplate($name);
140
    }
141

    
142
    /**
143
     * {@inheritdoc}
144
     */
145
    public function exists($name)
146
    {
147
        $name = $this->normalizeName($name);
148

    
149
        if (isset($this->cache[$name])) {
150
            return true;
151
        }
152

    
153
        try {
154
            return false !== $this->findTemplate($name, false);
155
        } catch (Twig_Error_Loader $exception) {
156
            return false;
157
        }
158
    }
159

    
160
    /**
161
     * {@inheritdoc}
162
     */
163
    public function isFresh($name, $time)
164
    {
165
        return filemtime($this->findTemplate($name)) <= $time;
166
    }
167

    
168
    protected function findTemplate($name)
169
    {
170
        $throw = func_num_args() > 1 ? func_get_arg(1) : true;
171
        $name = $this->normalizeName($name);
172

    
173
        if (isset($this->cache[$name])) {
174
            return $this->cache[$name];
175
        }
176

    
177
        if (isset($this->errorCache[$name])) {
178
            if (!$throw) {
179
                return false;
180
            }
181

    
182
            throw new Twig_Error_Loader($this->errorCache[$name]);
183
        }
184

    
185
        $this->validateName($name);
186

    
187
        list($namespace, $shortname) = $this->parseName($name);
188

    
189
        if (!isset($this->paths[$namespace])) {
190
            $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace);
191

    
192
            if (!$throw) {
193
                return false;
194
            }
195

    
196
            throw new Twig_Error_Loader($this->errorCache[$name]);
197
        }
198

    
199
        foreach ($this->paths[$namespace] as $path) {
200
            if (is_file($path.'/'.$shortname)) {
201
                if (false !== $realpath = realpath($path.'/'.$shortname)) {
202
                    return $this->cache[$name] = $realpath;
203
                }
204

    
205
                return $this->cache[$name] = $path.'/'.$shortname;
206
            }
207
        }
208

    
209
        $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace]));
210

    
211
        if (!$throw) {
212
            return false;
213
        }
214

    
215
        throw new Twig_Error_Loader($this->errorCache[$name]);
216
    }
217

    
218
    protected function parseName($name, $default = self::MAIN_NAMESPACE)
219
    {
220
        if (isset($name[0]) && '@' == $name[0]) {
221
            if (false === $pos = strpos($name, '/')) {
222
                throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
223
            }
224

    
225
            $namespace = substr($name, 1, $pos - 1);
226
            $shortname = substr($name, $pos + 1);
227

    
228
            return array($namespace, $shortname);
229
        }
230

    
231
        return array($default, $name);
232
    }
233

    
234
    protected function normalizeName($name)
235
    {
236
        return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
237
    }
238

    
239
    protected function validateName($name)
240
    {
241
        if (false !== strpos($name, "\0")) {
242
            throw new Twig_Error_Loader('A template name cannot contain NUL bytes.');
243
        }
244

    
245
        $name = ltrim($name, '/');
246
        $parts = explode('/', $name);
247
        $level = 0;
248
        foreach ($parts as $part) {
249
            if ('..' === $part) {
250
                --$level;
251
            } elseif ('.' !== $part) {
252
                ++$level;
253
            }
254

    
255
            if ($level < 0) {
256
                throw new Twig_Error_Loader(sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
257
            }
258
        }
259
    }
260
}
(3-3/4)