diff --git a/src/Symfony/Bridge/Twig/CHANGELOG.md b/src/Symfony/Bridge/Twig/CHANGELOG.md index 4be010ba20e56..be949ee860924 100644 --- a/src/Symfony/Bridge/Twig/CHANGELOG.md +++ b/src/Symfony/Bridge/Twig/CHANGELOG.md @@ -1,6 +1,11 @@ CHANGELOG ========= +2.7.0 +----- + + * added AssetExtension (provides the `asset` function) + 2.5.0 ----- diff --git a/src/Symfony/Bridge/Twig/Extension/AssetExtension.php b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php new file mode 100644 index 0000000000000..b35b957a27eb9 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/AssetExtension.php @@ -0,0 +1,110 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Twig\Extension; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\Asset\Packages; + +/** + * Twig extension for the Symfony Asset component. + * + * @author Fabien Potencier + */ +class AssetExtension extends \Twig_Extension +{ + private $packages; + private $requestStack; + + public function __construct(Packages $packages, RequestStack $requestStack = null) + { + $this->packages = $packages; + $this->requestStack = $requestStack; + } + + /** + * {@inheritdoc} + */ + public function getFunctions() + { + return array( + new \Twig_SimpleFunction('asset', array($this, 'getAssetUrl')), + new \Twig_SimpleFunction('assets_version', array($this, 'getAssetsVersion')), + ); + } + + /** + * Returns the public path of an asset. + * + * 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 bool $absolute Whether to return an absolute URL or a relative one + * @param string|bool|null $version A specific version + * + * @return string A public path which takes into account the base path and URL path + */ + public function getAssetUrl($path, $packageName = null, $absolute = false, $version = null) + { + $url = $this->packages->getUrl($path, $packageName, $version); + + if (!$absolute) { + return $url; + } + + return $this->ensureUrlIsAbsolute($url); + } + + /** + * Returns the version of the assets in a package. + * + * @param string $packageName + * + * @return int + */ + public function getAssetsVersion($packageName = null) + { + return $this->packages->getVersion($packageName); + } + + /** + * Ensures an URL is absolute, if possible. + * + * @param string $url The URL that has to be absolute + * + * @throws \RuntimeException + * + * @return string The absolute URL + */ + private function ensureUrlIsAbsolute($url) + { + if (false !== strpos($url, '://') || '//' === substr($url, 0, 2)) { + return $url; + } + + if (null === $this->requestStack) { + throw new \RuntimeException('To generate an absolute URL for an asset, the Symfony Routing component is required.'); + } + + return $this->requestStack->getMasterRequest()->getUriForPath($url); + } + + /** + * Returns the name of the extension. + * + * @return string The extension name + */ + public function getName() + { + return 'asset'; + } +} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 8d2af5c52cac6..99f8f2e75083c 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -37,6 +37,7 @@ }, "suggest": { "symfony/finder": "", + "symfony/asset": "For using the AssetExtension", "symfony/form": "For using the FormExtension", "symfony/http-kernel": "For using the HttpKernelExtension", "symfony/routing": "For using the RoutingExtension", diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TemplatingAssetHelperPass.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TemplatingAssetHelperPass.php index 39a4e13bbd143..7ccecb60f9fb1 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TemplatingAssetHelperPass.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/TemplatingAssetHelperPass.php @@ -13,54 +13,13 @@ use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Reference; +/** + * @deprecated since 2.7, will be removed in 3.0 + */ class TemplatingAssetHelperPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { - if (!$container->hasDefinition('templating.helper.assets')) { - return; - } - - $assetsHelperDefinition = $container->getDefinition('templating.helper.assets'); - $args = $assetsHelperDefinition->getArguments(); - - if ('request' === $this->getPackageScope($container, $args[0])) { - $assetsHelperDefinition->setScope('request'); - - return; - } - - if (!array_key_exists(1, $args)) { - return; - } - - if (!is_array($args[1])) { - return; - } - - foreach ($args[1] as $arg) { - if ('request' === $this->getPackageScope($container, $arg)) { - $assetsHelperDefinition->setScope('request'); - - break; - } - } - } - - private function getPackageScope(ContainerBuilder $container, $package) - { - if ($package instanceof Reference) { - return $container->findDefinition((string) $package)->getScope(); - } - - if ($package instanceof Definition) { - return $package->getScope(); - } - - // Someone did some voodoo with a compiler pass. So we ignore this - // 'package'. Can we be sure, it's a package anyway? } } diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 9354a549805a1..195234f603f8f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -311,6 +311,7 @@ private function addRequestSection(ArrayNodeDefinition $rootNode) private function addTemplatingSection(ArrayNodeDefinition $rootNode) { + /** @deprecated, should be removed in 3.0 */ $organizeUrls = function ($urls) { $urls += array( 'http' => array(), diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index c1f880b85a6c9..ac293814e47b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -458,7 +458,9 @@ private function registerRequestConfiguration(array $config, ContainerBuilder $c private function registerTemplatingConfiguration(array $config, $ide, ContainerBuilder $container, XmlFileLoader $loader) { $loader->load('templating.xml'); - $loader->load('templating_php.xml'); + $loader->load('assets.xml'); + + $this->createPackageDefinitions($config, $container); $links = array( 'textmate' => 'txmt://open?url=file://%%f&line=%%l', @@ -468,12 +470,9 @@ private function registerTemplatingConfiguration(array $config, $ide, ContainerB ); $container->setParameter('templating.helper.code.file_link_format', isset($links[$ide]) ? $links[$ide] : $ide); - $container->setParameter('templating.helper.form.resources', $config['form']['resources']); $container->setParameter('fragment.renderer.hinclude.global_template', $config['hinclude_default_template']); if ($container->getParameter('kernel.debug')) { - $loader->load('templating_debug.xml'); - $logger = new Reference('logger', ContainerInterface::IGNORE_ON_INVALID_REFERENCE); $container->getDefinition('templating.loader.cache') @@ -482,25 +481,8 @@ private function registerTemplatingConfiguration(array $config, $ide, ContainerB $container->getDefinition('templating.loader.chain') ->addTag('monolog.logger', array('channel' => 'templating')) ->addMethodCall('setLogger', array($logger)); - - $container->setDefinition('templating.engine.php', $container->findDefinition('debug.templating.engine.php')); - $container->setAlias('debug.templating.engine.php', 'templating.engine.php'); } - // create package definitions and add them to the assets helper - $defaultPackage = $this->createPackageDefinition($container, $config['assets_base_urls']['http'], $config['assets_base_urls']['ssl'], $config['assets_version'], $config['assets_version_format']); - $container->setDefinition('templating.asset.default_package', $defaultPackage); - $namedPackages = array(); - foreach ($config['packages'] as $name => $package) { - $namedPackage = $this->createPackageDefinition($container, $package['base_urls']['http'], $package['base_urls']['ssl'], $package['version'], $package['version_format'], $name); - $container->setDefinition('templating.asset.package.'.$name, $namedPackage); - $namedPackages[$name] = new Reference('templating.asset.package.'.$name); - } - $container->getDefinition('templating.helper.assets')->setArguments(array( - new Reference('templating.asset.default_package'), - $namedPackages, - )); - if (!empty($config['loaders'])) { $loaders = array_map(function ($loader) { return new Reference($loader); }, $config['loaders']); @@ -531,6 +513,23 @@ private function registerTemplatingConfiguration(array $config, $ide, ContainerB )); if (in_array('php', $config['engines'], true)) { + $loader->load('templating_php.xml'); + + $container->setParameter('templating.helper.form.resources', $config['form']['resources']); + + $packages = $container->getDefinition('templating.asset.packages'); + $container->getDefinition('templating.helper.assets') + ->replaceArgument(0, $packages->getArgument(0)) + ->replaceArgument(1, $packages->getArgument(1)) + ; + + if ($container->getParameter('kernel.debug')) { + $loader->load('templating_debug.xml'); + + $container->setDefinition('templating.engine.php', $container->findDefinition('debug.templating.engine.php')); + $container->setAlias('debug.templating.engine.php', 'templating.engine.php'); + } + $this->addClassesToCompile(array( 'Symfony\\Component\\Templating\\Storage\\FileStorage', 'Symfony\\Bundle\\FrameworkBundle\\Templating\\PhpEngine', @@ -552,71 +551,36 @@ private function registerTemplatingConfiguration(array $config, $ide, ContainerB } } - /** - * Returns a definition for an asset package. - */ - private function createPackageDefinition(ContainerBuilder $container, array $httpUrls, array $sslUrls, $version, $format, $name = null) + private function createPackageDefinitions(array $config, ContainerBuilder $container) { - if (!$httpUrls) { - $package = new DefinitionDecorator('templating.asset.path_package'); - $package - ->setPublic(false) - ->setScope('request') - ->replaceArgument(1, $version) - ->replaceArgument(2, $format) - ; - - return $package; - } - - if ($httpUrls == $sslUrls) { - $package = new DefinitionDecorator('templating.asset.url_package'); - $package - ->setPublic(false) - ->replaceArgument(0, $sslUrls) - ->replaceArgument(1, $version) - ->replaceArgument(2, $format) - ; + // create package definitions and add them to the assets helper + $defaultPackage = $this->createPackageDefinition($container, $config['assets_base_urls']['http'], $config['assets_base_urls']['ssl'], $config['assets_version'], $config['assets_version_format']); + $container->setDefinition('templating.asset.default_package', $defaultPackage); - return $package; + $namedPackages = array(); + foreach ($config['packages'] as $name => $package) { + $namedPackage[$name] = $this->createPackageDefinition($container, $package['base_urls']['http'], $package['base_urls']['ssl'], $package['version'], $package['version_format']); } - $prefix = $name ? 'templating.asset.package.'.$name : 'templating.asset.default_package'; - - $httpPackage = new DefinitionDecorator('templating.asset.url_package'); - $httpPackage - ->replaceArgument(0, $httpUrls) - ->replaceArgument(1, $version) - ->replaceArgument(2, $format) + $container->getDefinition('templating.asset.packages') + ->replaceArgument(0, $defaultPackage) + ->replaceArgument(1, $namedPackages) ; - $container->setDefinition($prefix.'.http', $httpPackage); - - if ($sslUrls) { - $sslPackage = new DefinitionDecorator('templating.asset.url_package'); - $sslPackage - ->replaceArgument(0, $sslUrls) - ->replaceArgument(1, $version) - ->replaceArgument(2, $format) - ; - } else { - $sslPackage = new DefinitionDecorator('templating.asset.path_package'); - $sslPackage - ->setScope('request') - ->replaceArgument(1, $version) - ->replaceArgument(2, $format) - ; - } - $container->setDefinition($prefix.'.ssl', $sslPackage); + } + + /** + * Returns a definition for an asset package. + */ + private function createPackageDefinition(ContainerBuilder $container, array $httpUrls, array $sslUrls, $version, $format) + { + $package = new DefinitionDecorator('templating.asset.package'); - $package = new DefinitionDecorator('templating.asset.request_aware_package'); - $package + return $package ->setPublic(false) - ->setScope('request') - ->replaceArgument(1, $prefix.'.http') - ->replaceArgument(2, $prefix.'.ssl') + ->replaceArgument(1, array_merge($httpUrls, $sslUrls), $version) + ->replaceArgument(2, $version) + ->replaceArgument(3, $format) ; - - return $package; } /** diff --git a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php index 10896786c9f05..09f7bd66b8aa4 100644 --- a/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php +++ b/src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php @@ -16,7 +16,6 @@ use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\AddConsoleCommandPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\FormPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingPass; -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingAssetHelperPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\RoutingResolverPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\ProfilerPass; use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TranslatorPass; @@ -76,7 +75,6 @@ public function build(ContainerBuilder $container) // but as late as possible to get resolved parameters $container->addCompilerPass(new RegisterListenersPass(), PassConfig::TYPE_BEFORE_REMOVING); $container->addCompilerPass(new TemplatingPass()); - $container->addCompilerPass(new TemplatingAssetHelperPass()); $container->addCompilerPass(new AddConstraintValidatorsPass()); $container->addCompilerPass(new AddValidatorInitializersPass()); $container->addCompilerPass(new AddConsoleCommandPass()); diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml new file mode 100644 index 0000000000000..686ee4b3026a3 --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/assets.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml index 6e482f8573e51..e309d3cd9fbf7 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml +++ b/src/Symfony/Bundle/FrameworkBundle/Resources/config/templating_php.xml @@ -19,9 +19,6 @@ Symfony\Component\Form\Extension\Templating\TemplatingRendererEngine Symfony\Component\Form\FormRenderer Symfony\Bundle\FrameworkBundle\Templating\GlobalVariables - Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage - Symfony\Component\Templating\Asset\UrlPackage - Symfony\Bundle\FrameworkBundle\Templating\Asset\PackageFactory @@ -43,29 +40,6 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Asset/PackageFactory.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Asset/PackageFactory.php index 0a63a29cd6cce..7c68b1d1e1964 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Asset/PackageFactory.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Asset/PackageFactory.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\FrameworkBundle\Templating\Asset; +trigger_error('The Symfony\Bundle\FrameworkBundle\Templating\Asset\PackageInterface is deprecated since version 2.7 and will be removed in 3.0.', E_USER_DEPRECATED); + use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Templating\Asset\PackageInterface; @@ -19,6 +21,8 @@ * Creates packages based on whether the current request is secure. * * @author Kris Wallsmith + * + * @deprecated since 2.7, will be removed in 3.0. */ class PackageFactory { diff --git a/src/Symfony/Bundle/FrameworkBundle/Templating/Asset/PathPackage.php b/src/Symfony/Bundle/FrameworkBundle/Templating/Asset/PathPackage.php index 6aa8c58824669..9207e8fb0df0e 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Templating/Asset/PathPackage.php +++ b/src/Symfony/Bundle/FrameworkBundle/Templating/Asset/PathPackage.php @@ -14,10 +14,14 @@ use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Templating\Asset\PathPackage as BasePathPackage; +trigger_error('The Symfony\Bundle\FrameworkBundle\Templating\Asset\PathPackage is deprecated since version 2.7 and will be removed in 3.0. Use Symfony\Component\Asset\RequestPathPackage instead.', E_USER_DEPRECATED); + /** * The path packages adds a version and a base path to asset URLs. * * @author Kris Wallsmith + * + * @deprecated since 2.7, will be removed in 3.0. Use Symfony\Component\Asset\RequestPathPackage instead. */ class PathPackage extends BasePathPackage { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TemplatingAssetHelperPassTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TemplatingAssetHelperPassTest.php deleted file mode 100644 index 3a096715e160b..0000000000000 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Compiler/TemplatingAssetHelperPassTest.php +++ /dev/null @@ -1,121 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; - -use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\TemplatingAssetHelperPass; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\Reference; - -class TemplatingAssetHelperPassTest extends \PHPUnit_Framework_TestCase -{ - public function getScopesTests() - { - return array( - array('container'), - array('request'), - ); - } - - /** @dataProvider getScopesTests */ - public function testFindLowestScopeInDefaultPackageWithReference($scope) - { - $container = new ContainerBuilder(); - - $defaultPackage = new Definition('stdClass'); - $defaultPackage->setScope($scope); - $container->setDefinition('default_package', $defaultPackage); - - $definition = new Definition('stdClass', array(new Reference('default_package'))); - $container->setDefinition('templating.helper.assets', $definition); - - $profilerPass = new TemplatingAssetHelperPass(); - $profilerPass->process($container); - - $this->assertSame($scope, $definition->getScope()); - } - - /** @dataProvider getScopesTests */ - public function testFindLowestScopeInDefaultPackageWithDefinition($scope) - { - $container = new ContainerBuilder(); - - $defaultPackage = new Definition('stdClass'); - $defaultPackage->setScope($scope); - - $definition = new Definition('stdClass', array($defaultPackage)); - $container->setDefinition('templating.helper.assets', $definition); - - $profilerPass = new TemplatingAssetHelperPass(); - $profilerPass->process($container); - - $this->assertSame($scope, $definition->getScope()); - } - - /** @dataProvider getScopesTests */ - public function testFindLowestScopeInNamedPackageWithReference($scope) - { - $container = new ContainerBuilder(); - - $defaultPackage = new Definition('stdClass'); - $container->setDefinition('default_package', $defaultPackage); - - $aPackage = new Definition('stdClass'); - $container->setDefinition('a_package', $aPackage); - - $bPackage = new Definition('stdClass'); - $bPackage->setScope($scope); - $container->setDefinition('b_package', $bPackage); - - $cPackage = new Definition('stdClass'); - $container->setDefinition('c_package', $cPackage); - - $definition = new Definition('stdClass', array(new Reference('default_package'), array( - new Reference('a_package'), - new Reference('b_package'), - new Reference('c_package'), - ))); - $container->setDefinition('templating.helper.assets', $definition); - - $profilerPass = new TemplatingAssetHelperPass(); - $profilerPass->process($container); - - $this->assertSame($scope, $definition->getScope()); - } - - /** @dataProvider getScopesTests */ - public function testFindLowestScopeInNamedPackageWithDefinition($scope) - { - $container = new ContainerBuilder(); - - $defaultPackage = new Definition('stdClass'); - - $aPackage = new Definition('stdClass'); - - $bPackage = new Definition('stdClass'); - $bPackage->setScope($scope); - - $cPackage = new Definition('stdClass'); - - $definition = new Definition('stdClass', array($defaultPackage, array( - $aPackage, - $bPackage, - $cPackage, - ))); - $container->setDefinition('templating.helper.assets', $definition); - - $profilerPass = new TemplatingAssetHelperPass(); - $profilerPass->process($container); - - $this->assertSame($scope, $definition->getScope()); - } -} diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php index 18d98b356dfed..9fbfd65950a53 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExceptionListenerPass.php @@ -28,9 +28,11 @@ public function process(ContainerBuilder $container) } // register the exception controller only if Twig is enabled - $engines = $container->getParameter('templating.engines'); - if (!in_array('twig', $engines)) { - $container->removeDefinition('twig.exception_listener'); + if ($container->hasParameter('templating.engines')) { + $engines = $container->getParameter('templating.engines'); + if (!in_array('twig', $engines)) { + $container->removeDefinition('twig.exception_listener'); + } } } } diff --git a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php index 069083d27f0f0..1938b4ba9be83 100644 --- a/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php +++ b/src/Symfony/Bundle/TwigBundle/DependencyInjection/Compiler/ExtensionPass.php @@ -27,6 +27,14 @@ public function process(ContainerBuilder $container) $container->getDefinition('twig.loader.filesystem')->addMethodCall('addPath', array(dirname(dirname($reflClass->getFileName())).'/Resources/views/Form')); } + if ($container->has('fragment.handler')) { + $container->getDefinition('twig.extension.actions')->addTag('twig.extension'); + } + + if ($container->has('templating.asset.packages')) { + $container->getDefinition('twig.extension.assets')->addTag('twig.extension'); + } + if ($container->has('translator')) { $container->getDefinition('twig.extension.trans')->addTag('twig.extension'); } diff --git a/src/Symfony/Bundle/TwigBundle/Extension/ActionsExtension.php b/src/Symfony/Bundle/TwigBundle/Extension/ActionsExtension.php index be96d69c04f24..7dce11181934d 100644 --- a/src/Symfony/Bundle/TwigBundle/Extension/ActionsExtension.php +++ b/src/Symfony/Bundle/TwigBundle/Extension/ActionsExtension.php @@ -13,6 +13,7 @@ use Symfony\Bundle\TwigBundle\TokenParser\RenderTokenParser; use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\HttpKernel\Fragment\FragmentHandler; /** * Twig extension for Symfony actions helper. @@ -23,16 +24,26 @@ */ class ActionsExtension extends \Twig_Extension { - private $container; + private $handler; /** - * Constructor. + * @param FragmentHandler|ContainerInterface $handler * - * @param ContainerInterface $container The service container + * @deprecated Passing a ContainerInterface as a first argument is deprecated as of 2.7 and will be removed in 3.0. */ - public function __construct(ContainerInterface $container) + public function __construct($handler) { - $this->container = $container; + if ($handler instanceof FragmentHandler) { + $this->handler = $handler; + } elseif ($handler instanceof ContainerInterface) { + trigger_error(sprintf('The ability to pass a ContainerInterface instance as a first argument to %s was deprecated in 2.7 and will be removed in 3.0. Please, pass a FragmentHandler instance instead.', __METHOD__), E_USER_DEPRECATED); + + $this->handler = $handler->get('fragment.handler'); + } else { + throw new \BadFunctionCallException(sprintf('%s takes a FragmentHandler or a ContainerInterface object as its first argument.', __METHOD__)); + } + + $this->handler = $handler; } /** @@ -41,11 +52,14 @@ public function __construct(ContainerInterface $container) * @param string $uri A URI * @param array $options An array of options * - * @see Symfony\Bundle\FrameworkBundle\Controller\ControllerResolver::render() + * @see FragmentHandler::render() */ public function renderUri($uri, array $options = array()) { - return $this->container->get('templating.helper.actions')->render($uri, $options); + $strategy = isset($options['strategy']) ? $options['strategy'] : 'inline'; + unset($options['strategy']); + + return $this->handler->render($uri, $strategy, $options); } /** diff --git a/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php b/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php index d596f97e528fc..1de9f8dd00296 100644 --- a/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php +++ b/src/Symfony/Bundle/TwigBundle/Extension/AssetsExtension.php @@ -11,6 +11,8 @@ namespace Symfony\Bundle\TwigBundle\Extension; +trigger_error('Symfony\Bundle\TwigBundle\Extension\AssetsExtension was deprecated in 2.7 and will be removed in 3.0. Please use Symfony\Component\Twig\Extension\AssetPackagesExtension instead.', E_USER_DEPRECATED); + use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\Routing\RequestContext; @@ -18,6 +20,8 @@ * Twig extension for Symfony assets helper. * * @author Fabien Potencier + * + * @deprecated Deprecated in 2.7, to be removed in 3.0. Use Symfony\Component\Twig\Extension\AssetPackagesExtension instead. */ class AssetsExtension extends \Twig_Extension { diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml index 3c81983dd3bea..260691c8ca90f 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/twig.xml @@ -11,7 +11,7 @@ Symfony\Bundle\TwigBundle\TwigEngine Symfony\Bundle\TwigBundle\CacheWarmer\TemplateCacheCacheWarmer Symfony\Bridge\Twig\Extension\TranslationExtension - Symfony\Bundle\TwigBundle\Extension\AssetsExtension + Symfony\Component\Twig\Extension\AssetExtension Symfony\Bundle\TwigBundle\Extension\ActionsExtension Symfony\Bridge\Twig\Extension\CodeExtension Symfony\Bridge\Twig\Extension\RoutingExtension @@ -65,14 +65,12 @@ - - - + + - - + diff --git a/src/Symfony/Component/Asset/.gitignore b/src/Symfony/Component/Asset/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Component/Asset/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Component/Asset/CHANGELOG.md b/src/Symfony/Component/Asset/CHANGELOG.md new file mode 100644 index 0000000000000..619a423402ada --- /dev/null +++ b/src/Symfony/Component/Asset/CHANGELOG.md @@ -0,0 +1,7 @@ +CHANGELOG +========= + +2.7.0 +----- + + * added the component diff --git a/src/Symfony/Component/Asset/LICENSE b/src/Symfony/Component/Asset/LICENSE new file mode 100644 index 0000000000000..43028bc600f26 --- /dev/null +++ b/src/Symfony/Component/Asset/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2015 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/Asset/Package.php b/src/Symfony/Component/Asset/Package.php new file mode 100644 index 0000000000000..cb2d6de708763 --- /dev/null +++ b/src/Symfony/Component/Asset/Package.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +/** + * Basic package that adds a version to asset URLs. + * + * @author Kris Wallsmith + */ +class Package implements PackageInterface +{ + private $version; + private $format; + + /** + * Constructor. + * + * @param string $version The package version + * @param string $format The format used to apply the version + */ + public function __construct($version = null, $format = '') + { + $this->version = $version; + $this->format = $format ?: '%s?%s'; + } + + /** + * {@inheritdoc} + */ + public function getVersion() + { + return $this->version; + } + + /** + * {@inheritdoc} + */ + public function getUrl($path, $version = null) + { + if ($this->isAbsoluteUrl($path)) { + return $path; + } + + return $this->applyVersion($path, $version); + } + + protected function isAbsoluteUrl($url) + { + return false !== strpos($url, '://') || '//' === substr($url, 0, 2); + } + + /** + * Applies version to the supplied path. + * + * @param string $path A path + * @param string|bool|null $version A specific version + * + * @return string The versionized path + */ + protected function applyVersion($path, $version = null) + { + $version = $version ?: $this->version; + if (!$version) { + return $path; + } + + $versionized = sprintf($this->format, ltrim($path, '/'), $version); + + if ($path && '/' == $path[0]) { + $versionized = '/'.$versionized; + } + + return $versionized; + } +} diff --git a/src/Symfony/Component/Asset/PackageInterface.php b/src/Symfony/Component/Asset/PackageInterface.php new file mode 100644 index 0000000000000..be13b114cbf50 --- /dev/null +++ b/src/Symfony/Component/Asset/PackageInterface.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +/** + * Asset package interface. + * + * @author Kris Wallsmith + */ +interface PackageInterface +{ + /** + * Returns the asset package version. + * + * @return string The version string + */ + public function getVersion(); + + /** + * Returns an absolute or root-relative public path. + * + * @param string $path A path + * @param string|bool|null $version A specific version for the path + * + * @return string The public path + */ + public function getUrl($path, $version = null); +} diff --git a/src/Symfony/Component/Asset/Packages.php b/src/Symfony/Component/Asset/Packages.php new file mode 100644 index 0000000000000..988dc09473a61 --- /dev/null +++ b/src/Symfony/Component/Asset/Packages.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +/** + * Helps manage asset URLs. + * + * @author Fabien Potencier + * @author Kris Wallsmith + */ +class Packages +{ + private $defaultPackage; + private $packages = array(); + + /** + * @param PackageInterface $defaultPackage The default package + * @param PackageInterface[] $packages Additional packages indexed by name + */ + public function __construct(PackageInterface $defaultPackage = null, array $packages = array()) + { + $this->defaultPackage = $defaultPackage; + + foreach ($packages as $name => $package) { + $this->addPackage($name, $package); + } + } + + /** + * Sets the default package. + * + * @param PackageInterface $defaultPackage The default package + */ + public function setDefaultPackage(PackageInterface $defaultPackage) + { + $this->defaultPackage = $defaultPackage; + } + + /** + * Adds a package. + * + * @param string $name The package name + * @param PackageInterface $package The package + */ + public function addPackage($name, PackageInterface $package) + { + $this->packages[$name] = $package; + } + + /** + * Returns an asset package. + * + * @param string $name The name of the package or null for the default package + * + * @return PackageInterface An asset package + * + * @throws \InvalidArgumentException If there is no package by that name + */ + public function getPackage($name = null) + { + if (null === $name) { + if (null === $this->defaultPackage) { + throw new \LogicException('There is no default asset package, configure one first.'); + } + + return $this->defaultPackage; + } + + if (!isset($this->packages[$name])) { + throw new \InvalidArgumentException(sprintf('There is no "%s" asset package.', $name)); + } + + return $this->packages[$name]; + } + + /** + * Gets the version to add to public URL. + * + * @param string $packageName A package name + * + * @return string The current version + */ + public function getVersion($packageName = null) + { + return $this->getPackage($packageName)->getVersion(); + } + + /** + * Returns the public path. + * + * 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|bool|null $version A specific version + * + * @return string A public path which takes into account the base path and URL path + */ + public function getUrl($path, $packageName = null, $version = null) + { + return $this->getPackage($packageName)->getUrl($path, $version); + } +} diff --git a/src/Symfony/Component/Asset/PathPackage.php b/src/Symfony/Component/Asset/PathPackage.php new file mode 100644 index 0000000000000..105b7f2af13d7 --- /dev/null +++ b/src/Symfony/Component/Asset/PathPackage.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +/** + * Package that adds a base path to asset URLs in addition to a version. + * + * @author Kris Wallsmith + */ +class PathPackage extends Package +{ + private $basePath; + + /** + * Constructor. + * + * @param string $basePath The base path to be prepended to relative paths + * @param string $version The package version + * @param string $format The format used to apply the version + */ + public function __construct($basePath, $version = null, $format = null) + { + parent::__construct($version, $format); + + if (!$basePath) { + $this->basePath = '/'; + } else { + if ('/' != $basePath[0]) { + $basePath = '/'.$basePath; + } + + $this->basePath = rtrim($basePath, '/').'/'; + } + } + + /** + * {@inheritdoc} + */ + public function getUrl($path, $version = null) + { + if ($this->isAbsoluteUrl($path)) { + return $path; + } + + $url = $this->applyVersion($path, $version); + + // apply the base path + if ('/' !== substr($url, 0, 1)) { + return $this->getBasePath().$url; + } + + return $url; + } + + /** + * Returns the base path. + * + * @return string The base path + */ + public function getBasePath() + { + return $this->basePath; + } +} diff --git a/src/Symfony/Component/Asset/README.md b/src/Symfony/Component/Asset/README.md new file mode 100644 index 0000000000000..99ad22dfe5e48 --- /dev/null +++ b/src/Symfony/Component/Asset/README.md @@ -0,0 +1,160 @@ +Asset Component +=============== + +The Asset component manages asset URLs. + +Versioned Asset URLs +-------------------- + +The basic `Package` adds a version to generated asset URLs: + +```php +use Symfony\Component\Asset\Package; + +$package = new Package('v1'); + +echo $package->getUrl('/me.png'); +// /me.png?v1 +``` + +The default format can be configured: + +```php +$package = new Package('v1', '%s?version=%s'); + +echo $package->getUrl('/me.png'); +// /me.png?version=v1 + +// put the version before the path +$package = new Package('v1', 'version-%2$s/%1$s'); + +echo $package->getUrl('/me.png'); +// /version-v1/me.png +``` + +Asset URLs Base Path +-------------------- + +When all assets are stored in a common path, use the `PathPackage` to avoid +repeating yourself: + +```php +use Symfony\Component\Asset\PathPackage; + +$package = new PathPackage('/images', 'v1'); + +echo $package->getUrl('/me.png'); +// /images/me.png?v1 +``` + +Asset URLs Base URLs +-------------------- + +If your assets are hosted on different domain name than the main website, use +the `UrlPackage` class: + +```php +use Symfony\Component\Asset\UrlPackage; + +$package = new UrlPackage('http://assets.example.com/images/', 'v1'); + +echo $package->getUrl('/me.png'); +// http://assets.example.com/images/me.png?v1 +``` + +One technique used to speed up page rendering in browsers is to use several +domains for assets; this is possible by passing more than one base URLs: + +```php +use Symfony\Component\Asset\UrlPackage; + +$urls = array( + 'http://a1.example.com/images/', + 'http://a2.example.com/images/', +); +$package = new UrlPackage($urls, 'v1'); + +echo $package->getUrl('/me.png'); +// http://a1.example.com/images/me.png?v1 +``` + +Note that it's also guaranteed that any given path will always use the same +base URL to be nice with HTTP caching mechanisms. + +HttpFoundation Integration +-------------------------- + +If you are using HttpFoundation for your project, use the `RequestPathPackage` +and `RequestUrlPackage` classes alternatives to `PathPackage` and `UrlPackage`: + +```php +use Symfony\Component\Asset\RequestPathPackage; + +$package = new RequestPathPackage($requestStack, 'images', 'v1'); + +echo $package->getUrl('/me.png'); +// /somewhere/images/me.png?v1 +``` + +In addition to the configured base path, `RequestPathPackage` also +automatically prepends the current request base URL to assets to allow your +website to be hosted anywhere under the web server root directory. + +```php +use Symfony\Component\Asset\RequestUrlPackage; + +$package = new RequestUrlPackage($requestStack, array('http://example.com/', 'https://example.com/'), 'v1'); + +echo $package->getUrl('/me.png'); +// https://example.com/images/me.png?v1 +``` + +`RequestUrlPackage` uses the current request scheme (HTTP or HTTPs) to select +an appropriate base URL (HTTPs or protocol-relative URLs for HTTPs requests, +any base URL for HTTP requests). + +Named Packages +-------------- + +The `Packages` class allows to easily manages several packages in a single +project by naming packages: + +```php +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\PathPackage; +use Symfony\Component\Asset\UrlPackage; +use Symfony\Component\Asset\Packages; + +// by default, just add a version to all assets +$defaultPackage = new Asset\Package('v1'); + +$namedPackages = array( + // images are hosted on another web server + 'img' => new Asset\UrlPackage('http://img.example.com/', 'v1'), + + // documents are stored deeply under the web root directory + // let's create a shortcut + 'doc' => new Asset\PathPackage('/somewhere/deep/for/documents', 'v1'), +); + +// bundle all packages to make it easy to use them +$packages = new Asset\Packages($defaultPackage, $namedPackages); + +echo $packages->getUrl('/some.css'); +// /some.css?v1 + +echo $packages->getUrl('/me.png', 'img'); +// http://img.example.com/me.png?v1 + +echo $packages->getUrl('/me.pdf', 'doc'); +// /somewhere/deep/for/documents/me.pdf?v1 +``` + +Resources +--------- + +You can run the unit tests with the following command: + + $ cd path/to/Symfony/Component/Asset/ + $ composer.phar install + $ phpunit diff --git a/src/Symfony/Component/Asset/RequestPathPackage.php b/src/Symfony/Component/Asset/RequestPathPackage.php new file mode 100644 index 0000000000000..00d8880f92841 --- /dev/null +++ b/src/Symfony/Component/Asset/RequestPathPackage.php @@ -0,0 +1,55 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Package that adds a base path to asset URLs in addition to a version. + * + * In addition to the provided base path, this package also automatically + * prepends the current request base path to allow a website to be hosted + * easily under any given path under the Web Server root directory. + * + * When no request is available, it falls back to only use the configured + * base path. + * + * @author Fabien Potencier + */ +class RequestPathPackage extends PathPackage +{ + private $requestStack; + + /** + * @param RequestStack $request The request stack + * @param string $version The version + * @param string $format The version format + */ + public function __construct(RequestStack $requestStack, $basePath = '', $version = null, $format = null) + { + $this->requestStack = $requestStack; + + parent::__construct($basePath, $version, $format); + } + + /** + * {@inheritdoc} + */ + public function getBasePath() + { + if (!$request = $this->requestStack->getCurrentRequest()) { + return parent::getBasePath(); + } + + return $request->getBasePath().parent::getBasePath(); + } +} diff --git a/src/Symfony/Component/Asset/RequestUrlPackage.php b/src/Symfony/Component/Asset/RequestUrlPackage.php new file mode 100644 index 0000000000000..3b297fb55467d --- /dev/null +++ b/src/Symfony/Component/Asset/RequestUrlPackage.php @@ -0,0 +1,92 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +use Symfony\Component\HttpFoundation\RequestStack; + +/** + * Package that adds a base URL to asset URLs in addition to a version. + * + * As this package is aware of the current HTTP request, it can + * determine the best base URL to use based on the current request + * scheme. + * + * * For HTTP request, it chooses between all base URLs; + * * For HTTPs requests, it chooses between HTTPs base URLs and relative protocol URLs + * or falls back to any base URL if no secure ones are available. + * + * When no request is available, it falls back to choose between all base URLs. + * + * @author Fabien Potencier + */ +class RequestUrlPackage extends UrlPackage +{ + private $requestStack; + private $sslPackage; + + /** + * @param RequestStack $request The request stack + * @param string|array $baseUrls Base asset URLs + * @param string $version The version + * @param string $format The version format + */ + public function __construct(RequestStack $requestStack, $baseUrls = array(), $version = null, $format = null) + { + $this->requestStack = $requestStack; + + if (!is_array($baseUrls)) { + $baseUrls = (array) $baseUrls; + } + + if (!$baseUrls) { + throw new \LogicException('You must provide at least one base URL.'); + } + + $sslUrls = $this->getSslUrls($baseUrls); + + parent::__construct($baseUrls, $version, $format); + + if ($sslUrls && $baseUrls !== $sslUrls) { + $this->sslPackage = new UrlPackage($sslUrls, $version, $format); + } + } + + /** + * {@inheritdoc} + */ + public function getUrl($path, $version = null) + { + if (null === $this->sslPackage) { + return parent::getUrl($path, $version); + } + + if (($request = $this->requestStack->getCurrentRequest()) && $request->isSecure()) { + return $this->sslPackage->getUrl($path, $version); + } + + return parent::getUrl($path, $version); + } + + private function getSslUrls($urls) + { + $sslUrls = array(); + foreach ($urls as $url) { + if ('https://' === substr($url, 0, 8) || '//' === substr($url, 0, 2)) { + $sslUrls[] = $url; + } elseif ('http://' !== substr($url, 0, 7)) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid URL', $url)); + } + } + + return $sslUrls; + } +} diff --git a/src/Symfony/Component/Asset/Tests/PackageTest.php b/src/Symfony/Component/Asset/Tests/PackageTest.php new file mode 100644 index 0000000000000..81384f1e1b98c --- /dev/null +++ b/src/Symfony/Component/Asset/Tests/PackageTest.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\Tests; + +use Symfony\Component\Asset\Package; + +class PackageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getConfigs + */ + public function testGetUrl($version, $format, $path, $expected) + { + $package = new Package($version, $format); + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getConfigs() + { + return array( + array('v1', '', 'http://example.com/foo', 'http://example.com/foo'), + array('v1', '', 'https://example.com/foo', 'https://example.com/foo'), + array('v1', '', '//example.com/foo', '//example.com/foo'), + + array('v1', '', '/foo', '/foo?v1'), + array('v1', '', 'foo', 'foo?v1'), + + array('', '', '/foo', '/foo'), + array('', '', 'foo', 'foo'), + + array('v1', 'version-%2$s/%1$s', '/foo', '/version-v1/foo'), + array('v1', 'version-%2$s/%1$s', 'foo', 'version-v1/foo'), + array('v1', 'version-%2$s/%1$s', 'foo/', 'version-v1/foo/'), + array('v1', 'version-%2$s/%1$s', '/foo/', '/version-v1/foo/'), + ); + } + + public function testGetUrlWithSpecificVersion() + { + $package = new Package('v1'); + $this->assertEquals('/foo?v2', $package->getUrl('/foo', 'v2')); + } + + public function testGetVersion() + { + $package = new Package('v1'); + $this->assertEquals('v1', $package->getVersion()); + } +} diff --git a/src/Symfony/Component/Asset/Tests/PackagesTest.php b/src/Symfony/Component/Asset/Tests/PackagesTest.php new file mode 100644 index 0000000000000..f973d40e4af49 --- /dev/null +++ b/src/Symfony/Component/Asset/Tests/PackagesTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\Tests; + +use Symfony\Component\Asset\Package; +use Symfony\Component\Asset\Packages; + +class PackagesTest extends \PHPUnit_Framework_TestCase +{ + public function testGetterSetters() + { + $packages = new Packages(); + $packages->setDefaultPackage($default = $this->getMock('Symfony\Component\Asset\PackageInterface')); + $packages->addPackage('a', $a = $this->getMock('Symfony\Component\Asset\PackageInterface')); + + $this->assertEquals($default, $packages->getPackage()); + $this->assertEquals($a, $packages->getPackage('a')); + + $packages = new Packages($default, array('a' => $a)); + + $this->assertEquals($default, $packages->getPackage()); + $this->assertEquals($a, $packages->getPackage('a')); + } + + public function testGetVersion() + { + $packages = new Packages(new Package('default'), array('a' => new Package('a'))); + + $this->assertEquals('default', $packages->getVersion()); + $this->assertEquals('a', $packages->getVersion('a')); + } + + public function testGetUrl() + { + $packages = new Packages(new Package('default'), array('a' => new Package('a'))); + + $this->assertEquals('/foo?default', $packages->getUrl('/foo')); + $this->assertEquals('/foo?a', $packages->getUrl('/foo', 'a')); + $this->assertEquals('/foo?v1', $packages->getUrl('/foo', null, 'v1')); + } + + /** + * @expectedException \LogicException + */ + public function testNoDefaultPackage() + { + $packages = new Packages(); + $packages->getPackage(); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testUndefinedPackage() + { + $packages = new Packages(); + $packages->getPackage('a'); + } +} diff --git a/src/Symfony/Component/Asset/Tests/PathPackageTest.php b/src/Symfony/Component/Asset/Tests/PathPackageTest.php new file mode 100644 index 0000000000000..aeb578df44f9d --- /dev/null +++ b/src/Symfony/Component/Asset/Tests/PathPackageTest.php @@ -0,0 +1,54 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\Tests; + +use Symfony\Component\Asset\PathPackage; + +class PathPackageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getConfigs + */ + public function testGetUrl($basePath, $format, $path, $expected) + { + $package = new PathPackage($basePath, 'v1', $format); + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getConfigs() + { + return array( + array('/foo', '', 'http://example.com/foo', 'http://example.com/foo'), + array('/foo', '', 'https://example.com/foo', 'https://example.com/foo'), + array('/foo', '', '//example.com/foo', '//example.com/foo'), + + array('', '', '/foo', '/foo?v1'), + + array('/foo', '', '/foo', '/foo?v1'), + array('/foo', '', 'foo', '/foo/foo?v1'), + array('foo', '', 'foo', '/foo/foo?v1'), + array('foo/', '', 'foo', '/foo/foo?v1'), + array('/foo/', '', 'foo', '/foo/foo?v1'), + + array('/foo', 'version-%2$s/%1$s', '/foo', '/version-v1/foo'), + array('/foo', 'version-%2$s/%1$s', 'foo', '/foo/version-v1/foo'), + array('/foo', 'version-%2$s/%1$s', 'foo/', '/foo/version-v1/foo/'), + array('/foo', 'version-%2$s/%1$s', '/foo/', '/version-v1/foo/'), + ); + } + + public function testGetUrlWithSpecificVersion() + { + $package = new PathPackage('v1'); + $this->assertEquals('/foo?v2', $package->getUrl('/foo', 'v2')); + } +} diff --git a/src/Symfony/Component/Asset/Tests/RequestPathPackageTest.php b/src/Symfony/Component/Asset/Tests/RequestPathPackageTest.php new file mode 100644 index 0000000000000..903425c046ef9 --- /dev/null +++ b/src/Symfony/Component/Asset/Tests/RequestPathPackageTest.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\Tests; + +use Symfony\Component\Asset\RequestPathPackage; + +class RequestPathPackageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getConfigs + */ + public function testGetUrl($basePathRequest, $basePath, $format, $path, $expected) + { + $package = new RequestPathPackage($this->getRequestStack($basePathRequest), $basePath, 'v1', $format); + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getConfigs() + { + return array( + array('', '/foo', '', '/foo', '/foo?v1'), + array('', '/foo', '', 'foo', '/foo/foo?v1'), + array('', 'foo', '', 'foo', '/foo/foo?v1'), + array('', 'foo/', '', 'foo', '/foo/foo?v1'), + array('', '/foo/', '', 'foo', '/foo/foo?v1'), + + array('/bar', '/foo', '', '/foo', '/foo?v1'), + array('/bar', '/foo', '', 'foo', '/bar/foo/foo?v1'), + array('/bar', 'foo', '', 'foo', '/bar/foo/foo?v1'), + array('/bar', 'foo/', '', 'foo', '/bar/foo/foo?v1'), + array('/bar', '/foo/', '', 'foo', '/bar/foo/foo?v1'), + + array(false, '/foo/', '', 'foo', '/foo/foo?v1'), + ); + } + + private function getRequestStack($basePath) + { + $stack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack'); + + if (false === $basePath) { + $stack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue(null)); + + return $stack; + } + + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $request->expects($this->any())->method('getBasePath')->will($this->returnValue($basePath)); + + $stack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue($request)); + + return $stack; + } +} diff --git a/src/Symfony/Component/Asset/Tests/RequestUrlPathPackageTest.php b/src/Symfony/Component/Asset/Tests/RequestUrlPathPackageTest.php new file mode 100644 index 0000000000000..d01879e84b015 --- /dev/null +++ b/src/Symfony/Component/Asset/Tests/RequestUrlPathPackageTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\Tests; + +use Symfony\Component\Asset\RequestUrlPackage; + +class RequestUrlPackageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getConfigs + */ + public function testGetUrl($secure, $basePath, $format, $path, $expected) + { + $package = new RequestUrlPackage($this->getRequestStack($secure), $basePath, 'v1', $format); + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getConfigs() + { + return array( + array(false, 'http://example.com', '', 'foo', 'http://example.com/foo?v1'), + array(false, array('http://example.com'), '', 'foo', 'http://example.com/foo?v1'), + array(false, array('http://example.com', 'https://example.com'), '', 'foo', 'http://example.com/foo?v1'), + array(false, array('http://example.com', 'https://example.com'), '', 'fooa', 'https://example.com/fooa?v1'), + array(false, array('http://example.com/bar'), '', 'foo', 'http://example.com/bar/foo?v1'), + array(false, array('http://example.com/bar/'), '', 'foo', 'http://example.com/bar/foo?v1'), + array(false, array('//example.com/bar/'), '', 'foo', '//example.com/bar/foo?v1'), + + array(true, array('http://example.com'), '', 'foo', 'http://example.com/foo?v1'), + array(true, array('http://example.com', 'https://example.com'), '', 'foo', 'https://example.com/foo?v1'), + ); + } + + /** + * @expectedException \LogicException + */ + public function testNoBaseUrls() + { + new RequestUrlPackage($this->getRequestStack(false), array()); + } + + /** + * @expectedException \InvalidArgumentException + */ + public function testWrongBaseUrl() + { + new RequestUrlPackage($this->getRequestStack(false), array('not-a-url')); + } + + private function getRequestStack($secure) + { + $request = $this->getMock('Symfony\Component\HttpFoundation\Request'); + $request->expects($this->any())->method('isSecure')->will($this->returnValue($secure)); + + $stack = $this->getMock('Symfony\Component\HttpFoundation\RequestStack'); + $stack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue($request)); + + return $stack; + } +} diff --git a/src/Symfony/Component/Asset/Tests/UrlPackageTest.php b/src/Symfony/Component/Asset/Tests/UrlPackageTest.php new file mode 100644 index 0000000000000..880d8a9af50d8 --- /dev/null +++ b/src/Symfony/Component/Asset/Tests/UrlPackageTest.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset\Tests; + +use Symfony\Component\Asset\UrlPackage; + +class UrlPackageTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getConfigs + */ + public function testGetUrl($baseUrls, $format, $path, $expected) + { + $package = new UrlPackage($baseUrls, 'v1', $format); + $this->assertEquals($expected, $package->getUrl($path)); + } + + public function getConfigs() + { + return array( + array('http://example.net', '', 'http://example.com/foo', 'http://example.com/foo'), + array('http://example.net', '', 'https://example.com/foo', 'https://example.com/foo'), + array('http://example.net', '', '//example.com/foo', '//example.com/foo'), + + array('http://example.com', '', '/foo', 'http://example.com/foo?v1'), + array('http://example.com', '', 'foo', 'http://example.com/foo?v1'), + array('http://example.com/', '', 'foo', 'http://example.com/foo?v1'), + array('http://example.com/foo', '', 'foo', 'http://example.com/foo/foo?v1'), + array('http://example.com/foo/', '', 'foo', 'http://example.com/foo/foo?v1'), + + array(array('http://example.com'), '', '/foo', 'http://example.com/foo?v1'), + array(array('http://example.com', 'http://example.net'), '', '/foo', 'http://example.com/foo?v1'), + array(array('http://example.com', 'http://example.net'), '', '/fooa', 'http://example.net/fooa?v1'), + + array('http://example.com', 'version-%2$s/%1$s', '/foo', 'http://example.com/version-v1/foo'), + array('http://example.com', 'version-%2$s/%1$s', 'foo', 'http://example.com/version-v1/foo'), + array('http://example.com', 'version-%2$s/%1$s', 'foo/', 'http://example.com/version-v1/foo/'), + array('http://example.com', 'version-%2$s/%1$s', '/foo/', 'http://example.com/version-v1/foo/'), + ); + } + + public function testGetUrlWithSpecificVersion() + { + $package = new UrlPackage('http://example.com'); + $this->assertEquals('http://example.com/foo?v2', $package->getUrl('/foo', 'v2')); + } + + /** + * @expectedException \LogicException + */ + public function testNoBaseUrls() + { + new UrlPackage(array(), 'v1'); + } +} diff --git a/src/Symfony/Component/Asset/UrlPackage.php b/src/Symfony/Component/Asset/UrlPackage.php new file mode 100644 index 0000000000000..0ce541d732a5a --- /dev/null +++ b/src/Symfony/Component/Asset/UrlPackage.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Asset; + +/** + * Package that adds a base URL to asset URLs in addition to a version. + * + * The package allows to use more than one base URLs in which case + * it randomly chooses one for each asset; it also guarantees that + * any given path will always use the same base URL to be nice with + * HTTP caching mechanisms. + * + * @author Kris Wallsmith + */ +class UrlPackage extends Package +{ + protected $baseUrls; + + /** + * Constructor. + * + * @param string|array $baseUrls Base asset URLs + * @param string $version The package version + * @param string $format The format used to apply the version + */ + public function __construct($baseUrls = array(), $version = null, $format = null) + { + parent::__construct($version, $format); + + if (!is_array($baseUrls)) { + $baseUrls = (array) $baseUrls; + } + + if (!$baseUrls) { + throw new \LogicException('You must provide at least one base URL.'); + } + + $this->baseUrls = array(); + foreach ($baseUrls as $baseUrl) { + $this->baseUrls[] = rtrim($baseUrl, '/'); + } + } + + /** + * {@inheritdoc} + */ + public function getUrl($path, $version = null) + { + if ($this->isAbsoluteUrl($path)) { + return $path; + } + + $url = $this->applyVersion($path, $version); + + if ($url && '/' != $url[0]) { + $url = '/'.$url; + } + + return $this->getBaseUrl($path).$url; + } + + /** + * Returns the base URL for a path. + * + * @param string $path + * + * @return string The base URL + */ + public function getBaseUrl($path) + { + switch ($count = count($this->baseUrls)) { + case 1: + return $this->baseUrls[0]; + + default: + return $this->baseUrls[fmod(hexdec(substr(hash('sha256', $path), 0, 10)), $count)]; + } + } +} diff --git a/src/Symfony/Component/Asset/composer.json b/src/Symfony/Component/Asset/composer.json new file mode 100644 index 0000000000000..2f73863ff2678 --- /dev/null +++ b/src/Symfony/Component/Asset/composer.json @@ -0,0 +1,37 @@ +{ + "name": "symfony/asset", + "type": "library", + "description": "Symfony Asset Component", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "symfony/http-foundation": "" + }, + "require-dev": { + "symfony/http-foundation": "~2.4" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\Asset\\": "" } + }, + "target-dir": "Symfony/Component/Asset", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + } +} diff --git a/src/Symfony/Component/Asset/phpunit.xml.dist b/src/Symfony/Component/Asset/phpunit.xml.dist new file mode 100644 index 0000000000000..34ca2f294395d --- /dev/null +++ b/src/Symfony/Component/Asset/phpunit.xml.dist @@ -0,0 +1,29 @@ + + + + + + + + + + + ./Tests/ + + + + + + ./ + + ./vendor + ./Tests + + + + diff --git a/src/Symfony/Component/Templating/Asset/Package.php b/src/Symfony/Component/Templating/Asset/Package.php index 02a1269bd95ad..1690ac9bf713e 100644 --- a/src/Symfony/Component/Templating/Asset/Package.php +++ b/src/Symfony/Component/Templating/Asset/Package.php @@ -11,69 +11,15 @@ namespace Symfony\Component\Templating\Asset; +use Symfony\Component\Asset\Package as AssetPackage; + /** * The basic package will add a version to asset URLs. * * @author Kris Wallsmith + * + * @deprecated since 2.7, to be removed in 3.0. Use the Asset Component instead. */ -class Package implements PackageInterface +class Package extends AssetPackage implements PackageInterface { - private $version; - private $format; - - /** - * Constructor. - * - * @param string $version The package version - * @param string $format The format used to apply the version - */ - public function __construct($version = null, $format = '') - { - $this->version = $version; - $this->format = $format ?: '%s?%s'; - } - - /** - * {@inheritdoc} - */ - public function getVersion() - { - return $this->version; - } - - /** - * {@inheritdoc} - */ - public function getUrl($path, $version = null) - { - if (false !== strpos($path, '://') || 0 === strpos($path, '//')) { - return $path; - } - - return $this->applyVersion($path, $version); - } - - /** - * Applies version to the supplied path. - * - * @param string $path A path - * @param string|bool|null $version A specific version - * - * @return string The versionized path - */ - protected function applyVersion($path, $version = null) - { - $version = null !== $version ? $version : $this->version; - if (null === $version || false === $version) { - return $path; - } - - $versionized = sprintf($this->format, ltrim($path, '/'), $version); - - if ($path && '/' == $path[0]) { - $versionized = '/'.$versionized; - } - - return $versionized; - } } diff --git a/src/Symfony/Component/Templating/Asset/PackageInterface.php b/src/Symfony/Component/Templating/Asset/PackageInterface.php index 7317b555ac476..d65297cbfecf5 100644 --- a/src/Symfony/Component/Templating/Asset/PackageInterface.php +++ b/src/Symfony/Component/Templating/Asset/PackageInterface.php @@ -11,27 +11,15 @@ namespace Symfony\Component\Templating\Asset; +use Symfony\Component\Asset\PackageInterface as AssetPackageInterface; + /** * Asset package interface. * * @author Kris Wallsmith + * + * @deprecated since 2.7, to be removed in 3.0. Use the Asset Component instead. */ -interface PackageInterface +interface PackageInterface extends AssetPackageInterface { - /** - * Returns the asset package version. - * - * @return string The version string - */ - public function getVersion(); - - /** - * Returns an absolute or root-relative public path. - * - * @param string $path A path - * @param string|bool|null $version A specific version for the path - * - * @return string The public path - */ - public function getUrl($path, $version = null); } diff --git a/src/Symfony/Component/Templating/Asset/PathPackage.php b/src/Symfony/Component/Templating/Asset/PathPackage.php index 1806107f6dfca..75090757ca9c2 100644 --- a/src/Symfony/Component/Templating/Asset/PathPackage.php +++ b/src/Symfony/Component/Templating/Asset/PathPackage.php @@ -11,63 +11,15 @@ namespace Symfony\Component\Templating\Asset; +use Symfony\Component\Asset\PathPackage as AssetPathPackage; + /** * The path packages adds a version and a base path to asset URLs. * * @author Kris Wallsmith + * + * @deprecated since 2.7, to be removed in 3.0. Use the Asset Component instead. */ -class PathPackage extends Package +class PathPackage extends AssetPathPackage implements PackageInterface { - private $basePath; - - /** - * Constructor. - * - * @param string $basePath The base path to be prepended to relative paths - * @param string $version The package version - * @param string $format The format used to apply the version - */ - public function __construct($basePath = null, $version = null, $format = null) - { - parent::__construct($version, $format); - - if (!$basePath) { - $this->basePath = '/'; - } else { - if ('/' != $basePath[0]) { - $basePath = '/'.$basePath; - } - - $this->basePath = rtrim($basePath, '/').'/'; - } - } - - /** - * {@inheritdoc} - */ - public function getUrl($path, $version = null) - { - if (false !== strpos($path, '://') || 0 === strpos($path, '//')) { - return $path; - } - - $url = $this->applyVersion($path, $version); - - // apply the base path - if ('/' !== substr($url, 0, 1)) { - $url = $this->basePath.$url; - } - - return $url; - } - - /** - * Returns the base path. - * - * @return string The base path - */ - public function getBasePath() - { - return $this->basePath; - } } diff --git a/src/Symfony/Component/Templating/Asset/UrlPackage.php b/src/Symfony/Component/Templating/Asset/UrlPackage.php index 00a21670f4459..6853444d29739 100644 --- a/src/Symfony/Component/Templating/Asset/UrlPackage.php +++ b/src/Symfony/Component/Templating/Asset/UrlPackage.php @@ -11,72 +11,15 @@ namespace Symfony\Component\Templating\Asset; +use Symfony\Component\Asset\UrlPackage as AssetUrlPackage; + /** * The URL packages adds a version and a base URL to asset URLs. * * @author Kris Wallsmith + * + * @deprecated since 2.7, to be removed in 3.0. Use the Asset Component instead. */ -class UrlPackage extends Package +class UrlPackage extends AssetUrlPackage implements PackageInterface { - private $baseUrls; - - /** - * Constructor. - * - * @param string|array $baseUrls Base asset URLs - * @param string $version The package version - * @param string $format The format used to apply the version - */ - public function __construct($baseUrls = array(), $version = null, $format = null) - { - parent::__construct($version, $format); - - if (!is_array($baseUrls)) { - $baseUrls = (array) $baseUrls; - } - - $this->baseUrls = array(); - foreach ($baseUrls as $baseUrl) { - $this->baseUrls[] = rtrim($baseUrl, '/'); - } - } - - /** - * {@inheritdoc} - */ - public function getUrl($path, $version = null) - { - if (false !== strpos($path, '://') || 0 === strpos($path, '//')) { - return $path; - } - - $url = $this->applyVersion($path, $version); - - if ($url && '/' != $url[0]) { - $url = '/'.$url; - } - - return $this->getBaseUrl($path).$url; - } - - /** - * Returns the base URL for a path. - * - * @param string $path - * - * @return string The base URL - */ - public function getBaseUrl($path) - { - switch ($count = count($this->baseUrls)) { - case 0: - return ''; - - case 1: - return $this->baseUrls[0]; - - default: - return $this->baseUrls[fmod(hexdec(substr(hash('sha256', $path), 0, 10)), $count)]; - } - } } diff --git a/src/Symfony/Component/Templating/composer.json b/src/Symfony/Component/Templating/composer.json index ab98c9d00ec39..2ae135554ee88 100644 --- a/src/Symfony/Component/Templating/composer.json +++ b/src/Symfony/Component/Templating/composer.json @@ -16,7 +16,8 @@ } ], "require": { - "php": ">=5.3.3" + "php": ">=5.3.3", + "symfony/asset": "~2.7" }, "require-dev": { "psr/log": "~1.0"