Issue #956186 by catch, dpolant, SteffenR, yhahn, lauriii, nod_, sokrplare, rwaery.11, Supreetam09, effulgentsia, geek-merlin: Allow AJAX to use GET requests

merge-requests/3445/merge
Lauri Eskola 2023-04-07 12:48:30 +03:00
parent d4b5939879
commit 29763eaaf5
No known key found for this signature in database
GPG Key ID: 382FC0F5B0DF53F8
11 changed files with 52 additions and 50 deletions

View File

@ -132,7 +132,7 @@ class AjaxResponseAttachmentsProcessor implements AttachmentsResponseProcessorIn
* An array of commands ready to be returned as JSON. * An array of commands ready to be returned as JSON.
*/ */
protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) { protected function buildAttachmentsCommands(AjaxResponse $response, Request $request) {
$ajax_page_state = $request->request->all('ajax_page_state'); $ajax_page_state = $request->get('ajax_page_state');
// Aggregate CSS/JS if necessary, but only during normal site operation. // Aggregate CSS/JS if necessary, but only during normal site operation.
$optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess'); $optimize_css = !defined('MAINTENANCE_MODE') && $this->config->get('css.preprocess');

View File

@ -244,6 +244,7 @@ abstract class RenderElement extends PluginBase implements ElementInterface {
* - #ajax['event'] * - #ajax['event']
* - #ajax['prevent'] * - #ajax['prevent']
* - #ajax['url'] * - #ajax['url']
* - #ajax['type']
* - #ajax['callback'] * - #ajax['callback']
* - #ajax['options'] * - #ajax['options']
* - #ajax['wrapper'] * - #ajax['wrapper']
@ -340,6 +341,7 @@ abstract class RenderElement extends PluginBase implements ElementInterface {
// to be substantially different for a JavaScript triggered submission. // to be substantially different for a JavaScript triggered submission.
$settings += [ $settings += [
'url' => NULL, 'url' => NULL,
'type' => 'POST',
'options' => ['query' => []], 'options' => ['query' => []],
'dialogType' => 'ajax', 'dialogType' => 'ajax',
]; ];

View File

@ -65,7 +65,7 @@ class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function applies(RouteMatchInterface $route_match) { public function applies(RouteMatchInterface $route_match) {
$ajax_page_state = $this->requestStack->getCurrentRequest()->request->all('ajax_page_state'); $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state');
return !empty($ajax_page_state['theme']) && isset($ajax_page_state['theme_token']); return !empty($ajax_page_state['theme']) && isset($ajax_page_state['theme_token']);
} }
@ -73,7 +73,7 @@ class AjaxBasePageNegotiator implements ThemeNegotiatorInterface {
* {@inheritdoc} * {@inheritdoc}
*/ */
public function determineActiveTheme(RouteMatchInterface $route_match) { public function determineActiveTheme(RouteMatchInterface $route_match) {
$ajax_page_state = $this->requestStack->getCurrentRequest()->request->all('ajax_page_state'); $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state');
$theme = $ajax_page_state['theme']; $theme = $ajax_page_state['theme'];
$token = $ajax_page_state['theme_token']; $token = $ajax_page_state['theme_token'];

View File

@ -325,6 +325,13 @@
elementSettings.url = href; elementSettings.url = href;
elementSettings.event = 'click'; 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); Drupal.ajax(elementSettings);
}); });
}; };
@ -391,6 +398,7 @@
*/ */
Drupal.Ajax = function (base, element, elementSettings) { Drupal.Ajax = function (base, element, elementSettings) {
const defaults = { const defaults = {
type: 'POST',
event: element ? 'mousedown' : null, event: element ? 'mousedown' : null,
keypress: true, keypress: true,
selector: base ? `#${base}` : null, selector: base ? `#${base}` : null,
@ -591,7 +599,7 @@
}, },
dataType: 'json', dataType: 'json',
jsonp: false, jsonp: false,
type: 'POST', type: ajax.type,
}; };
if (elementSettings.dialog) { if (elementSettings.dialog) {

View File

@ -465,7 +465,7 @@ class BigPipe {
// - the HTML to load the CSS can be rendered. // - the HTML to load the CSS can be rendered.
// - the HTML to load the JS (at the top) can be rendered. // - the HTML to load the JS (at the top) can be rendered.
$fake_request = $this->requestStack->getMainRequest()->duplicate(); $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 { try {
$html_response = $this->filterEmbeddedResponse($fake_request, $html_response); $html_response = $this->filterEmbeddedResponse($fake_request, $html_response);
} }
@ -575,7 +575,7 @@ class BigPipe {
// - the attachments associated with the response are finalized, which // - the attachments associated with the response are finalized, which
// allows us to track the total set of asset libraries sent in the // allows us to track the total set of asset libraries sent in the
// initial HTML response plus all embedded AJAX responses sent so far. // 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 { try {
$ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response); $ajax_response = $this->filterEmbeddedResponse($fake_request, $ajax_response);
} }

View File

@ -90,6 +90,7 @@
this.element_settings = { this.element_settings = {
url: ajaxPath + queryString, url: ajaxPath + queryString,
submit: settings, submit: settings,
type: 'GET',
setClick: true, setClick: true,
event: 'click', event: 'click',
selector, selector,
@ -127,6 +128,7 @@
const selfSettings = $.extend({}, this.element_settings, { const selfSettings = $.extend({}, this.element_settings, {
event: 'RefreshView', event: 'RefreshView',
base: this.selector, base: this.selector,
type: 'GET',
element: this.$view.get(0), element: this.$view.get(0),
}); });
this.refreshViewAjax = Drupal.ajax(selfSettings); this.refreshViewAjax = Drupal.ajax(selfSettings);
@ -201,6 +203,7 @@
submit: viewData, submit: viewData,
base: false, base: false,
element: link, element: link,
type: 'GET',
}); });
this.pagerAjax = Drupal.ajax(selfSettings); this.pagerAjax = Drupal.ajax(selfSettings);
}; };

