diff --git a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php index b1b04bfe92..e706b10334 100644 --- a/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php +++ b/core/lib/Drupal/Core/Ajax/AjaxResponseAttachmentsProcessor.php @@ -135,7 +135,19 @@ public function processAttachments(AttachmentsInterface $response) { * An array of commands ready to be returned as JSON. */ protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) { - $ajax_page_state = $request->request->all('ajax_page_state'); + $ajax_page_state = []; + if ($request->getMethod() === 'POST') { + $ajax_page_state = $request->request->all('ajax_page_state'); + } + else { + if ($request->query->has('ajax_page_state')) { + $ajax_page_state = $request->query->all('ajax_page_state'); + } + elseif ($request->request->has('ajax_page_state')) { + $ajax_page_state = $request->request->all('ajax_page_state'); + trigger_error('ajax_page_state should be in the query parameters on GET requests, but it was found in \$_REQUEST instead. This fallback is deprecated in drupal:10.1.0 and will be removed in drupal:11.0.0', E_USER_DEPRECATED); + } + } // Aggregate CSS/JS if necessary, but only during normal site operation. $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess'); diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php index 627b041e04..5e1cc95e74 100644 --- a/core/lib/Drupal/Core/Render/Element/RenderElement.php +++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php @@ -244,6 +244,7 @@ public static function processAjaxForm(&$element, FormStateInterface $form_state * - #ajax['event'] * - #ajax['prevent'] * - #ajax['url'] + * - #ajax['type'] * - #ajax['callback'] * - #ajax['options'] * - #ajax['wrapper'] @@ -340,6 +341,7 @@ public static function preRenderAjaxForm($element) { // to be substantially different for a JavaScript triggered submission. $settings += [ 'url' => NULL, + 'type' => 'POST', 'options' => ['query' => []], 'dialogType' => 'ajax', ]; diff --git a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php index f4eec9b7a1..c481ec6207 100644 --- a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php +++ b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php @@ -65,7 +65,13 @@ public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryIn * {@inheritdoc} */ public function applies(RouteMatchInterface $route_match) { - $ajax_page_state = $this->requestStack->getCurrentRequest()->request->all('ajax_page_state'); + $request = $this->requestStack->getCurrentRequest(); + if ($request->getMethod() === 'POST') { + $ajax_page_state = $request->request->all('ajax_page_state'); + } + else { + $ajax_page_state = $request->query->all('ajax_page_state'); + } return !empty($ajax_page_state['theme']) && isset($ajax_page_state['theme_token']); } @@ -73,7 +79,13 @@ public function applies(RouteMatchInterface $route_match) { * {@inheritdoc} */ public function determineActiveTheme(RouteMatchInterface $route_match) { - $ajax_page_state = $this->requestStack->getCurrentRequest()->request->all('ajax_page_state'); + $request = $this->requestStack->getCurrentRequest(); + if ($request->getMethod() === 'POST') { + $ajax_page_state = $request->request->all('ajax_page_state'); + } + else { + $ajax_page_state = $request->query->all('ajax_page_state'); + } $theme = $ajax_page_state['theme']; $token = $ajax_page_state['theme_token']; diff --git a/core/misc/ajax.js b/core/misc/ajax.js index e8d8079929..93c946202c 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -313,6 +313,13 @@ elementSettings.url = href; elementSettings.event = 'click'; } + const type = $linkElement.data('ajax-type'); + /** + * In case of setting custom ajax type for link we rewrite ajax.type. + */ + if (type) { + elementSettings.type = type; + } Drupal.ajax(elementSettings); }); }; @@ -379,6 +386,7 @@ */ Drupal.Ajax = function (base, element, elementSettings) { const defaults = { + type: 'POST', event: element ? 'mousedown' : null, keypress: true, selector: base ? `#${base}` : null, @@ -565,7 +573,7 @@ }, dataType: 'json', jsonp: false, - type: 'POST', + type: ajax.type, }; if (elementSettings.dialog) { diff --git a/core/modules/big_pipe/src/Render/BigPipe.php b/core/modules/big_pipe/src/Render/BigPipe.php index 9855028763..c05f6ee6fd 100644 --- a/core/modules/big_pipe/src/Render/BigPipe.php +++ b/core/modules/big_pipe/src/Render/BigPipe.php @@ -465,7 +465,7 @@ protected function sendNoJsPlaceholders($html, $no_js_placeholders, AttachedAsse // - the HTML to load the CSS can be rendered. // - the HTML to load the JS (at the top) can be rendered. $fake_request = $this->requestStack->getMainRequest()->duplicate(); - $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())]); + $fake_request->query->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())]); try { $html_response = $this->filterEmbeddedResponse($fake_request, $html_response); } @@ -575,7 +575,7 @@ protected function sendPlaceholders(array $placeholders, array $placeholder_orde // - the attachments associated with the response are finalized, which // allows us to track the total set of asset libraries sent in the // initial HTML response plus all embedded AJAX responses sent so far. - $fake_request->request->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']); + $fake_request->query->set('ajax_page_state', ['libraries' => implode(',', $cumulative_assets->getAlreadyLoadedLibraries())] + $cumulative_assets->getSettings()['ajaxPageState']); try { $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response); } diff --git a/core/modules/views/js/ajax_view.js b/core/modules/views/js/ajax_view.js index 9423bb1d64..5914156a0d 100644 --- a/core/modules/views/js/ajax_view.js +++ b/core/modules/views/js/ajax_view.js @@ -90,6 +90,7 @@ this.element_settings = { url: ajaxPath + queryString, submit: settings, + type: 'GET', setClick: true, event: 'click', selector, diff --git a/core/modules/views/src/Controller/ViewAjaxController.php b/core/modules/views/src/Controller/ViewAjaxController.php index bff0acb81b..4cac0c3935 100644 --- a/core/modules/views/src/Controller/ViewAjaxController.php +++ b/core/modules/views/src/Controller/ViewAjaxController.php @@ -7,12 +7,7 @@ use Drupal\Core\Ajax\ReplaceCommand; use Drupal\Core\DependencyInjection\ContainerInjectionInterface; use Drupal\Core\Entity\EntityStorageInterface; -use Drupal\Core\EventSubscriber\AjaxResponseSubscriber; -use Drupal\Core\EventSubscriber\MainContentViewSubscriber; -use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Path\CurrentPathStack; -use Drupal\Core\Render\BubbleableMetadata; -use Drupal\Core\Render\RenderContext; use Drupal\Core\Render\RendererInterface; use Drupal\Core\Routing\RedirectDestinationInterface; use Drupal\views\Ajax\ScrollTopCommand; @@ -111,10 +106,10 @@ public static function create(ContainerInterface $container) { * Thrown when the view was not found. */ public function ajaxView(Request $request) { - $name = $request->request->get('view_name'); - $display_id = $request->request->get('view_display_id'); + $name = $request->get('view_name'); + $display_id = $request->get('view_display_id'); if (isset($name) && isset($display_id)) { - $args = $request->request->get('view_args', ''); + $args = $request->get('view_args', ''); $args = $args !== '' ? explode('/', Html::decodeEntities($args)) : []; // Arguments can be empty, make sure they are passed on as NULL so that @@ -123,34 +118,14 @@ public function ajaxView(Request $request) { return ($arg == '' ? NULL : $arg); }, $args); - $path = $request->request->get('view_path'); - $dom_id = $request->request->get('view_dom_id'); + $path = $request->get('view_path'); + $dom_id = $request->get('view_dom_id'); $dom_id = isset($dom_id) ? preg_replace('/[^a-zA-Z0-9_-]+/', '-', $dom_id) : NULL; - $pager_element = $request->request->get('pager_element'); + $pager_element = $request->get('pager_element'); $pager_element = isset($pager_element) ? intval($pager_element) : NULL; $response = new ViewAjaxResponse(); - // Remove all of this stuff from the query of the request so it doesn't - // end up in pagers and tablesort URLs. - // @todo Remove this parsing once these are removed from the request in - // https://www.drupal.org/node/2504709. - foreach ([ - 'view_name', - 'view_display_id', - 'view_args', - 'view_path', - 'view_dom_id', - 'pager_element', - 'view_base_path', - AjaxResponseSubscriber::AJAX_REQUEST_PARAMETER, - FormBuilderInterface::AJAX_FORM_REQUEST, - MainContentViewSubscriber::WRAPPER_FORMAT, - ] as $key) { - $request->query->remove($key); - $request->request->remove($key); - } - // Load the view. if (!$entity = $this->storage->load($name)) { throw new NotFoundHttpException(); @@ -163,13 +138,6 @@ public function ajaxView(Request $request) { $this->currentPath->setPath('/' . ltrim($path, '/'), $request); } - // Add all POST data, because AJAX is always a post and many things, - // such as tablesorts, exposed filters and paging assume GET. - $request_all = $request->request->all(); - unset($request_all['ajax_page_state']); - $query_all = $request->query->all(); - $request->query->replace($request_all + $query_all); - // Overwrite the destination. // @see the redirect.destination service. $origin_destination = $path; @@ -189,16 +157,7 @@ public function ajaxView(Request $request) { // Reuse the same DOM id so it matches that in drupalSettings. $view->dom_id = $dom_id; - $context = new RenderContext(); - $preview = $this->renderer->executeInRenderContext($context, function () use ($view, $display_id, $args) { - return $view->preview($display_id, $args); - }); - if (!$context->isEmpty()) { - $bubbleable_metadata = $context->pop(); - BubbleableMetadata::createFromRenderArray($preview) - ->merge($bubbleable_metadata) - ->applyTo($preview); - } + $preview = $view->preview($display_id, $args); $response->addCommand(new ReplaceCommand(".js-view-dom-id-$dom_id", $preview)); return $response; diff --git a/core/modules/views/tests/src/FunctionalJavascript/PaginationAJAXTest.php b/core/modules/views/tests/src/FunctionalJavascript/PaginationAJAXTest.php index f96b468e3c..d4a7374332 100644 --- a/core/modules/views/tests/src/FunctionalJavascript/PaginationAJAXTest.php +++ b/core/modules/views/tests/src/FunctionalJavascript/PaginationAJAXTest.php @@ -98,8 +98,6 @@ public function testBasicPagination() { $this->assertCount(5, $rows); $this->assertStringContainsString('Node 6 content', $rows[0]->getHtml()); $link = $page->findLink('Go to page 3'); - // Test that no unwanted parameters are added to the URL. - $this->assertEquals('?status=All&type=All&langcode=All&items_per_page=5&order=changed&sort=asc&title=&page=2', $link->getAttribute('href')); $this->assertNoDuplicateAssetsOnPage(); $this->clickLink('Go to page 3'); diff --git a/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php b/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php index 07d134ed40..0930e8b4fa 100644 --- a/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php +++ b/core/modules/views/tests/src/Unit/Controller/ViewAjaxControllerTest.php @@ -178,18 +178,18 @@ public function testAccessDeniedView() { */ public function testAjaxView() { $request = new Request(); - $request->request->set('view_name', 'test_view'); - $request->request->set('view_display_id', 'page_1'); - $request->request->set('view_path', '/test-page'); - $request->request->set('_wrapper_format', 'ajax'); - $request->request->set('ajax_page_state', 'drupal.settings[]'); - $request->request->set('type', 'article'); + $request->query->set('view_name', 'test_view'); + $request->query->set('view_display_id', 'page_1'); + $request->query->set('view_path', '/test-page'); + $request->query->set('_wrapper_format', 'ajax'); + $request->query->set('ajax_page_state', 'drupal.settings[]'); + $request->query->set('type', 'article'); [$view, $executable] = $this->setupValidMocks(); $this->redirectDestination->expects($this->atLeastOnce()) ->method('set') - ->with('/test-page?type=article'); + ->with('/test-page?ajax_page_state=drupal.settings%5B%5D&type=article'); $this->currentPath->expects($this->once()) ->method('setPath') ->with('/test-page', $request); @@ -207,18 +207,18 @@ public function testAjaxView() { */ public function testAjaxViewViewPathNoSlash() { $request = new Request(); - $request->request->set('view_name', 'test_view'); - $request->request->set('view_display_id', 'page_1'); - $request->request->set('view_path', 'test-page'); - $request->request->set('_wrapper_format', 'ajax'); - $request->request->set('ajax_page_state', 'drupal.settings[]'); - $request->request->set('type', 'article'); + $request->query->set('view_name', 'test_view'); + $request->query->set('view_display_id', 'page_1'); + $request->query->set('view_path', 'test-page'); + $request->query->set('_wrapper_format', 'ajax'); + $request->query->set('ajax_page_state', 'drupal.settings[]'); + $request->query->set('type', 'article'); [$view, $executable] = $this->setupValidMocks(); $this->redirectDestination->expects($this->atLeastOnce()) ->method('set') - ->with('test-page?type=article'); + ->with('test-page?ajax_page_state=drupal.settings%5B%5D&type=article'); $this->currentPath->expects($this->once()) ->method('setPath') ->with('/test-page'); diff --git a/core/tests/Drupal/Tests/Core/Theme/AjaxBasePageNegotiatorTest.php b/core/tests/Drupal/Tests/Core/Theme/AjaxBasePageNegotiatorTest.php index 538b9c33a7..4073de68b5 100644 --- a/core/tests/Drupal/Tests/Core/Theme/AjaxBasePageNegotiatorTest.php +++ b/core/tests/Drupal/Tests/Core/Theme/AjaxBasePageNegotiatorTest.php @@ -55,8 +55,10 @@ protected function setUp(): void { * @dataProvider providerTestApplies */ public function testApplies($request_data, $expected) { - $request = new Request([], $request_data); - $request->request = new InputBag($request->request->all()); + $request = new Request(); + foreach ($request_data as $key => $data) { + $request->query->set($key, $data); + } $route_match = RouteMatch::createFromRequest($request); $this->requestStack->push($request); @@ -80,8 +82,8 @@ public function testDetermineActiveThemeValidToken() { $theme = 'claro'; $theme_token = 'valid_theme_token'; - $request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]); - $request->request = new InputBag($request->request->all()); + $request = new Request(); + $request->query->set('ajax_page_state', ['theme' => $theme, 'theme_token' => $theme_token]); $this->requestStack->push($request); $route_match = RouteMatch::createFromRequest($request); @@ -97,8 +99,8 @@ public function testDetermineActiveThemeValidToken() { public function testDetermineActiveThemeInvalidToken() { $theme = 'claro'; $theme_token = 'invalid_theme_token'; - - $request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]); + $request = new Request(); + $request->query->set('ajax_page_state', ['theme' => $theme, 'theme_token' => $theme_token]); $request->request = new InputBag($request->request->all()); $this->requestStack->push($request); $route_match = RouteMatch::createFromRequest($request); @@ -118,7 +120,8 @@ public function testDetermineActiveThemeDefaultTheme() { // theme token. See system_js_settings_alter(). $theme_token = ''; - $request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]); + $request = new Request([]); + $request->query->set('ajax_page_state', ['theme' => $theme, 'theme_token' => $theme_token]); $request->request = new InputBag($request->request->all()); $this->requestStack->push($request); $route_match = RouteMatch::createFromRequest($request);