diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 248c795..89cdc53 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1697,6 +1697,25 @@ function template_preprocess_table(&$variables) { } /** + * Prepares variables for tablesort indicator templates. + * + * Default template: tablesort-indicator.html.twig. + * + * @param array $variables + * An associative array containing: + * - style: Set to either 'asc' or 'desc'. This determines which icon to show. + */ +function template_preprocess_tablesort_indicator(&$variables) { + // Provide the image attributes for an ascending or descending image. + if ($variables['style'] == 'asc') { + $variables['arrow_asc'] = file_create_url('core/misc/arrow-asc.png'); + } + else { + $variables['arrow_desc'] = file_create_url('core/misc/arrow-desc.png'); + } +} + +/** * Prepares variables for item list templates. * * Default template: item-list.html.twig. diff --git a/core/lib/Drupal/Core/CoreServiceProvider.php b/core/lib/Drupal/Core/CoreServiceProvider.php index 42c7dea..34ad258 100644 --- a/core/lib/Drupal/Core/CoreServiceProvider.php +++ b/core/lib/Drupal/Core/CoreServiceProvider.php @@ -84,6 +84,9 @@ public static function registerTwig(ContainerBuilder $container) { ->addArgument(DRUPAL_ROOT); $container->setAlias('twig.loader', 'twig.loader.filesystem'); + $twig_extension = new Definition('Drupal\Core\Template\TwigExtension'); + $twig_extension->addMethodCall('setGenerators', array(new Reference('url_generator'))); + $container->register('twig', 'Drupal\Core\Template\TwigEnvironment') ->addArgument(new Reference('twig.loader')) ->addArgument(array( @@ -99,7 +102,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..05c7390 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,35 @@ 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; + return $this; + } + + /** * {@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 + new \Twig_SimpleFunction('url', array($this, 'getUrl'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + new \Twig_SimpleFunction('path', array($this, 'getPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), + new \Twig_SimpleFunction('url_from_path', array($this, 'getUrlFromPath'), array('is_safe_callback' => array($this, 'isUrlGenerationSafe'))), ); } @@ -88,4 +112,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/node/templates/node-add-list.html.twig b/core/modules/node/templates/node-add-list.html.twig index 7323896..258d3e6 100644 --- a/core/modules/node/templates/node-add-list.html.twig +++ b/core/modules/node/templates/node-add-list.html.twig @@ -24,7 +24,7 @@ {% else %}
- {% set create_content = url('admin/structure/types/add') %} + {% set create_content = path('node.type_add') %} {% trans %} You have not created any content types yet. Go to the content type creation page to add a new content type. {% endtrans %} diff --git a/core/modules/system/src/Tests/Theme/EngineTwigTest.php b/core/modules/system/src/Tests/Theme/EngineTwigTest.php index 2e89f47..0f69878 100644 --- a/core/modules/system/src/Tests/Theme/EngineTwigTest.php +++ b/core/modules/system/src/Tests/Theme/EngineTwigTest.php @@ -41,4 +41,26 @@ function testTwigVariableDataTypes() { } } + /** + * Tests the url and url_generate Twig functions. + */ + 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'); + $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('
+ {% set create_content = path('node.type_add') %} {% trans %} - You have not created any content types yet. Go to the content type creation page to add a new content type. + You have not created any content types yet. Go to the content type creation page to add a new content type. {% endtrans %}
{% endif %}