View File

@ -12,8 +12,6 @@ use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber; use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBuilderInterface; use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\Path\CurrentPathStack; use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface; use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RedirectDestinationInterface; use Drupal\Core\Routing\RedirectDestinationInterface;
use Drupal\Core\Ajax\ScrollTopCommand; use Drupal\Core\Ajax\ScrollTopCommand;
@ -112,10 +110,10 @@ class ViewAjaxController implements ContainerInjectionInterface {
* Thrown when the view was not found. * Thrown when the view was not found.
*/ */
public function ajaxView(Request $request) { public function ajaxView(Request $request) {
$name = $request->request->get('view_name'); $name = $request->get('view_name');
$display_id = $request->request->get('view_display_id'); $display_id = $request->get('view_display_id');
if (isset($name) && isset($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)) : []; $args = $args !== '' ? explode('/', Html::decodeEntities($args)) : [];
// Arguments can be empty, make sure they are passed on as NULL so that // Arguments can be empty, make sure they are passed on as NULL so that
@ -124,10 +122,10 @@ class ViewAjaxController implements ContainerInjectionInterface {
return ($arg == '' ? NULL : $arg); return ($arg == '' ? NULL : $arg);
}, $args); }, $args);
$path = $request->request->get('view_path'); $path = $request->get('view_path');
$dom_id = $request->request->get('view_dom_id'); $dom_id = $request->get('view_dom_id');
$dom_id = isset($dom_id) ? preg_replace('/[^a-zA-Z0-9_-]+/', '-', $dom_id) : NULL; $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; $pager_element = isset($pager_element) ? intval($pager_element) : NULL;
$response = new ViewAjaxResponse(); $response = new ViewAjaxResponse();
@ -164,10 +162,9 @@ class ViewAjaxController implements ContainerInjectionInterface {
$this->currentPath->setPath('/' . ltrim($path, '/'), $request); $this->currentPath->setPath('/' . ltrim($path, '/'), $request);
} }
// Add all POST data, because AJAX is always a post and many things, // Add all POST data, because AJAX is sometimes a POST and many things,
// such as tablesorts, exposed filters and paging assume GET. // such as tablesorts, exposed filters and paging assume GET.
$request_all = $request->request->all(); $request_all = $request->request->all();
unset($request_all['ajax_page_state']);
$query_all = $request->query->all(); $query_all = $request->query->all();
$request->query->replace($request_all + $query_all); $request->query->replace($request_all + $query_all);
@ -190,16 +187,7 @@ class ViewAjaxController implements ContainerInjectionInterface {
// Reuse the same DOM id so it matches that in drupalSettings. // Reuse the same DOM id so it matches that in drupalSettings.
$view->dom_id = $dom_id; $view->dom_id = $dom_id;
$context = new RenderContext(); $preview = $view->preview($display_id, $args);
$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);
}
$response->addCommand(new ReplaceCommand(".js-view-dom-id-$dom_id", $preview)); $response->addCommand(new ReplaceCommand(".js-view-dom-id-$dom_id", $preview));
$response->addCommand(new PrependCommand(".js-view-dom-id-$dom_id", ['#type' => 'status_messages'])); $response->addCommand(new PrependCommand(".js-view-dom-id-$dom_id", ['#type' => 'status_messages']));

