diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 7e6e12f..0e9dfe1 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -452,11 +452,18 @@ function _batch_finished() { if ($_batch['form_state']->getRedirect() === NULL) { $redirect = $_batch['batch_redirect'] ?: $_batch['source_url']; $options = UrlHelper::parse($redirect); - if (!UrlHelper::isExternal($options['path'])) { - $options['path'] = $GLOBALS['base_url'] . '/' . $options['path']; + // Any path with a scheme does not correspond to a route. + if (parse_url($options['path'], PHP_URL_SCHEME)) { + $redirect = Url::unrouted($options['path'], $options); + } + else { + $redirect = \Drupal::pathValidator()->getUrlIfValid($options['path']); + if (!$redirect) { + // Stay on the same page if the redirect was invalid. + $redirect = Url::routed(''); + } + $redirect->setOptions($options); } - $redirect = Url::createFromPath($options['path']); - $redirect->setOptions($options); $_batch['form_state']->setRedirectUrl($redirect); } diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 5a6859d..d763045 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -543,7 +543,7 @@ function install_run_task($task, &$install_state) { } // Process the batch. For progressive batches, this will redirect. // Otherwise, the batch will complete. - $response = batch_process(install_redirect_url($install_state), install_full_redirect_url($install_state)); + $response = batch_process(preg_replace('@^core/@', 'base://', install_redirect_url($install_state)), install_full_redirect_url($install_state)); if ($response instanceof Response) { // Save $_SESSION data from batch. \Drupal::service('session_manager')->save(); diff --git a/core/lib/Drupal/Core/Menu/MenuLinkBase.php b/core/lib/Drupal/Core/Menu/MenuLinkBase.php index b09bfa9..c0f05e0 100644 --- a/core/lib/Drupal/Core/Menu/MenuLinkBase.php +++ b/core/lib/Drupal/Core/Menu/MenuLinkBase.php @@ -141,9 +141,7 @@ public function getUrlObject($title_attribute = TRUE) { return new Url($this->pluginDefinition['route_name'], $this->pluginDefinition['route_parameters'], $options); } else { - $url = Url::createFromPath($this->pluginDefinition['url']); - $url->setOptions($options); - return $url; + return Url::unrouted($this->pluginDefinition['url'], $options);; } } diff --git a/core/lib/Drupal/Core/Path/PathValidator.php b/core/lib/Drupal/Core/Path/PathValidator.php index 1b519c7..d7506c6 100644 --- a/core/lib/Drupal/Core/Path/PathValidator.php +++ b/core/lib/Drupal/Core/Path/PathValidator.php @@ -99,7 +99,7 @@ public function getUrlIfValid($path) { if (empty($parsed_url['path'])) { return FALSE; } - return Url::createFromPath($path); + return Url::unrouted($path); } $path = ltrim($path, '/'); diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index d97f017..04a961c 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -7,10 +7,10 @@ namespace Drupal\Core; -use Drupal\Component\Utility\UrlHelper; use Drupal\Core\DependencyInjection\DependencySerializationTrait; use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Session\AccountInterface; +use Drupal\Core\Utility\UnroutedUrlAssemblerInterface; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\Request; @@ -28,6 +28,13 @@ class Url { protected $urlGenerator; /** + * The unrouted URL assembler. + * + * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface + */ + protected $urlAssembler; + + /** * The access manager * * @var \Drupal\Core\Access\AccessManagerInterface @@ -56,20 +63,27 @@ class Url { protected $options = array(); /** - * Indicates whether this URL is external. + * Indicates whether this object contains an external URL. * * @var bool */ protected $external = FALSE; /** - * The external path. + * Indicates whether this URL is a URI instead of a route. + * + * @var bool + */ + protected $unrouted = FALSE; + + /** + * The non-route URI. * - * Only used if self::$external is TRUE. + * Only used if self::$unrouted is TRUE. * * @var string */ - protected $path; + protected $uri; /** * Constructs a new Url object. @@ -103,44 +117,81 @@ public function __construct($route_name, $route_parameters = array(), $options = } /** - * Returns the Url object matching a path. READ THE FOLLOWING SECURITY NOTE. + * Creates a new routed (internal to Drupal) Url object. * - * SECURITY NOTE: The path is not checked to be valid and accessible by the - * current user to allow storing and reusing Url objects by different users. - * The 'path.validator' service getUrlIfValid() method should be used instead - * of this one if validation and access check is desired. Otherwise, - * 'access_manager' service checkNamedRoute() method should be used on the - * router name and parameters stored in the Url object returned by this - * method. + * @param string $route_name + * The name of the route + * @param array $route_parameters + * (optional) An associative array of parameter names and values. + * @param array $options + * (optional) An associative array of additional 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. + * - '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 + * absolute link (beginning with http:). Useful for links that will be + * displayed outside the site, such as in an RSS feed. + * - 'language': An optional language object used to look up the alias + * for the URL. If $options['language'] is omitted, it defaults to the + * current language for the language type LanguageInterface::TYPE_URL. + * - 'https': Whether this URL should point to a secure location. If not + * defined, the current scheme is used, so the user stays on HTTP or HTTPS + * respectively. if mixed mode sessions are permitted, TRUE enforces HTTPS + * and FALSE enforces HTTP. * - * @param string $path - * A path (e.g. 'node/1', 'http://drupal.org'). + * @return \Drupal\Core\Url + * A new routed (internal to Drupal) Url object. + */ + public static function routed($route_name, $route_parameters = array(), $options = array()) { + return new static($route_name, $route_parameters, $options); + } + + /** + * Creates a new unrouted (external to Drupal) Url object. * - * @return static - * An Url object. Warning: the object is created even if the current user - * can not access the path. + * @param string $uri + * The URI of the external resource including the scheme. For Drupal paths + * that are not handled by the routing system, you may use base:// for the + * scheme. + * @param array $options + * (optional) An associative array of additional 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. + * - '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 + * absolute link (beginning with http:). Useful for links that will be + * displayed outside the site, such as in an RSS feed. + * - 'language': An optional language object used to look up the alias + * for the URL. If $options['language'] is omitted, it defaults to the + * current language for the language type LanguageInterface::TYPE_URL. + * - 'https': Whether this URL should point to a secure location. If not + * defined, the current scheme is used, so the user stays on HTTP or HTTPS + * respectively. if mixed mode sessions are permitted, TRUE enforces HTTPS + * and FALSE enforces HTTP. * - * @throws \Drupal\Core\Routing\MatchingRouteNotFoundException - * Thrown when the path cannot be matched. + * @return \Drupal\Core\Url + * A new unrouted (external to Drupal) Url object. */ - public static function createFromPath($path) { - if (UrlHelper::isExternal($path)) { - $url = new static($path); - $url->setExternal(); - return $url; - } - - // Special case the front page route. - if ($path == '') { - return new static($path); - } - else { - return static::createFromRequest(Request::create("/$path")); - } + public static function unrouted($uri, $options = array()) { + $url = new static($uri, array(), $options); + $url->setUnrouted(); + return $url; } /** - * Returns the Url object matching a request. READ THE SECURITY NOTE ON createFromPath(). + * Returns the Url object matching a request. + * + * SECURITY NOTE: The request path is not checked to be valid and accessible + * by the current user to allow storing and reusing Url objects by different + * users. The 'path.validator' service getUrlIfValid() method should be used + * instead of this one if validation and access check is desired. Otherwise, + * 'access_manager' service checkNamedRoute() method should be used on the + * router name and parameters stored in the Url object returned by this + * method. * * @param \Symfony\Component\HttpFoundation\Request $request * A request object. @@ -163,23 +214,20 @@ public static function createFromRequest(Request $request) { } /** - * Sets this Url to be external. + * Sets this Url to encapsulate an unrouted URI. * * @return $this */ - protected function setExternal() { - $this->external = TRUE; - - // What was passed in as the route name is actually the path. - $this->path = $this->routeName; - + protected function setUnrouted() { + $this->unrouted = TRUE; + // What was passed in as the route name is actually the URI. + $this->uri = $this->routeName; // Set empty route name and parameters. $this->routeName = NULL; $this->routeParameters = array(); - // Flag the path as external so the UrlGenerator does not need to check. - $this->options['external'] = TRUE; - - return $this; + if ($this->external = strpos($this->uri, 'base://') !== 0) { + $this->options['external'] = TRUE; + } } /** @@ -192,15 +240,24 @@ public function isExternal() { } /** + * Indicates if this Url has a Drupal route. + * + * @return bool + */ + public function isRouted() { + return !$this->unrouted; + } + + /** * Returns the route name. * * @return string * * @throws \UnexpectedValueException. - * If this is an external URL with no corresponding route. + * If this is a URI with no corresponding route. */ public function getRouteName() { - if ($this->isExternal()) { + if ($this->unrouted) { throw new \UnexpectedValueException('External URLs do not have an internal route name.'); } @@ -213,10 +270,10 @@ public function getRouteName() { * @return array * * @throws \UnexpectedValueException. - * If this is an external URL with no corresponding route. + * If this is a URI with no corresponding route. */ public function getRouteParameters() { - if ($this->isExternal()) { + if ($this->unrouted) { throw new \UnexpectedValueException('External URLs do not have internal route parameters.'); } @@ -230,10 +287,13 @@ public function getRouteParameters() { * The array of parameters. * * @return $this + * + * @throws \UnexpectedValueException. + * If this is a URI with no corresponding route. */ public function setRouteParameters($parameters) { - if ($this->isExternal()) { - throw new \Exception('External URLs do not have route parameters.'); + if ($this->unrouted) { + throw new \UnexpectedValueException('External URLs do not have route parameters.'); } $this->routeParameters = $parameters; return $this; @@ -248,10 +308,13 @@ public function setRouteParameters($parameters) { * The route parameter. * * @return $this + * + * @throws \UnexpectedValueException. + * If this is a URI with no corresponding route. */ public function setRouteParameter($key, $value) { - if ($this->isExternal()) { - throw new \Exception('External URLs do not have route parameters.'); + if ($this->unrouted) { + throw new \UnexpectedValueException('External URLs do not have route parameters.'); } $this->routeParameters[$key] = $value; return $this; @@ -312,22 +375,22 @@ public function setOption($name, $value) { } /** - * Returns the external path of the URL. + * Returns the URI of the URL. * - * Only to be used if self::$external is TRUE. + * Only to be used if self::$unrouted is TRUE. * * @return string - * The external path. + * A URI not connected to a route. May be an external URL. * * @throws \UnexpectedValueException - * Thrown when the path was requested for an internal URL. + * Thrown when the uri was requested for route. */ - public function getPath() { - if (!$this->isExternal()) { - throw new \UnexpectedValueException('Internal URLs do not have external paths.'); + public function getUri() { + if (!$this->unrouted) { + throw new \UnexpectedValueException('Internal routes do not have URIs.'); } - return $this->path; + return $this->uri; } /** @@ -344,11 +407,11 @@ public function setAbsolute($absolute = TRUE) { } /** - * Generates the path for this Url object. + * Generates the URI for this Url object. */ public function toString() { - if ($this->isExternal()) { - return $this->urlGenerator()->generateFromPath($this->getPath(), $this->getOptions()); + if ($this->unrouted) { + return $this->unroutedUrlAssembler()->assemble($this->getUri(), $this->getOptions()); } return $this->urlGenerator()->generateFromRoute($this->getRouteName(), $this->getRouteParameters(), $this->getOptions()); @@ -361,9 +424,9 @@ public function toString() { * An associative array containing all the properties of the route. */ public function toArray() { - if ($this->isExternal()) { + if ($this->unrouted) { return array( - 'path' => $this->getPath(), + 'path' => $this->getUri(), 'options' => $this->getOptions(), ); } @@ -383,9 +446,9 @@ public function toArray() { * An associative array suitable for a render array. */ public function toRenderArray() { - if ($this->isExternal()) { + if ($this->unrouted) { return array( - '#href' => $this->getPath(), + '#href' => $this->getUri(), '#options' => $this->getOptions(), ); } @@ -408,14 +471,14 @@ public function toRenderArray() { * The internal path for this route. * * @throws \UnexpectedValueException. - * If this is an external URL with no corresponding system path. + * If this is a URI with no corresponding system path. * * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. * System paths should not be used - use route names and parameters. */ public function getInternalPath() { - if ($this->isExternal()) { - throw new \UnexpectedValueException('External URLs do not have internal representations.'); + if ($this->unrouted) { + throw new \UnexpectedValueException('Unrouted URIs do not have internal representations.'); } return $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters()); } @@ -473,6 +536,19 @@ protected function urlGenerator() { } /** + * Gets the unrouted URL assembler for non-Drupal URLs. + * + * @return \Drupal\Core\Utility\UnroutedUrlAssemblerInterface + * The unrouted URL assembler. + */ + protected function unroutedUrlAssembler() { + if (!$this->urlAssembler) { + $this->urlAssembler = \Drupal::service('unrouted_url_assembler'); + } + return $this->urlAssembler; + } + + /** * Sets the URL generator. * * @param \Drupal\Core\Routing\UrlGeneratorInterface @@ -485,4 +561,17 @@ public function setUrlGenerator(UrlGeneratorInterface $url_generator) { return $this; } + /** + * Sets the unrouted URL assembler. + * + * @param \Drupal\Core\Utility\UnroutedUrlAssemblerInterface + * The unrouted URL assembler. + * + * @return $this + */ + public function setUnroutedUrlAssembler(UnroutedUrlAssemblerInterface $url_assembler) { + $this->urlAssembler = $url_assembler; + return $this; + } + } diff --git a/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php b/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php index cba87fe..36fca11 100644 --- a/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php +++ b/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php @@ -12,23 +12,24 @@ interface UnroutedUrlAssemblerInterface { /** - * Builds a domain-local or external URL from a path or URL. + * Builds a domain-local or external URL from a URI. * * For actual implementations the logic probably has to be split up between - * domain-local and external URLs. + * domain-local URIs and external URLs. * * @param string $uri - * A path on the same domain or external URL being linked to, such as "foo" + * A local URI or an external URL being linked to, such as "base://foo" * or "http://example.com/foo". * - If you provide a full URL, it will be considered an external URL as * long as it has an allowed protocol. - * - If you provide only a path (e.g. "foo"), it will be - * considered a URL local to the same domain. Additional query - * arguments for local paths must be supplied in $options['query'], not - * included in $path. + * - If you provide only a local URI (e.g. "base://foo"), it will be + * considered a path local to Drupal, but not handled by the routing + * system. The base path (the subdirectory where the front controller + * is found) will be added to the path. Additional query arguments for + * local paths must be supplied in $options['query'], not part of $uri. * - If your external URL contains a query (e.g. http://example.com/foo?a=b), * then you can either URL encode the query keys and values yourself and - * include them in $path, or use $options['query'] to let this method + * include them in $uri, or use $options['query'] to let this method * URL encode them. * * @param array $options diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index aecd844..5e85078 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -26,6 +26,7 @@ function aggregator_help($route_name, RouteMatchInterface $route_match) { $output .= '

' . t('The Aggregator module is an on-site syndicator and news reader that gathers and displays fresh content from RSS-, RDF-, and Atom-based feeds made available across the web. Thousands of sites (particularly news sites and blogs) publish their latest headlines in feeds, using a number of standardized XML-based formats. For more information, see the online documentation for the Aggregator module.', array('!aggregator-module' => 'https://drupal.org/documentation/modules/aggregator')) . '

'; $output .= '

' . t('Uses') . '

'; $output .= '
'; + // Check if the aggregator sources View is enabled. if ($url = $path_validator->getUrlIfValid('aggregator/sources')) { $output .= '
' . t('Viewing feeds') . '
'; $output .= '
' . t('Users view feed content in the main aggregator display, or by their source (usually via an RSS feed reader). The most recent content in a feed can be displayed as a block through the Blocks administration page.', array('!aggregator' => \Drupal::url('aggregator.page_last'), '!aggregator-sources' => $url->toString(), '!admin-block' => \Drupal::url('block.admin_display'))) . '
'; @@ -33,6 +34,7 @@ function aggregator_help($route_name, RouteMatchInterface $route_match) { $output .= '
' . t('Adding, editing, and deleting feeds') . '
'; $output .= '
' . t('Administrators can add, edit, and delete feeds, and choose how often to check each feed for newly updated items on the Feed aggregator administration page.', array('!feededit' => \Drupal::url('aggregator.admin_overview'))) . '
'; $output .= '
' . t('OPML integration') . '
'; + // Check if the aggregator opml View is enabled. if ($url = $path_validator->getUrlIfValid('aggregator/opml')) { $output .= '
' . t('A machine-readable OPML file of all feeds is available. OPML is an XML-based file format used to share outline-structured information such as a list of RSS feeds. Feeds can also be imported via an OPML file.', array('!aggregator-opml' => $url->toString(), '!import-opml' => \Drupal::url('aggregator.opml_add'))) . '
'; } diff --git a/core/modules/aggregator/src/Entity/Item.php b/core/modules/aggregator/src/Entity/Item.php index 493e4cb..d760594 100644 --- a/core/modules/aggregator/src/Entity/Item.php +++ b/core/modules/aggregator/src/Entity/Item.php @@ -244,7 +244,7 @@ public function getListCacheTags() { * Entity URI callback. */ public static function buildUri(ItemInterface $item) { - return Url::createFromPath($item->getLink()); + return Url::unrouted($item->getLink()); } } diff --git a/core/modules/field_ui/src/FieldUI.php b/core/modules/field_ui/src/FieldUI.php index 77ec651..be74fff 100644 --- a/core/modules/field_ui/src/FieldUI.php +++ b/core/modules/field_ui/src/FieldUI.php @@ -51,15 +51,15 @@ public static function getNextDestination(array $destinations) { $next_destination += array( 'route_parameters' => array(), ); - $next_destination = new Url($next_destination['route_name'], $next_destination['route_parameters'], $next_destination['options']); + $next_destination = Url::routed($next_destination['route_name'], $next_destination['route_parameters'], $next_destination['options']); } else { $options = UrlHelper::parse($next_destination); if ($destinations) { $options['query']['destinations'] = $destinations; } - $next_destination = Url::createFromPath($options['path']); - $next_destination->setOptions($options); + // Redirect to any given path within the same domain. + $next_destination = Url::unrouted('base://' . $options['path']); } return $next_destination; } diff --git a/core/modules/link/link.module b/core/modules/link/link.module index d33181e..c9837a3 100644 --- a/core/modules/link/link.module +++ b/core/modules/link/link.module @@ -69,6 +69,6 @@ function template_preprocess_link_formatter_link_separate(&$variables) { $variables['link'] = \Drupal::linkGenerator()->generateFromUrl($variables['url_title'], $variables['url']); } else { - $variables['link'] = l($variables['url_title'], $variables['url']->getPath(), $variables['url']->getOptions()); + $variables['link'] = l($variables['url_title'], $variables['url']->getUri(), $variables['url']->getOptions()); } } diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php index 05caf97..2d9cc98 100644 --- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php +++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php @@ -13,6 +13,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\link\LinkItemInterface; +use Symfony\Component\HttpFoundation\Request; /** * Plugin implementation of the 'link' formatter. @@ -163,7 +164,7 @@ public function viewElements(FieldItemListInterface $items) { '#options' => $url->getOptions(), ); if ($url->isExternal()) { - $element[$delta]['#href'] = $url->getPath(); + $element[$delta]['#href'] = $url->getUri(); } else { $element[$delta]['#route_name'] = $url->getRouteName(); @@ -206,11 +207,10 @@ protected function buildUrl(LinkItemInterface $item) { } if ($item->isExternal()) { - $url = Url::createFromPath($item->url); - $url->setOptions($options); + $url = Url::unrouted($item->url, $options); } else { - $url = new Url($item->route_name, (array) $item->route_parameters, (array) $options); + $url = Url::routed($item->route_name, (array) $item->route_parameters, (array) $options); } return $url; diff --git a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php index 55f3489..150cc72 100644 --- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php @@ -107,7 +107,7 @@ public function getUrlObject() { else { $path = $this->getUrl(); if (isset($path)) { - $url = Url::createFromPath($path); + $url = Url::unrouted($path); } else { $url = new Url(''); diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php index 2d37566..4fb93b2 100644 --- a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php +++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php @@ -198,7 +198,7 @@ public function extractFormValues(array &$form, FormStateInterface $form_state) if ($extracted) { if ($extracted->isExternal()) { - $new_definition['url'] = $extracted->getPath(); + $new_definition['url'] = $extracted->getUri(); } else { $new_definition['route_name'] = $extracted->getRouteName(); diff --git a/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php b/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php index 8c23c07..8478bae 100644 --- a/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php +++ b/core/modules/rdf/src/Tests/Field/LinkFieldRdfaTest.php @@ -75,7 +75,8 @@ public function testAllFormattersInternal() { // Set up test values. $this->testValue = 'admin'; $this->entity = entity_create('entity_test', array()); - $this->entity->{$this->fieldName}->url = $this->testValue; + $this->entity->{$this->fieldName}->route_name = 'system.admin'; + $this->entity->{$this->fieldName}->url = 'admin'; // Set up the expected result. // AssertFormatterRdfa looks for a full path. @@ -94,7 +95,8 @@ public function testAllFormattersFront() { // Set up test values. $this->testValue = ''; $this->entity = entity_create('entity_test', array()); - $this->entity->{$this->fieldName}->url = $this->testValue; + $this->entity->{$this->fieldName}->route_name = $this->testValue; + $this->entity->{$this->fieldName}->url = ''; // Set up the expected result. $expected_rdf = array( diff --git a/core/modules/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php index df96d6f..4bd041e 100644 --- a/core/modules/shortcut/src/Entity/Shortcut.php +++ b/core/modules/shortcut/src/Entity/Shortcut.php @@ -14,6 +14,7 @@ use Drupal\Core\Field\BaseFieldDefinition; use Drupal\Core\Url; use Drupal\shortcut\ShortcutInterface; +use Symfony\Component\HttpFoundation\Request; /** * Defines the shortcut entity class. @@ -134,7 +135,15 @@ public static function preCreate(EntityStorageInterface $storage, array &$values public function preSave(EntityStorageInterface $storage) { parent::preSave($storage); - $url = Url::createFromPath($this->path->value); + // @todo fix PathValidatorInterface::getUrlIfValid() so we can use it + // here. The problem is that we need an exception, not a FALSE + // return value. https://www.drupal.org/node/2346695 + if ($this->path->value == '') { + $url = new Url($this->path->value); + } + else { + $url = Url::createFromRequest(Request::create("/{$this->path->value}")); + } $this->setRouteName($url->getRouteName()); $this->setRouteParams($url->getRouteParameters()); } diff --git a/core/modules/system/system.module b/core/modules/system/system.module index b478fb4..f74ecde 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -457,7 +457,7 @@ function system_authorized_get_url(array $options = array()) { // Force HTTPS if available, regardless of what the caller specifies. $options['https'] = TRUE; // Prefix with $base_url so url() treats it as an external link. - $url = Url::createFromPath($base_url . '/core/authorize.php'); + $url = Url::unrouted('base://core/authorize.php'); $url_options = $url->getOptions(); $url->setOptions($options + $url_options); return $url; diff --git a/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php b/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php index 7fc352e..3eb9cc4 100644 --- a/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php +++ b/core/modules/system/tests/modules/form_test/src/Form/FormTestRedirectForm.php @@ -54,7 +54,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { public function submitForm(array &$form, FormStateInterface $form_state) { if (!$form_state->isValueEmpty('redirection')) { if (!$form_state->isValueEmpty('destination')) { - $form_state->setRedirectUrl(Url::createFromPath($GLOBALS['base_url'] . '/' . $form_state->getValue('destination'))); + $form_state->setRedirectUrl(Url::unrouted('base://' . $form_state->getValue('destination'))); } } else { diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index b9174bc..815d1cf 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -298,7 +298,7 @@ function toolbar_menu_navigation_links(array $tree) { $url = $link->getUrlObject(); if ($url->isExternal()) { // This is an unusual case, so just get a distinct, safe string. - $id = substr(Crypt::hashBase64($url->getPath()), 0, 16); + $id = substr(Crypt::hashBase64($url->getUri()), 0, 16); } else { $id = str_replace(array('.', '<', '>'), array('-', '', ''), $url->getRouteName()); diff --git a/core/modules/update/update.report.inc b/core/modules/update/update.report.inc index 9ee4dbf..b5215e2 100644 --- a/core/modules/update/update.report.inc +++ b/core/modules/update/update.report.inc @@ -6,6 +6,7 @@ */ use Drupal\Core\Template\Attribute; +use Drupal\Core\Url; /** * Prepares variables for project status report templates. @@ -135,7 +136,7 @@ function template_preprocess_update_project_status(&$variables) { // Set the project title and URL. $variables['title'] = (isset($project['title'])) ? $project['title'] : $project['name']; - $variables['url'] = (isset($project['link'])) ? url($project['link']) : NULL; + $variables['url'] = (isset($project['link'])) ? Url::unrouted($project['link'])->toString() : NULL; $variables['install_type'] = $project['install_type']; if ($project['install_type'] == 'dev' && !empty($project['datestamp'])) { diff --git a/core/modules/user/user.module b/core/modules/user/user.module index e33cd28..71ecd5e 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -576,6 +576,7 @@ function template_preprocess_username(&$variables) { $variables['name'] = String::checkPlain($name); $variables['profile_access'] = \Drupal::currentUser()->hasPermission('access user profiles'); + $external = FALSE; // Populate link path and attributes if appropriate. if ($variables['uid'] && $variables['profile_access']) { // We are linking to a local user. @@ -589,10 +590,19 @@ function template_preprocess_username(&$variables) { $variables['attributes']['rel'] = 'nofollow'; $variables['link_path'] = $account->homepage; $variables['homepage'] = $account->homepage; + $external = TRUE; } - // We have a link path, so we should generate a link using url(). + // We have a link path, so we should generate a URL. if (isset($variables['link_path'])) { - $variables['attributes']['href'] = url($variables['link_path'], $variables['link_options']); + if ($external) { + $variables['attributes']['href'] = \Drupal::service('unrouted_url_assembler') + ->assemble($variables['link_path'], $variables['link_options']); + } + else { + $variables['attributes']['href'] = Url::routed('entity.user.canonical', array( + 'user' => $variables['uid'], + ))->toString(); + } } } diff --git a/core/modules/views_ui/src/Tests/RedirectTest.php b/core/modules/views_ui/src/Tests/RedirectTest.php index 30d387c..519ec79 100644 --- a/core/modules/views_ui/src/Tests/RedirectTest.php +++ b/core/modules/views_ui/src/Tests/RedirectTest.php @@ -27,17 +27,17 @@ class RedirectTest extends UITestBase { public function testRedirect() { $view_name = 'test_view'; - $random_destination = $this->randomMachineName(); + $random_destination = 'node'; $edit_path = "admin/structure/views/view/$view_name/edit"; - $this->drupalPostForm($edit_path, array(), t('Save') , array('query' => array('destination' => $random_destination))); - $this->assertUrl($random_destination, array(), 'Make sure the user got redirected to the expected page defined in the destination.'); + $this->drupalPostForm($edit_path, array(), t('Save') , array('query' => array('destination' => 'node'))); + $this->assertUrl('node', array(), 'Make sure the user got redirected to the expected page defined in the destination.'); // Setup a view with a certain page display path. If you change the path // but have the old url in the destination the user should be redirected to // the new path. $view_name = 'test_redirect_view'; - $new_path = $this->randomMachineName(); + $new_path = 'user'; $edit_path = "admin/structure/views/view/$view_name/edit"; $path_edit_path = "admin/structure/views/nojs/display/$view_name/page_1/path"; diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php index 12083cf..a0f7ca6 100644 --- a/core/modules/views_ui/src/ViewEditForm.php +++ b/core/modules/views_ui/src/ViewEditForm.php @@ -20,6 +20,7 @@ use Drupal\Core\Url; use Drupal\user\TempStoreFactory; use Drupal\views\Views; +use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -322,10 +323,7 @@ public function save(array $form, FormStateInterface $form_state) { $query->remove('destination'); } } - if (!UrlHelper::isExternal($destination)) { - $destination = $GLOBALS['base_url'] . '/' . $destination; - } - $form_state->setRedirectUrl(Url::createFromPath($destination)); + $form_state->setRedirectUrl(Url::createFromRequest(Request::create("/$destination"))); } $view->save(); diff --git a/core/tests/Drupal/Tests/Core/ExternalUrlTest.php b/core/tests/Drupal/Tests/Core/ExternalUrlTest.php index 57a51d4..0fa610d 100644 --- a/core/tests/Drupal/Tests/Core/ExternalUrlTest.php +++ b/core/tests/Drupal/Tests/Core/ExternalUrlTest.php @@ -2,7 +2,7 @@ /** * @file - * Contains \Drupal\Tests\Core\ExternalUrlTest. + * Contains \Drupal\Tests\Core\UnroutedUrlTest. */ namespace Drupal\Tests\Core; @@ -15,16 +15,16 @@ /** * @coversDefaultClass \Drupal\Core\Url - * @group ExternalUrlTest + * @group UrlTest */ -class ExternalUrlTest extends UnitTestCase { +class UnroutedUrlTest extends UnitTestCase { /** - * The URL generator + * The URL assembler * - * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject + * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlGenerator; + protected $urlAssembler; /** * The router. @@ -34,11 +34,18 @@ class ExternalUrlTest extends UnitTestCase { protected $router; /** - * An external URL to test. + * An unrouted, external URL to test. * * @var string */ - protected $path = 'http://drupal.org'; + protected $unrouted_external = 'http://drupal.org'; + + /** + * An unrouted, internal URL to test. + * + * @var string + */ + protected $unrouted_internal = 'base://robots.txt'; /** * {@inheritdoc} @@ -46,15 +53,15 @@ class ExternalUrlTest extends UnitTestCase { protected function setUp() { parent::setUp(); - $this->urlGenerator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); - $this->urlGenerator->expects($this->any()) - ->method('generateFromPath') + $this->urlAssembler = $this->getMock('Drupal\Core\Utility\UnroutedUrlAssemblerInterface'); + $this->urlAssembler->expects($this->any()) + ->method('assemble') ->will($this->returnArgument(0)); $this->router = $this->getMock('Drupal\Tests\Core\Routing\TestRouterInterface'); $container = new ContainerBuilder(); $container->set('router.no_access_checks', $this->router); - $container->set('url_generator', $this->urlGenerator); + $container->set('unrouted_url_assembler', $this->urlAssembler); \Drupal::setContainer($container); } @@ -62,13 +69,20 @@ protected function setUp() { * Tests the createFromPath method. * * @covers ::createFromPath() - * @covers ::setExternal() */ public function testCreateFromPath() { - $url = Url::createFromPath($this->path); - $this->assertInstanceOf('Drupal\Core\Url', $url); - $this->assertTrue($url->isExternal()); - return $url; + $urls = [ + Url::unrouted($this->unrouted_external), + Url::unrouted($this->unrouted_internal) + ]; + + $this->assertInstanceOf('Drupal\Core\Url', $urls[0]); + $this->assertTrue($urls[0]->isExternal()); + + $this->assertInstanceOf('Drupal\Core\Url', $urls[1]); + $this->assertFalse($urls[1]->isExternal()); + + return $urls; } /** @@ -96,8 +110,9 @@ public function testCreateFromRequest() { * * @covers ::isExternal() */ - public function testIsExternal(Url $url) { - $this->assertTrue($url->isExternal()); + public function testIsExternal(array $urls) { + $this->assertTrue($urls[0]->isExternal()); + $this->assertFalse($urls[1]->isExternal()); } /** @@ -107,8 +122,9 @@ public function testIsExternal(Url $url) { * * @covers ::toString() */ - public function testToString(Url $url) { - $this->assertSame($this->path, $url->toString()); + public function testToString(array $urls) { + $this->assertSame($this->unrouted_external, $urls[0]->toString()); + $this->assertSame($this->unrouted_internal, $urls[1]->toString()); } /** @@ -118,12 +134,18 @@ public function testToString(Url $url) { * * @covers ::toArray() */ - public function testToArray(Url $url) { + public function testToArray(array $urls) { $expected = array( - 'path' => $this->path, + 'path' => $this->unrouted_external, 'options' => array('external' => TRUE), ); - $this->assertSame($expected, $url->toArray()); + $this->assertSame($expected, $urls[0]->toArray()); + + $expected = array( + 'path' => $this->unrouted_internal, + 'options' => array(), + ); + $this->assertSame($expected, $urls[1]->toArray()); } /** @@ -135,8 +157,8 @@ public function testToArray(Url $url) { * * @covers ::getRouteName() */ - public function testGetRouteName(Url $url) { - $url->getRouteName(); + public function testGetRouteName(array $urls) { + $urls[0]->getRouteName(); } /** @@ -148,8 +170,8 @@ public function testGetRouteName(Url $url) { * * @covers ::getRouteParameters() */ - public function testGetRouteParameters(Url $url) { - $url->getRouteParameters(); + public function testGetRouteParameters(array $urls) { + $urls[0]->getRouteParameters(); } /** @@ -161,8 +183,8 @@ public function testGetRouteParameters(Url $url) { * * @expectedException \Exception */ - public function testGetInternalPath(Url $url) { - $this->assertNull($url->getInternalPath()); + public function testGetInternalPath(array $urls) { + $this->assertNull($urls[0]->getInternalPath()); } /** @@ -170,10 +192,11 @@ public function testGetInternalPath(Url $url) { * * @depends testCreateFromPath * - * @covers ::getPath() + * @covers ::getUri() */ - public function testGetPath(Url $url) { - $this->assertNotNull($url->getPath()); + public function testGetUri(array $urls) { + $this->assertNotNull($urls[0]->getUri()); + $this->assertNotNull($urls[1]->getUri()); } /** @@ -183,8 +206,9 @@ public function testGetPath(Url $url) { * * @covers ::getOptions() */ - public function testGetOptions(Url $url) { - $this->assertInternalType('array', $url->getOptions()); + public function testGetOptions(array $urls) { + $this->assertInternalType('array', $urls[0]->getOptions()); + $this->assertInternalType('array', $urls[1]->getOptions()); } } diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php index 133a73c..7cc514a 100644 --- a/core/tests/Drupal/Tests/Core/UrlTest.php +++ b/core/tests/Drupal/Tests/Core/UrlTest.php @@ -102,8 +102,8 @@ public function testCreateFromPath() { $urls = array(); foreach ($this->map as $index => $values) { - $path = trim(array_pop($values), '/'); - $url = Url::createFromPath($path); + $path = array_pop($values); + $url = Url::createFromRequest(Request::create("$path")); $this->assertSame($values, array_values($url->toArray())); $urls[$index] = $url; } @@ -131,7 +131,7 @@ protected function getRequestConstraint($path) { * @covers ::createFromPath() */ public function testCreateFromPathFront() { - $url = Url::createFromPath(''); + $url = Url::routed(''); $this->assertSame('', $url->getRouteName()); } @@ -148,7 +148,7 @@ public function testCreateFromPathInvalid() { ->with($this->getRequestConstraint('/non-existent')) ->will($this->throwException(new ResourceNotFoundException())); - $this->assertNull(Url::createFromPath('non-existent')); + $this->assertNull(Url::createFromRequest(Request::create('/non-existent'))); } /** @@ -209,6 +209,9 @@ public function testIsExternal($urls) { /** * Tests the getPath() method for internal URLs. * + * @param \Drupal\Core\Url[] $urls + * Array of URL objects. + * * @depends testCreateFromPath * * @expectedException \UnexpectedValueException @@ -217,7 +220,7 @@ public function testIsExternal($urls) { */ public function testGetPathForInternalUrl($urls) { foreach ($urls as $url) { - $url->getPath(); + $url->getUri(); } } @@ -227,8 +230,8 @@ public function testGetPathForInternalUrl($urls) { * @covers ::getPath */ public function testGetPathForExternalUrl() { - $url = Url::createFromPath('http://example.com/test'); - $this->assertEquals('http://example.com/test', $url->getPath()); + $url = Url::unrouted('http://example.com/test'); + $this->assertEquals('http://example.com/test', $url->getUri()); } /** @@ -292,7 +295,7 @@ public function testGetRouteName($urls) { * @expectedException \UnexpectedValueException */ public function testGetRouteNameWithExternalUrl() { - $url = Url::createFromPath('http://example.com'); + $url = Url::unrouted('http://example.com'); $url->getRouteName(); } @@ -319,7 +322,7 @@ public function testGetRouteParameters($urls) { * @expectedException \UnexpectedValueException */ public function testGetRouteParametersWithExternalUrl() { - $url = Url::createFromPath('http://example.com'); + $url = Url::unrouted('http://example.com'); $url->getRouteParameters(); } diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php index 7926b2f..8c38e75 100644 --- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php @@ -40,6 +40,13 @@ class LinkGeneratorTest extends UnitTestCase { protected $moduleHandler; /** + * The mocked URL Assembler service. + * + * @var \PHPUnit_Framework_MockObject_MockObject|\Drupal\Core\Utility\UnroutedUrlAssemblerInterface + */ + protected $urlAssembler; + + /** * Contains the LinkGenerator default options. */ protected $defaultOptions = array( @@ -60,6 +67,7 @@ protected function setUp() { $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface'); $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->moduleHandler); + $this->urlAssembler = $this->getMock('\Drupal\Core\Utility\UnroutedUrlAssemblerInterface'); } /** @@ -142,17 +150,18 @@ public function testGenerateFromUrl() { * @covers ::generateFromUrl() */ public function testGenerateFromUrlExternal() { - $this->urlGenerator->expects($this->once()) - ->method('generateFromPath') - ->with('http://drupal.org', array('set_active_class' => TRUE, 'external' => TRUE) + $this->defaultOptions) - ->will($this->returnArgument(0)); - $this->moduleHandler->expects($this->once()) ->method('alter') ->with('link', $this->isType('array')); - $url = Url::createFromPath('http://drupal.org'); + $this->urlAssembler->expects($this->once()) + ->method('assemble') + ->with('http://drupal.org', array('set_active_class' => TRUE, 'external' => TRUE) + $this->defaultOptions) + ->willReturnArgument(0); + + $url = Url::unrouted('http://drupal.org'); $url->setUrlGenerator($this->urlGenerator); + $url->setUnroutedUrlAssembler($this->urlAssembler); $url->setOption('set_active_class', TRUE); $result = $this->linkGenerator->generateFromUrl('Drupal', $url);