diff --git a/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php b/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php index 20834d7fa207..9dd6a2b96314 100644 --- a/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php +++ b/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php @@ -12,6 +12,7 @@ namespace Symfony\Bundle\TwigBundle\Extension; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\Routing\RequestContext; /** * Twig extension for Symfony assets helper @@ -21,10 +22,12 @@ class AssetsExtension extends \Twig_Extension { private $container; + private $context; - public function __construct(ContainerInterface $container) + public function __construct(ContainerInterface $container, RequestContext $requestContext) { $this->container = $container; + $this->context = $requestContext; } /** @@ -45,14 +48,21 @@ public function getFunctions() * * Absolute paths (i.e. http://...) are returned unmodified. * - * @param string $path A public path - * @param string $packageName The name of the asset package to use + * @param string $path A public path + * @param string $packageName The name of the asset package to use + * @param Boolean $absolute Whether to return an absolute URL or a relative one * * @return string A public path which takes into account the base path and URL path */ - public function getAssetUrl($path, $packageName = null) + public function getAssetUrl($path, $packageName = null, $absolute = false) { - return $this->container->get('templating.helper.assets')->getUrl($path, $packageName); + $url = $this->container->get('templating.helper.assets')->getUrl($path, $packageName); + + if (!$absolute) { + return $url; + } + + return $this->ensureUrlIsAbsolute($url); } /** @@ -76,4 +86,33 @@ public function getName() { return 'assets'; } + + /** + * Ensures an URL is absolute, if possible. + * + * @param string $url The URL that has to be absolute + * + * @return string The absolute URL + */ + private function ensureUrlIsAbsolute($url) + { + if (false !== strpos($url, '://') || 0 === strpos($url, '//')) { + return $url; + } + + if ('' === $host = $this->context->getHost()) { + return $url; + } + + $scheme = $this->context->getScheme(); + $port = ''; + + if ('http' === $scheme && 80 != $this->context->getHttpPort()) { + $port = ':'.$this->context->getHttpPort(); + } elseif ('https' === $scheme && 443 != $this->context->getHttpsPort()) { + $port = ':'.$this->context->getHttpsPort(); + } + + return $scheme.'://'.$host.$port.$url; + } } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 5efbe9d6d4f4..945bd876a07a 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -66,6 +66,7 @@ + diff --git a/src/Symfony/Bundle/TwigBundle/Tests/Extension/AssetsExtensionTest.php b/src/Symfony/Bundle/TwigBundle/Tests/Extension/AssetsExtensionTest.php new file mode 100644 index 000000000000..5652af57dd23 --- /dev/null +++ b/src/Symfony/Bundle/TwigBundle/Tests/Extension/AssetsExtensionTest.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\TwigBundle\Tests\Extension; + +use Symfony\Bundle\TwigBundle\Extension\AssetsExtension; +use Symfony\Bundle\TwigBundle\Tests\TestCase; +use Symfony\Component\Routing\RequestContext; + +class AssetsExtensionTest extends TestCase +{ + /** + * @dataProvider provideGetGetAssetUrlArguments + */ + public function testGetAssetUrl($path, $packageName, $absolute, $relativeUrl, $expectedUrl, $scheme, $host, $httpPort, $httpsPort) + { + $helper = $this->createHelperMock($path, $packageName, $relativeUrl); + $container = $this->createContainerMock($helper); + + $context = $this->createRequestContextMock($scheme, $host, $httpPort, $httpsPort); + + $extension = new AssetsExtension($container, $context); + $this->assertEquals($expectedUrl, $extension->getAssetUrl($path, $packageName, $absolute)); + } + + public function testGetAssetWithtoutHost() + { + $path = '/path/to/asset'; + $packageName = null; + $relativeUrl = '/bundle-name/path/to/asset'; + + $helper = $this->createHelperMock($path, $packageName, $relativeUrl); + $container = $this->createContainerMock($helper); + + $context = $this->createRequestContextMock('http', '', 80, 443); + + $extension = new AssetsExtension($container, $context); + $this->assertEquals($relativeUrl, $extension->getAssetUrl($path, $packageName, true)); + } + + public function provideGetGetAssetUrlArguments() + { + return array( + array('/path/to/asset', 'package-name', false, '/bundle-name/path/to/asset', '/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', 'package-name', false, 'http://subdomain.symfony.com/bundle-name/path/to/asset', 'http://subdomain.symfony.com/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', null, false, '/bundle-name/path/to/asset', '/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', 'package-name', true, '/bundle-name/path/to/asset', 'http://symfony.com/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', 'package-name', true, 'http://subdomain.symfony.com/bundle-name/path/to/asset', 'http://subdomain.symfony.com/bundle-name/path/to/asset', 'http', 'symfony.com', 80, null), + array('/path/to/asset', null, true, '/bundle-name/path/to/asset', 'https://symfony.com:92/bundle-name/path/to/asset', 'https', 'symfony.com', null, 92), + array('/path/to/asset', null, true, '/bundle-name/path/to/asset', 'http://symfony.com:660/bundle-name/path/to/asset', 'http', 'symfony.com', 660, null), + ); + } + + private function createRequestContextMock($scheme, $host, $httpPort, $httpsPort) + { + $context = $this->getMockBuilder('Symfony\Component\Routing\RequestContext') + ->disableOriginalConstructor() + ->getMock(); + $context->expects($this->any()) + ->method('getScheme') + ->will($this->returnValue($scheme)); + $context->expects($this->any()) + ->method('getHost') + ->will($this->returnValue($host)); + $context->expects($this->any()) + ->method('getHttpPort') + ->will($this->returnValue($httpPort)); + $context->expects($this->any()) + ->method('getHttpsPort') + ->will($this->returnValue($httpsPort)); + + return $context; + } + + private function createContainerMock($helper) + { + $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface'); + $container->expects($this->any()) + ->method('get') + ->with('templating.helper.assets') + ->will($this->returnValue($helper)); + + return $container; + } + + private function createHelperMock($path, $packageName, $returnValue) + { + $helper = $this->getMockBuilder('Symfony\Component\Templating\Helper\CoreAssetsHelper') + ->disableOriginalConstructor() + ->getMock(); + $helper->expects($this->any()) + ->method('getUrl') + ->with($path, $packageName) + ->will($this->returnValue($returnValue)); + + return $helper; + } +} diff --git a/src/Symfony/Bundle/TwigBundle/composer.json b/src/Symfony/Bundle/TwigBundle/composer.json index 88d866989ad2..49e32a193e09 100644 --- a/src/Symfony/Bundle/TwigBundle/composer.json +++ b/src/Symfony/Bundle/TwigBundle/composer.json @@ -23,7 +23,9 @@ "require-dev": { "symfony/stopwatch": "~2.2", "symfony/dependency-injection": "~2.0", - "symfony/config": "~2.2" + "symfony/config": "~2.2", + "symfony/routing": "~2.1", + "symfony/templating": "~2.1" }, "autoload": { "psr-0": { "Symfony\\Bundle\\TwigBundle\\": "" }