Permalink
Browse files

fixed the filesystem loader with relative paths

  • Loading branch information...
fabpot committed Oct 19, 2016
1 parent 74c1a5d commit a343c92805c3a9b0f982b19914dc301bb6d37f3a
Showing with 86 additions and 28 deletions.
  1. +1 −0 CHANGELOG
  2. +15 −0 doc/api.rst
  3. +42 −23 lib/Twig/Loader/Filesystem.php
  4. +28 −5 test/Twig/Tests/Loader/FilesystemTest.php
View
@@ -1,5 +1,6 @@
* 1.27.0 (2016-XX-XX)
* fixed the filesystem loader with relative paths
* deprecated Twig_Node::getLine() in favor of Twig_Node::getTemplateLine()
* deprecated Twig_Template::getSource() in favor of Twig_Template::getSourceContext()
* deprecated Twig_Node::getFilename() in favor of Twig_Node::getTemplateName()
View
@@ -156,6 +156,9 @@ Here is a list of the built-in loaders Twig provides:
.. versionadded:: 1.10
The ``prependPath()`` and support for namespaces were added in Twig 1.10.
.. versionadded:: 1.27
Relative paths support was added in Twig 1.27.
``Twig_Loader_Filesystem`` loads templates from the file system. This loader
can find templates in folders on the file system and is the preferred way to
load them::
@@ -190,6 +193,18 @@ Namespaced templates can be accessed via the special
$twig->render('@admin/index.html', array());
``Twig_Loader_Filesystem`` support absolute and relative paths. Using relative
paths is preferred as it makes the cache keys independent of the project root
directory (for instance, it allows warming the cache from a build server where
the directory might be different from the one used on production servers)::
$loader = new Twig_Loader_Filesystem('templates', getcwd().'/..');
.. note::
When not passing the root path as a second argument, Twig uses ``getcwd()``
for relative paths.
``Twig_Loader_Array``
.....................
@@ -23,13 +23,21 @@ class Twig_Loader_Filesystem implements Twig_LoaderInterface, Twig_ExistsLoaderI
protected $cache = array();
protected $errorCache = array();
private $rootPath;
/**
* Constructor.
*
* @param string|array $paths A path or an array of paths where to look for templates
* @param string|array $paths A path or an array of paths where to look for templates
* @param string|null $rootPath The root path common to all relative paths (null for getcwd())
*/
public function __construct($paths = array())
public function __construct($paths = array(), $rootPath = null)
{
$this->rootPath = (null === $rootPath ? getcwd() : $rootPath).DIRECTORY_SEPARATOR;
if (false !== $realPath = realpath($rootPath)) {
$this->rootPath = $realPath.DIRECTORY_SEPARATOR;
}
if ($paths) {
$this->setPaths($paths);
}
@@ -81,7 +89,7 @@ public function setPaths($paths, $namespace = self::MAIN_NAMESPACE)
* Adds a path where templates are stored.
*
* @param string $path A path where to look for templates
* @param string $namespace A path name
* @param string $namespace A path namespace
*
* @throws Twig_Error_Loader
*/
@@ -90,8 +98,9 @@ public function addPath($path, $namespace = self::MAIN_NAMESPACE)
// invalidate the cache
$this->cache = $this->errorCache = array();
if (!is_dir($path)) {
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
$checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;
if (!is_dir($checkPath)) {
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
}
$this->paths[$namespace][] = rtrim($path, '/\\');
@@ -101,7 +110,7 @@ public function addPath($path, $namespace = self::MAIN_NAMESPACE)
* Prepends a path where templates are stored.
*
* @param string $path A path where to look for templates
* @param string $namespace A path name
* @param string $namespace A path namespace
*
* @throws Twig_Error_Loader
*/
@@ -110,8 +119,9 @@ public function prependPath($path, $namespace = self::MAIN_NAMESPACE)
// invalidate the cache
$this->cache = $this->errorCache = array();
if (!is_dir($path)) {
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist.', $path));
$checkPath = $this->isAbsolutePath($path) ? $path : $this->rootPath.$path;
if (!is_dir($checkPath)) {
throw new Twig_Error_Loader(sprintf('The "%s" directory does not exist ("%s").', $path, $checkPath));
}
$path = rtrim($path, '/\\');
@@ -146,7 +156,13 @@ public function getSourceContext($name)
*/
public function getCacheKey($name)
{
return $this->findTemplate($name);
$path = $this->findTemplate($name);
$len = strlen($this->rootPath);
if (0 === strncmp($this->rootPath, $path, $len)) {
return substr($path, $len);
}
return $path;
}
/**
@@ -207,8 +223,16 @@ protected function findTemplate($name)
}
foreach ($this->paths[$namespace] as $path) {
if (!$this->isAbsolutePath($path)) {
$path = $this->rootPath.'/'.$path;
}
if (is_file($path.'/'.$shortname)) {
return $this->cache[$name] = $this->normalizePath($path.'/'.$shortname);
if (false !== $realpath = realpath($path.'/'.$shortname)) {
return $this->cache[$name] = $realpath;
}
return $this->cache[$name] = $path.'/'.$shortname;
}
}
@@ -264,19 +288,14 @@ protected function validateName($name)
}
}
private function normalizePath($path)
private function isAbsolutePath($file)
{
$parts = explode('/', str_replace('\\', '/', $path));
$hasProto = false !== strpos($path, '://');
$new = array();
foreach ($parts as $i => $part) {
if ('..' === $part) {
array_pop($new);
} elseif ('.' !== $part && ('' !== $part || 0 === $i || $hasProto && $i < 3)) {
$new[] = $part;
}
}
return implode('/', $new);
return strspn($file, '/\\', 0, 1)
|| (strlen($file) > 3 && ctype_alpha($file[0])
&& substr($file, 1, 1) === ':'
&& strspn($file, '/\\', 2, 1)
)
|| null !== parse_url($file, PHP_URL_SCHEME)
;
}
}
@@ -62,9 +62,9 @@ public function getSecurityTests()
/**
* @dataProvider getBasePaths
*/
public function testPaths($basePath)
public function testPaths($basePath, $cacheKey, $rootPath)
{
$loader = new Twig_Loader_Filesystem(array($basePath.'/normal', $basePath.'/normal_bis'));
$loader = new Twig_Loader_Filesystem(array($basePath.'/normal', $basePath.'/normal_bis'), $rootPath);
$loader->setPaths(array($basePath.'/named', $basePath.'/named_bis'), 'named');
$loader->addPath($basePath.'/named_ter', 'named');
$loader->addPath($basePath.'/normal_ter');
@@ -87,7 +87,7 @@ public function testPaths($basePath)
), $loader->getPaths('named'));
// do not use realpath here as it would make the test unuseful
$this->assertEquals(str_replace('\\', '/', $basePath.'/named_quater/named_absolute.html'), str_replace('\\', '/', $loader->getCacheKey('@named/named_absolute.html')));
$this->assertEquals($cacheKey, str_replace('\\', '/', $loader->getCacheKey('@named/named_absolute.html')));
$this->assertEquals("path (final)\n", $loader->getSource('index.html'));
$this->assertEquals("path (final)\n", $loader->getSource('@__main__/index.html'));
$this->assertEquals("named path (final)\n", $loader->getSource('@named/index.html'));
@@ -96,8 +96,31 @@ public function testPaths($basePath)
public function getBasePaths()
{
return array(
array(dirname(__FILE__).'/Fixtures'),
array('test/Twig/Tests/Loader/Fixtures'),
array(
dirname(__FILE__).'/Fixtures',
'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html',
null,
),
array(
dirname(__FILE__).'/Fixtures/../Fixtures',
'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html',
null,
),
array(
'test/Twig/Tests/Loader/Fixtures',
'test/Twig/Tests/Loader/Fixtures/named_quater/named_absolute.html',
getcwd(),
),
array(
'Fixtures',
'Fixtures/named_quater/named_absolute.html',
getcwd().'/test/Twig/Tests/Loader',
),
array(
'Fixtures',
'Fixtures/named_quater/named_absolute.html',
getcwd().'/test/../test/Twig/Tests/Loader',
),
);
}

0 comments on commit a343c92

Please sign in to comment.