From 61a79e24e037e5ecb2a9345481d0d95eeafef1cb Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Thu, 27 Feb 2025 21:07:31 +0100
Subject: [PATCH 1/9] Add failing tests

---
 .../menu_link_access_test.info.yml            |  4 +
 .../src/Hook/MenuLinkAccessTestHooks.php      | 37 ++++++++
 .../src/Functional/MenuUiNodeAccessTest.php   | 86 +++++++++++++++++++
 3 files changed, 127 insertions(+)
 create mode 100644 core/modules/menu_ui/tests/modules/menu_link_access_test/menu_link_access_test.info.yml
 create mode 100644 core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php
 create mode 100644 core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php

diff --git a/core/modules/menu_ui/tests/modules/menu_link_access_test/menu_link_access_test.info.yml b/core/modules/menu_ui/tests/modules/menu_link_access_test/menu_link_access_test.info.yml
new file mode 100644
index 000000000000..36ba82e66771
--- /dev/null
+++ b/core/modules/menu_ui/tests/modules/menu_link_access_test/menu_link_access_test.info.yml
@@ -0,0 +1,4 @@
+name: 'Tests menu link access'
+type: module
+package: Testing
+version: VERSION
diff --git a/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php b/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php
new file mode 100644
index 000000000000..0ae635625145
--- /dev/null
+++ b/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php
@@ -0,0 +1,37 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\menu_link_access_test\Hook;
+
+use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for menu_link_access_test.
+ */
+class MenuLinkAccessTestHooks {
+
+  /**
+   * Implements hook_ENTITY_TYPE_access().
+   */
+  #[Hook('menu_link_content_access')]
+  public function entityTestAccess(EntityInterface $entity, $operation, AccountInterface $account): AccessResultInterface {
+    if (in_array($operation, ['update', 'delete'])) {
+      return AccessResult::forbidden();
+    }
+
+    return AccessResult::neutral();
+  }
+
+  /**
+   * Implements hook_ENTITY_TYPE_create_access().
+   */
+  #[Hook('menu_link_content_create_access')]
+  public function entityTestCreateAccess(AccountInterface $account, $context, $entity_bundle): AccessResultInterface {
+    return AccessResult::forbidden();
+  }
+
+}
diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php
new file mode 100644
index 000000000000..2a7e855322bd
--- /dev/null
+++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php
@@ -0,0 +1,86 @@
+<?php
+
+declare(strict_types=1);
+
+namespace Drupal\Tests\menu_ui\Functional;
+
+use Drupal\menu_link_content\Entity\MenuLinkContent;
+use Drupal\node\Entity\Node;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Edit a node when you don't have permission to add or edit menu links.
+ */
+class MenuUiNodeAccessTest extends BrowserTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = [
+    'menu_ui',
+    'test_page_test',
+    'node',
+    'menu_link_access_test',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $defaultTheme = 'stark';
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp(): void {
+    parent::setUp();
+
+    $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
+  }
+
+  /**
+   * Tests menu link create access is enforced.
+   */
+  public function testMenuLinkCreateAccess(): void {
+    $this->drupalLogin($this->drupalCreateUser([
+      'administer menu',
+      'edit any page content',
+    ]));
+    $node = Node::create([
+      'type' => 'page',
+      'title' => $this->randomMachineName(),
+      'uid' => $this->rootUser->id(),
+      'status' => 1,
+    ]);
+    $node->save();
+
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->assertSession()->elementNotExists('css', 'input[name="menu[title]"]');
+  }
+
+  /**
+   * Tests menu link edit/delete access is enforced.
+   */
+  public function testMenuLinkEditAccess(): void {
+    $this->drupalLogin($this->drupalCreateUser([
+      'administer menu',
+      'edit any page content',
+    ]));
+    $mainLinkTitle = $this->randomMachineName();
+    $node = Node::create([
+      'type' => 'page',
+      'title' => $this->randomMachineName(),
+      'uid' => $this->rootUser->id(),
+      'status' => 1,
+    ]);
+    $node->save();
+    MenuLinkContent::create([
+      'link' => [['uri' => 'entity:node/' . $node->id()]],
+      'title' => $mainLinkTitle,
+      'menu_name' => 'main',
+    ])->save();
+
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->assertSession()->elementNotExists('css', 'input[name="menu[title]"]');
+  }
+
+}
-- 
GitLab


From 5b8d3e46e50cac1044d15f65cdf0d35d6a10b59b Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Thu, 27 Feb 2025 21:14:00 +0100
Subject: [PATCH 2/9] Add fix

---
 core/modules/menu_ui/src/Hook/MenuUiHooks.php | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
