added a relative_path Twig function

This commit is contained in:
Fabien Potencier 2015-01-05 14:25:19 +01:00
parent ee27ed8376
commit 0ec852d79f
5 changed files with 189 additions and 3 deletions

View File

@ -4,7 +4,7 @@ CHANGELOG
2.7.0
-----
* added an HttpFoundation extension (provides the `absolute_url` function)
* added an HttpFoundation extension (provides the `absolute_url` and the `relative_path` functions)
2.5.0
-----

View File

@ -12,7 +12,7 @@
namespace Symfony\Bridge\Twig\Extension;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Asset\Packages;
use Symfony\Component\HttpFoundation\Request;
/**
* Twig extension for the Symfony HttpFoundation component.
@ -35,17 +35,20 @@ class HttpFoundationExtension extends \Twig_Extension
{
return array(
new \Twig_SimpleFunction('absolute_url', array($this, 'generateAbsoluteUrl')),
new \Twig_SimpleFunction('relative_path', array($this, 'generateRelativePath')),
);
}
/**
* Returns the absolute URL for the given path.
* Returns the absolute URL for the given absolute or relative path.
*
* This method returns the path unchanged if no request is available.
*
* @param string $path The path
*
* @return string The absolute URL
*
* @see Request::getUriForPath()
*/
public function generateAbsoluteUrl($path)
{
@ -57,9 +60,43 @@ class HttpFoundationExtension extends \Twig_Extension
return $path;
}
if (!$path || '/' !== $path[0]) {
$prefix = $request->getPathInfo();
$last = strlen($prefix) - 1;
if ($last !== $pos = strrpos($prefix, '/')) {
$prefix = substr($prefix, 0, $pos).'/';
}
$path = $prefix.$path;
}
return $request->getUriForPath($path);
}
/**
* Returns a relative path based on the current Request.
*
* This method returns the path unchanged if no request is available.
*
* @param string $path The path
*
* @return string The relative path
*
* @see Request::getRelativeUriForPath()
*/
public function generateRelativePath($path)
{
if (false !== strpos($path, '://') || '//' === substr($path, 0, 2)) {
return $path;
}
if (!$request = $this->requestStack->getMasterRequest()) {
return $path;
}
return $request->getRelativeUriForPath($path);
}
/**
* Returns the name of the extension.
*

View File

@ -0,0 +1,74 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bridge\Twig\Tests\Extension;
use Symfony\Bridge\Twig\Extension\HttpFoundationExtension;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Request;
class HttpFoundationExtensionTest extends \PHPUnit_Framework_TestCase
{
/**
* @dataProvider getGenerateAbsoluteUrlData()
*/
public function testGenerateAbsoluteUrl($expected, $path, $pathinfo)
{
$stack = new RequestStack();
$stack->push(Request::create($pathinfo));
$extension = new HttpFoundationExtension($stack);
$this->assertEquals($expected, $extension->generateAbsoluteUrl($path));
}
public function getGenerateAbsoluteUrlData()
{
return array(
array('http://localhost/foo.png', '/foo.png', '/foo/bar.html'),
array('http://localhost/foo/foo.png', 'foo.png', '/foo/bar.html'),
array('http://localhost/foo/foo.png', 'foo.png', '/foo/bar'),
array('http://localhost/foo/bar/foo.png', 'foo.png', '/foo/bar/'),
array('http://example.com/baz', 'http://example.com/baz', '/'),
array('https://example.com/baz', 'https://example.com/baz', '/'),
array('//example.com/baz', '//example.com/baz', '/'),
);
}
/**
* @dataProvider getGenerateRelativePathData()
*/
public function testGenerateRelativePath($expected, $path, $pathinfo)
{
if (!method_exists('Symfony\Component\HttpFoundation\Request', 'getRelativeUriForPath')) {
$this->markTestSkipped('Your version of Symfony HttpFoundation is too old.');
}
$stack = new RequestStack();
$stack->push(Request::create($pathinfo));
$extension = new HttpFoundationExtension($stack);
$this->assertEquals($expected, $extension->generateRelativePath($path));
}
public function getGenerateRelativePathData()
{
return array(
array('../foo.png', '/foo.png', '/foo/bar.html'),
array('../baz/foo.png', '/baz/foo.png', '/foo/bar.html'),
array('baz/foo.png', 'baz/foo.png', '/foo/bar.html'),
array('http://example.com/baz', 'http://example.com/baz', '/'),
array('https://example.com/baz', 'https://example.com/baz', '/'),
array('//example.com/baz', '//example.com/baz', '/'),
);
}
}

