diff --git a/core/core.services.yml b/core/core.services.yml index afccb31..3aaf711 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -798,7 +798,7 @@ services: arguments: ['@request_stack', '@url_generator'] unrouted_url_assembler: class: Drupal\Core\Utility\UnroutedUrlAssembler - arguments: ['@request_stack', '@path_processor_manager', '%filter_protocols%'] + arguments: ['@request_stack', '@router.request_context', '@path_processor_manager', '%filter_protocols%'] link_generator: class: Drupal\Core\Utility\LinkGenerator arguments: ['@url_generator', '@module_handler', '@renderer'] diff --git a/core/lib/Drupal/Core/Routing/RequestContext.php b/core/lib/Drupal/Core/Routing/RequestContext.php index 2953251..c3f5e2f 100644 --- a/core/lib/Drupal/Core/Routing/RequestContext.php +++ b/core/lib/Drupal/Core/Routing/RequestContext.php @@ -27,6 +27,20 @@ class RequestContext extends SymfonyRequestContext { protected $completeBaseUrl; /** + * The base path relative to the location of index.php. + * + * This base path does not take into account the base path of the current + * front controller, so it points to /d8 even if /d8/core/update.php is + * requested. + * + * Unlike the base URL this base path will never include 'index.php' itself. + * + * @see \Symfony\Component\HttpFoundation\Request::getBaseUrl + * @see \Symfony\Component\HttpFoundation\Request::getBasePath + */ + protected $indexPhpBasePath; + + /** * Populates the context from the current request from the request stack. * * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack @@ -47,6 +61,9 @@ public function fromRequest(Request $request) { if (isset($GLOBALS['base_url'])) { $this->setCompleteBaseUrl($GLOBALS['base_url']); } + if (isset($GLOBALS['base_path'])) { + $this->setIndexPhpBasePath($GLOBALS['base_path']); + } } /** @@ -69,4 +86,25 @@ public function setCompleteBaseUrl($complete_base_url) { $this->completeBaseUrl = $complete_base_url; } + /** + * Sets the index php base path. + * + * @param string $base_path + * The base path. + */ + public function setIndexPhpBasePath($base_path) { + $this->indexPhpBasePath = $base_path; + } + + /** + * Gets the base path relative to the location of index.php + * + * Note, it always has a trailing slash. + * + * @return string + */ + public function getIndexPhpBasePath() { + return $this->indexPhpBasePath; + } + } diff --git a/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php b/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php index dc1c237..a4c4246 100644 --- a/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php +++ b/core/lib/Drupal/Core/Utility/UnroutedUrlAssembler.php @@ -10,6 +10,7 @@ use Drupal\Component\Utility\UrlHelper; use Drupal\Core\GeneratedUrl; use Drupal\Core\PathProcessor\OutboundPathProcessorInterface; +use Drupal\Core\Routing\RequestContext; use Symfony\Component\HttpFoundation\RequestStack; /** @@ -34,19 +35,29 @@ class UnroutedUrlAssembler implements UnroutedUrlAssemblerInterface { protected $pathProcessor; /** + * The request context. + * + * @var \Drupal\Core\Routing\RequestContext + */ + protected $requestContext; + + /** * Constructs a new unroutedUrlAssembler object. * * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack * A request stack object. + * @param \Drupal\Core\Routing\RequestContext $request_context + * The request context. * @param \Drupal\Core\PathProcessor\OutboundPathProcessorInterface $path_processor * The output path processor. * @param string[] $filter_protocols * (optional) An array of protocols allowed for URL generation. */ - public function __construct(RequestStack $request_stack, OutboundPathProcessorInterface $path_processor, array $filter_protocols = ['http', 'https']) { + public function __construct(RequestStack $request_stack, RequestContext $request_context, OutboundPathProcessorInterface $path_processor, array $filter_protocols = ['http', 'https']) { UrlHelper::setAllowedProtocols($filter_protocols); $this->requestStack = $request_stack; $this->pathProcessor = $path_processor; + $this->requestContext = $request_context; } /** @@ -128,7 +139,7 @@ protected function buildLocalUrl($uri, array $options = [], $collect_bubbleable_ $uri = ltrim($uri, '/'); // Add any subdirectory where Drupal is installed. - $current_base_path = $request->getBasePath() . '/'; + $current_base_path = $this->requestContext->getIndexPhpBasePath(); if ($options['absolute']) { $current_base_url = $request->getSchemeAndHttpHost() . $current_base_path; @@ -156,6 +167,7 @@ protected function buildLocalUrl($uri, array $options = [], $collect_bubbleable_ $prefix = empty($uri) ? rtrim($options['prefix'], '/') : $options['prefix']; $uri = str_replace('%2F', '/', rawurlencode($prefix . $uri)); + $uri = $options['script'] && ($uri != '') ? '/' . trim($uri, '/') : $uri; $query = $options['query'] ? ('?' . UrlHelper::buildQuery($options['query'])) : ''; $url = $base . $options['script'] . $uri . $query . $options['fragment']; return $collect_bubbleable_metadata ? $generated_url->setGeneratedUrl($url) : $url; diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php index 0239a41..f0ffa81 100644 --- a/core/modules/system/src/Controller/DbUpdateController.php +++ b/core/modules/system/src/Controller/DbUpdateController.php @@ -240,8 +240,7 @@ protected function info(Request $request) { '#type' => 'link', '#title' => $this->t('Continue'), '#attributes' => array('class' => array('button', 'button--primary')), - // @todo Revisit once https://www.drupal.org/node/2548095 is in. - '#url' => Url::fromUri('base://selection'), + '#url' => Url::fromUri('base://update.php/selection', ['script' => '']), ); return $build; } @@ -631,7 +630,7 @@ protected function triggerBatch(Request $request) { batch_set($batch); // @todo Revisit once https://www.drupal.org/node/2548095 is in. - return batch_process(Url::fromUri('base://results'), Url::fromUri('base://start')); + return batch_process(Url::fromUri('base://update.php/results', ['script' => '']), Url::fromUri('base://update.php/start', ['script' => ''])); } /** diff --git a/core/modules/system/system.module b/core/modules/system/system.module index d3b55f9..893cb1b 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -423,7 +423,9 @@ function system_authorized_get_url(array $options = array()) { // the correct usage for this case. $url = Url::fromUri('base:core/authorize.php'); $url_options = $url->getOptions(); - $url->setOptions($options + $url_options); + $url_options = $options + $url_options; + $url_options['script'] = ''; + $url->setOptions($url_options); return $url; } diff --git a/core/modules/update/update.authorize.inc b/core/modules/update/update.authorize.inc index b96c904..e01d0fd 100644 --- a/core/modules/update/update.authorize.inc +++ b/core/modules/update/update.authorize.inc @@ -108,6 +108,7 @@ function update_authorize_run_install($filetransfer, $project, $updater_name, $l // @todo Use a different finished callback for different messages? 'finished' => 'update_authorize_install_batch_finished', 'file' => drupal_get_path('module', 'update') . '/update.authorize.inc', + 'urlj' ); batch_set($batch); diff --git a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php index 98dcb1d..913cfe7 100644 --- a/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php +++ b/core/modules/views/tests/src/Unit/Plugin/field/FieldPluginBaseTest.php @@ -10,7 +10,8 @@ use Drupal\Core\GeneratedUrl; use Drupal\Core\Language\Language; use Drupal\Core\Render\Markup; -use Drupal\Core\Url; + use Drupal\Core\Routing\RequestContext; + use Drupal\Core\Url; use Drupal\Core\Utility\LinkGenerator; use Drupal\Core\Utility\LinkGeneratorInterface; use Drupal\Core\Utility\UnroutedUrlAssembler; @@ -128,6 +129,13 @@ class FieldPluginBaseTest extends UnitTestCase { protected $renderer; /** + * The request context. + * + * @var \Drupal\Core\Routing\RequestContext + */ + protected $requestContext; + + /** * {@inheritdoc} */ protected function setUp() { @@ -151,6 +159,9 @@ protected function setUp() { $this->requestStack = new RequestStack(); $this->requestStack->push(new Request()); + $this->requestContext = new RequestContext(); + $this->requestContext->setIndexPhpBasePath('/'); + $this->requestContext->fromRequestStack($this->requestStack); $this->unroutedUrlAssembler = $this->getMock('Drupal\Core\Utility\UnroutedUrlAssemblerInterface'); $this->linkGenerator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface'); @@ -171,7 +182,7 @@ protected function setUp() { */ protected function setUpUrlIntegrationServices() { $this->pathProcessor = $this->getMock('Drupal\Core\PathProcessor\OutboundPathProcessorInterface'); - $this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $this->pathProcessor); + $this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $this->requestContext, $this->pathProcessor); \Drupal::getContainer()->set('unrouted_url_assembler', $this->unroutedUrlAssembler); diff --git a/core/tests/Drupal/KernelTests/Core/Routing/RequestContextIntegrationTest.php b/core/tests/Drupal/KernelTests/Core/Routing/RequestContextIntegrationTest.php new file mode 100644 index 0000000..964da0b --- /dev/null +++ b/core/tests/Drupal/KernelTests/Core/Routing/RequestContextIntegrationTest.php @@ -0,0 +1,109 @@ +preHandle($request); + + /** @var \Drupal\Core\Routing\RequestContext $request_context */ + $request_context = \Drupal::service('router.request_context'); + $request_context->fromRequest($request); + + $this->assertEquals($expected_index_php, $request_context->getIndexPhpBasePath()); + } + + public function providerTestGetIndexPhpBasePath() { + $data = []; + $data[] = [$this->setupRequest(FALSE, ''), '/']; + $data[] = [$this->setupRequest(FALSE, 'index.php'), '/']; + $data[] = [$this->setupRequest(TRUE, ''), '/subdir/']; + $data[] = [$this->setupRequest(TRUE, 'index.php'), '/subdir/']; + + return $data; + } + + /** + * Sets up a request for testing purposes. + * + * @param bool $subdir + * Should a subdir be used + * @param string $script_path + * A script path. + * + * @return \Symfony\Component\HttpFoundation\Request + */ + protected function setupRequest($subdir, $script_path = "") { + $base_dir = $subdir ? '/subdir/' : '/'; + $default_script_path = $script_path; + if (empty($default_script_path)) { + $default_script_path = "index.php"; + } + // 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. + // + // Common values, for any kind of request: + // + // DOCUMENT_ROOT = __ROOT__ + // SERVER_NAME = www.example.com + // SERVER_ADDR = 127.0.0.1 + // SERVER_PORT = 7777 + // HTTP_HOST = www.example.com:7777 + // HTTP_ORIGIN = www.example.com:7777 + // + // For a __ROOT__/index.php request: + // + // REQUEST_URI = / + // SCRIPT_NAME = /index.php + // PHP_SELF = /index.php + // SCRIPT_FILENAME = __ROOT__/index.php + // + // For a __ROOT__/subdir/index.php request: + // + // REQUEST_URI = /subdir/ + // SCRIPT_NAME = /subdir/index.php + // PHP_SELF = /subdir/index.php + // SCRIPT_FILENAME = __ROOT__/subdir/index.php + // + // For a __ROOT__/core/authorize.php request, the differences are: + // + // REQUEST_URI = /core/authorize.php + // SCRIPT_NAME = /core/authorize.php + // PHP_SELF = /core/authorize.php + // SCRIPT_FILENAME = __ROOT__/core/authorize.php + // + // For a __ROOT__/subdir/core/authorize.php request, the differences are: + // + // REQUEST_URI = /subdir/core/authorize.php + // SCRIPT_NAME = /subdir/core/authorize.php + // PHP_SELF = /subdir/core/authorize.php + // SCRIPT_FILENAME = __ROOT__/subdir/core/authorize.php + // + $server = [ + 'REQUEST_URI' => $base_dir . $script_path, + 'SCRIPT_NAME' => $base_dir . $default_script_path, + 'SCRIPT_FILENAME' => $this->root . $base_dir . $default_script_path, + 'SERVER_NAME' => 'www.example.com', + ]; + $request = Request::create($base_dir); // do we need to put script_path here sometimes? + $request->server->add($server); + + return $request; + } + +} diff --git a/core/tests/Drupal/Tests/Core/Utility/UnroutedUrlAssemblerTest.php b/core/tests/Drupal/Tests/Core/Utility/UnroutedUrlAssemblerTest.php index e13b85b..56bb731 100644 --- a/core/tests/Drupal/Tests/Core/Utility/UnroutedUrlAssemblerTest.php +++ b/core/tests/Drupal/Tests/Core/Utility/UnroutedUrlAssemblerTest.php @@ -9,6 +9,7 @@ use Drupal\Core\GeneratedUrl; use Drupal\Core\Render\BubbleableMetadata; +use Drupal\Core\Routing\RequestContext; use Drupal\Core\Utility\UnroutedUrlAssembler; use Drupal\Tests\UnitTestCase; use Symfony\Component\HttpFoundation\Request; @@ -49,6 +50,13 @@ class UnroutedUrlAssemblerTest extends UnitTestCase { protected $pathProcessor; /** + * The request context. + * + * @var \Drupal\Core\Routing\RequestContext + */ + protected $requestContext; + + /** * {@inheritdoc} */ protected function setUp() { @@ -56,7 +64,8 @@ protected function setUp() { $this->requestStack = new RequestStack(); $this->pathProcessor = $this->getMock('Drupal\Core\PathProcessor\OutboundPathProcessorInterface'); - $this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $this->pathProcessor); + $this->requestContext = new RequestContext(); + $this->unroutedUrlAssembler = new UnroutedUrlAssembler($this->requestStack, $this->requestContext, $this->pathProcessor); } /** @@ -111,8 +120,12 @@ public function providerTestAssembleWithExternalUrl() { * * @dataProvider providerTestAssembleWithLocalUri */ - public function testAssembleWithLocalUri($uri, array $options, $subdir, $expected) { - $this->setupRequestStack($subdir); + public function testAssembleWithLocalUri($uri, array $options, $subdir, $script_path, $expected) { + $this->setupRequestStack($subdir, $script_path); + + if ($script_path) { + $options['script'] = $script_path; + } $this->assertEquals($expected, $this->unroutedUrlAssembler->assemble($uri, $options)); } @@ -122,15 +135,23 @@ public function testAssembleWithLocalUri($uri, array $options, $subdir, $expecte */ public function providerTestAssembleWithLocalUri() { return [ - ['base:example', [], FALSE, '/example'], - ['base:example', ['query' => ['foo' => 'bar']], FALSE, '/example?foo=bar'], - ['base:example', ['query' => ['foo' => '"bar"']], FALSE, '/example?foo=%22bar%22'], - ['base:example', ['query' => ['foo' => '"bar"', 'zoo' => 'baz']], FALSE, '/example?foo=%22bar%22&zoo=baz'], - ['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'], - ['base:/drupal.org', [], FALSE, '/drupal.org'], + ['base:example', [], FALSE, '', '/example'], + ['base:example', ['query' => ['foo' => 'bar']], FALSE, '', '/example?foo=bar'], + ['base:example', ['query' => ['foo' => '"bar"']], FALSE, '', '/example?foo=%22bar%22'], + ['base:example', ['query' => ['foo' => '"bar"', 'zoo' => 'baz']], FALSE, '', '/example?foo=%22bar%22&zoo=baz'], + ['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'], + ['base:/drupal.org', [], FALSE, '', '/drupal.org'], + 'update-php' => ['base:selection', [], FALSE, 'update.php', '/update.php/selection'], + 'update-php-subdir' => ['base:selection', [], TRUE, 'update.php', '/subdir/update.php/selection'], + 'update-php2' => ['base:/selection', [], FALSE, 'update.php', '/update.php/selection'], + 'update-php-subdir2' => ['base:/selection', [], TRUE, 'update.php', '/subdir/update.php/selection'], + 'authorize-php' => ['base:core/authorize.php', [], FALSE, '', '/core/authorize.php'], + 'authorize-php-subdir' => ['base:core/authorize.php', [], TRUE, '', '/subdir/core/authorize.php'], + 'authorize-php-script-path' => ['base:', [], FALSE, 'core/authorize.php', '/core/authorize.php'], + 'authorize-php-subdir-script-path' => ['base:', [], TRUE, 'core/authorize.php', '/subdir/core/authorize.php'], ]; } @@ -172,25 +193,23 @@ public function testAssembleWithEnabledProcessing() { /** * Setups the request stack for a given subdir. * - * @param string $subdir - * The wanted subdir. + * @param bool $subdir + * Whether a subdir should be used. */ - protected function setupRequestStack($subdir) { - $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' => $this->root . '/index.php', - 'SERVER_NAME' => 'http://www.example.com', - ]; - $request = Request::create('/subdir/'); - } - else { - $request = Request::create('/'); + protected function setupRequestStack($subdir, $script_path = "") { + $base_dir = $subdir ? '/subdir/' : '/'; + $default_script_path = $script_path; + if (empty($default_script_path)) { + $default_script_path = "index.php"; } + $server = [ + 'REQUEST_URI' => $base_dir . $script_path, + 'SCRIPT_NAME' => $base_dir . $default_script_path, + 'SCRIPT_FILENAME' => $this->root . $base_dir . $default_script_path, + 'SERVER_NAME' => 'www.example.com', + ]; + $request = Request::create($base_dir); // do we need to put script_path here sometimes? + $this->requestContext->setIndexPhpBasePath($base_dir); $request->server->add($server); $this->requestStack->push($request); }