Verified Commit 9b7c3929 authored by Lee Rowlands's avatar Lee Rowlands
Browse files

Issue #3348592 by alexpott, kunal_sahu, joseph.olstad, catch, larowlan,...

Issue #3348592 by alexpott, kunal_sahu, joseph.olstad, catch, larowlan, amaria, james.williams, longwave: [regression] Language switcher block throws exception when no route is matched

(cherry picked from commit fca42b9d)
parent 1b1d4314
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -84,7 +84,11 @@ protected function blockAccess(AccountInterface $account) {
  public function build() {
    $build = [];
    $type = $this->getDerivativeId();
    $links = $this->languageManager->getLanguageSwitchLinks($type, Url::fromRouteMatch(\Drupal::routeMatch()));
    $route_match = \Drupal::routeMatch();
    // If there is no route match, for example when creating blocks on 404 pages
    // for logged-in users with big_pipe enabled using the front page instead.
    $url = $route_match->getRouteObject() ? Url::fromRouteMatch($route_match) : Url::fromRoute('<front>');
    $links = $this->languageManager->getLanguageSwitchLinks($type, $url);

    if (isset($links->links)) {
      $build = [
+175 −0
Original line number Diff line number Diff line
@@ -8,6 +8,8 @@
use Drupal\Core\Language\LanguageInterface;
use Drupal\Tests\BrowserTestBase;

// cspell:ignore publi publié

/**
 * Functional tests for the language switching feature.
 *
@@ -50,6 +52,7 @@ protected function setUp(): void {
      'administer blocks',
      'administer languages',
      'access administration pages',
      'access content',
    ]);
    $this->drupalLogin($admin_user);
  }
@@ -82,6 +85,18 @@ public function testLanguageBlock() {

    $this->doTestLanguageBlockAuthenticated($block->label());
    $this->doTestLanguageBlockAnonymous($block->label());
    $this->doTestLanguageBlock404($block->label(), 'system/404');

    // Test 404s with big_pipe where the behaviour is different for logged-in
    // users.
    \Drupal::service('module_installer')->install(['big_pipe']);
    $this->rebuildAll();
    $this->doTestLanguageBlock404($block->label(), 'system/404');
    $this->drupalLogin($this->drupalCreateUser());
    // @todo This is testing the current behaviour with the big_pipe module
    //   enabled. This behaviour is a bug will be fixed in
    //   https://www.drupal.org/project/drupal/issues/3349201.
    $this->doTestLanguageBlock404($block->label(), '<front>');
  }

  /**
@@ -187,6 +202,52 @@ protected function doTestLanguageBlockAnonymous($block_label) {
    $this->assertSame(['English', 'français'], $labels, 'The language links labels are in their own language on the language switcher block.');
  }

  /**
   * Tests the language switcher block on 404 pages.
   *
   * @param string $block_label
   *   The label of the language switching block.
   * @param string $system_path
   *   The expected system path for the links in the language switcher.
   *
   * @see self::testLanguageBlock()
   */
  protected function doTestLanguageBlock404(string $block_label, string $system_path) {
    $this->drupalGet('does-not-exist-' . $this->randomMachineName());
    $this->assertSession()->pageTextContains($block_label);

    // Assert that each list item and anchor element has the appropriate data-
    // attributes.
    $language_switchers = $this->xpath('//div[@id=:id]/ul/li', [':id' => 'block-test-language-block']);
    $list_items = [];
    $anchors = [];
    $labels = [];
    foreach ($language_switchers as $list_item) {
      $list_items[] = [
        'hreflang' => $list_item->getAttribute('hreflang'),
        'data-drupal-link-system-path' => $list_item->getAttribute('data-drupal-link-system-path'),
      ];

      $link = $list_item->find('xpath', 'a');
      $anchors[] = [
        'hreflang' => $link->getAttribute('hreflang'),
        'data-drupal-link-system-path' => $link->getAttribute('data-drupal-link-system-path'),
      ];
      $labels[] = $link->getText();
    }
    $expected_list_items = [
      0 => ['hreflang' => 'en', 'data-drupal-link-system-path' => $system_path],
      1 => ['hreflang' => 'fr', 'data-drupal-link-system-path' => $system_path],
    ];
    $this->assertSame($expected_list_items, $list_items, 'The list items have the correct attributes that will allow the drupal.active-link library to mark them as active.');
    $expected_anchors = [
      0 => ['hreflang' => 'en', 'data-drupal-link-system-path' => $system_path],
      1 => ['hreflang' => 'fr', 'data-drupal-link-system-path' => $system_path],
    ];
    $this->assertSame($expected_anchors, $anchors, 'The anchors have the correct attributes that will allow the drupal.active-link library to mark them as active.');
    $this->assertSame(['English', 'français'], $labels, 'The language links labels are in their own language on the language switcher block.');
  }

  /**
   * Tests language switcher links for domain based negotiation.
   */
@@ -445,6 +506,120 @@ public function testLanguageSessionSwitchLinks() {
    $this->assertSession()->addressEquals('user/2');
  }

  /**
   * Test that the language switching block does not expose restricted paths.
   */
  public function testRestrictedPaths(): void {
    \Drupal::service('module_installer')->install(['node']);
    $entity_type_manager = \Drupal::entityTypeManager();

    // Add the French language.
    ConfigurableLanguage::createFromLangcode('fr')->save();

    // Enable URL language detection and selection.
    $this->config('language.types')
      ->set('negotiation.language_interface.enabled.language-url', 1)
      ->save();

    // Enable the language switching block.
    $block = $this->drupalPlaceBlock('language_block:' . LanguageInterface::TYPE_INTERFACE);

    // Create a node type and make it translatable.
    $entity_type_manager->getStorage('node_type')
      ->create([
        'type' => 'page',
        'name' => 'Page',
      ])
      ->save();

    // Create a published node with an unpublished translation.
    $node = $entity_type_manager->getStorage('node')
      ->create([
        'type' => 'page',
        'title' => $this->randomMachineName(),
        'status' => 1,
      ]);
    $node->save();
    $node->addTranslation('fr', ['title' => 'Non publié', 'status' => 0]);
    $node->save();

    // Create path aliases.
    $alias_storage = $entity_type_manager->getStorage('path_alias');
    $alias_storage->create([
      'path' => '/user/1',
      'alias' => '/secret-identity/peter-parker',
    ])->save();
    $alias_storage->create([
      'path' => '/node/1',
      'langcode' => 'en',
      'alias' => '/press-release/published-report',
    ])->save();
    $alias_storage->create([
      'path' => '/node/1',
      'langcode' => 'fr',
      'alias' => '/press-release/rapport-non-publié',
    ])->save();

    // Visit a restricted user page.
    // Assert that the language switching block is displayed on the
    // access-denied page, but it does not contain the path alias.
    $this->assertLinkMarkup('/user/1', 403, $block->label(), 'peter-parker');

    // Visit the node and its translation. Use internal paths and aliases. The
    // non-ASCII character may be escaped, so remove it from the search string.
    $this->assertLinkMarkup('/node/1', 200, $block->label(), 'rapport-non-publi');
    $this->assertLinkMarkup('/press-release/published-report', 200, $block->label(), 'rapport-non-publi');
    $this->assertLinkMarkup('/fr/node/1', 403, $block->label(), 'rapport-non-publi');
    $this->assertLinkMarkup('/fr/press-release/rapport-non-publié', 403, $block->label(), 'rapport-non-publi');

    // Test as a user with access to other users and unpublished content.
    $privileged_user = $this->drupalCreateUser([
      'access user profiles',
      'bypass node access',
    ]);
    $this->drupalLogin($privileged_user);
    $this->assertLinkMarkup('/user/1', 200, $block->label(), 'peter-parker', TRUE);
    $this->assertLinkMarkup('/node/1', 200, $block->label(), 'rapport-non-publi', TRUE);
    $this->assertLinkMarkup('/press-release/published-report', 200, $block->label(), 'rapport-non-publi', TRUE);
    $this->assertLinkMarkup('/fr/node/1', 200, $block->label(), 'rapport-non-publi', TRUE);
    $this->assertLinkMarkup('/fr/press-release/rapport-non-publié', 200, $block->label(), 'rapport-non-publi', TRUE);

    // Test as an anonymous user.
    $this->drupalLogout();
    $this->assertLinkMarkup('/user/1', 403, $block->label(), 'peter-parker');
    $this->assertLinkMarkup('/node/1', 200, $block->label(), 'rapport-non-publi');
    $this->assertLinkMarkup('/press-release/published-report', 200, $block->label(), 'rapport-non-publi');
    $this->assertLinkMarkup('/fr/node/1', 403, $block->label(), 'rapport-non-publi');
    $this->assertLinkMarkup('/fr/press-release/rapport-non-publié', 403, $block->label(), 'rapport-non-publi');
  }

  /**
   * Asserts that restricted text is or is not present in the page response.
   *
   * @param string $path
   *   The path to test.
   * @param int $status
   *   The HTTP status code, such as 200 or 403.
   * @param string $marker
   *   Text that should always be present.
   * @param string $restricted
   *   Text that should be tested.
   * @param bool $found
   *   (optional) If TRUE, then the restricted text is present. Defaults to
   *   FALSE.
   */
  protected function assertLinkMarkup(string $path, int $status, string $marker, string $restricted, bool $found = FALSE): void {
    $this->drupalGet($path);
    $this->assertSession()->statusCodeEquals($status);
    $this->assertSession()->pageTextContains($marker);
    if ($found) {
      $this->assertSession()->responseContains($restricted);
    }
    else {
      $this->assertSession()->responseNotContains($restricted);
    }
  }

  /**
   * Saves the native name of a language entity in configuration as a label.
   *