index ac7c7602f3be..74c7b78fca85 100644
--- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php
+++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
@@ -16,6 +16,7 @@
 use Drupal\Core\Url;
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\menu_link_content\Entity\MenuLinkContent;
 
 /**
  * Hook implementations for menu_ui.
@@ -86,6 +87,22 @@ public function blockViewSystemMenuBlockAlter(array &$build, BlockPluginInterfac
     }
   }
 
+  /**
+   * Check if user is allowed to use the menu link subform.
+   */
+  private function getMenuLinkContentAccess(array $defaults) {
+    if (!empty($defaults['entity_id'])) {
+      $entity = MenuLinkContent::load($defaults['entity_id']);
+
+      // The form can be used to edit or delete the menu link.
+      return $entity->access('update', NULL, TRUE)->andIf($entity->access('delete', NULL, TRUE));
+    }
+    else {
+      // If the node has no corresponding menu link, users needs to permission to create one.
+      return \Drupal::entityTypeManager()->getAccessControlHandler('menu_link_content')->createAccess(NULL, NULL, [], TRUE);
+    }
+  }
+
   /**
    * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
    *
@@ -128,7 +145,7 @@ public function formNodeFormAlter(&$form, FormStateInterface $form_state) : void
     $form['menu'] = [
       '#type' => 'details',
       '#title' => $this->t('Menu settings'),
-      '#access' => \Drupal::currentUser()->hasPermission('administer menu'),
+      '#access' => $this->getMenuLinkContentAccess($defaults),
       '#open' => (bool) $defaults['id'],
       '#group' => 'advanced',
       '#attached' => [
-- 
GitLab


From 1638a359cfc0f3544c8fc644309f716f899aebd9 Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Thu, 27 Feb 2025 21:24:44 +0100
Subject: [PATCH 3/9] Fix types

---
 core/modules/menu_ui/src/Hook/MenuUiHooks.php                  | 3 ++-
 .../menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
index 74c7b78fca85..fab544f24ce9 100644
--- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php
+++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
@@ -17,6 +17,7 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\menu_link_content\Entity\MenuLinkContent;
+use Drupal\Core\Access\AccessResultInterface;
 
 /**
  * Hook implementations for menu_ui.
@@ -90,7 +91,7 @@ public function blockViewSystemMenuBlockAlter(array &$build, BlockPluginInterfac
   /**
    * Check if user is allowed to use the menu link subform.
    */
