diff --git a/core/core.services.yml b/core/core.services.yml index 91a2c2b..7abab67 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -489,6 +489,9 @@ services: arguments: ['@router.route_provider', '@path_processor_manager', '@route_processor_manager', '@config.factory', '@settings', '@logger.channel.default', '@request_stack'] calls: - [setContext, ['@?router.request_context']] + unrouted_url_assembler: + class: Drupal\Core\Utility\UnroutedUrlAssembler + arguments: ['@request_stack', '@config.factory' ] link_generator: class: Drupal\Core\Utility\LinkGenerator arguments: ['@url_generator', '@module_handler'] diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 7e6e12f..e36cab7 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -452,11 +452,17 @@ 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']; + if (UrlHelper::isExternal($options['path'])) { + $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/common.inc b/core/includes/common.inc index 1379b4e..f46c9a9 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -639,6 +639,9 @@ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) { * alternative than url(). * * @see \Drupal\Core\Routing\UrlGeneratorInterface::generateFromPath(). + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * System paths should not be used - use route names and parameters. */ function url($path = NULL, array $options = array()) { return \Drupal::urlGenerator()->generateFromPath($path, $options); diff --git a/core/lib/Drupal/Core/Menu/MenuLinkBase.php b/core/lib/Drupal/Core/Menu/MenuLinkBase.php index b09bfa9..6edbcc0 100644 --- a/core/lib/Drupal/Core/Menu/MenuLinkBase.php +++ b/core/lib/Drupal/Core/Menu/MenuLinkBase.php @@ -141,8 +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); + $url = Url::unrouted($this->pluginDefinition['url'], $options); return $url; } } 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..4ee3692 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 URL builder. + * + * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface + */ + protected $urlAssember; + + /** * 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. @@ -102,46 +116,27 @@ public function __construct($route_name, $route_parameters = array(), $options = $this->options = $options; } + public static function routed($route_name, $route_parameters = array(), $options = array()) { + return new static($route_name, $route_parameters, $options); + } + + public static function unrouted($uri, $options = array()) { + $url = new static($uri, array(), $options); + $url->setUnrouted(); + return $url; + } + /** - * Returns the Url object matching a path. READ THE FOLLOWING SECURITY NOTE. + * Returns the Url object matching a request. * - * 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, + * 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 string $path - * A path (e.g. 'node/1', 'http://drupal.org'). - * - * @return static - * An Url object. Warning: the object is created even if the current user - * can not access the path. - * - * @throws \Drupal\Core\Routing\MatchingRouteNotFoundException - * Thrown when the path cannot be matched. - */ - 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")); - } - } - - /** - * Returns the Url object matching a request. READ THE SECURITY NOTE ON createFromPath(). - * * @param \Symfony\Component\HttpFoundation\Request $request * A request object. * @@ -163,23 +158,18 @@ public static function createFromRequest(Request $request) { } /** - * Sets this Url to be external. + * Sets this Url to encapselate 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; + $this->external = strpos($this->uri, 'base://') !== 0; } /** @@ -197,10 +187,10 @@ public function isExternal() { * @return string * * @throws \UnexpectedValueException. - * If this is an external URL with no corresponding route. + * If this is an 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 +203,10 @@ public function getRouteName() { * @return array * * @throws \UnexpectedValueException. - * If this is an external URL with no corresponding route. + * If this is an 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 +220,13 @@ public function getRouteParameters() { * The array of parameters. * * @return $this + * + * @throws \UnexpectedValueException. + * If this is an 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 +241,13 @@ public function setRouteParameters($parameters) { * The route parameter. * * @return $this + * + * @throws \UnexpectedValueException. + * If this is an 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 +308,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 +340,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()); @@ -363,7 +359,7 @@ public function toString() { public function toArray() { if ($this->isExternal()) { return array( - 'path' => $this->getPath(), + 'path' => $this->getUri(), 'options' => $this->getOptions(), ); } @@ -383,9 +379,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,13 +404,13 @@ 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 an 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()) { + if ($this->unrouted) { throw new \UnexpectedValueException('External URLs do not have internal representations.'); } return $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters()); @@ -473,6 +469,19 @@ protected function urlGenerator() { } /** + * Gets the URL builder for non-Drupal URLs. + * + * @return \Drupal\Core\Utility\UnroutedUrlAssemblerInterface + * The URL builder. + */ + protected function unroutedUrlAssembler() { + if (!$this->urlAssember) { + $this->urlAssember = \Drupal::service('unrouted_url_assembler'); + } + return $this->urlAssember; + } + + /** * Sets the URL generator. * * @param \Drupal\Core\Routing\UrlGeneratorInterface @@ -485,4 +494,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->urlAssember = $url_assembler; + return $this; + } + } diff --git a/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php b/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php new file mode 100644 index 0000000..05fb19f --- /dev/null +++ b/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php @@ -0,0 +1,152 @@ +get('system.filter')->get('protocols') ?: ['http', 'https']; + UrlHelper::setAllowedProtocols($allowed_protocols); + $this->requestStack = $request_stack; + } + + /** + * {@inheritdoc} + * + * This is a helper function that calls buildExternalUrl() or buildLocalUrl() + * based on a check of whether the path is a valid external URL. + */ + public function assemble($uri, array $options = []) { + // Note that UrlHelper::isExternal will return FALSE if the $uri has a + // disallowed protocol. This is later made safe since we always add at + // least a leading slash. + if (strpos($uri, 'base://') === 0) { + return $this->buildLocalUrl($uri, $options); + } + elseif (UrlHelper::isExternal($uri)) { + // UrlHelper::isExternal() only returns true for safe protocols. + return $this->buildExternalUrl($uri, $options); + } + throw new \InvalidArgumentException('You must use a valid URI scheme. Use base:// for a path e.g. to a Drupal file that needs the base path.'); + } + + /** + * {@inheritdoc} + */ + protected function buildExternalUrl($uri, array $options = []) { + $this->addOptionDefaults($options); + // Split off the fragment. + if (strpos($uri, '#') !== FALSE) { + list($uri, $old_fragment) = explode('#', $uri, 2); + // If $options contains no fragment, take it over from the path. + if (isset($old_fragment) && !$options['fragment']) { + $options['fragment'] = '#' . $old_fragment; + } + } + + if (isset($options['https'])) { + if ($options['https'] === TRUE) { + $uri = str_replace('http://', 'https://', $uri); + } + elseif ($options['https'] === FALSE) { + $uri = str_replace('https://', 'http://', $uri); + } + } + // Append the query. + if ($options['query']) { + $uri .= (strpos($uri, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($options['query']); + } + // Reassemble. + return $uri . $options['fragment']; + } + + /** + * {@inheritdoc} + */ + protected function buildLocalUrl($uri, array $options = []) { + $this->addOptionDefaults($options); + $request = $this->requestStack->getCurrentRequest(); + + // Remove the base:// scheme. + $uri = substr($uri, 7); + // Add any subdirectory where Drupal is installed. + $current_base_path = $request->getBasePath() . '/'; + + if ($options['absolute']) { + $current_base_url = $request->getSchemeAndHttpHost() . $current_base_path; + if (isset($options['https'])) { + if (!empty($options['https'])) { + $base = str_replace('http://', 'https://', $current_base_url); + $options['absolute'] = TRUE; + } + else { + $base = str_replace('https://', 'http://', $current_base_url); + $options['absolute'] = TRUE; + } + } + else { + $base = $current_base_url; + } + } + else { + $base = $current_base_path; + } + + $prefix = empty($uri) ? rtrim($options['prefix'], '/') : $options['prefix']; + + $uri = str_replace('%2F', '/', rawurlencode($prefix . $uri)); + $query = $options['query'] ? ('?' . UrlHelper::buildQuery($options['query'])) : ''; + return $base . $options['script'] . $uri . $query . $options['fragment']; + } + + /** + * Merges in default defaults + * + * @param array $options + * The options to merge in the defaults. + */ + protected function addOptionDefaults(array &$options) { + // Merge in defaults. + $options += [ + 'fragment' => '', + 'query' => [], + 'absolute' => FALSE, + 'prefix' => '', + 'script' => '', + ]; + + if (isset($options['fragment']) && $options['fragment'] !== '') { + $options['fragment'] = '#' . $options['fragment']; + } + } + +} diff --git a/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php b/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php new file mode 100644 index 0000000..d8d0e64 --- /dev/null +++ b/core/lib/Drupal/Core/Utility/UnroutedUrlAssemblerInterface.php @@ -0,0 +1,54 @@ +' . 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 5df7013..cb7f0b9 100644 --- a/core/modules/aggregator/src/Entity/Item.php +++ b/core/modules/aggregator/src/Entity/Item.php @@ -245,7 +245,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/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php index 05caf97..f1d179f 100644 --- a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php +++ b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php @@ -163,7 +163,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 +206,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 c39d156..b44fffc 100644 --- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php +++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php @@ -108,7 +108,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/shortcut/src/Entity/Shortcut.php b/core/modules/shortcut/src/Entity/Shortcut.php index a3e15e7..fa1e1e0 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. @@ -135,7 +136,7 @@ public static function preCreate(EntityStorageInterface $storage, array &$values public function preSave(EntityStorageInterface $storage) { parent::preSave($storage); - $url = Url::createFromPath($this->path->value); + $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 3f19f14..bc88307 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -452,7 +452,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..e885ce5 100644 --- a/core/modules/update/update.report.inc +++ b/core/modules/update/update.report.inc @@ -135,7 +135,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'])) ? \Drupal::service('unrouted_url_assembler')->assemble($project['link']) : 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..65b4b04 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -590,9 +590,9 @@ function template_preprocess_username(&$variables) { $variables['link_path'] = $account->homepage; $variables['homepage'] = $account->homepage; } - // 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']); + $variables['attributes']['href'] = \Drupal::service('unrouted_url_assembler')->assemble($variables['link_path'], $variables['link_options']); } } diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php index 12083cf..d236756 100644 --- a/core/modules/views_ui/src/ViewEditForm.php +++ b/core/modules/views_ui/src/ViewEditForm.php @@ -322,10 +322,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..5073689 100644 --- a/core/tests/Drupal/Tests/Core/ExternalUrlTest.php +++ b/core/tests/Drupal/Tests/Core/ExternalUrlTest.php @@ -20,11 +20,11 @@ class ExternalUrlTest extends UnitTestCase { /** - * The URL generator + * The URL assembler * * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit_Framework_MockObject_MockObject */ - protected $urlGenerator; + protected $urlAssembler; /** * The router. @@ -46,15 +46,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); } @@ -65,7 +65,7 @@ protected function setUp() { * @covers ::setExternal() */ public function testCreateFromPath() { - $url = Url::createFromPath($this->path); + $url = Url::unrouted($this->path); $this->assertInstanceOf('Drupal\Core\Url', $url); $this->assertTrue($url->isExternal()); return $url; @@ -172,8 +172,8 @@ public function testGetInternalPath(Url $url) { * * @covers ::getPath() */ - public function testGetPath(Url $url) { - $this->assertNotNull($url->getPath()); + public function testGetUri(Url $url) { + $this->assertNotNull($url->getUri()); } /** diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php index 133a73c..4f5108f 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'))); } /** @@ -227,8 +227,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 +292,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 +319,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..72dd40b 100644 --- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php +++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php @@ -151,7 +151,7 @@ public function testGenerateFromUrlExternal() { ->method('alter') ->with('link', $this->isType('array')); - $url = Url::createFromPath('http://drupal.org'); + $url = Url::unrouted('http://drupal.org'); $url->setUrlGenerator($this->urlGenerator); $url->setOption('set_active_class', TRUE); diff --git a/core/tests/Drupal/Tests/Core/Utility/UnroutedUrlAssemblerTest.php b/core/tests/Drupal/Tests/Core/Utility/UnroutedUrlAssemblerTest.php new file mode 100644 index 0000000..492fb79 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Utility/UnroutedUrlAssemblerTest.php @@ -0,0 +1,129 @@ +requestStack = new RequestStack(); + $this->configFactory = $this->getConfigFactoryStub(['system.filter' => []]); + $this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $this->configFactory); + } + + /** + * @covers ::assemble + * @expectedException \InvalidArgumentException + */ + public function testAssembleWithNeitherExternalNokDomainLocalUri() { + $this->unroutedUrlAssembler->assemble('wrong-url'); + } + + /** + * @covers ::assemble + * @covers ::buildExternalUrl + * + * @dataProvider providerTestAssembleWithExternalUrl + */ + public function testAssembleWithExternalUrl($uri, array $options, $expected) { + $this->assertEquals($expected, $this->unroutedUrlAssembler->assemble($uri, $options)); + } + + /** + * Provides test data for testAssembleWithExternalUrl + */ + public function providerTestAssembleWithExternalUrl() { + return [ + ['http://example.com/test', [], 'http://example.com/test'], + ['http://example.com/test', ['fragment' => 'example'], 'http://example.com/test#example'], + ['http://example.com/test', ['fragment' => 'example'], 'http://example.com/test#example'], + ['http://example.com/test', ['query' => ['foo' => 'bar']], 'http://example.com/test?foo=bar'], + ['http://example.com/test', ['https' => TRUE], 'https://example.com/test'], + ['https://example.com/test', ['https' => FALSE], 'http://example.com/test'], + ['https://example.com/test?foo=1#bar', [], 'https://example.com/test?foo=1#bar'], + ]; + } + + /** + * @covers ::assemble + * @covers::buildLocalUrl + * + * @dataProvider providerTestAssembleWithLocalUri + */ + public function testAssembleWithLocalUri($uri, array $options, $subdir, $expected) { + $server = []; + if ($subdir) { + // Setup a fake request which looks like a Drupal installed under the + // subdir "subdir" on the domain www.example.com. + // To reproduce the values install Drupal like that and use a debugger. + $server = [ + 'SCRIPT_NAME' => '/subdir/index.php', + 'SCRIPT_FILENAME' => DRUPAL_ROOT . '/index.php', + 'SERVER_NAME' => 'http://www.example.com', + ]; + $request = Request::create('/subdir'); + } + else { + $request = Request::create('/'); + } + $request->server->add($server); + $this->requestStack->push($request); + + $this->assertEquals($expected, $this->unroutedUrlAssembler->assemble($uri, $options)); + } + + /** + * @return array + */ + public function providerTestAssembleWithLocalUri() { + return [ + ['base://example', [], FALSE, '/example'], + ['base://example', ['query' => ['foo' => 'bar']], FALSE, '/example?foo=bar'], + ['base://example', ['fragment' => 'example', ], FALSE, '/example#example'], + ['base://example', [], TRUE, '/subdir/example'], + ['base://example', ['query' => ['foo' => 'bar']], TRUE, '/subdir/example?foo=bar'], + ['base://example', ['fragment' => 'example', ], TRUE, '/subdir/example#example'], + ]; + } + +} +