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