diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index a7f5ef4..eb8ce9b 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -81,6 +81,11 @@ public static function registerTwig(ContainerBuilder $container) { ->addArgument(DRUPAL_ROOT); $container->setAlias('twig.loader', 'twig.loader.filesystem'); + $twig_extension = new Definition('Drupal\Core\Template\TwigExtension'); + // When in the installer these services are not yet available. + if (!drupal_installation_attempted()) { + $twig_extension->addMethodCall('setGenerators', array(new Reference('url_generator'))); + } $container->register('twig', 'Drupal\Core\Template\TwigEnvironment') ->addArgument(new Reference('twig.loader')) ->addArgument(array( @@ -96,7 +101,7 @@ public static function registerTwig(ContainerBuilder $container) { )) ->addArgument(new Reference('module_handler')) ->addArgument(new Reference('theme_handler')) - ->addMethodCall('addExtension', array(new Definition('Drupal\Core\Template\TwigExtension'))) + ->addMethodCall('addExtension', array($twig_extension)) // @todo Figure out what to do about debugging functions. // @see http://drupal.org/node/1804998 ->addMethodCall('addExtension', array(new Definition('Twig_Extension_Debug'))) diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 69e0bb3..47e445b 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -12,6 +12,8 @@ namespace Drupal\Core\Template; +use Drupal\Core\Routing\UrlGeneratorInterface; + /** * A class providing Drupal Twig extensions. * @@ -22,13 +24,34 @@ class TwigExtension extends \Twig_Extension { /** + * The URL generator. + * + * @var \Drupal\Core\Routing\UrlGeneratorInterface + */ + protected $urlGenerator; + + /** + * Constructs \Drupal\Core\Template\TwigExtension. + * + * @param \Drupal\Core\Routing\UrlGeneratorInterface $url_generator + * The URL generator. + */ + public function setGenerators(UrlGeneratorInterface $url_generator) { + $this->urlGenerator = $url_generator; + } + + /** * {@inheritdoc} */ public function getFunctions() { return array( - new \Twig_SimpleFunction('url', 'url'), // This function will receive a renderable array, if an array is detected. new \Twig_SimpleFunction('render_var', 'twig_render_var'), + // The url and path function are defined in close parallel to those found + // in \Symfony\Bridge\Twig\Extension\RoutingExtension + 'url' => new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + 'path' => new \Twig_SimpleFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + 'url_from_path' => new \Twig_SimpleFunction('url_from_path', array($this, 'getUrlFromPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), ); } @@ -88,4 +111,101 @@ public function getName() { return 'drupal_core'; } + /** + * Generates a URL path given a route name and parameters. + * + * @param $name + * The name of the route. + * @param array $parameters + * An associative array of route parameters names and values. + * @param array $options + * (optional) An associative array of additional options. The 'absolute' + * option is forced to be FALSE. + * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromRoute(). + * + * @return string + * The generated URL path (relative URL) for the given route. + */ + public function getPath($name, $parameters = array(), $options = array()) { + $options['absolute'] = FALSE; + return $this->urlGenerator->generateFromRoute($name, $parameters, $options); + } + + /** + * Generates an absolute URL given a route name and parameters. + * + * @param $name + * The name of the route. + * @param array $parameters + * An associative array of route parameter names and values. + * @param array $options + * (optional) An associative array of additional options. The 'absolute' + * option is forced to be TRUE. + * + * @return string + * The generated absolute URL for the given route. + * + * @todo Add an option for scheme-relative URLs. + */ + public function getUrl($name, $parameters = array(), $options = array()) { + $options['absolute'] = TRUE; + return $this->urlGenerator->generateFromRoute($name, $parameters, $options); + } + + /** + * Generates an absolute URL given a path. + * + * @param string $path + * The path. + * @param array $options + * (optional) An associative array of additional options. The 'absolute' + * option is forced to be TRUE. + * + * @return string + * The generated absolute URL for the given path. + */ + public function getUrlFromPath($path, $options = array()) { + $options['absolute'] = TRUE; + return $this->urlGenerator->generateFromPath($path, $options); + } + + /** + * Determines at compile time whether the generated URL will be safe. + * + * Saves the unneeded automatic escaping for performance reasons. + * + * The URL generation process percent encodes non-alphanumeric characters. + * Thus, the only character within an URL that must be escaped in HTML is the + * ampersand ("&") which separates query params. Thus we cannot mark + * the generated URL as always safe, but only when we are sure there won't be + * multiple query params. This is the case when there are none or only one + * constant parameter given. E.g. we know beforehand this will not need to + * be escaped: + * - path('route') + * - path('route', {'param': 'value'}) + * But the following may need to be escaped: + * - path('route', var) + * - path('route', {'param': ['val1', 'val2'] }) // a sub-array + * - path('route', {'param1': 'value1', 'param2': 'value2'}) + * If param1 and param2 reference placeholders in the route, it would not + * need to be escaped, but we don't know that in advance. + * + * @param \Twig_Node $args_node + * The arguments of the path/url functions. + * + * @return array + * An array with the contexts the URL is safe + */ + public function isUrlGenerationSafe(\Twig_Node $args_node) { + // Support named arguments. + $parameter_node = $args_node->hasNode('parameters') ? $args_node->getNode('parameters') : ($args_node->hasNode(1) ? $args_node->getNode(1) : NULL); + + if (!isset($parameter_node) || $parameter_node instanceof \Twig_Node_Expression_Array && count($parameter_node) <= 2 && + (!$parameter_node->hasNode(1) || $parameter_node->getNode(1) instanceof \Twig_Node_Expression_Constant)) { + return array('html'); + } + + return array(); + } + } diff --git a/core/modules/system/src/Tests/Theme/EngineTwigTest.php b/core/modules/system/src/Tests/Theme/EngineTwigTest.php index 2e89f47..d295182 100644 --- a/core/modules/system/src/Tests/Theme/EngineTwigTest.php +++ b/core/modules/system/src/Tests/Theme/EngineTwigTest.php @@ -41,4 +41,27 @@ function testTwigVariableDataTypes() { } } + /** + * Tests the url and url_generate Twig function. + */ + public function testTwigUrlGenerator() { + $this->drupalGet('twig-theme-test/url-generator'); + // Find the absolute URL of the current site. + $url_generator = $this->container->get('url_generator'); + $link_generator = $this->container->get('link_generator'); + $expected = array( + 'path (as route) not absolute: ' . $url_generator->generateFromRoute('user.register'), + 'url (as route) absolute: ' . $url_generator->generateFromRoute('user.register', array(), array('absolute' => TRUE)), + 'path (as route) not absolute with fragment: ' . $url_generator->generateFromRoute('user.register', array(), array('fragment' => 'bottom')), + 'url (as route) absolute despite option: ' . $url_generator->generateFromRoute('user.register', array(), array('absolute' => TRUE)), + 'url (as route) absolute with fragment: ' . $url_generator->generateFromRoute('user.register', array(), array('absolute' => TRUE, 'fragment' => 'bottom')), + ); + // Make sure we got something. + $content = $this->drupalGetContent(); + $this->assertFalse(empty($content), 'Page content is not empty'); + foreach ($expected as $string) { + $this->assertRaw('
' . $string . '
'); + } + } + } diff --git a/core/modules/system/templates/tablesort-indicator.html.twig b/core/modules/system/templates/tablesort-indicator.html.twig index 1f0eb9d..ab4e2c2 100644 --- a/core/modules/system/templates/tablesort-indicator.html.twig +++ b/core/modules/system/templates/tablesort-indicator.html.twig @@ -10,7 +10,7 @@ */ #} {% if style == 'asc' -%} - {{ 'sort ascending'|t }} + {{ 'sort ascending'|t }} {% else -%} - {{ 'sort descending'|t }} + {{ 'sort descending'|t }} {% endif %} diff --git a/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php b/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php index 45654ea..d454e5f 100644 --- a/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php +++ b/core/modules/system/tests/modules/twig_theme_test/src/TwigThemeTestController.php @@ -28,4 +28,13 @@ public function transBlockRender() { ); } + /** + * Renders for testing url_generator functions in a Twig template. + */ + public function urlGeneratorRender() { + return array( + '#theme' => 'twig_theme_test_url_generator', + ); + } + } diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.url_generator.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.url_generator.html.twig new file mode 100644 index 0000000..131a612 --- /dev/null +++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.url_generator.html.twig @@ -0,0 +1,7 @@ +{# Test the url and path twig functions #} +
path (as route) not absolute: {{ path('user.register') }}
+
url (as route) absolute: {{ url('user.register') }}
+ +
path (as route) not absolute with fragment: {{ path('user.register', {}, {'fragment': 'bottom' }) }}
+
url (as route) absolute despite option: {{ url('user.register', {}, {'absolute': false }) }}
+
url (as route) absolute with fragment: {{ url('user.register', {}, {'fragment': 'bottom' }) }}
diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module index 5874cca..0497c7b 100644 --- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module +++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.module @@ -23,6 +23,10 @@ function twig_theme_test_theme($existing, $type, $theme, $path) { 'variables' => array('script' => ''), 'template' => 'twig-raw-test', ); + $items['twig_theme_test_url_generator'] = array( + 'variables' => array(), + 'template' => 'twig_theme_test.url_generator', + ); return $items; } diff --git a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml index 96befd4..75b9bbb 100644 --- a/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml +++ b/core/modules/system/tests/modules/twig_theme_test/twig_theme_test.routing.yml @@ -11,3 +11,10 @@ twig_theme_test.trans: _content: '\Drupal\twig_theme_test\TwigThemeTestController::transBlockRender' requirements: _access: 'TRUE' + +twig_theme_test_url_generator: + path: '/twig-theme-test/url-generator' + defaults: + _content: '\Drupal\twig_theme_test\TwigThemeTestController::urlGeneratorRender' + requirements: + _access: 'TRUE' diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh index 88ba503..af935b9 100755 --- a/core/scripts/run-tests.sh +++ b/core/scripts/run-tests.sh @@ -336,6 +336,7 @@ function simpletest_script_init() { } $_SERVER['HTTP_HOST'] = $host; + $_SERVER['SERVER_PORT'] = isset($parsed_url['port']) ? $parsed_url['port'] : 80; $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; $_SERVER['SERVER_ADDR'] = '127.0.0.1'; $_SERVER['SERVER_PORT'] = $port;