Issue #2418613 by pwolanin: Fix #0 bug in toUriString() method in Url class, clarify toString() vs toUriString()

8.0.x
webchick 2015-02-02 11:21:00 -08:00
parent 10e4e0c756
commit b01c7c4583
2 changed files with 61 additions and 11 deletions

View File

@ -250,13 +250,18 @@ class Url {
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])); 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' => '']; $uri_parts += ['path' => ''];
// Discard empty fragment in $options for consistency with parse_url().
if (isset($options['fragment']) && strlen($options['fragment']) == 0) {
unset($options['fragment']);
}
// Extract query parameters and fragment and merge them into $uri_options, // Extract query parameters and fragment and merge them into $uri_options,
// but preserve the original $options for the fallback case. // but preserve the original $options for the fallback case.
$uri_options = $options; $uri_options = $options;
if (!empty($uri_parts['fragment'])) { if (isset($uri_parts['fragment'])) {
$uri_options += ['fragment' => $uri_parts['fragment']]; $uri_options += ['fragment' => $uri_parts['fragment']];
}
unset($uri_parts['fragment']); unset($uri_parts['fragment']);
}
if (!empty($uri_parts['query'])) { if (!empty($uri_parts['query'])) {
$uri_query = []; $uri_query = [];
parse_str($uri_parts['query'], $uri_query); parse_str($uri_parts['query'], $uri_query);
@ -302,7 +307,7 @@ class Url {
* @throws \InvalidArgumentException * @throws \InvalidArgumentException
* Thrown if the entity URI is invalid. * Thrown if the entity URI is invalid.
*/ */
protected static function fromEntityUri(array $uri_parts, $options, $uri) { protected static function fromEntityUri(array $uri_parts, array $options, $uri) {
list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2); list($entity_type_id, $entity_id) = explode('/', $uri_parts['path'], 2);
if ($uri_parts['scheme'] != 'entity' || $entity_id === '') { 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])); 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]));
@ -410,12 +415,12 @@ class Url {
} }
/** /**
* Return a URI string that represents tha data in the Url object. * Generates a URI string that represents tha data in the Url object.
* *
* The URI will typically have the scheme of route: even if the object was * The URI will typically have the scheme of route: even if the object was
* constructed using an entity: or user-path: scheme. A user-path: URI * constructed using an entity: or user-path: scheme. A user-path: URI that
* that does not match a Drupal route with be returned here with the base: * does not match a Drupal route with be returned here with the base: scheme,
* scheme, and external URLs will be returned in their original form. * and external URLs will be returned in their original form.
* *
* @return string * @return string
* A URI representation of the Url object data. * A URI representation of the Url object data.
@ -431,7 +436,7 @@ class Url {
$uri = $this->uri; $uri = $this->uri;
} }
$query = !empty($this->options['query']) ? ('?' . UrlHelper::buildQuery($this->options['query'])) : ''; $query = !empty($this->options['query']) ? ('?' . UrlHelper::buildQuery($this->options['query'])) : '';
$fragment = !empty($this->options['fragment']) ? '#' . $this->options['fragment'] : ''; $fragment = isset($this->options['fragment']) && strlen($this->options['fragment']) ? '#' . $this->options['fragment'] : '';
return $uri . $query . $fragment; return $uri . $query . $fragment;
} }
@ -580,7 +585,7 @@ class Url {
} }
/** /**
* Returns the URI of the URL. * Returns the URI value for this Url object.
* *
* Only to be used if self::$unrouted is TRUE. * Only to be used if self::$unrouted is TRUE.
* *
@ -599,7 +604,7 @@ class Url {
} }
/** /**
* Sets the absolute value for this Url. * Sets the value of the absolute option for this Url.
* *
* @param bool $absolute * @param bool $absolute
* (optional) Whether to make this Url absolute or not. Defaults to TRUE. * (optional) Whether to make this Url absolute or not. Defaults to TRUE.
@ -612,7 +617,19 @@ class Url {
} }
/** /**
* Generates the URI for this Url object. * Generates the string URL representation for this Url object.
*
* For an external URL, the string will contain the input plus any query
* string or fragment specified by the options array.
*
* If this Url object was constructed from a Drupal route or from an internal
* URI (URIs using the user-path:, base:, or entity: schemes), the returned
* string will either be a relative URL like /node/1 or an absolute URL like
* http://example.com/node/1 depending on the options array, plus any
* specified query string or fragment.
*
* @return string
* A string URL.
*/ */
public function toString() { public function toString() {
if ($this->unrouted) { if ($this->unrouted) {

View File

@ -491,6 +491,33 @@ class UrlTest extends UnitTestCase {
NULL, NULL,
NULL, NULL,
], ],
[
// Ensure a fragment of #0 is handled correctly.
'entity:test_entity/1#0',
[],
'entity.test_entity.canonical',
['test_entity' => '1'],
NULL,
'0',
],
// Ensure an empty fragment of # is in options discarded as expected.
[
'entity:test_entity/1',
['fragment' => ''],
'entity.test_entity.canonical',
['test_entity' => '1'],
NULL,
NULL,
],
// Ensure an empty fragment of # in the URI is discarded as expected.
[
'entity:test_entity/1#',
[],
'entity.test_entity.canonical',
['test_entity' => '1'],
NULL,
NULL,
],
[ [
'entity:test_entity/2?page=1&foo=bar#bottom', 'entity:test_entity/2?page=1&foo=bar#bottom',
[], 'entity.test_entity.canonical', [], 'entity.test_entity.canonical',
@ -613,6 +640,12 @@ class UrlTest extends UnitTestCase {
['route:entity.test_entity.canonical;test_entity=1', [], 'route:entity.test_entity.canonical;test_entity=1'], ['route:entity.test_entity.canonical;test_entity=1', [], 'route:entity.test_entity.canonical;test_entity=1'],
['route:entity.test_entity.canonical;test_entity=1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'], ['route:entity.test_entity.canonical;test_entity=1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
['route:entity.test_entity.canonical;test_entity=1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'], ['route:entity.test_entity.canonical;test_entity=1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
// Check that an empty fragment is discarded.
['route:entity.test_entity.canonical;test_entity=1?page=2#', [], 'route:entity.test_entity.canonical;test_entity=1?page=2'],
// Check that an empty fragment is discarded.
['route:entity.test_entity.canonical;test_entity=1?page=2', ['fragment' => ''], 'route:entity.test_entity.canonical;test_entity=1?page=2'],
// Check that a fragment of #0 is preserved.
['route:entity.test_entity.canonical;test_entity=1?page=2#0', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#0'],
]; ];
} }