From e6fe418084972b7edfa6dc6fa171fb5d92794bf1 Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Mon, 5 Feb 2024 17:59:07 +0000
Subject: [PATCH] Issue #3101344 by Akhil Babu, Alex Bukach, ravi.shankar,
 quietone, agentrickard, kriboogh, allaprishchepa, flyke, Mschudders,
 rgpublic, smustgrave, borisson_: hook_node_grants implementations lead to a
 'URL Alias' validation error when saving translated nodes

(cherry picked from commit f665483c346b421c8c0f699f7518ac159fb58724)
---
 .../node/src/NodeGrantDatabaseStorage.php     |  12 +-
 .../path_test_node_grants.info.yml            |   5 +
 .../path_test_node_grants.module              |  16 +++
 .../PathWithNodeAccessGrantsTest.php          | 111 ++++++++++++++++++
 4 files changed, 141 insertions(+), 3 deletions(-)
 create mode 100644 core/modules/path/tests/modules/path_test_node_grants/path_test_node_grants.info.yml
 create mode 100644 core/modules/path/tests/modules/path_test_node_grants/path_test_node_grants.module
 create mode 100644 core/modules/path/tests/src/Functional/PathWithNodeAccessGrantsTest.php

diff --git a/core/modules/node/src/NodeGrantDatabaseStorage.php b/core/modules/node/src/NodeGrantDatabaseStorage.php
index a8289edb7edf..45d9f85546d5 100644
--- a/core/modules/node/src/NodeGrantDatabaseStorage.php
+++ b/core/modules/node/src/NodeGrantDatabaseStorage.php
@@ -82,10 +82,16 @@ public function access(NodeInterface $node, $operation, AccountInterface $accoun
     $query->addExpression('1');
     // Only interested for granting in the current operation.
     $query->condition('grant_' . $operation, 1, '>=');
-    // Check for grants for this node and the correct langcode.
+    // Check for grants for this node and the correct langcode. New translations
+    // do not yet have a langcode and must check the fallback node record.
     $nids = $query->andConditionGroup()
-      ->condition('nid', $node->id())
-      ->condition('langcode', $node->language()->getId());
+      ->condition('nid', $node->id());
+    if (!$node->isNewTranslation()) {
+      $nids->condition('langcode', $node->language()->getId());
+    }
+    else {
+      $nids->condition('fallback', 1);
+    }
     // If the node is published, also take the default grant into account. The
     // default is saved with a node ID of 0.
     $status = $node->isPublished();
diff --git a/core/modules/path/tests/modules/path_test_node_grants/path_test_node_grants.info.yml b/core/modules/path/tests/modules/path_test_node_grants/path_test_node_grants.info.yml
new file mode 100644
index 000000000000..8839376ae2eb
--- /dev/null
+++ b/core/modules/path/tests/modules/path_test_node_grants/path_test_node_grants.info.yml
@@ -0,0 +1,5 @@
+name: 'Path test with node grants'
+type: module
+description: 'Tests URL alias with hook_node_grants implementation'
+package: Testing
+version: VERSION
diff --git a/core/modules/path/tests/modules/path_test_node_grants/path_test_node_grants.module b/core/modules/path/tests/modules/path_test_node_grants/path_test_node_grants.module
new file mode 100644
index 000000000000..62345733f0d8
--- /dev/null
+++ b/core/modules/path/tests/modules/path_test_node_grants/path_test_node_grants.module
@@ -0,0 +1,16 @@
+<?php
+
+/**
+ * @file
+ * Contains hook implementations for the Path test with node grants module.
+ */
+
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Implements hook_node_grants().
+ */
+function path_test_node_grants_node_grants(AccountInterface $account, $operation): array {
+  $grants = [];
+  return $grants;
+}
diff --git a/core/modules/path/tests/src/Functional/PathWithNodeAccessGrantsTest.php b/core/modules/path/tests/src/Functional/PathWithNodeAccessGrantsTest.php
new file mode 100644
index 000000000000..54e9ff1d4c81
--- /dev/null
+++ b/core/modules/path/tests/src/Functional/PathWithNodeAccessGrantsTest.php
@@ -0,0 +1,111 @@
+<?php
+
+namespace Drupal\Tests\path\Functional;
+
+use Drupal\Tests\content_moderation\Traits\ContentModerationTestTrait;
+use Drupal\Tests\content_translation\Traits\ContentTranslationTestTrait;
+
+/**
+ * Confirm that paths work with node access grants implementations.
+ *
+ * @group path
+ */
+class PathWithNodeAccessGrantsTest extends PathTestBase {
+
+  use ContentTranslationTestTrait;
+  use ContentModerationTestTrait;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  protected static $modules = [
+    'path',
+    'locale',
+    'locale_test',
+    'content_translation',
+    'content_moderation',
+    'path_test_node_grants',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    // Create a workflow for basic page.
+    $workflow = $this->createEditorialWorkflow();
+    $this->addEntityTypeAndBundleToWorkflow($workflow, 'node', 'page');
+
+    // Login as admin user to configure language detection and selection.
+    $admin_user = $this->drupalCreateUser([
+      'edit any page content',
+      'create page content',
+      'administer url aliases',
+      'create url aliases',
+      'administer languages',
+      'access administration pages',
+    ]);
+    $this->drupalLogin($admin_user);
+    // Enable French language.
+    static::createLanguageFromLangcode('fr');
+    // Enable URL language detection and selection.
+    $edit = ['language_interface[enabled][language-url]' => 1];
+    $this->drupalGet('admin/config/regional/language/detection');
+    $this->submitForm($edit, 'Save settings');
+    // Enable translation for page node.
+    static::enableContentTranslation('node', 'page');
+    static::setFieldTranslatable('node', 'page', 'body', TRUE);
+
+    $definitions = \Drupal::service('entity_field.manager')->getFieldDefinitions('node', 'page');
+    $this->assertTrue($definitions['path']->isTranslatable(), 'Node path is translatable.');
+    $this->assertTrue($definitions['body']->isTranslatable(), 'Node body is translatable.');
+  }
+
+  /**
+   * Tests alias functionality through the admin interfaces.
+   */
+  public function testAliasTranslation() : void {
+    // Rebuild the permissions to update 'node_access' table.
+    node_access_rebuild();
+    $alias = $this->randomMachineName();
+    $permissions = [
+      'access administration pages',
+      'view any unpublished content',
+      'use editorial transition create_new_draft',
+      'use editorial transition publish',
+      'create content translations',
+      'create page content',
+      'create url aliases',
+      'edit any page content',
+      'translate any entity',
+    ];
+    $this->drupalLogin($this->drupalCreateUser($permissions));
+    // Create a node, add URL alias and publish it.
+    $this->drupalGet('node/add/page');
+    $edit['title[0][value]'] = 'test';
+    $edit['path[0][alias]'] = '/' . $alias;
+    $edit['moderation_state[0][state]'] = 'published';
+    $this->submitForm($edit, 'Save');
+    // Add french translation.
+    $this->drupalGet('node/1/translations');
+    $this->clickLink('Add');
+    $this->submitForm(['moderation_state[0][state]' => 'published'], 'Save (this translation)');
+    // Translation should be saved.
+    $this->assertSession()->pageTextContains('Basic page test has been updated.');
+    // There shouldn't be any validation errors.
+    $this->assertSession()->pageTextNotContains("Either the path '/node/1' is invalid or you do not have access to it.");
+    // Translation should be saved with the given alias.
+    $this->container->get('path_alias.manager')->cacheClear();
+    $translation_alias = $this->container->get('path_alias.manager')->getAliasByPath('/node/1', 'fr');
+    $this->assertSame('/' . $alias, $translation_alias);
+  }
+
+}
-- 
GitLab