vendor/twig/twig/src/Loader/FilesystemLoader.php line 37

Open in your IDE?
  1. <?php
  2. /*
  3. * This file is part of Twig.
  4. *
  5. * (c) 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. namespace Twig\Loader;
  11. use Twig\Error\LoaderError;
  12. use Twig\Source;
  13. /**
  14. * Loads template from the filesystem.
  15. *
  16. * @author Fabien Potencier <fabien@symfony.com>
  17. */
  18. class FilesystemLoader implements LoaderInterface, ExistsLoaderInterface, SourceContextLoaderInterface
  19. {
  20. /** Identifier of the main namespace. */
  21. public const MAIN_NAMESPACE = '__main__';
  22. protected $paths = [];
  23. protected $cache = [];
  24. protected $errorCache = [];
  25. private $rootPath;
  26. /**
  27. * @param string|array $paths A path or an array of paths where to look for templates
  28. * @param string|null $rootPath The root path common to all relative paths (null for getcwd())
  29. */
  30. public function __construct($paths = [], string $rootPath = null)
  31. {
  32. $this->rootPath = (null === $rootPath ? getcwd() : $rootPath).\DIRECTORY_SEPARATOR;
  33. if (null !== $rootPath && false !== ($realPath = realpath($rootPath))) {
  34. $this->rootPath = $realPath.\DIRECTORY_SEPARATOR;
  35. }
  36. if ($paths) {
  37. $this->setPaths($paths);
  38. }
  39. }
  40. /**
  41. * Returns the paths to the templates.
  42. *
  43. * @param string $namespace A path namespace
  44. *
  45. * @return array The array of paths where to look for templates
  46. */
  47. public function getPaths($namespace = self::MAIN_NAMESPACE)
  48. {
  49. return isset($this->paths[$namespace]) ? $this->paths[$namespace] : [];
  50. }
  51. /**
  52. * Returns the path namespaces.
  53. *
  54. * The main namespace is always defined.
  55. *
  56. * @return array The array of defined namespaces
  57. */
  58. public function getNamespaces()
  59. {
  60. return array_keys($this->paths);
  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 = [$paths];
  72. }
  73. $this->paths[$namespace] = [];
  74. foreach ($paths as $path) {
  75. $this->addPath($path, $namespace);
  76. }
  77. }
  78. /**
  79. * Adds a path where templates are stored.
  80. *
  81. * @param string $path A path where to look for templates
  82. * @param string $namespace A path namespace
  83. *
  84. * @throws LoaderError
  85. */
  86. public function addPath($path, $namespace = self::MAIN_NAMESPACE)
  87. {
  88. // invalidate the cache
  89. $this->cache = $this->errorCache = [];
  90. $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;
  91. if (!is_dir($checkPath)) {
  92. throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
  93. }
  94. $this->paths[$namespace][] = rtrim($path, '/\\');
  95. }
  96. /**
  97. * Prepends a path where templates are stored.
  98. *
  99. * @param string $path A path where to look for templates
  100. * @param string $namespace A path namespace
  101. *
  102. * @throws LoaderError
  103. */
  104. public function prependPath($path, $namespace = self::MAIN_NAMESPACE)
  105. {
  106. // invalidate the cache
  107. $this->cache = $this->errorCache = [];
  108. $checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;
  109. if (!is_dir($checkPath)) {
  110. throw new LoaderError(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
  111. }
  112. $path = rtrim($path, '/\\');
  113. if (!isset($this->paths[$namespace])) {
  114. $this->paths[$namespace][] = $path;
  115. } else {
  116. array_unshift($this->paths[$namespace], $path);
  117. }
  118. }
  119. public function getSourceContext($name)
  120. {
  121. if (null === ($path = $this->findTemplate($name)) || false === $path) {
  122. return new Source('', $name, '');
  123. }
  124. return new Source(file_get_contents($path), $name, $path);
  125. }
  126. public function getCacheKey($name)
  127. {
  128. if (null === ($path = $this->findTemplate($name)) || false === $path) {
  129. return '';
  130. }
  131. $len = \strlen($this->rootPath);
  132. if (0 === strncmp($this->rootPath, $path, $len)) {
  133. return substr($path, $len);
  134. }
  135. return $path;
  136. }
  137. public function exists($name)
  138. {
  139. $name = $this->normalizeName($name);
  140. if (isset($this->cache[$name])) {
  141. return true;
  142. }
  143. return null !== ($path = $this->findTemplate($name, false)) && false !== $path;
  144. }
  145. public function isFresh($name, $time)
  146. {
  147. // false support to be removed in 3.0
  148. if (null === ($path = $this->findTemplate($name)) || false === $path) {
  149. return false;
  150. }
  151. return filemtime($path) < $time;
  152. }
  153. /**
  154. * Checks if the template can be found.
  155. *
  156. * In Twig 3.0, findTemplate must return a string or null (returning false won't work anymore).
  157. *
  158. * @param string $name The template name
  159. * @param bool $throw Whether to throw an exception when an error occurs
  160. *
  161. * @return string|false|null The template name or false/null
  162. */
  163. protected function findTemplate($name, $throw = true)
  164. {
  165. $name = $this->normalizeName($name);
  166. if (isset($this->cache[$name])) {
  167. return $this->cache[$name];
  168. }
  169. if (isset($this->errorCache[$name])) {
  170. if (!$throw) {
  171. return false;
  172. }
  173. throw new LoaderError($this->errorCache[$name]);
  174. }
  175. try {
  176. list($namespace, $shortname) = $this->parseName($name);
  177. $this->validateName($shortname);
  178. } catch (LoaderError $e) {
  179. if (!$throw) {
  180. return false;
  181. }
  182. throw $e;
  183. }
  184. if (!isset($this->paths[$namespace])) {
  185. $this->errorCache[$name] = sprintf('There are no registered paths for namespace "%s".', $namespace);
  186. if (!$throw) {
  187. return false;
  188. }
  189. throw new LoaderError($this->errorCache[$name]);
  190. }
  191. foreach ($this->paths[$namespace] as $path) {
  192. if (!$this->isAbsolutePath($path)) {
  193. $path = $this->rootPath.$path;
  194. }
  195. if (is_file($path.'/'.$shortname)) {
  196. if (false !== $realpath = realpath($path.'/'.$shortname)) {
  197. return $this->cache[$name] = $realpath;
  198. }
  199. return $this->cache[$name] = $path.'/'.$shortname;
  200. }
  201. }
  202. $this->errorCache[$name] = sprintf('Unable to find template "%s" (looked into: %s).', $name, implode(', ', $this->paths[$namespace]));
  203. if (!$throw) {
  204. return false;
  205. }
  206. throw new LoaderError($this->errorCache[$name]);
  207. }
  208. private function normalizeName($name)
  209. {
  210. return preg_replace('#/{2,}#', '/', str_replace('\\', '/', (string) $name));
  211. }
  212. private function parseName($name, $default = self::MAIN_NAMESPACE)
  213. {
  214. if (isset($name[0]) && '@' == $name[0]) {
  215. if (false === $pos = strpos($name, '/')) {
  216. throw new LoaderError(sprintf('Malformed namespaced template name "%s" (expecting "@namespace/template_name").', $name));
  217. }
  218. $namespace = substr($name, 1, $pos - 1);
  219. $shortname = substr($name, $pos + 1);
  220. return [$namespace, $shortname];
  221. }
  222. return [$default, $name];
  223. }
  224. private function validateName($name)
  225. {
  226. if (false !== strpos($name, "\0")) {
  227. throw new LoaderError('A template name cannot contain NUL bytes.');
  228. }
  229. $name = ltrim($name, '/');
  230. $parts = explode('/', $name);
  231. $level = 0;
  232. foreach ($parts as $part) {
  233. if ('..' === $part) {
  234. --$level;
  235. } elseif ('.' !== $part) {
  236. ++$level;
  237. }
  238. if ($level < 0) {
  239. throw new LoaderError(sprintf('Looks like you try to load a template outside configured directories (%s).', $name));
  240. }
  241. }
  242. }
  243. private function isAbsolutePath($file)
  244. {
  245. return strspn($file, '/\\', 0, 1)
  246. || (\strlen($file) > 3 && ctype_alpha($file[0])
  247. && ':' === $file[1]
  248. && strspn($file, '/\\', 2, 1)
  249. )
  250. || null !== parse_url($file, \PHP_URL_SCHEME)
  251. ;
  252. }
  253. }
  254. class_alias('Twig\Loader\FilesystemLoader', 'Twig_Loader_Filesystem');