Verified Commit b6b8e0f4 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #2820347 by acbramley, rsmylski, Lendude, jibran, Dinesh18, xaqrox, xjm,...

Issue #2820347 by acbramley, rsmylski, Lendude, jibran, Dinesh18, xaqrox, xjm, fenstrat, esolitos, maximpodorov, tameeshb, plach, catch, alexpott, gambry, larowlan: Exposed filter reset redirects user to 404 page on AJAX view when placed as a block
parent ece5db40
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -160,7 +160,7 @@ public function ajaxView(Request $request) {
        $response->setView($view);
        // Fix the current path for paging.
        if (!empty($path)) {
          $this->currentPath->setPath('/' . $path, $request);
          $this->currentPath->setPath('/' . ltrim($path, '/'), $request);
        }

        // Add all POST data, because AJAX is always a post and many things,
+36 −3
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@
use Drupal\Component\Utility\Html;
use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Path\CurrentPathStack;
use Drupal\Core\Render\Element\Checkboxes;
use Drupal\Core\Url;
use Drupal\views\ExposedFormCache;
@@ -24,21 +25,39 @@ class ViewsExposedForm extends FormBase {
   */
  protected $exposedFormCache;


  /**
   * The current path stack.
   *
   * @var \Drupal\Core\Path\CurrentPathStack
   */
  protected $currentPathStack;

  /**
   * Constructs a new ViewsExposedForm
   *
   * @param \Drupal\views\ExposedFormCache $exposed_form_cache
   *   The exposed form cache.
   * @param \Drupal\Core\Path\CurrentPathStack $current_path_stack
   *   The current path stack.
   */
  public function __construct(ExposedFormCache $exposed_form_cache) {
  public function __construct(ExposedFormCache $exposed_form_cache, CurrentPathStack $current_path_stack = NULL) {
    $this->exposedFormCache = $exposed_form_cache;
    if ($current_path_stack === NULL) {
      @trigger_error('The path.current service must be passed to ViewsExposedForm::__construct(), it is required before Drupal 9.0.0. See https://www.drupal.org/node/3066604', E_USER_DEPRECATED);
      $current_path_stack = \Drupal::service('path.current');
    }
    $this->currentPathStack = $current_path_stack;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static($container->get('views.exposed_form_cache'));
    return new static(
      $container->get('views.exposed_form_cache'),
      $container->get('path.current')
    );
  }

  /**
@@ -113,7 +132,21 @@ public function buildForm(array $form, FormStateInterface $form_state) {
      '#id' => Html::getUniqueId('edit-submit-' . $view->storage->id()),
    ];

    $form['#action'] = $view->hasUrl() ? $view->getUrl()->toString() : Url::fromRoute('<current>')->toString();
    if (!$view->hasUrl()) {
      // On any non views.ajax route, use the current route for the form action.
      if ($this->getRouteMatch()->getRouteName() !== 'views.ajax') {
        $form_action = Url::fromRoute('<current>')->toString();
      }
      else {
        // On the views.ajax route, set the action to the page we were on.
        $form_action = Url::fromUserInput($this->currentPathStack->getPath())->toString();
      }
    }
    else {
      $form_action = $view->getUrl()->toString();
    }

    $form['#action'] = $form_action;
    $form['#theme'] = $view->buildThemeFunctions('views_exposed_form');
    $form['#id'] = Html::cleanCssIdentifier('views_exposed_form-' . $view->storage->id() . '-' . $display['id']);

+80 −0
Original line number Diff line number Diff line
langcode: en
status: true
dependencies:
  config:
    - core.entity_view_mode.node.teaser
  module:
    - node
id: test_block_exposed_ajax
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
display:
  default:
    display_options:
      access:
        type: none
      cache:
        type: tag
      exposed_form:
        options:
          submit_button: Apply
          reset_button: true
        type: basic
      filters:
        type:
          expose:
            identifier: type
            label: 'Content: Type'
            operator_id: type_op
            reduce: false
          exposed: true
          field: type
          id: type
          table: node_field_data
          plugin_id: in_operator
          entity_type: node
          entity_field: type
      pager:
        type: full
      query:
        options:
          query_comment: ''
        type: views_query
      style:
        type: default
      row:
        type: 'entity:node'
      display_extenders: {  }
      use_ajax: true
    display_plugin: default
    display_title: Master
    id: default
    position: 0
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_interface'
        - url
        - url.query_args
        - 'user.node_grants:view'
      tags: {  }
  block_1:
    display_plugin: block
    id: block_1
    display_title: Block
    position: 2
    display_options:
      display_extenders: {  }
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_interface'
        - url
        - url.query_args
        - 'user.node_grants:view'
      tags: {  }
+96 −0
Original line number Diff line number Diff line
langcode: en
status: true
dependencies:
  config:
    - core.entity_view_mode.node.teaser
  module:
    - node
id: test_block_exposed_ajax_with_page
label: ''
module: views
description: ''
tag: ''
base_table: node_field_data
base_field: nid
core: '8'
display:
  default:
    display_options:
      access:
        type: none
      cache:
        type: tag
      exposed_form:
        options:
          submit_button: Apply
          reset_button: true
        type: basic
      filters:
        type:
          expose:
            identifier: type
            label: 'Content: Type'
            operator_id: type_op
            reduce: false
          exposed: true
          field: type
          id: type
          table: node_field_data
          plugin_id: in_operator
          entity_type: node
          entity_field: type
      pager:
        type: full
      query:
        options:
          query_comment: ''
        type: views_query
      style:
        type: default
      row:
        type: 'entity:node'
      display_extenders: {  }
      use_ajax: true
    display_plugin: default
    display_title: Master
    id: default
    position: 0
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_interface'
        - url
        - url.query_args
        - 'user.node_grants:view'
      tags: {  }
  block_1:
    display_plugin: block
    id: block_1
    display_title: Block
    position: 2
    display_options:
      display_extenders: {  }
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_interface'
        - url
        - url.query_args
        - 'user.node_grants:view'
      tags: {  }
  page_1:
    display_plugin: page
    id: page_1
    display_title: Page
    position: 2
    display_options:
      display_extenders: {  }
      path: some-path
    cache_metadata:
      max-age: -1
      contexts:
        - 'languages:language_interface'
        - url
        - url.query_args
        - 'user.node_grants:view'
      tags: {  }
+91 −0
Original line number Diff line number Diff line
<?php

namespace Drupal\Tests\views\FunctionalJavascript;

use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\node\Traits\ContentTypeCreationTrait;
use Drupal\Tests\node\Traits\NodeCreationTrait;
use Drupal\views\Tests\ViewTestData;

/**
 * Tests the exposed filter ajax functionality in a block.
 *
 * @group views
 */
class BlockExposedFilterAJAXTest extends WebDriverTestBase {

  use ContentTypeCreationTrait;
  use NodeCreationTrait;

  /**
   * {@inheritdoc}
   */
  public static $modules = ['node', 'views', 'block', 'views_test_config'];

  public static $testViews = ['test_block_exposed_ajax', 'test_block_exposed_ajax_with_page'];

  /**
   * {@inheritdoc}
   */
  public function setUp() {
    parent::setUp();
    ViewTestData::createTestViews(self::class, ['views_test_config']);
    $this->createContentType(['type' => 'page']);
    $this->createContentType(['type' => 'article']);
    $this->createNode(['title' => 'Page A']);
    $this->createNode(['title' => 'Page B']);
    $this->createNode(['title' => 'Article A', 'type' => 'article']);

    $this->drupalLogin($this->drupalCreateUser([
      'access content',
    ]));
  }

  /**
   * Tests if exposed filtering and reset works with a views block and ajax.
   */
  public function testExposedFilteringAndReset() {
    $node = $this->createNode();
    $block = $this->drupalPlaceBlock('views_block:test_block_exposed_ajax-block_1');
    $this->drupalGet($node->toUrl());

    $page = $this->getSession()->getPage();

    // Ensure that the Content we're testing for is present.
    $html = $page->getHtml();
    $this->assertContains('Page A', $html);
    $this->assertContains('Page B', $html);
    $this->assertContains('Article A', $html);

    // Filter by page type.
    $this->submitForm(['type' => 'page'], t('Apply'));
    $this->assertSession()->waitForElementRemoved('xpath', '//*[text()="Article A"]');

    // Verify that only the page nodes are present.
    $html = $page->getHtml();
    $this->assertContains('Page A', $html);
    $this->assertContains('Page B', $html);
    $this->assertNotContains('Article A', $html);

    // Reset the form.
    $this->submitForm([], t('Reset'));
    // Assert we are still on the node page.
    $html = $page->getHtml();
    // Repeat the original tests.
    $this->assertContains('Page A', $html);
    $this->assertContains('Page B', $html);
    $this->assertContains('Article A', $html);
    $this->assertSession()->addressEquals('node/' . $node->id());

    $block->delete();
    // Do the same test with a block that has a page display to test the user
    // is redirected to the page display.
    $this->drupalPlaceBlock('views_block:test_block_exposed_ajax_with_page-block_1');
    $this->drupalGet($node->toUrl());
    $this->submitForm(['type' => 'page'], t('Apply'));
    $this->assertSession()->waitForElementRemoved('xpath', '//*[text()="Article A"]');
    $this->submitForm([], t('Reset'));
    $this->assertSession()->addressEquals('some-path');
  }

}
Loading