View File

@ -157,7 +157,7 @@ class ViewsForm implements FormInterface, ContainerInjectionInterface {
$form = []; $form = [];
$query = $this->requestStack->getCurrentRequest()->query->all(); $query = $this->requestStack->getCurrentRequest()->query->all();
$query = UrlHelper::filterQueryParameters($query, [], ''); $query = UrlHelper::filterQueryParameters($query, ['_wrapper_format'], '');
$options = ['query' => $query]; $options = ['query' => $query];
$form['#action'] = $view->hasUrl() ? $view->getUrl()->setOptions($options)->toString() : Url::fromRoute('<current>')->setOptions($options)->toString(); $form['#action'] = $view->hasUrl() ? $view->getUrl()->setOptions($options)->toString() : Url::fromRoute('<current>')->setOptions($options)->toString();

View File

@ -98,8 +98,6 @@ class PaginationAJAXTest extends WebDriverTestBase {
$this->assertCount(5, $rows); $this->assertCount(5, $rows);
$this->assertStringContainsString('Node 6 content', $rows[0]->getHtml()); $this->assertStringContainsString('Node 6 content', $rows[0]->getHtml());
$link = $page->findLink('Go to page 3'); $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&page=2', $link->getAttribute('href'));
$this->assertNoDuplicateAssetsOnPage(); $this->assertNoDuplicateAssetsOnPage();
$this->clickLink('Go to page 3'); $this->clickLink('Go to page 3');

View File

@ -182,18 +182,18 @@ class ViewAjaxControllerTest extends UnitTestCase {
*/ */
public function testAjaxView() { public function testAjaxView() {
$request = new Request(); $request = new Request();
$request->request->set('view_name', 'test_view'); $request->query->set('view_name', 'test_view');
$request->request->set('view_display_id', 'page_1'); $request->query->set('view_display_id', 'page_1');
$request->request->set('view_path', '/test-page'); $request->query->set('view_path', '/test-page');
$request->request->set('_wrapper_format', 'ajax'); $request->query->set('_wrapper_format', 'ajax');
$request->request->set('ajax_page_state', 'drupal.settings[]'); $request->query->set('ajax_page_state', 'drupal.settings[]');
$request->request->set('type', 'article'); $request->query->set('type', 'article');
[$view, $executable] = $this->setupValidMocks(); [$view, $executable] = $this->setupValidMocks();
$this->redirectDestination->expects($this->atLeastOnce()) $this->redirectDestination->expects($this->atLeastOnce())
->method('set') ->method('set')
->with('/test-page?type=article'); ->with('/test-page?ajax_page_state=drupal.settings%5B%5D&type=article');
$this->currentPath->expects($this->once()) $this->currentPath->expects($this->once())
->method('setPath') ->method('setPath')
->with('/test-page', $request); ->with('/test-page', $request);
@ -211,18 +211,18 @@ class ViewAjaxControllerTest extends UnitTestCase {
*/ */
public function testAjaxViewViewPathNoSlash() { public function testAjaxViewViewPathNoSlash() {
$request = new Request(); $request = new Request();
$request->request->set('view_name', 'test_view'); $request->query->set('view_name', 'test_view');
$request->request->set('view_display_id', 'page_1'); $request->query->set('view_display_id', 'page_1');
$request->request->set('view_path', 'test-page'); $request->query->set('view_path', 'test-page');
$request->request->set('_wrapper_format', 'ajax'); $request->query->set('_wrapper_format', 'ajax');
$request->request->set('ajax_page_state', 'drupal.settings[]'); $request->query->set('ajax_page_state', 'drupal.settings[]');
$request->request->set('type', 'article'); $request->query->set('type', 'article');
[$view, $executable] = $this->setupValidMocks(); [$view, $executable] = $this->setupValidMocks();
$this->redirectDestination->expects($this->atLeastOnce()) $this->redirectDestination->expects($this->atLeastOnce())
->method('set') ->method('set')
->with('test-page?type=article'); ->with('test-page?ajax_page_state=drupal.settings%5B%5D&type=article');
$this->currentPath->expects($this->once()) $this->currentPath->expects($this->once())
->method('setPath') ->method('setPath')
->with('/test-page'); ->with('/test-page');

View File

@ -55,8 +55,10 @@ class AjaxBasePageNegotiatorTest extends UnitTestCase {
* @dataProvider providerTestApplies * @dataProvider providerTestApplies
*/ */
public function testApplies($request_data, $expected) { public function testApplies($request_data, $expected) {
$request = new Request([], $request_data); $request = new Request();
$request->request = new InputBag($request->request->all()); foreach ($request_data as $key => $data) {
$request->query->set($key, $data);
}
$route_match = RouteMatch::createFromRequest($request); $route_match = RouteMatch::createFromRequest($request);
$this->requestStack->push($request); $this->requestStack->push($request);
@ -80,8 +82,8 @@ class AjaxBasePageNegotiatorTest extends UnitTestCase {
$theme = 'claro'; $theme = 'claro';
$theme_token = 'valid_theme_token'; $theme_token = 'valid_theme_token';
$request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]); $request = new Request();
$request->request = new InputBag($request->request->all()); $request->query->set('ajax_page_state', ['theme' => $theme, 'theme_token' => $theme_token]);
$this->requestStack->push($request); $this->requestStack->push($request);
$route_match = RouteMatch::createFromRequest($request); $route_match = RouteMatch::createFromRequest($request);
@ -97,8 +99,8 @@ class AjaxBasePageNegotiatorTest extends UnitTestCase {
public function testDetermineActiveThemeInvalidToken() { public function testDetermineActiveThemeInvalidToken() {
$theme = 'claro'; $theme = 'claro';
$theme_token = 'invalid_theme_token'; $theme_token = 'invalid_theme_token';
$request = new Request();
$request = new Request([], ['ajax_page_state' => ['theme' => $theme, 'theme_token' => $theme_token]]); $request->query->set('ajax_page_state', ['theme' => $theme, 'theme_token' => $theme_token]);
$request->request = new InputBag($request->request->all()); $request->request = new InputBag($request->request->all());
$this->requestStack->push($request); $this->requestStack->push($request);
$route_match = RouteMatch::createFromRequest($request); $route_match = RouteMatch::createFromRequest($request);
@ -118,7 +120,8 @@ class AjaxBasePageNegotiatorTest extends UnitTestCase {
// theme token. See system_js_settings_alter(). // theme token. See system_js_settings_alter().
$theme_token = ''; $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()); $request->request = new InputBag($request->request->all());
$this->requestStack->push($request); $this->requestStack->push($request);
$route_match = RouteMatch::createFromRequest($request); $route_match = RouteMatch::createFromRequest($request);