View File

@ -1137,6 +1137,61 @@ class Request
return $this->getSchemeAndHttpHost().$this->getBaseUrl().$path;
}
/**
* Returns the path as relative reference from the current Request path.
*
* Only the URIs path component (no schema, host etc.) is relevant and must be given.
* Both paths must be absolute and not contain relative parts.
* Relative URLs from one resource to another are useful when generating self-contained downloadable document archives.
* Furthermore, they can be used to reduce the link size in documents.
*
* Example target paths, given a base path of "/a/b/c/d":
* - "/a/b/c/d" -> ""
* - "/a/b/c/" -> "./"
* - "/a/b/" -> "../"
* - "/a/b/c/other" -> "other"
* - "/a/x/y" -> "../../x/y"
*
* @param string $path The target path
*
* @return string The relative target path
*/
public function getRelativeUriForPath($path)
{
// be sure that we are dealing with an absolute path
if (!isset($path[0]) || '/' !== $path[0]) {
return $path;
}
if ($path === $basePath = $this->getPathInfo()) {
return '';
}
$sourceDirs = explode('/', isset($basePath[0]) && '/' === $basePath[0] ? substr($basePath, 1) : $basePath);
$targetDirs = explode('/', isset($path[0]) && '/' === $path[0] ? substr($path, 1) : $path);
array_pop($sourceDirs);
$targetFile = array_pop($targetDirs);
foreach ($sourceDirs as $i => $dir) {
if (isset($targetDirs[$i]) && $dir === $targetDirs[$i]) {
unset($sourceDirs[$i], $targetDirs[$i]);
} else {
break;
}
}
$targetDirs[] = $targetFile;
$path = str_repeat('../', count($sourceDirs)).implode('/', $targetDirs);
// A reference to the same base directory or an empty subdirectory must be prefixed with "./".
// This also applies to a segment with a colon character (e.g., "file:colon") that cannot be used
// as the first segment of a relative-path reference, as it would be mistaken for a scheme name
// (see http://tools.ietf.org/html/rfc3986#section-4.2).
return !isset($path[0]) || '/' === $path[0]
|| false !== ($colonPos = strpos($path, ':')) && ($colonPos < ($slashPos = strpos($path, '/')) || false === $slashPos)
? "./$path" : $path;
}
/**
* Generates the normalized query string for the Request.
*

View File

@ -575,6 +575,26 @@ class RequestTest extends \PHPUnit_Framework_TestCase
$this->assertEquals('http://servername/some/path', $request->getUriForPath('/some/path'));
}
/**
* @dataProvider getRelativeUriForPathData()
*/
public function testGetRelativeUriForPath($expected, $pathinfo, $path)
{
$this->assertEquals($expected, Request::create($pathinfo)->getRelativeUriForPath($path));
}
public function getRelativeUriForPathData()
{
return array(
array('me.png', '/foo', '/me.png'),
array('../me.png', '/foo/bar', '/me.png'),
array('me.png', '/foo/bar', '/foo/me.png'),
array('../baz/me.png', '/foo/bar/b', '/foo/baz/me.png'),
array('../../fooz/baz/me.png', '/foo/bar/b', '/fooz/baz/me.png'),
array('baz/me.png', '/foo/bar/b', 'baz/me.png'),
);
}
/**
* @covers Symfony\Component\HttpFoundation\Request::getUserInfo
*/