Commit da1b6642 authored by webchick's avatar webchick

Issue #2346361 by pwolanin, dawehner: Add a UnroutedUrlAssembler and put it into the container.

parent 6caeaf03
......@@ -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']
......
<?php
/**
* @file
* Contains Drupal\Core\Utility\UnroutedUrlAssembler.
*/
namespace Drupal\Core\Utility;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* Provides a way to build external or non Drupal local domain URLs.
*
* It takes into account configured safe HTTP protocols.
*/
class UnroutedUrlAssembler implements UnroutedUrlAssemblerInterface {
/**
* A request stack object.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* Constructs a new unroutedUrlAssembler object.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config
* The config factory.
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
* A request stack object.
*/
public function __construct(RequestStack $request_stack, ConfigFactoryInterface $config) {
$allowed_protocols = $config->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'];
}
}
}
<?php
/**
* @file
* Contains Drupal\Core\Utility\UnroutedUrlAssemblerInterface.
*/
namespace Drupal\Core\Utility;
/**
* Provides a way to build external or non Drupal local domain URLs.
*/
interface UnroutedUrlAssemblerInterface {
/**
* Builds a domain-local or external URL from a path or URL.
*
* For actual implementations the logic probably has to be split up between
* domain-local and external URLs.
*
* @param string $uri
* A path on the same domain or external URL being linked to, such as "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 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
* URL encode them.
*
* @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.
* - '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.
* - '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. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can
* only be enforced when the variable 'https' is set to TRUE.
*
* @return
* A string containing a relative or absolute URL.
*/
public function assemble($uri, array $options = array());
}
<?php
/**
* @file
* Contains \Drupal\Tests\Core\Utility\UnroutedUrlAssemblerTest.
*/
namespace Drupal\Tests\Core\Utility;
use Drupal\Core\Utility\UnroutedUrlAssembler;
use Drupal\Tests\UnitTestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* @coversDefaultClass \Drupal\Core\Utility\UnroutedUrlAssembler
* @group Utility
*/
class UnroutedUrlAssemblerTest extends UnitTestCase {
/**
* The request stack.
*
* @var \Symfony\Component\HttpFoundation\RequestStack
*/
protected $requestStack;
/**
* The mocked config factory.
*
* @var \Drupal\Core\Config\ConfigFactoryInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $configFactory;
/**
* The tested unrouted url assembler.
*
* @var \Drupal\Core\Utility\UnroutedUrlAssembler
*/
protected $unroutedUrlAssembler;
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->requestStack = new RequestStack();
$this->configFactory = $this->getConfigFactoryStub(['system.filter' => []]);
$this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $this->configFactory);
}
/**
* @covers ::assemble
* @expectedException \InvalidArgumentException
*/
public function testAssembleWithNeitherExternalNorDomainLocalUri() {
$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'],
];
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment