diff --git a/core/lib/Drupal/Core/Form/ConfirmFormHelper.php b/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
index b58078d..73ba98e 100644
--- a/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
+++ b/core/lib/Drupal/Core/Form/ConfirmFormHelper.php
@@ -34,9 +34,7 @@ public static function buildCancelLink(ConfirmFormInterface $form, Request $requ
// If a destination is specified, that serves as the cancel link.
if ($query->has('destination')) {
$options = UrlHelper::parse($query->get('destination'));
- // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is
- // resolved.
- $url = Url::fromUri('base://' . $options['path'], $options);
+ $url = Url::fromUri('user-path://' . $options['path'], $options);
}
// Check for a route-based cancel link.
else {
diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php
index 999f447..62d6eb8 100644
--- a/core/lib/Drupal/Core/Url.php
+++ b/core/lib/Drupal/Core/Url.php
@@ -197,8 +197,11 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
* generated by Drupal), use Url::fromRoute().
*
* @param string $uri
- * The URI of the external resource including the scheme. For Drupal paths
- * that are not handled by the routing system, use base:// for the scheme.
+ * The URI of the external resource including the scheme. For user input
+ * that may correspond to a Drupal route, use user-path:// for the scheme.
+ * For paths that are known not to be handled by the Drupal routing system
+ * (such as static files), use base:// for the scheme to get a link
+ * relative to the Drupal base path (like the HTML element).
* @param array $options
* (optional) An associative array of additional URL options, with the
* following elements:
@@ -216,8 +219,14 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
* defined, the current scheme is used, so the user stays on HTTP or HTTPS
* respectively. TRUE enforces HTTPS and FALSE enforces HTTP.
*
+ * Note: the user-path:// scheme should be avoided except when processing actual
+ * user input that may or may not correspond to a Drupal route. Normally use
+ * Url::fromRoute() for code linking to any any Drupal page.
+ *
+ * You can call access() on the returned object to do access checking.
+ *
* @return \Drupal\Core\Url
- * A new Url object for an unrouted (non-Drupal) URL.
+ * A new Url object.
*
* @throws \InvalidArgumentException
* Thrown when the passed in path has no scheme.
@@ -225,12 +234,21 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) {
* @see static::fromRoute()
*/
public static function fromUri($uri, $options = array()) {
- if (!parse_url($uri, PHP_URL_SCHEME)) {
- throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base:// for a path, e.g., to a Drupal file that needs the base path. Do not use this for internal paths controlled by Drupal.', ['@uri' => $uri]));
+ $scheme = parse_url($uri, PHP_URL_SCHEME);
+ if (!$scheme) {
+ throw new \InvalidArgumentException(String::format('The URI "@uri" is invalid. You must use a valid URI scheme. Use base:// for a path, e.g., to a Drupal file that needs the base path. Do not use this for internal paths controlled by Drupal. Use user-path:// for user input without a scheme.', ['@uri' => $uri]));
+ }
+ if ($scheme == 'user-path'){
+ $path = substr($uri, 12);
+ $url = \Drupal::pathValidator()
+ ->getUrlIfValidWithoutAccessCheck($path) ?: Url::fromUri('base://' . $path);
+ // Allow to specify additional or override options from the path.
+ $url->setOptions($options + $url->getOptions());
+ }
+ else {
+ $url = new static($uri, array(), $options);
+ $url->setUnrouted();
}
-
- $url = new static($uri, array(), $options);
- $url->setUnrouted();
return $url;
}
@@ -552,7 +570,11 @@ public function getInternalPath() {
* Returns TRUE if the user has access to the url, otherwise FALSE.
*/
public function access(AccountInterface $account = NULL) {
- return $this->accessManager()->checkNamedRoute($this->getRouteName(), $this->getRouteParameters(), $account);
+ if ($this->isRouted()) {
+ return $this->accessManager()
+ ->checkNamedRoute($this->getRouteName(), $this->getRouteParameters(), $account);
+ }
+ return TRUE;
}
/**
diff --git a/core/modules/dblog/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php
index e8b1ba5..732816c 100644
--- a/core/modules/dblog/src/Controller/DbLogController.php
+++ b/core/modules/dblog/src/Controller/DbLogController.php
@@ -255,11 +255,11 @@ public function eventDetails($event_id) {
),
array(
array('data' => $this->t('Location'), 'header' => TRUE),
- $this->l($dblog->location, $dblog->location ? Url::fromUri('base://' . $dblog->location) : Url::fromRoute('')),
+ $this->l($dblog->location, $dblog->location ? Url::fromUri('user-path://' . $dblog->location) : Url::fromRoute('')),
),
array(
array('data' => $this->t('Referrer'), 'header' => TRUE),
- $this->l($dblog->referer, $dblog->referer ? Url::fromUri('base://' . $dblog->referer) : Url::fromRoute('')),
+ $this->l($dblog->referer, $dblog->referer ? Url::fromUri('user-path://' . $dblog->location) : Url::fromRoute('')),
),
array(
array('data' => $this->t('Message'), 'header' => TRUE),
diff --git a/core/modules/field_ui/src/FieldUI.php b/core/modules/field_ui/src/FieldUI.php
index 4bbd93a..6ea2aa6 100644
--- a/core/modules/field_ui/src/FieldUI.php
+++ b/core/modules/field_ui/src/FieldUI.php
@@ -61,9 +61,7 @@ public static function getNextDestination(array $destinations) {
$options['query']['destinations'] = $destinations;
}
// Redirect to any given path within the same domain.
- // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is
- // resolved.
- $next_destination = Url::fromUri('base://' . $options['path']);
+ $next_destination = Url::fromUri('user-path://' . $options['path']);
}
return $next_destination;
}
diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php
index e33e53a..1b09140 100644
--- a/core/modules/menu_ui/src/Tests/MenuTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuTest.php
@@ -115,8 +115,8 @@ function testMenu() {
// Verify delete link exists and reset link does not exist.
$this->drupalGet('admin/structure/menu/manage/' . $this->menu->id());
- $this->assertLinkByHref('admin/structure/menu/item/' . $this->items[0]->id() . '/delete');
- $this->assertNoLinkByHref(Url::fromUri('base://admin/structure/menu/link/' . $this->items[0]->getPluginId() . '/reset')->toString());
+ $this->assertLinkByHref(Url::fromRoute('entity.menu_link_content.delete_form', ['menu_link_content' => $this->items[0]->id()])->toString());
+ $this->assertNoLinkByHref(Url::fromRoute('menu_ui.link_reset', ['menu_link_plugin' => $this->items[0]->getPluginId()])->toString());
// Check delete and reset access.
$this->drupalGet('admin/structure/menu/item/' . $this->items[0]->id() . '/delete');
$this->assertResponse(200);
diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml
index 2bb9e62..f5e0e99 100644
--- a/core/modules/system/system.routing.yml
+++ b/core/modules/system/system.routing.yml
@@ -383,7 +383,6 @@ system.theme_settings_theme:
options:
_only_fragment: TRUE
-
'':
path: ''
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 925c84e..f287a19 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,8 +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')) {
- // @todo Use Url::fromPath() once https://www.drupal.org/node/2351379 is
- // resolved.
+ // The destination is a random URL, so we can't use routed URLs.
$form_state->setRedirectUrl(Url::fromUri('base://' . $form_state->getValue('destination')));
}
}
diff --git a/core/modules/views_ui/src/ViewListBuilder.php b/core/modules/views_ui/src/ViewListBuilder.php
index f9ac0e1..b318a3c 100644
--- a/core/modules/views_ui/src/ViewListBuilder.php
+++ b/core/modules/views_ui/src/ViewListBuilder.php
@@ -263,7 +263,9 @@ protected function getDisplayPaths(EntityInterface $view) {
if ($display->hasPath()) {
$path = $display->getPath();
if ($view->status() && strpos($path, '%') === FALSE) {
- $all_paths[] = \Drupal::l('/' . $path, Url::fromUri('base://' . $path));
+ // @todo Use the method on the executable when
+ // https://www.drupal.org/node/2364157 is available.
+ $all_paths[] = \Drupal::l('/' . $path, Url::fromUri('user-path://' . $path));
}
else {
$all_paths[] = String::checkPlain('/' . $path);
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index e8464fa..a42fb24 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -702,7 +702,7 @@ public function renderPreview($display_id, $args = array()) {
Xss::filterAdmin($this->executable->getTitle()),
);
if (isset($path)) {
- $path = \Drupal::l($path, Url::fromUri('base://' . $path));
+ $path = \Drupal::l($path, Url::fromUri('user-path://' . $path));
}
else {
$path = t('This display has no path.');
diff --git a/core/tests/Drupal/Tests/Core/Form/ConfirmFormHelperTest.php b/core/tests/Drupal/Tests/Core/Form/ConfirmFormHelperTest.php
index b80567f..a96ac58 100644
--- a/core/tests/Drupal/Tests/Core/Form/ConfirmFormHelperTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/ConfirmFormHelperTest.php
@@ -10,6 +10,7 @@
use Drupal\Core\Form\ConfirmFormHelper;
use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpFoundation\Request;
/**
@@ -95,8 +96,20 @@ public function testCancelLinkRouteWithUrl() {
public function testCancelLinkDestination() {
$query = array('destination' => 'baz');
$form = $this->getMock('Drupal\Core\Form\ConfirmFormInterface');
+
+ $path_validator = $this->getMock('Drupal\Core\Path\PathValidatorInterface');
+ $container_builder = new ContainerBuilder();
+ $container_builder->set('path.validator', $path_validator);
+ \Drupal::setContainer($container_builder);
+
+ $url = Url::fromRoute('test_route');
+ $path_validator->expects($this->atLeastOnce())
+ ->method('getUrlIfValidWithoutAccessCheck')
+ ->with('baz')
+ ->willReturn($url);
+
$link = ConfirmFormHelper::buildCancelLink($form, new Request($query));
- $this->assertSame('base://' . $query['destination'], $link['#url']->getUri());
+ $this->assertSame($url, $link['#url']);
}
}
diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php
index cea8770..a08fe59 100644
--- a/core/tests/Drupal/Tests/Core/UrlTest.php
+++ b/core/tests/Drupal/Tests/Core/UrlTest.php
@@ -58,6 +58,13 @@ class UrlTest extends UnitTestCase {
protected $map;
/**
+ * The mocked path validator.
+ *
+ * @var \Drupal\Core\Path\PathValidatorInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ protected $pathValidator;
+
+ /**
* {@inheritdoc}
*/
protected function setUp() {
@@ -90,10 +97,13 @@ protected function setUp() {
->will($this->returnValueMap($alias_map));
$this->router = $this->getMock('Drupal\Tests\Core\Routing\TestRouterInterface');
+ $this->pathValidator = $this->getMock('Drupal\Core\Path\PathValidatorInterface');
+
$this->container = new ContainerBuilder();
$this->container->set('router.no_access_checks', $this->router);
$this->container->set('url_generator', $this->urlGenerator);
$this->container->set('path.alias_manager', $this->pathAliasManager);
+ $this->container->set('path.validator', $this->pathValidator);
\Drupal::setContainer($this->container);
}
@@ -160,6 +170,36 @@ public function testFromRouteFront() {
}
/**
+ * Tests fromUri() method with a user-entered path not matching any route.
+ *
+ * @covers ::fromUri
+ */
+ public function testFromRoutedPathWithInvalidRoute() {
+ $this->pathValidator->expects($this->once())
+ ->method('getUrlIfValidWithoutAccessCheck')
+ ->with('invalid-path')
+ ->willReturn(FALSE);
+ $url = Url::fromUri('user-path://invalid-path');
+ $this->assertSame(FALSE, $url->isRouted());
+ $this->assertSame('base://invalid-path', $url->getUri());
+ }
+
+ /**
+ * Tests fromUri() method with user-entered path matching a valid route.
+ *
+ * @covers ::fromUri
+ */
+ public function testFromRoutedPathWithValidRoute() {
+ $url = Url::fromRoute('test_route');
+ $this->pathValidator->expects($this->once())
+ ->method('getUrlIfValidWithoutAccessCheck')
+ ->with('valid-path')
+ ->willReturn($url);
+ $result_url = Url::fromUri('user-path://valid-path');
+ $this->assertSame($url, $result_url);
+ }
+
+ /**
* Tests the createFromRequest method.
*
* @covers ::createFromRequest