Filesystem.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) 2009 Fabien Potencier
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. /**
  11. * Loads template from the filesystem.
  12. *
  13. * @author Fabien Potencier <fabien@symfony.com>
  14. */
  15. class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderInterface
  16. {
  17. /** Identifier of the main namespace. */
  18. const MAIN_NAMESPACE = '__main__';
  19. protected $paths = array();
  20. protected $cache = array();
  21. protected $errorCache = array();
  22. /**
  23. * Constructor.
  24. *
  25. * @param string|array $paths A path or an array of paths where to look for templates
  26. */
  27. public function __construct($paths = array())
  28. {
  29. if ($paths) {
  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 = self::MAIN_NAMESPACE)
  41. {
  42. return isset($this->paths[$namespace]) ? $this->paths[$namespace] : array();
  43. }
  44. /**
  45. * Returns the path namespaces.
  46. *
  47. * The main namespace is always defined.
  48. *
  49. * @return array The array of defined namespaces
  50. */
  51. public function getNamespaces()
  52. {
  53. return array_keys($this->paths);
  54. }
  55. /**
  56. * Sets the paths where templates are stored.
  57. *
  58. * @param string|array $paths A path or an array of paths where to look for templates
  59. * @param string $namespace A path namespace
  60. */
  61. public function setPaths($paths, $namespace = self::MAIN_NAMESPACE)
  62. {
  63. if (!is_array($paths)) {
  64. $paths = array($paths);
  65. }
  66. $this->paths[$namespace] = array();
  67. foreach ($paths as $path) {
  68. $this->addPath($path, $namespace);
  69. }
  70. }
  71. /**
  72. * Adds a path where templates are stored.
  73. *
  74. * @param string $path A path where to look for templates
  75. * @param string $namespace A path name
  76. *
  77. * @throws Twig_Error_Loader
  78. */
  79. public function addPath($path, $namespace = self::MAIN_NAMESPACE)
  80. {
  81. // invalidate the cache
  82. $this->cache = $this->errorCache = array();
  83. if (!is_dir($path)) {
  84. throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
  85. }
  86. $this->paths[$namespace][] = rtrim($path, '/\\');
  87. }
  88. /**
  89. * Prepends a path where templates are stored.
  90. *
  91. * @param string $path A path where to look for templates
  92. * @param string $namespace A path name
  93. *
  94. * @throws Twig_Error_Loader
  95. */
  96. public function prependPath($path, $namespace = self::MAIN_NAMESPACE)
  97. {
  98. // invalidate the cache
  99. $this->cache = $this->errorCache = array();
  100. if (!is_dir($path)) {
  101. throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
  102. }
  103. $path = rtrim($path, '/\\');
  104. if (!isset($this->paths[$namespace])) {
  105. $this->paths[$namespace][] = $path;
  106. } else {
  107. array_unshift($this->paths[$namespace], $path);
  108. }
  109. }
  110. /**
  111. * {@inheritdoc}
  112. */
  113. public function getSource($name)
  114. {
  115. return file_get_contents($this->findTemplate($name));
  116. }
  117. /**
  118. * {@inheritdoc}
  119. */
  120. public function getCacheKey($name)
  121. {
  122. return $this->findTemplate($name);
  123. }
  124. /**
  125. * {@inheritdoc}
  126. */
  127. public function exists($name)
  128. {
  129. $name = $this->normalizeName($name);
  130. if (isset($this->cache[$name])) {
  131. return true;
  132. }
  133. try {
  134. return false !== $this->findTemplate($name, false);
  135. } catch (Twig_Error_Loader $exception) {
  136. return false;
  137. }
  138. }
  139. /**
  140. * {@inheritdoc}
  141. */
  142. public function isFresh($name, $time)
  143. {
  144. return filemtime($this->findTemplate($name)) <= $time;
  145. }
  146. protected function findTemplate($name)
  147. {
  148. $throw = func_num_args() > 1 ? func_get_arg(1) : true;
  149. $name = $this->normalizeName($name);
  150. if (isset($this->cache[$name])) {
  151. return $this->cache[$name];
  152. }
  153. if (isset($this->errorCache[$name])) {
  154. if (!$throw) {
  155. return false;
  156. }
  157. throw new Twig_Error_Loader($this->errorCache[$name]);
  158. }
  159. $this->validateName($name);
  160. list($namespace, $shortname) = $this->parseName($name);
  161. if (!isset($this->paths[$namespace])) {
  162. $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace);
  163. if (!$throw) {
  164. return false;
  165. }
  166. throw new Twig_Error_Loader($this->errorCache[$name]);
  167. }
  168. foreach ($this->paths[$namespace] as $path) {
  169. if (is_file($path.'/'.$shortname)) {
  170. if (false !== $realpath = realpath($path.'/'.$shortname)) {
  171. return $this->cache[$name] = $realpath;
  172. }
  173. return $this->cache[$name] = $path.'/'.$shortname;
  174. }
  175. }
  176. $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace]));
  177. if (!$throw) {
  178. return false;
  179. }
  180. throw new Twig_Error_Loader($this->errorCache[$name]);
  181. }
  182. protected function parseName($name, $default = self::MAIN_NAMESPACE)
  183. {
  184. if (isset($name[0]) && '@' == $name[0]) {
  185. if (false === $pos = strpos($name, '/')) {
  186. throw new Twig_Error_Loader(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
  187. }
  188. $namespace = substr($name, 1, $pos - 1);
  189. $shortname = substr($name, $pos + 1);
  190. return array($namespace, $shortname);
  191. }
  192. return array($default, $name);
  193. }
  194. protected function normalizeName($name)
  195. {
  196. return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
  197. }
  198. protected function validateName($name)
  199. {
  200. if (false !== strpos($name, "\0")) {
  201. throw new Twig_Error_Loader('A template name cannot contain NUL bytes.');
  202. }
  203. $name = ltrim($name, '/');
  204. $parts = explode('/', $name);
  205. $level = 0;
  206. foreach ($parts as $part) {
  207. if ('..' === $part) {
  208. --$level;
  209. } elseif ('.' !== $part) {
  210. ++$level;
  211. }
  212. if ($level < 0) {
  213. throw new Twig_Error_Loader(sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
  214. }
  215. }
  216. }
  217. }