diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 02f40b7..bbf5c43 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -16,7 +16,6 @@ use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; -use Symfony\Component\Routing\Exception\ResourceNotFoundException; use Symfony\Component\Routing\Route; /** @@ -923,6 +922,7 @@ function _menu_link_translate(&$item) { * @return bool * TRUE if the user has access or FALSE if the user should be presented * with access denied. + * */ function menu_item_route_access(Route $route, $href, &$map, Request $request = NULL) { if (!isset($request)) { @@ -937,9 +937,6 @@ function menu_item_route_access(Route $route, $href, &$map, Request $request = N catch (NotFoundHttpException $e) { return FALSE; } - catch (ResourceNotFoundException $e) { - return FALSE; - } // Populate the map with any matching values from the request. $path_bits = explode('/', trim($route->getPath(), '/')); diff --git a/core/lib/Drupal.php b/core/lib/Drupal.php index f98d607..0ec88d7 100644 --- a/core/lib/Drupal.php +++ b/core/lib/Drupal.php @@ -5,7 +5,6 @@ * Contains Drupal. */ -use Drupal\Core\Url; use Symfony\Component\DependencyInjection\ContainerInterface; /** @@ -518,13 +517,7 @@ public static function linkGenerator() { * @see \Drupal\Core\Utility\LinkGeneratorInterface::generate() */ public static function l($text, $route_name, array $parameters = array(), array $options = array()) { - $link_generator = static::linkGenerator(); - if ($route_name instanceof Url) { - return $link_generator->generateFromUrl($text, $route_name); - } - else { - return $link_generator->generate($text, $route_name, $parameters, $options); - } + return static::$container->get('link_generator')->generate($text, $route_name, $parameters, $options); } /** diff --git a/core/lib/Drupal/Core/Routing/MatchingRouteNotFoundException.php b/core/lib/Drupal/Core/Routing/MatchingRouteNotFoundException.php new file mode 100644 index 0000000..6856580 --- /dev/null +++ b/core/lib/Drupal/Core/Routing/MatchingRouteNotFoundException.php @@ -0,0 +1,17 @@ +match('/' . $path); + try { + $result = \Drupal::service('router')->match('/' . $path); + } + catch (ResourceNotFoundException $e) { + throw new MatchingRouteNotFoundException($e->getMessage()); + } $route_name = $result[RouteObjectInterface::ROUTE_NAME]; $route_parameters = $result['_raw_variables']->all(); } @@ -135,11 +142,16 @@ public static function createFromPath($path) { * @return static * A Url object. * - * @throws \Symfony\Component\Routing\Exception\ResourceNotFoundException + * @throws \Drupal\Core\Routing\MatchingRouteNotFoundException * Thrown when the request cannot be matched. */ public static function createFromRequest(Request $request) { - $result = \Drupal::service('router')->matchRequest($request); + try { + $result = \Drupal::service('router')->matchRequest($request); + } + catch (ResourceNotFoundException $e) { + throw new MatchingRouteNotFoundException($e->getMessage()); + } $route_name = $result[RouteObjectInterface::ROUTE_NAME]; $route_parameters = $result['_raw_variables']->all(); return new static($route_name, $route_parameters); diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php index 5c06ab8..5e8bf6d 100644 --- a/core/lib/Drupal/Core/Utility/LinkGenerator.php +++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php @@ -15,6 +15,7 @@ use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Template\Attribute; use Drupal\Core\Routing\UrlGeneratorInterface; +use Drupal\Core\Session\AccountInterface; use Drupal\Core\Url; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; @@ -99,14 +100,27 @@ public function getActive() { /** * {@inheritdoc} + * + * For anonymous users, the "active" class will be calculated on the server, + * because most sites serve each anonymous user the same cached page anyway. + * For authenticated users, the "active" class will be calculated on the + * client (through JavaScript), only data- attributes are added to links to + * prevent breaking the render cache. The JavaScript is added in + * system_page_build(). + * + * @see system_page_build() */ public function generateFromUrl($text, Url $url) { - // @todo Inject the service when drupal_render() is converted to one. - $text = is_array($text) ? drupal_render($text) : $text; + // Start building a structured representation of our link to be altered later. + $variables = array( + // @todo Inject the service when drupal_render() is converted to one. + 'text' => is_array($text) ? drupal_render($text) : $text, + 'url' => $url, + 'options' => $url->getOptions(), + ); // Merge in default options. - $options = $url->getOptions(); - $options += array( + $variables['options'] += array( 'attributes' => array(), 'query' => array(), 'html' => FALSE, @@ -117,65 +131,54 @@ public function generateFromUrl($text, Url $url) { // Add a hreflang attribute if we know the language of this link's url and // hreflang has not already been set. - if (!empty($options['language']) && !isset($options['attributes']['hreflang'])) { - $options['attributes']['hreflang'] = $options['language']->id; + if (!empty($variables['options']['language']) && !isset($variables['options']['attributes']['hreflang'])) { + $variables['options']['attributes']['hreflang'] = $variables['options']['language']->id; } // Set the "active" class if the 'set_active_class' option is not empty. - if (!empty($options['set_active_class'])) { + if (!empty($variables['options']['set_active_class'])) { // Add a "data-drupal-link-query" attribute to let the // drupal.active-link library know the query in a standardized manner. - if (!empty($options['query'])) { - $query = $options['query']; + if (!empty($variables['options']['query'])) { + $query = $variables['options']['query']; ksort($query); - $options['attributes']['data-drupal-link-query'] = Json::encode($query); + $variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query); } // Add a "data-drupal-link-system-path" attribute to let the // drupal.active-link library know the path in a standardized manner. - if (!isset($options['attributes']['data-drupal-link-system-path'])) { - $options['attributes']['data-drupal-link-system-path'] = $this->aliasManager->getSystemPath($url->getInternalPath()); + if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) { + $path = $url->getInternalPath(); + $variables['options']['attributes']['data-drupal-link-system-path'] = $this->aliasManager->getSystemPath($path); } } // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags() // only when a quick strpos() gives suspicion tags are present. - if (isset($options['attributes']['title']) && strpos($options['attributes']['title'], '<') !== FALSE) { - $options['attributes']['title'] = strip_tags($options['attributes']['title']); + if (isset($variables['options']['attributes']['title']) && strpos($variables['options']['attributes']['title'], '<') !== FALSE) { + $variables['options']['attributes']['title'] = strip_tags($variables['options']['attributes']['title']); } - // Update the options before allowing altering. - $url->setOptions($options); - // Allow other modules to modify the structure of the link. - $this->moduleHandler->alter('link', $text, $url); + $this->moduleHandler->alter('link', $variables); // Move attributes out of options. generateFromRoute(() doesn't need them. - $attributes = new Attribute($options['attributes']); - unset($options['attributes']); - $url->setOptions($options); + $attributes = new Attribute($variables['options']['attributes']); + unset($variables['options']['attributes']); + $url->setOptions($variables['options']); // The result of the url generator is a plain-text URL. Because we are using // it here in an HTML argument context, we need to encode it properly. $url = String::checkPlain($url->toString()); // Sanitize the link text if necessary. - $text = $options['html'] ? $text : String::checkPlain($text); + $text = $variables['options']['html'] ? $text : String::checkPlain($text); return '' . $text . ''; } /** * {@inheritdoc} - * - * For anonymous users, the "active" class will be calculated on the server, - * because most sites serve each anonymous user the same cached page anyway. - * For authenticated users, the "active" class will be calculated on the - * client (through JavaScript), only data- attributes are added to links to - * prevent breaking the render cache. The JavaScript is added in - * system_page_build(). - * - * @see system_page_build() */ public function generate($text, $route_name, array $parameters = array(), array $options = array()) { $url = new Url($route_name, $parameters, $options); diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php index 42159d6..3321c72 100644 --- a/core/modules/system/system.api.php +++ b/core/modules/system/system.api.php @@ -5,7 +5,6 @@ * Hooks provided by Drupal core and the System module. */ -use Drupal\Core\Url; use Drupal\Core\Utility\UpdateException; /** @@ -3158,35 +3157,47 @@ function hook_filetransfer_info_alter(&$filetransfer_info) { /** * Alter the parameters for links. * - * @param string $text - * The link text for the anchor tag as a translated string. - * @param \Drupal\Core\Url $url - * The URL object used for the link. Url::getOptions() can contain many - * keys, but the following are guaranteed to exist: - * - query: An array of query key/value-pairs (without any URL-encoding) to - * append to the URL. - * - absolute: Whether to force the output to be an absolute link (beginning - * with http:). Useful for links that will be displayed outside the site, - * such as in an RSS feed. Defaults to FALSE. - * - language: An optional language object. May affect the rendering of the - * anchor tag, such as by adding a language prefix to the path. - * - attributes: An associative array of HTML attributes to apply to the - * anchor tag. If element 'class' is included, it must be an array; 'title' - * must be a string; other elements are more flexible, as they just need - * to work as an argument for the constructor of the class - * \Drupal\Core\Template\Attribute($options['attributes']). - * - html: Whether or not HTML should be allowed as the link text. If FALSE, - * the text will be run through - * \Drupal\Component\Utility\String::checkPlain() before being output. - * - set_active_class: Whether to set the 'active' class or not. + * @param array $variables + * An associative array of variables defining a link. The link may be either a + * "route link" using \Drupal\Core\Utility\LinkGenerator::link(), which is + * exposed as the 'link_generator' service or a link generated by l(). If the + * link is a "route link", 'route_name' will be set, otherwise 'path' will be + * set. The following keys can be altered: + * - text: The link text for the anchor tag as a translated string. + * - url_is_active: Whether or not the link points to the currently active + * URL. + * - path: If this link is being generated by l(), this system path, relative + * path, or external URL will be passed to url() to generate the href + * attribute for this link. + * - url: The \Drupal\Core\Url object. + * - options: An associative array of additional options that will be passed + * to either \Drupal\Core\Routing\UrlGenerator::generateFromPath() or + * \Drupal\Core\Routing\UrlGenerator::generateFromRoute() to generate the + * href attribute for this link, and also used when generating the link. + * Defaults to an empty array. It may contain the following elements: + * - 'query': An array of query key/value-pairs (without any URL-encoding) to + * append to the URL. + * - absolute: Whether to force the output to be an absolute link (beginning + * with http:). Useful for links that will be displayed outside the site, + * such as in an RSS feed. Defaults to FALSE. + * - language: An optional language object. May affect the rendering of + * the anchor tag, such as by adding a language prefix to the path. + * - attributes: An associative array of HTML attributes to apply to the + * anchor tag. If element 'class' is included, it must be an array; 'title' + * must be a string; other elements are more flexible, as they just need + * to work as an argument for the constructor of the class + * Drupal\Core\Template\Attribute($options['attributes']). + * - html: Whether or not HTML should be allowed as the link text. If FALSE, + * the text will be run through + * \Drupal\Component\Utility\String::checkPlain() before being output. * * @see \Drupal\Core\Routing\UrlGenerator::generateFromPath() * @see \Drupal\Core\Routing\UrlGenerator::generateFromRoute() */ -function hook_link_alter(&$text, Url $url) { +function hook_link_alter(&$variables) { // Add a warning to the end of route links to the admin section. - if (strpos($url->getRouteName(), 'admin') !== FALSE) { - $text .= ' (Warning!)'; + if (strpos($variables['url']->getRouteName(), 'admin') !== FALSE) { + $variables['text'] .= ' (Warning!)'; } } diff --git a/core/tests/Drupal/Tests/Core/DrupalTest.php b/core/tests/Drupal/Tests/Core/DrupalTest.php index 2a9e764..b37010f 100644 --- a/core/tests/Drupal/Tests/Core/DrupalTest.php +++ b/core/tests/Drupal/Tests/Core/DrupalTest.php @@ -7,7 +7,6 @@ namespace Drupal\Tests\Core; -use Drupal\Core\Url; use Drupal\Tests\UnitTestCase; /** @@ -297,30 +296,7 @@ public function testL() { ->will($this->returnValue('link_html_string')); $this->setMockContainerService('link_generator', $generator); - $link = \Drupal::l('Test title', 'test_route', $route_parameters, $options); - $this->assertSame('link_html_string', $link); - $this->assertInternalType('string', $link); - } - - /** - * Tests the l() method. - * - * @see \Drupal\Core\Utility\LinkGeneratorInterface::generateFromUrl() - */ - public function testLWithUrl() { - $route_parameters = array('test_parameter' => 'test'); - $options = array('test_option' => 'test'); - $url = new Url('test_route', $route_parameters, $options); - $generator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface'); - $generator->expects($this->once()) - ->method('generateFromUrl') - ->with('Test title', $url) - ->will($this->returnValue('link_html_string')); - $this->setMockContainerService('link_generator', $generator); - - $link = \Drupal::l('Test title', $url); - $this->assertSame('link_html_string', $link); - $this->assertInternalType('string', $link); + $this->assertInternalType('string', \Drupal::l('Test title', 'test_route', $route_parameters, $options)); } /** diff --git a/core/tests/Drupal/Tests/Core/ExternalUrlTest.php b/core/tests/Drupal/Tests/Core/ExternalUrlTest.php index cb21fb8..99eccea 100644 --- a/core/tests/Drupal/Tests/Core/ExternalUrlTest.php +++ b/core/tests/Drupal/Tests/Core/ExternalUrlTest.php @@ -93,7 +93,7 @@ public function testCreateFromPath() { * * @covers ::createFromRequest() * - * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + * @expectedException \Drupal\Core\Routing\MatchingRouteNotFoundException */ public function testCreateFromRequest() { $request = new Request(); diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php index ac64fe7..7a61b2c 100644 --- a/core/tests/Drupal/Tests/Core/UrlTest.php +++ b/core/tests/Drupal/Tests/Core/UrlTest.php @@ -14,8 +14,6 @@ use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\ResourceNotFoundException; -use Symfony\Component\Routing\Matcher\RequestMatcherInterface; -use Symfony\Component\Routing\RouterInterface; /** * Tests the \Drupal\Core\Url class. @@ -89,7 +87,7 @@ protected function setUp() { * @covers ::createFromPath() */ public function testCreateFromPath() { - $this->router->expects($this->exactly(3)) + $this->router->expects($this->any()) ->method('match') ->will($this->returnValueMap(array( array('/node', array( @@ -131,7 +129,7 @@ public function testCreateFromPathFront() { * * @covers ::createFromPath() * - * @expectedException \Symfony\Component\Routing\Exception\ResourceNotFoundException + * @expectedException \Drupal\Core\Routing\MatchingRouteNotFoundException */ public function testCreateFromPathInvalid() { $this->router->expects($this->once()) @@ -167,6 +165,23 @@ public function testCreateFromRequest() { } /** + * Tests that an invalid request will thrown an exception. + * + * @covers ::createFromRequest() + * + * @expectedException \Drupal\Core\Routing\MatchingRouteNotFoundException + */ + public function testCreateFromRequestInvalid() { + $request = new Request(); + $this->router->expects($this->once()) + ->method('matchRequest') + ->with($request) + ->will($this->throwException(new ResourceNotFoundException())); + + Url::createFromRequest($request); + } + + /** * Tests the isExternal() method. * * @depends testCreateFromPath diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php index 23dd3ee..d9a3078 100644 --- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php @@ -8,6 +8,7 @@ namespace Drupal\Tests\Core\Utility { use Drupal\Core\Language\Language; +use Drupal\Core\Url; use Drupal\Core\Utility\LinkGenerator; use Drupal\Tests\UnitTestCase; use Symfony\Cmf\Component\Routing\RouteObjectInterface; @@ -18,6 +19,8 @@ * Tests the link generator. * * @see \Drupal\Core\Utility\LinkGenerator + * + * @coversDefaultClass \Drupal\Core\Utility\LinkGenerator */ class LinkGeneratorTest extends UnitTestCase { @@ -150,6 +153,34 @@ public function testGenerateHrefs($route_name, array $parameters, $absolute, $ur } /** + * Tests the generateFromUrl() method. + * + * @covers ::generateFromUrl() + */ + public function testGenerateFromUrl() { + $this->urlGenerator->expects($this->once()) + ->method('generateFromRoute') + ->with('test_route_1', array(), array('fragment' => 'the-fragment') + $this->defaultOptions) + ->will($this->returnValue('/test-route-1#the-fragment')); + + $this->moduleHandler->expects($this->once()) + ->method('alter') + ->with('link', $this->isType('array')); + + $url = new Url('test_route_1', array(), array('fragment' => 'the-fragment')); + $url->setUrlGenerator($this->urlGenerator); + + $result = $this->linkGenerator->generateFromUrl('Test', $url); + $this->assertTag(array( + 'tag' => 'a', + 'attributes' => array( + 'href' => '/test-route-1#the-fragment', + ), + 'content' => 'Test', + ), $result); + } + + /** * Tests the link method with additional attributes. * * @see \Drupal\Core\Utility\LinkGenerator::generate()