diff --git a/core/core.services.yml b/core/core.services.yml index 6954b56..b605968 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -566,7 +566,7 @@ services: - { name: backend_overridable } path.matcher: class: Drupal\Core\Path\PathMatcher - arguments: ['@config.factory'] + arguments: ['@config.factory', '@current_route_match'] path.validator: class: Drupal\Core\Path\PathValidator arguments: ['@router', '@router.no_access_checks', '@current_user', '@path_processor_manager'] diff --git a/core/includes/batch.inc b/core/includes/batch.inc index 088889d..a38e00b 100644 --- a/core/includes/batch.inc +++ b/core/includes/batch.inc @@ -450,18 +450,20 @@ function _batch_finished() { } if ($_batch['form_state']->getRedirect() === NULL) { $redirect = $_batch['batch_redirect'] ?: $_batch['source_url']; - $options = UrlHelper::parse($redirect); // Any path with a scheme does not correspond to a route. - if (parse_url($options['path'], PHP_URL_SCHEME)) { - $redirect = Url::fromUri($options['path'], $options); - } - else { - $redirect = \Drupal::pathValidator()->getUrlIfValid($options['path']); - if (!$redirect) { - // Stay on the same page if the redirect was invalid. - $redirect = Url::fromRoute(''); + if (!$redirect instanceof Url) { + $options = UrlHelper::parse($redirect); + if (parse_url($options['path'], PHP_URL_SCHEME)) { + $redirect = Url::fromUri($options['path'], $options); + } + else { + $redirect = \Drupal::pathValidator()->getUrlIfValid($options['path']); + if (!$redirect) { + // Stay on the same page if the redirect was invalid. + $redirect = Url::fromRoute(''); + } + $redirect->setOptions($options); } - $redirect->setOptions($options); } $_batch['form_state']->setRedirectUrl($redirect); } @@ -480,16 +482,17 @@ function _batch_finished() { $_SESSION['batch_form_state'] = $_batch['form_state']; } $callback = $_batch['redirect_callback']; + /** @var \Drupal\Core\Url $source_url */ + $source_url = $_batch['source_url']; if (is_callable($callback)) { $callback($_batch['source_url'], array('query' => array('op' => 'finish', 'id' => $_batch['id']))); } elseif ($callback === NULL) { // Default to RedirectResponse objects when nothing specified. - $url = _url($_batch['source_url'], array( - 'absolute' => TRUE, - 'query' => array('op' => 'finish', 'id' => $_batch['id']), - )); - return new RedirectResponse($url); + $url = $source_url + ->setAbsolute() + ->setOption('query', ['op' => 'finish', 'id' => $_batch['id']]); + return new RedirectResponse($url->toString()); } } } diff --git a/core/includes/common.inc b/core/includes/common.inc index e7d19ed..8c681b2 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -23,6 +23,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Language\LanguageInterface; use Drupal\Core\Site\Settings; +use Drupal\Core\Url; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Request; use Drupal\Core\PhpStorage\PhpStorageFactory; @@ -260,7 +261,7 @@ function drupal_get_destination() { $destination = array('destination' => $query->get('destination')); } else { - $path = current_path(); + $path = Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath(); $query = UrlHelper::buildQuery(UrlHelper::filterQueryParameters($query->all())); if ($query != '') { $path .= '?' . $query; @@ -1469,18 +1470,20 @@ function _drupal_add_js($data = NULL, $options = NULL) { // Instead of running the hook_url_outbound_alter() again here, extract // them from url(). // @todo Make this less hacky: http://drupal.org/node/1547376. - $scriptPath = $GLOBALS['script_path']; + $request = \Drupal::request(); + $scriptPath = $request->getScriptName(); + $pathPrefix = ''; - $current_query = \Drupal::service('request_stack')->getCurrentRequest()->query->all(); + $current_query = $request->query->all(); _url('', array('script' => &$scriptPath, 'prefix' => &$pathPrefix)); - $current_path = current_path(); + $current_path = \Drupal::routeMatch()->getRouteName() ? Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath() : ''; $current_path_is_admin = FALSE; // The function path_is_admin() is not available on update.php pages. if (!(defined('MAINTENANCE_MODE'))) { $current_path_is_admin = \Drupal::service('router.admin_context')->isAdminRoute(); } $path = array( - 'basePath' => base_path(), + 'baseUrl' => $request->getBaseUrl() . '/', 'scriptPath' => $scriptPath, 'pathPrefix' => $pathPrefix, 'currentPath' => $current_path, diff --git a/core/includes/form.inc b/core/includes/form.inc index 075e37b..1452a63 100644 --- a/core/includes/form.inc +++ b/core/includes/form.inc @@ -15,6 +15,7 @@ use Drupal\Core\Form\OptGroup; use Drupal\Core\Render\Element; use Drupal\Core\Template\Attribute; +use Drupal\Core\Url; use Symfony\Component\HttpFoundation\RedirectResponse; /** @@ -827,7 +828,7 @@ function batch_process($redirect = NULL, $url = 'batch', $redirect_callback = NU 'progressive' => TRUE, 'url' => $url, 'url_options' => array(), - 'source_url' => current_path(), + 'source_url' => Url::fromRouteMatch(\Drupal::routeMatch()), 'batch_redirect' => $redirect, 'theme' => \Drupal::theme()->getActiveTheme()->getName(), 'redirect_callback' => $redirect_callback, diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php index dc40e4f..9df163f 100644 --- a/core/lib/Drupal/Core/DrupalKernel.php +++ b/core/lib/Drupal/Core/DrupalKernel.php @@ -22,6 +22,7 @@ use Drupal\Core\PageCache\RequestPolicyInterface; use Drupal\Core\PhpStorage\PhpStorageFactory; use Drupal\Core\Site\Settings; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\DependencyInjection\Dumper\PhpDumper; @@ -32,6 +33,7 @@ use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\TerminableInterface; use Composer\Autoload\ClassLoader; +use Symfony\Component\Routing\Route; /** * The DrupalKernel class is the core of Drupal itself. @@ -585,6 +587,8 @@ public function prepareLegacyRequest(Request $request) { $this->preHandle($request); // Enter the request scope so that current_user service is available for // locale/translation sake. + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('')); + $request->attributes->set(RouteObjectInterface::ROUTE_NAME, ''); $this->container->get('request_stack')->push($request); $this->container->get('router.request_context')->fromRequest($request); return $this; diff --git a/core/lib/Drupal/Core/Path/PathMatcher.php b/core/lib/Drupal/Core/Path/PathMatcher.php index 95c56dd..a7eed13 100644 --- a/core/lib/Drupal/Core/Path/PathMatcher.php +++ b/core/lib/Drupal/Core/Path/PathMatcher.php @@ -8,6 +8,8 @@ namespace Drupal\Core\Path; use Drupal\Core\Config\ConfigFactoryInterface; +use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Url; /** * Provides a path matcher. @@ -43,13 +45,23 @@ class PathMatcher implements PathMatcherInterface { protected $configFactory; /** + * The current route match. + * + * @var \Drupal\Core\Routing\RouteMatchInterface + */ + protected $routeMatch; + + /** * Creates a new PathMatcher. * * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The config factory. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The current route match. */ - public function __construct(ConfigFactoryInterface $config_factory) { + public function __construct(ConfigFactoryInterface $config_factory, RouteMatchInterface $route_match) { $this->configFactory = $config_factory; + $this->routeMatch = $route_match; } /** @@ -84,7 +96,13 @@ public function matchPath($path, $patterns) { public function isFrontPage() { // Cache the result as this is called often. if (!isset($this->isCurrentFrontPage)) { - $this->isCurrentFrontPage = (current_path() == $this->getFrontPagePath()); + $this->isCurrentFrontPage = FALSE; + // Ensure that the code can also be executed when there is no active + // route match, like on exception responses. + if ($this->routeMatch->getRouteName()) { + $url = Url::fromRouteMatch($this->routeMatch); + $this->isCurrentFrontPage = ($url->getRouteName() && $url->getInternalPath() === $this->getFrontPagePath()); + } } return $this->isCurrentFrontPage; } @@ -98,6 +116,8 @@ public function isFrontPage() { protected function getFrontPagePath() { // Lazy-load front page config. if (!isset($this->frontPage)) { + // @todo page.front should store the route name, see + // https://www.drupal.org/node/2371823 $this->frontPage = $this->configFactory->get('system.site') ->get('page.front'); } diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php index d9888c0..bd30b47 100644 --- a/core/lib/Drupal/Core/Render/Element/RenderElement.php +++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php @@ -10,6 +10,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Plugin\PluginBase; use Drupal\Core\Render\Element; +use Drupal\Core\Url; /** * Provides a base class for render element plugins. @@ -136,7 +137,7 @@ public static function processAjaxForm(&$element, FormStateInterface $form_state /** * Adds Ajax information about an element to communicate with JavaScript. * - * If #ajax['path'] is set on an element, this additional JavaScript is added + * If #ajax['url'] is set on an element, this additional JavaScript is added * to the page header to attach the Ajax behaviors. See ajax.js for more * information. * @@ -145,7 +146,7 @@ public static function processAjaxForm(&$element, FormStateInterface $form_state * Properties used: * - #ajax['event'] * - #ajax['prevent'] - * - #ajax['path'] + * - #ajax['url'] * - #ajax['options'] * - #ajax['wrapper'] * - #ajax['parameters'] @@ -248,8 +249,13 @@ public static function preRenderAjaxForm($element) { } // Change path to URL. - $settings['url'] = isset($settings['path']) ? _url($settings['path'], $settings['options']) : NULL; - unset($settings['path'], $settings['options']); + if (isset($settings['url']) && $settings['url'] instanceof Url) { + $settings['url'] = $settings['url']->setOptions($settings['options'])->toString(); + } + else { + $settings['url'] = NULL; + } + unset($settings['options']); // Add special data to $settings['submit'] so that when this element // triggers an Ajax submission, Drupal's form processing can determine which diff --git a/core/lib/Drupal/Core/Routing/NullGenerator.php b/core/lib/Drupal/Core/Routing/NullGenerator.php index 6ec6e11..ca05d3e 100644 --- a/core/lib/Drupal/Core/Routing/NullGenerator.php +++ b/core/lib/Drupal/Core/Routing/NullGenerator.php @@ -38,6 +38,12 @@ protected function getRoute($name) { if ($name === '') { return new Route('/'); } + elseif ($name == '') { + return new Route($this->requestStack->getCurrentRequest()->getPathInfo()); + } + elseif ($name == '') { + return new Route(''); + } throw new RouteNotFoundException(); } diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index 9ea38b2..39ce125 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -9,6 +9,7 @@ use Drupal\Component\Utility\String; use Drupal\Core\DependencyInjection\DependencySerializationTrait; +use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Routing\UrlGeneratorInterface; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Utility\UnroutedUrlAssemblerInterface; @@ -87,6 +88,13 @@ class Url { protected $uri; /** + * Stores the internal path, if already requested by getInternalPath + * + * @var string + */ + protected $internalPath; + + /** * Constructs a new Url object. * * In most cases, use Url::fromRoute() or Url::fromUri() rather than @@ -166,6 +174,23 @@ public static function fromRoute($route_name, $route_parameters = array(), $opti } /** + * Creates a new URL object from a route match. + * + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The route match. + * + * @return $this + */ + public static function fromRouteMatch(RouteMatchInterface $route_match) { + if ($route_match->getRouteObject()) { + return new static($route_match->getRouteName(), $route_match->getRawParameters()->all()); + } + else { + throw new \InvalidArgumentException('Route required'); + } + } + + /** * Creates a new Url object for a URI that does not have a Drupal route. * * This method is for generating URLs for URIs that do not have Drupal @@ -510,7 +535,11 @@ public function getInternalPath() { if ($this->unrouted) { throw new \UnexpectedValueException('Unrouted URIs do not have internal representations.'); } - return $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters()); + + if (!isset($this->internalPath)) { + $this->internalPath = $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters()); + } + return $this->internalPath; } /** @@ -582,12 +611,13 @@ protected function unroutedUrlAssembler() { * Sets the URL generator. * * @param \Drupal\Core\Routing\UrlGeneratorInterface - * The URL generator. + * (optional) The URL generator, specify NULL to reset it. * * @return $this */ - public function setUrlGenerator(UrlGeneratorInterface $url_generator) { + public function setUrlGenerator(UrlGeneratorInterface $url_generator = NULL) { $this->urlGenerator = $url_generator; + $this->internalPath = NULL; return $this; } diff --git a/core/misc/ajax.js b/core/misc/ajax.js index c432706..82d6044 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -9,7 +9,7 @@ * page. The request returns an array of commands encoded in JSON, which is * then executed to make any changes that are necessary to the page. * - * Drupal uses this file to enhance form elements with #ajax['path'] and + * Drupal uses this file to enhance form elements with #ajax['url'] and * #ajax['wrapper'] properties. If set, this file will automatically be included * to provide Ajax capabilities. */ diff --git a/core/misc/drupal.js b/core/misc/drupal.js index ce13994..dcfad00 100644 --- a/core/misc/drupal.js +++ b/core/misc/drupal.js @@ -326,7 +326,7 @@ if (window.jQuery) { * Returns the URL to a Drupal page. */ Drupal.url = function (path) { - return drupalSettings.path.basePath + drupalSettings.path.scriptPath + drupalSettings.path.pathPrefix + path; + return drupalSettings.path.baseUrl + drupalSettings.path.pathPrefix + path; }; /** diff --git a/core/misc/machine-name.js b/core/misc/machine-name.js index 7ec93f5..02819ac 100644 --- a/core/misc/machine-name.js +++ b/core/misc/machine-name.js @@ -173,7 +173,7 @@ * The transliterated source string. */ transliterate: function (source, settings) { - return $.get(drupalSettings.path.basePath + 'machine_name/transliterate', { + return $.get(Drupal.url('machine_name/transliterate'), { text: source, langcode: drupalSettings.langcode, replace_pattern: settings.replace_pattern, diff --git a/core/modules/field/src/Plugin/views/field/Field.php b/core/modules/field/src/Plugin/views/field/Field.php index 1f78154..95dd59e 100644 --- a/core/modules/field/src/Plugin/views/field/Field.php +++ b/core/modules/field/src/Plugin/views/field/Field.php @@ -469,7 +469,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { '#options' => $formatters, '#default_value' => $this->options['type'], '#ajax' => array( - 'path' => views_ui_build_form_path($form_state), + 'url' => views_ui_build_form_url($form_state), ), '#submit' => array(array($this, 'submitTemporaryForm')), '#executes_submit_callback' => TRUE, diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php index 7a9e05f..34c20e8 100644 --- a/core/modules/file/src/Element/ManagedFile.php +++ b/core/modules/file/src/Element/ManagedFile.php @@ -9,6 +9,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element\FormElement; +use Drupal\Core\Url; use Drupal\file\Entity\File; /** @@ -144,7 +145,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st $element['#tree'] = TRUE; $ajax_settings = [ - 'path' => 'file/ajax', + 'url' => Url::fromRoute('file.ajax_upload'), 'options' => [ 'query' => [ 'element_parents' => implode('/', $element['#array_parents']), @@ -219,7 +220,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st } // Add the upload progress callback. - $element['upload_button']['#ajax']['progress']['path'] = 'file/progress/' . $upload_progress_key; + $element['upload_button']['#ajax']['progress']['url'] = Url::fromRoute('file.ajax_progress'); } // The file upload field itself. diff --git a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php index ab0c6a8..d5a3e3d 100644 --- a/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php +++ b/core/modules/file/src/Plugin/Field/FieldWidget/FileWidget.php @@ -15,6 +15,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\file\Element\ManagedFile; +use Drupal\Core\Url; /** * Plugin implementation of the 'file_generic' widget. @@ -381,7 +382,7 @@ public static function process($element, FormStateInterface $form_state, $form) // file, the entire group of file fields is updated together. if ($element['#cardinality'] != 1) { $parents = array_slice($element['#array_parents'], 0, -1); - $new_path = 'file/ajax'; + $new_url = Url::fromRoute('file.ajax_upload'); $new_options = array( 'query' => array( 'element_parents' => implode('/', $parents), @@ -392,7 +393,7 @@ public static function process($element, FormStateInterface $form_state, $form) $new_wrapper = $field_element['#id'] . '-ajax-wrapper'; foreach (Element::children($element) as $key) { if (isset($element[$key]['#ajax'])) { - $element[$key]['#ajax']['path'] = $new_path; + $element[$key]['#ajax']['url'] = $new_url->setOptions($new_options); $element[$key]['#ajax']['options'] = $new_options; $element[$key]['#ajax']['wrapper'] = $new_wrapper; } diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index 601a149..9399122 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -297,7 +297,7 @@ function shortcut_preprocess_page(&$variables) { // we do not want to display it on "access denied" or "page not found" // pages). if (shortcut_set_edit_access()->isAllowed() && !\Drupal::request()->attributes->has('exception')) { - $link = current_path(); + $link = Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath(); $route_match = \Drupal::routeMatch(); $query = array( diff --git a/core/modules/simpletest/src/InstallerTestBase.php b/core/modules/simpletest/src/InstallerTestBase.php index 92af365..2065075 100644 --- a/core/modules/simpletest/src/InstallerTestBase.php +++ b/core/modules/simpletest/src/InstallerTestBase.php @@ -164,13 +164,6 @@ protected function setUp() { ->set('interface.default', 'test_mail_collector') ->save(); - // When running from run-tests.sh we don't get an empty current path which - // would indicate we're on the home page. - $path = current_path(); - if (empty($path)) { - _current_path('run-tests'); - } - $this->isInstalled = TRUE; } diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php index 9350dfa..93a5047 100644 --- a/core/modules/simpletest/src/WebTestBase.php +++ b/core/modules/simpletest/src/WebTestBase.php @@ -932,12 +932,6 @@ protected function setUp() { // DrupalKernel::prepareLegacyRequest() -> DrupalKernel::boot() but that // appears to be calling a different container. $this->container->get('stream_wrapper_manager')->register(); - // Temporary fix so that when running from run-tests.sh we don't get an - // empty current path which would indicate we're on the home page. - $path = current_path(); - if (empty($path)) { - _current_path('run-tests'); - } } /** diff --git a/core/modules/system/src/Tests/Common/JavaScriptTest.php b/core/modules/system/src/Tests/Common/JavaScriptTest.php index a8a4874..4555917 100644 --- a/core/modules/system/src/Tests/Common/JavaScriptTest.php +++ b/core/modules/system/src/Tests/Common/JavaScriptTest.php @@ -42,6 +42,9 @@ protected function setUp() { // Reset _drupal_add_js() statics before each test. drupal_static_reset('_drupal_add_js'); + + $this->installSchema('system', 'router'); + \Drupal::service('router.builder')->rebuild(); } protected function tearDown() { diff --git a/core/modules/system/src/Tests/Common/PageRenderTest.php b/core/modules/system/src/Tests/Common/PageRenderTest.php index e9eb691..b879f37 100644 --- a/core/modules/system/src/Tests/Common/PageRenderTest.php +++ b/core/modules/system/src/Tests/Common/PageRenderTest.php @@ -21,7 +21,10 @@ class PageRenderTest extends KernelTestBase { * Tests hook_page_attachments() exceptions. */ function testHookPageAttachmentsExceptions() { - $this->enableModules(['common_test']); + $this->enableModules(['common_test', 'system']); + $this->installSchema('system', 'router'); + \Drupal::service('router.builder')->rebuild(); + $this->assertPageRenderHookExceptions('common_test', 'hook_page_attachments'); } @@ -29,7 +32,10 @@ function testHookPageAttachmentsExceptions() { * Tests hook_page_attachments_alter() exceptions. */ function testHookPageAlter() { - $this->enableModules(['common_test']); + $this->enableModules(['common_test', 'system']); + $this->installSchema('system', 'router'); + \Drupal::service('router.builder')->rebuild(); + $this->assertPageRenderHookExceptions('common_test', 'hook_page_attachments_alter'); } diff --git a/core/modules/system/src/Tests/Path/UrlAlterFunctionalTest.php b/core/modules/system/src/Tests/Path/UrlAlterFunctionalTest.php index 7f64ddb..56728c1 100644 --- a/core/modules/system/src/Tests/Path/UrlAlterFunctionalTest.php +++ b/core/modules/system/src/Tests/Path/UrlAlterFunctionalTest.php @@ -75,15 +75,6 @@ function testUrlAlter() { } /** - * Test current_path() and request_path(). - */ - function testCurrentUrlRequestedPath() { - $this->drupalGet('url-alter-test/bar'); - $this->assertRaw('request_path=url-alter-test/bar', 'request_path() returns the requested path.'); - $this->assertRaw('current_path=url-alter-test/foo', 'current_path() returns the internal path.'); - } - - /** * Assert that an outbound path is altered to an expected value. * * @param $original diff --git a/core/modules/system/src/Tests/Theme/TableTest.php b/core/modules/system/src/Tests/Theme/TableTest.php index b781d20..5e83c91 100644 --- a/core/modules/system/src/Tests/Theme/TableTest.php +++ b/core/modules/system/src/Tests/Theme/TableTest.php @@ -24,6 +24,16 @@ class TableTest extends DrupalUnitTestBase { public static $modules = array('system'); /** + * {@inheritdoc} + */ + protected function setUp() { + parent::setUp(); + + $this->installSchema('system', 'router'); + \Drupal::service('router.builder')->rebuild(); + } + + /** * Tableheader.js provides 'sticky' table headers, and is included by default. */ function testThemeTableStickyHeaders() { diff --git a/core/modules/system/src/Tests/Theme/ThemeTest.php b/core/modules/system/src/Tests/Theme/ThemeTest.php index dfe249c..7516b70 100644 --- a/core/modules/system/src/Tests/Theme/ThemeTest.php +++ b/core/modules/system/src/Tests/Theme/ThemeTest.php @@ -10,6 +10,9 @@ use Drupal\Component\Serialization\Json; use Drupal\simpletest\WebTestBase; use Drupal\test_theme\ThemeClass; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\Routing\Route; /** * Tests low-level theme functions. @@ -137,14 +140,16 @@ public function testThemeOnNonHtmlRequest() { * Ensure page-front template suggestion is added when on front page. */ function testFrontPageThemeSuggestion() { - $original_path = _current_path(); - // Set the current path to node because theme_get_suggestions() will query - // it to see if we are on the front page. - \Drupal::config('system.site')->set('page.front', 'node')->save(); - _current_path('node'); - $suggestions = theme_get_suggestions(array('node'), 'page'); + // Set the current route to user.login because theme_get_suggestions() will + // query it to see if we are on the front page. + $request = Request::create('/user/login'); + $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'user.login'); + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, new Route('/user/login')); + \Drupal::requestStack()->push($request); + \Drupal::config('system.site')->set('page.front', 'user/login')->save(); + $suggestions = theme_get_suggestions(array('user', 'login'), 'page'); // Set it back to not annoy the batch runner. - _current_path($original_path); + \Drupal::requestStack()->pop(); $this->assertTrue(in_array('page__front', $suggestions), 'Front page template was suggested.'); } diff --git a/core/modules/system/system.module b/core/modules/system/system.module index 9334a88..41c23c1 100644 --- a/core/modules/system/system.module +++ b/core/modules/system/system.module @@ -241,7 +241,12 @@ function system_hook_info() { * Implements hook_theme_suggestions_HOOK(). */ function system_theme_suggestions_html(array $variables) { - $path_args = explode('/', current_path()); + if (\Drupal::service('path.matcher')->isFrontPage()) { + $path_args = ['']; + } + else { + $path_args = explode('/', Url::fromRoute('')->getInternalPath()); + } return theme_get_suggestions($path_args, 'html'); } @@ -249,7 +254,12 @@ function system_theme_suggestions_html(array $variables) { * Implements hook_theme_suggestions_HOOK(). */ function system_theme_suggestions_page(array $variables) { - $path_args = explode('/', current_path()); + if (\Drupal::service('path.matcher')->isFrontPage()) { + $path_args = ['']; + } + else { + $path_args = explode('/', Url::fromRoute('')->getInternalPath()); + } return theme_get_suggestions($path_args, 'page'); } @@ -625,7 +635,7 @@ function system_page_attachments(array &$page) { $page['#post_render_cache']['\Drupal\system\Controller\SystemController::setLinkActiveClass'] = array( // Collect the current state that determines whether a link is active. array( - 'path' => current_path(), + 'path' => \Drupal::routeMatch()->getRouteName() ? Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath() : '', 'front' => drupal_is_front_page(), 'language' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(), 'query' => \Drupal::request()->query->all(), diff --git a/core/modules/system/tests/modules/url_alter_test/src/Controller/URLAlterTestController.php b/core/modules/system/tests/modules/url_alter_test/src/Controller/URLAlterTestController.php deleted file mode 100644 index da06222..0000000 --- a/core/modules/system/tests/modules/url_alter_test/src/Controller/URLAlterTestController.php +++ /dev/null @@ -1,26 +0,0 @@ -getRouteName(), $route_names)) { $this->keyValueStore->delete($key); } else { diff --git a/core/modules/update/update.module b/core/modules/update/update.module index cdfc40c..a68bff2 100644 --- a/core/modules/update/update.module +++ b/core/modules/update/update.module @@ -120,25 +120,26 @@ function update_page_top() { /** @var \Drupal\Core\Routing\AdminContext $admin_context */ $admin_context = \Drupal::service('router.admin_context'); if ($admin_context->isAdminRoute(\Drupal::request()->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) && \Drupal::currentUser()->hasPermission('administer site configuration')) { - $current_path = current_path(); - switch ($current_path) { + $route_name = \Drupal::routeMatch()->getRouteName(); + switch ($route_name) { // These pages don't need additional nagging. - case 'admin/appearance/update': - case 'admin/appearance/install': - case 'admin/modules/update': - case 'admin/modules/install': - case 'admin/reports/updates': - case 'admin/reports/updates/update': - case 'admin/reports/updates/install': - case 'admin/reports/updates/settings': - case 'admin/reports/status': - case 'admin/update/ready': + // @todo find out the corresponding route for that. + case 'update.theme_update': + case 'system.theme_install': + case 'update.module_update': + case 'update.module_install': + case 'update.status': + case 'update.report_update': + case 'update.report_install': + case 'update.settings': + case 'system.status': + case 'update.confirmation_page': return; // If we are on the appearance or modules list, display a detailed report // of the update status. - case 'admin/appearance': - case 'admin/modules': + case 'system.themes_page': + case 'system.modules_list': $verbose = TRUE; break; diff --git a/core/modules/user/src/Plugin/Block/UserLoginBlock.php b/core/modules/user/src/Plugin/Block/UserLoginBlock.php index a2111cc..350a19e 100644 --- a/core/modules/user/src/Plugin/Block/UserLoginBlock.php +++ b/core/modules/user/src/Plugin/Block/UserLoginBlock.php @@ -7,6 +7,7 @@ namespace Drupal\user\Plugin\Block; +use Drupal\Core\Routing\UrlGeneratorTrait; use Drupal\Core\Url; use Drupal\Core\Session\AccountInterface; use Drupal\Core\Block\BlockBase; @@ -23,6 +24,8 @@ */ class UserLoginBlock extends BlockBase { + use UrlGeneratorTrait; + /** * {@inheritdoc} */ @@ -41,7 +44,7 @@ public function build() { unset($form['pass']['#description']); $form['name']['#size'] = 15; $form['pass']['#size'] = 15; - $form['#action'] = _url(current_path(), array('query' => drupal_get_destination(), 'external' => FALSE)); + $form['#action'] = $this->url('', [], ['query' => drupal_get_destination(), 'external' => FALSE]); // Build action links. $items = array(); if (\Drupal::config('user.settings')->get('register') != USER_REGISTER_ADMINISTRATORS_ONLY) { diff --git a/core/modules/views/js/base.js b/core/modules/views/js/base.js index e7aa903..69fd6a3 100644 --- a/core/modules/views/js/base.js +++ b/core/modules/views/js/base.js @@ -62,7 +62,7 @@ */ Drupal.Views.getPath = function (href) { href = Drupal.Views.pathPortion(href); - href = href.substring(drupalSettings.path.basePath.length, href.length); + href = href.substring(drupalSettings.path.baseUrl, href.length); // 3 is the length of the '?q=' added to the url without clean urls. if (href.substring(0, 3) === '?q=') { href = href.substring(3, href.length); diff --git a/core/modules/views/views.module b/core/modules/views/views.module index 507758c..8d5eeca 100644 --- a/core/modules/views/views.module +++ b/core/modules/views/views.module @@ -15,6 +15,7 @@ use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Render\Element; use Drupal\Core\Routing\RouteMatchInterface; +use Drupal\Core\Url; use Drupal\views\Plugin\Derivative\ViewsLocalTask; use Drupal\Core\Template\AttributeArray; use Drupal\views\ViewExecutable; @@ -61,7 +62,7 @@ function views_views_pre_render($view) { 'view_name' => $view->storage->id(), 'view_display_id' => $view->current_display, 'view_args' => String::checkPlain(implode('/', $view->args)), - 'view_path' => String::checkPlain(current_path()), + 'view_path' => String::checkPlain(Url::fromRoute('')->toString()), 'view_base_path' => $view->getPath(), 'view_dom_id' => $view->dom_id, // To fit multiple views on a page, the programmer may have diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc index d4fb797..c3ab4d9 100644 --- a/core/modules/views/views.theme.inc +++ b/core/modules/views/views.theme.inc @@ -301,10 +301,12 @@ function template_preprocess_views_view_summary(&$variables) { $active_urls = array( // Force system path. \Drupal::url('', [], ['alias' => TRUE]), - _url(current_path(), array('alias' => TRUE)), // force system path + // Force system path. + Url::fromRouteMatch(\Drupal::routeMatch())->setOption('alias', TRUE)->toString(), // Could be an alias. \Drupal::url(''), - _url(current_path()), // could be an alias + // Could be an alias. + Url::fromRouteMatch(\Drupal::routeMatch())->toString(), ); $active_urls = array_combine($active_urls, $active_urls); diff --git a/core/modules/views_ui/admin.inc b/core/modules/views_ui/admin.inc index 61bd9a8..aee3fbb 100644 --- a/core/modules/views_ui/admin.inc +++ b/core/modules/views_ui/admin.inc @@ -8,6 +8,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\Tags; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\ViewExecutable; use Drupal\views\Views; @@ -322,20 +323,31 @@ function views_ui_standard_display_dropdown(&$form, FormStateInterface $form_sta /** * Create the menu path for one of our standard AJAX forms based upon known * information about the form. + * + * @return \Drupal\Core\Url + * The URL object pointing to the form URL. */ -function views_ui_build_form_path(FormStateInterface $form_state) { +function views_ui_build_form_url(FormStateInterface $form_state) { $ajax = !$form_state->get('ajax') ? 'nojs' : 'ajax'; $name = $form_state->get('view')->id(); $form_key = $form_state->get('form_key'); $display_id = $form_state->get('display_id'); - $path = "admin/structure/views/$ajax/$form_key/$name/$display_id"; + + $form_key = str_replace('-', '_', $form_key); + $route_name = "views_ui.form_{$form_key}"; + $route_parameters = [ + 'js' => $ajax, + 'view' => $name, + 'display_id' => $display_id + ]; + $url = Url::fromRoute($route_name, $route_parameters); if ($type = $form_state->get('type')) { - $path .= '/' . $type; + $url->setRouteParameter('type', $type); } if ($id = $form_state->get('id')) { - $path .= '/' . $id; + $url->setRouteParameter('id', $id); } - return $path; + return $url; } /** diff --git a/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php b/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php index bdad87b..3f0ee62 100644 --- a/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php +++ b/core/modules/views_ui/src/Form/Ajax/ConfigHandler.php @@ -8,9 +8,11 @@ namespace Drupal\views_ui\Form\Ajax; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; use Drupal\views\ViewStorageInterface; use Drupal\views\ViewExecutable; use Drupal\views\Views; +use Symfony\Component\HttpFoundation\Request; /** * Provides a form for configuring an item in the Views UI. @@ -51,7 +53,7 @@ public function getFormId() { /** * {@inheritdoc} */ - public function buildForm(array $form, FormStateInterface $form_state) { + public function buildForm(array $form, FormStateInterface $form_state, Request $request = NULL) { $view = $form_state->get('view'); $display_id = $form_state->get('display_id'); $type = $form_state->get('type'); @@ -173,7 +175,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '#submit' => array(array($this, 'remove')), '#limit_validation_errors' => array(array('override')), '#ajax' => array( - 'path' => current_path(), + 'url' => Url::fromRoute(''), ), ); } diff --git a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php index 6d558cd..e265ce7 100644 --- a/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php +++ b/core/modules/views_ui/src/Form/Ajax/ViewsFormBase.php @@ -147,11 +147,11 @@ public function getForm(ViewStorageInterface $view, $display_id, $js) { $form_class = get_class($form_state->getFormObject()); $form_state->setUserInput(array()); - $form_path = views_ui_build_form_path($form_state); + $form_url = views_ui_build_form_url($form_state); if (!$form_state->get('ajax')) { - return new RedirectResponse(_url($form_path, array('absolute' => TRUE))); + return new RedirectResponse($form_url->setAbsolute()->toString()); } - $form_state->set('path', $form_path); + $form_state->set('url', $form_url); $response = views_ajax_form_wrapper($form_class, $form_state); } elseif (!$form_state->get('ajax')) { diff --git a/core/modules/views_ui/src/ViewPreviewForm.php b/core/modules/views_ui/src/ViewPreviewForm.php index bdfb80f..38b6aa5 100644 --- a/core/modules/views_ui/src/ViewPreviewForm.php +++ b/core/modules/views_ui/src/ViewPreviewForm.php @@ -8,6 +8,7 @@ namespace Drupal\views_ui; use Drupal\Core\Form\FormStateInterface; +use Drupal\Core\Url; /** * Form controller for the Views preview form. @@ -87,7 +88,7 @@ protected function actions(array $form, FormStateInterface $form_state) { '#submit' => array('::submitPreview'), '#id' => 'preview-submit', '#ajax' => array( - 'path' => 'admin/structure/views/view/' . $view->id() . '/preview/' . $this->displayID, + 'url' => Url::fromRoute('entity.view.preview_form', ['view' => $view->id(), 'display_id' => $this->displayID]), 'wrapper' => 'views-preview-wrapper', 'event' => 'click', 'progress' => array('type' => 'fullscreen'), diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php index 75c309c..9716499 100644 --- a/core/modules/views_ui/src/ViewUI.php +++ b/core/modules/views_ui/src/ViewUI.php @@ -22,6 +22,9 @@ use Drupal\views\Plugin\views\query\Sql; use Drupal\views\Entity\View; use Drupal\views\ViewStorageInterface; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; +use Symfony\Component\HttpFoundation\ParameterBag; +use Symfony\Component\HttpFoundation\Request; /** * Stores UI related temporary settings. @@ -324,7 +327,7 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form // Views provides its own custom handling of AJAX form submissions. Usually // this happens at the same path, but custom paths may be specified in // $form_state. - $form_path = $form_state->get('path') ?: current_path(); + $form_url = $form_state->get('url') ?: Url::fromRouteMatch(\Drupal::routeMatch()); // Forms that are purely informational set an ok_button flag, so we know not // to create an "Apply" button for them. @@ -341,7 +344,7 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form '#submit' => array(array($this, 'standardSubmit')), '#button_type' => 'primary', '#ajax' => array( - 'path' => $form_path, + 'url' => $form_url, ), ); // Form API button click detection requires the button's #value to be the @@ -369,7 +372,7 @@ public function getStandardButtons(&$form, FormStateInterface $form_state, $form '#submit' => array($cancel_submit), '#validate' => array(), '#ajax' => array( - 'path' => $form_path, + 'path' => $form_url, ), '#limit_validation_errors' => array(), ); @@ -557,7 +560,8 @@ public function endQueryCapture() { public function renderPreview($display_id, $args = array()) { // Save the current path so it can be restored before returning from this function. - $old_q = current_path(); + $request_stack = \Drupal::requestStack(); + $current_request = $request_stack->getCurrentRequest(); // Determine where the query and performance statistics should be output. $config = \Drupal::config('views.settings'); @@ -606,15 +610,22 @@ public function renderPreview($display_id, $args = array()) { } // Make view links come back to preview. - $this->override_path = 'admin/structure/views/view/' . $this->id() . '/preview/' . $display_id; // Also override the current path so we get the pager. - $original_path = current_path(); - $q = _current_path($this->override_path); - if ($args) { - $q .= '/' . implode('/', $args); - _current_path($q); + $request = new Request(); + $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'entity.view.preview_form'); + $request->attributes->set(RouteObjectInterface::ROUTE_OBJECT, \Drupal::service('router.route_provider')->getRouteByName('entity.view.preview_form')); + $request->attributes->set('view', $this->storage); + $request->attributes->set('display_id', $display_id); + $raw_parameters = new ParameterBag(); + $raw_parameters->set('view', $this->id()); + $raw_parameters->set('display_id', $display_id); + $request->attributes->set('_raw_variables', $raw_parameters); + + foreach ($args as $key => $arg) { + $request->attributes->set('arg_' . $key, $arg); } + $request_stack->push($request); // Suppress contextual links of entities within the result set during a // Preview. @@ -642,10 +653,6 @@ public function renderPreview($display_id, $args = array()) { views_ui_contextual_links_suppress_pop(); - // Reset variables. - unset($this->override_path); - _current_path($original_path); - // Prepare the query information and statistics to show either above or // below the view preview. if ($show_info || $show_query || $show_stats) { @@ -757,7 +764,11 @@ public function renderPreview($display_id, $args = array()) { $output .= $preview . drupal_render($table); } - _current_path($old_q); + // Ensure that we just remove an additional request we pushed earlier. + // This could happen if $errors was not empty. + if ($request_stack->getCurrentRequest() != $current_request) { + $request_stack->pop(); + } return $output; } diff --git a/core/tests/Drupal/Tests/Core/Path/PathMatcherTest.php b/core/tests/Drupal/Tests/Core/Path/PathMatcherTest.php index 17e5035..01c6ab9 100644 --- a/core/tests/Drupal/Tests/Core/Path/PathMatcherTest.php +++ b/core/tests/Drupal/Tests/Core/Path/PathMatcherTest.php @@ -37,7 +37,8 @@ protected function setUp() { ), ) ); - $this->pathMatcher = new PathMatcher($config_factory_stub); + $route_match = $this->getMock('Drupal\Core\Routing\RouteMatchInterface'); + $this->pathMatcher = new PathMatcher($config_factory_stub, $route_match); } /** diff --git a/core/tests/Drupal/Tests/Core/UrlTest.php b/core/tests/Drupal/Tests/Core/UrlTest.php index 119eee6..a93226a 100644 --- a/core/tests/Drupal/Tests/Core/UrlTest.php +++ b/core/tests/Drupal/Tests/Core/UrlTest.php @@ -9,12 +9,14 @@ use Drupal\Core\Access\AccessManagerInterface; use Drupal\Core\DependencyInjection\ContainerBuilder; +use Drupal\Core\Routing\RouteMatch; use Drupal\Core\Url; use Drupal\Tests\UnitTestCase; use Symfony\Cmf\Component\Routing\RouteObjectInterface; use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Routing\Exception\ResourceNotFoundException; +use Symfony\Component\Routing\Route; /** * @coversDefaultClass \Drupal\Core\Url @@ -218,6 +220,37 @@ public function testGetUriForExternalUrl() { } /** + * Tests the getInternalPath method(). + * + * @param \Drupal\Core\Url[] $urls + * Array of URL objects. + * + * @covers ::getInternalPath + * + * @depends testUrlFromRequest + */ + public function testGetInternalPath($urls) { + $map = []; + $map[] = ['view.frontpage.page_1', [], '/node']; + $map[] = ['node_view', ['node' => '1'], '/node/1']; + $map[] = ['node_edit', ['node' => '2'], '/node/2/edit']; + + foreach ($urls as $index => $url) { + // Clone the url so that there is no leak of internal state into the + // other ones. + $url = clone $url; + $url_generator = $this->getMock('Drupal\Core\Routing\UrlGeneratorInterface'); + $url_generator->expects($this->once()) + ->method('getPathFromRoute') + ->will($this->returnValueMap($map, $index)); + $url->setUrlGenerator($url_generator); + + $url->getInternalPath(); + $url->getInternalPath(); + } + } + + /** * Tests the toString() method. * * @param \Drupal\Core\Url[] $urls @@ -374,6 +407,17 @@ public function testRenderAccess($access) { } /** + * Tests the fromRouteMatch() method. + */ + public function testFromRouteMatch() { + $route = new Route('/test-route/{foo}'); + $route_match = new RouteMatch('test_route', $route, ['foo' => (object) [1]], ['foo' => 1]); + $url = Url::fromRouteMatch($route_match); + $this->assertSame('test_route', $url->getRouteName()); + $this->assertEquals(['foo' => '1'] , $url->getRouteParameters()); + } + + /** * Creates a mock access manager for the access tests. * * @param bool $access