diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index 4ee3692..0511193 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -32,7 +32,7 @@ class Url { * * @var \Drupal\Core\Utility\UnroutedUrlAssemblerInterface */ - protected $urlAssember; + protected $urlAssembler; /** * The access manager @@ -116,10 +116,66 @@ public function __construct($route_name, $route_parameters = array(), $options = $this->options = $options; } + /** + * Creates a new routed (internal to Drupal) URL object. + * + * @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. + * + * @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. + * + * @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. + * + * @return \Drupal\Core\Url + * A new unrouted (external to Drupal) URL object. + */ public static function unrouted($uri, $options = array()) { $url = new static($uri, array(), $options); $url->setUnrouted(); @@ -169,7 +225,9 @@ protected function setUnrouted() { // Set empty route name and parameters. $this->routeName = NULL; $this->routeParameters = array(); - $this->external = strpos($this->uri, 'base://') !== 0; + if ($this->external = strpos($this->uri, 'base://') !== 0) { + $this->options['external'] = TRUE; + } } /** @@ -475,10 +533,10 @@ protected function urlGenerator() { * The URL builder. */ protected function unroutedUrlAssembler() { - if (!$this->urlAssember) { - $this->urlAssember = \Drupal::service('unrouted_url_assembler'); + if (!$this->urlAssembler) { + $this->urlAssembler = \Drupal::service('unrouted_url_assembler'); } - return $this->urlAssember; + return $this->urlAssembler; } /** @@ -503,7 +561,7 @@ public function setUrlGenerator(UrlGeneratorInterface $url_generator) { * @return $this */ public function setUnroutedUrlAssembler(UnroutedUrlAssemblerInterface $url_assembler) { - $this->urlAssember = $url_assembler; + $this->urlAssembler = $url_assembler; return $this; } diff --git a/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php b/core/modules/link/src/Plugin/Field/FieldFormatter/LinkFormatter.php index f1d179f..a23972f 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. @@ -206,7 +207,18 @@ protected function buildUrl(LinkItemInterface $item) { } if ($item->isExternal()) { - $url = Url::unrouted($item->url, $options); + // @todo LinkItemInterface shouldn't treat items with no scheme as + // external, including . + if (parse_url($item->url, PHP_URL_SCHEME)) { + $url = Url::unrouted($item->url, $options); + } + elseif ($item->url == '') { + $url = new Url(''); + } + else { + // No base path - assume internal. + $url = Url::createFromRequest(Request::create('/' . ltrim($item->url, '/'))); + } } else { $url = Url::routed($item->route_name, (array) $item->route_parameters, (array) $options); diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 65b4b04..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 URL. if (isset($variables['link_path'])) { - $variables['attributes']['href'] = \Drupal::service('unrouted_url_assembler')->assemble($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/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php index 4f5108f..7cc514a 100644 --- a/core/tests/Drupal/Tests/Core/UrlTest.php +++ b/core/tests/Drupal/Tests/Core/UrlTest.php @@ -103,7 +103,7 @@ public function testCreateFromPath() { $urls = array(); foreach ($this->map as $index => $values) { $path = array_pop($values); - $url = Url::createFromRequest(Request::create("/$path")); + $url = Url::createFromRequest(Request::create("$path")); $this->assertSame($values, array_values($url->toArray())); $urls[$index] = $url; } @@ -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(); } } diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php index 72dd40b..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')); + $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);