-  private function getMenuLinkContentAccess(array $defaults) {
+  private function getMenuLinkContentAccess(array $defaults): AccessResultInterface {
     if (!empty($defaults['entity_id'])) {
       $entity = MenuLinkContent::load($defaults['entity_id']);
 
diff --git a/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php b/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php
index 0ae635625145..586d2a793691 100644
--- a/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php
+++ b/core/modules/menu_ui/tests/modules/menu_link_access_test/src/Hook/MenuLinkAccessTestHooks.php
@@ -5,6 +5,7 @@
 namespace Drupal\menu_link_access_test\Hook;
 
 use Drupal\Core\Access\AccessResult;
+use Drupal\Core\Access\AccessResultInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Hook\Attribute\Hook;
-- 
GitLab


From 09d533ab39c4ced1026c9c7de04840be3325f4b1 Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Thu, 27 Feb 2025 21:44:34 +0100
Subject: [PATCH 4/9] Users that can't see unpublished nodes can't edit their
 menu link

So fix test accordingly
Submitting menu[enabled] now fails
---
 core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php
index 69b11f376d03..0025ae08718c 100644
--- a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php
+++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeTest.php
@@ -191,7 +191,9 @@ public function testMenuNodeFormWidget(): void {
     $this->drupalGet('test-page');
     $this->assertSession()->linkNotExists($node_title, 'Found no menu link with the node unpublished');
     // Assert that the link exists if published.
-    $edit['status[value]'] = TRUE;
+    $edit = [
+      'status[value]' => TRUE,
+    ];
     $this->drupalGet('node/' . $node->id() . '/edit');
     $this->submitForm($edit, 'Save');
     $this->drupalGet('test-page');
-- 
GitLab


From 09480daa5f47b614975bf3c9bcb0f44ec915b1b9 Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Thu, 27 Feb 2025 21:55:36 +0100
Subject: [PATCH 5/9] Missing @group annotation

---
 .../menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php       | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php
index 2a7e855322bd..93acdc88754f 100644
--- a/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php
+++ b/core/modules/menu_ui/tests/src/Functional/MenuUiNodeAccessTest.php
@@ -10,6 +10,8 @@
 
 /**
  * Edit a node when you don't have permission to add or edit menu links.
+ *
+ * @group menu_ui
  */
 class MenuUiNodeAccessTest extends BrowserTestBase {
 
-- 
GitLab


From 0f20ad4c6b0bf71182081439ee220ddc360111ca Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Fri, 28 Feb 2025 10:48:41 +0100
Subject: [PATCH 6/9] Make method protected

---
 core/modules/menu_ui/src/Hook/MenuUiHooks.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
index fab544f24ce9..82669191754b 100644
--- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php
+++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
@@ -91,7 +91,7 @@ public function blockViewSystemMenuBlockAlter(array &$build, BlockPluginInterfac
   /**
    * Check if user is allowed to use the menu link subform.
    */
-  private function getMenuLinkContentAccess(array $defaults): AccessResultInterface {
+  protected function getMenuLinkContentAccess(array $defaults): AccessResultInterface {
     if (!empty($defaults['entity_id'])) {
       $entity = MenuLinkContent::load($defaults['entity_id']);
 
-- 
GitLab


From 34f662df962569b25d8f4c04528d29370d009e60 Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Fri, 28 Feb 2025 10:52:05 +0100
Subject: [PATCH 7/9] Inject entity type manager service

---
 core/modules/menu_ui/src/Hook/MenuUiHooks.php | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
index 82669191754b..32a4ab93813b 100644
--- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php
+++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
@@ -18,6 +18,7 @@
 use Drupal\Core\Hook\Attribute\Hook;
 use Drupal\menu_link_content\Entity\MenuLinkContent;
 use Drupal\Core\Access\AccessResultInterface;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 
 /**
  * Hook implementations for menu_ui.
@@ -26,6 +27,10 @@ class MenuUiHooks {
 
   use StringTranslationTrait;
 
+  public function __construct(
+    protected EntityTypeManagerInterface $entityTypeManager,
+  ) {}
+
   /**
    * Implements hook_help().
    */
@@ -100,7 +105,7 @@ protected function getMenuLinkContentAccess(array $defaults): AccessResultInterf
     }
     else {
       // If the node has no corresponding menu link, users needs to permission to create one.
-      return \Drupal::entityTypeManager()->getAccessControlHandler('menu_link_content')->createAccess(NULL, NULL, [], TRUE);
+      return $this->entityTypeManager->getAccessControlHandler('menu_link_content')->createAccess(NULL, NULL, [], TRUE);
     }
   }
 
-- 
GitLab


From 879fa6e993654f7420ce2e0516d510464460e781 Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Fri, 28 Feb 2025 10:59:23 +0100
Subject: [PATCH 8/9] Fix related test

---
 core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php b/core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php
index f87a2639b877..f3f6d51e512e 100644
--- a/core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php
+++ b/core/modules/menu_ui/tests/src/Kernel/MenuBlockTest.php
@@ -71,7 +71,7 @@ public function testOperationLinks(): void {
     ]);
 
     // Test when user does have "administer menu" permission.
-    $menuUiEntityOperation = new MenuUiHooks();
+    $menuUiEntityOperation = new MenuUiHooks(\Drupal::entityTypeManager());
     $this->assertEquals([
       'menu-edit' => [
         'title' => 'Edit menu',
-- 
GitLab


From 964f455afcd29fd729087f5e0a2ab64ca1303a15 Mon Sep 17 00:00:00 2001
From: Pierre Rudloff <contact@rudloff.pro>
Date: Fri, 28 Feb 2025 19:15:21 +0100
Subject: [PATCH 9/9] Add missing params to docblocks

---
 core/modules/menu_ui/src/Hook/MenuUiHooks.php | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/core/modules/menu_ui/src/Hook/MenuUiHooks.php b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
index 32a4ab93813b..9b32ffa18247 100644
--- a/core/modules/menu_ui/src/Hook/MenuUiHooks.php
+++ b/core/modules/menu_ui/src/Hook/MenuUiHooks.php
@@ -27,6 +27,12 @@ class MenuUiHooks {
 
   use StringTranslationTrait;
 
+  /**
+   * Constructs a new MenuUiHooks object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entityTypeManager
+   *   The entity type manager.
+   */
   public function __construct(
     protected EntityTypeManagerInterface $entityTypeManager,
   ) {}
@@ -95,6 +101,11 @@ public function blockViewSystemMenuBlockAlter(array &$build, BlockPluginInterfac
 
   /**
    * Check if user is allowed to use the menu link subform.
+   *
+   * @param array $defaults
+   *   An array that contains default values for the menu link form.
+   *
+   * @see menu_ui_get_menu_link_defaults()
    */
   protected function getMenuLinkContentAccess(array $defaults): AccessResultInterface {
     if (!empty($defaults['entity_id'])) {
-- 
GitLab