diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index 70e0461..7125bc1 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -214,7 +214,7 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) { * (optional) An associative array of additional URL options, with the * following elements: * - 'query': An array of query key/value-pairs (without any URL-encoding) - * to append to the URL. Merged with the parameters array. + * to append to the URL. * - 'fragment': A fragment identifier (named anchor) to append to the URL. * Do not include the leading '#' character. * - 'absolute': Defaults to FALSE. Whether to force the output to be an @@ -247,27 +247,30 @@ public static function fromUri($uri, $options = []) { throw new \InvalidArgumentException(String::format('The URI "@uri" is malformed.', ['@uri' => $uri])); } if (empty($uri_parts['scheme'])) { - throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base: for items like a static file that needs the base path. Use user-path: for user input without a scheme. Use entity: for referencing the canonical route of a content entity.', ['@uri' => $uri])); + throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base: for items like a static file that needs the base path. Use user-path: for user input without a scheme. Use entity: for referencing the canonical route of a content entity. Use route: for directly representing a route name and parameters.', ['@uri' => $uri])); + } + $uri_parts += ['path' => '']; + // Extract query parameters and fragment and merge them into $uri_options, + // but preserve the original $options for the fallback case. + if (!empty($uri_parts['fragment'])) { + $uri_options = $options + ['fragment' => $uri_parts['fragment']]; } - $uri_parts += ['path' => '', 'fragment' => '']; - // Extract query parameters and fragment and merge them into $options. - $options += ['fragment' => $uri_parts['fragment']]; unset($uri_parts['fragment']); if (!empty($uri_parts['query'])) { $uri_query = []; parse_str($uri_parts['query'], $uri_query); - $options['query'] += $uri_query; + $uri_options['query'] = isset($uri_options['query']) ? $uri_options['query'] + $uri_query : $uri_query; unset($uri_parts['query']); } if ($uri_parts['scheme'] === 'entity') { - $url = static::fromEntityUri($uri_parts, $options); + $url = static::fromEntityUri($uri_parts, $uri_options, $uri); } elseif ($uri_parts['scheme'] === 'user-path') { - $url = static::fromUserPathUri($uri_parts, $options); + $url = static::fromUserPathUri($uri_parts, $uri_options); } elseif ($uri_parts['scheme'] === 'route') { - $url = static::fromRouteUri($uri_parts, $options); + $url = static::fromRouteUri($uri_parts, $uri_options, $uri); } else { $url = new static($uri, array(), $options); @@ -285,7 +288,8 @@ public static function fromUri($uri, $options = []) { * Create a new Url object for entity URIs. * * @param array $uri_parts - * Parts from an URI of the form entity:{entity_type}/{entity_id}. + * Parts from an URI of the form entity:{entity_type}/{entity_id} as from + * parse_url(). * @param array $options * An array of options, see static::fromUri() for details. * @@ -295,7 +299,7 @@ public static function fromUri($uri, $options = []) { * @throws \InvalidArgumentException * Thrown if the entity URI is invalid. */ - protected static function fromEntityUri(array $uri_parts, $options) { + protected static function fromEntityUri(array $uri_parts, $options, $uri) { list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2); if ($uri_parts['scheme'] != 'entity' || $entity_id === '') { throw new \InvalidArgumentException(String::format('The entity URI "@uri" is invalid. You must specify the entity id in the URL. e.g., entity:node/1 for loading the canonical path to node entity with id 1.', ['@uri' => $uri])); @@ -308,7 +312,7 @@ protected static function fromEntityUri(array $uri_parts, $options) { * Creates a new Url object for 'user-path:' URIs. * * @param array $uri_parts - * Parts from an URI of the form user-path:{path}. + * Parts from an URI of the form user-path:{path} as from parse_url(). * @param array $options * An array of options, see static::fromUri() for details. * @@ -328,18 +332,21 @@ protected static function fromUserPathUri(array $uri_parts, array $options) { * Creates a new Url object for 'route:' URIs. * * @param array $uri_parts - * Parts from an URI of the form route:{path}. Where path is the route name - * optionally followed by a ";" followed by query parameters in key=value - * format with & separators. + * Parts from an URI of the form route:{path} as from parse_url(). Where + * path is the route name optionally followed by a ";" followed by route + * parameters in key=value format with & separators. * @param array $options * An array of options, see static::fromUri() for details. * * @return \Drupal\Core\Url * A new Url object for a 'route:' URI. */ - protected static function fromRouteUri(array $uri_parts, array $options) { + protected static function fromRouteUri(array $uri_parts, array $options, $uri) { $route_parts = explode(';', $uri_parts['path'], 2); $route_name = $route_parts[0]; + if ($route_name === '') { + throw new \InvalidArgumentException(String::format('The route URI "@uri" is invalid. You must have a route name in the URI. e.g., route:system.admin', ['@uri' => $uri])); + } $route_parameters = []; if (!empty($route_parts[1])) { parse_str($route_parts[1], $route_parameters); diff --git a/core/modules/system/src/Tests/Common/UrlTest.php b/core/modules/system/src/Tests/Common/UrlTest.php index 45ae2d3..0b8a223 100644 --- a/core/modules/system/src/Tests/Common/UrlTest.php +++ b/core/modules/system/src/Tests/Common/UrlTest.php @@ -265,7 +265,7 @@ function testExternalUrls() { // Verify external URL can contain a query string. $url = $test_url . '?drupal=awesome'; $result = Url::fromUri($url)->toString(); - $this->assertEqual($url, $result, 'External URL with query string works without a query string in $options.'); + $this->assertEqual($url, $result); // Verify external URL can be extended with a query string. $url = $test_url; @@ -277,6 +277,6 @@ function testExternalUrls() { $url = $test_url . '?drupal=awesome'; $query = array($this->randomMachineName(5) => $this->randomMachineName(5)); $result = Url::fromUri($url, array('query' => $query))->toString(); - $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result, 'External URL query string can be extended with a custom query string in $options.'); + $this->assertEqual($url . '&' . http_build_query($query, '', '&'), $result); } } diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php index 9c5efcb..6e2634b 100644 --- a/core/tests/Drupal/Tests/Core/UrlTest.php +++ b/core/tests/Drupal/Tests/Core/UrlTest.php @@ -520,7 +520,7 @@ public function testInvalidEntityUriParameter() { } /** - * Tests the toUriString() method with entity: URI. + * Tests the toUriString() method with entity: URIs. * * @covers ::toUriString */ @@ -534,7 +534,7 @@ public function testToUriStringForEntity() { } /** - * Tests the toUriString() method with user-path: URI. + * Tests the toUriString() method with user-path: URIs. * * @covers ::toUriString */ @@ -555,7 +555,7 @@ public function testToUriStringForUserPath() { } /** - * Tests the toUriString() method with route: URI. + * Tests the toUriString() method with route: URIs. * * @covers ::toUriString */