Commit 326695026c7 for woocommerce
commit 326695026c79d5ec1913edf4a73331ea42299d6c
Author: Chris Lilitsas <1105590+xristos3490@users.noreply.github.com>
Date: Mon Jul 27 11:35:10 2026 +0300
Fix: Persist nav-menu hidden snapshot only for the current user (#66977)
fix: persist nav-menu hidden snapshot only for the current user
diff --git a/plugins/woocommerce/changelog/66976-fix-nav-menu-hidden-snapshot-current-user b/plugins/woocommerce/changelog/66976-fix-nav-menu-hidden-snapshot-current-user
new file mode 100644
index 00000000000..0b0e5fc20e3
--- /dev/null
+++ b/plugins/woocommerce/changelog/66976-fix-nav-menu-hidden-snapshot-current-user
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Persist the default nav-menu hidden meta box snapshot only for the current user on the Appearance > Menus screen, matching WordPress core, so a read of another user's option (e.g. user-switching or admin tooling) can no longer overwrite that user's own first-visit snapshot.
diff --git a/plugins/woocommerce/includes/admin/class-wc-admin-menus.php b/plugins/woocommerce/includes/admin/class-wc-admin-menus.php
index 8bcef39f77b..c0ec14282c4 100644
--- a/plugins/woocommerce/includes/admin/class-wc-admin-menus.php
+++ b/plugins/woocommerce/includes/admin/class-wc-admin-menus.php
@@ -444,7 +444,9 @@ class WC_Admin_Menus {
* across the multiple reads in a single page load and, crucially, across
* later requests: boxes registered by plugins installed after the first
* visit stay visible, matching vanilla WordPress instead of defaulting to
- * hidden on every recompute.
+ * hidden on every recompute. As in core, only the current user's snapshot is
+ * persisted, so reads on behalf of another user never overwrite that user's
+ * own first-visit snapshot.
*
* @since 11.1.0
*
@@ -511,7 +513,16 @@ class WC_Admin_Menus {
// Persist the snapshot, mirroring core's wp_initial_nav_menu_meta_boxes(),
// so later reads in this request and in future requests return this same
// list instead of recomputing it against a changed meta box registry.
- update_user_meta( $user->ID, 'metaboxhidden_nav-menus', $hidden );
+ //
+ // Like core, only the current user's snapshot is persisted. This filter
+ // runs for whichever user's option is being read, so a read performed on
+ // behalf of another user (user-switching or admin tooling) still receives
+ // the computed default below for display, but must not consume that user's
+ // durable first-visit snapshot from this request's meta box registry.
+ $current_user_id = get_current_user_id();
+ if ( $current_user_id && (int) $user->ID === $current_user_id ) {
+ update_user_meta( $current_user_id, 'metaboxhidden_nav-menus', $hidden );
+ }
return $hidden;
}
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-admin-menus-test.php b/plugins/woocommerce/tests/php/includes/class-wc-admin-menus-test.php
index 85e3f284e2f..052de06d8ba 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-admin-menus-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-admin-menus-test.php
@@ -208,6 +208,7 @@ class WC_Admin_Menus_Test extends WC_Unit_Test_Case {
public function test_filter_keeps_endpoints_box_visible_after_late_registration() {
$GLOBALS['wp_meta_boxes'] = $this->get_nav_meta_boxes(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$user_id = $this->factory->user->create();
+ wp_set_current_user( $user_id );
$menus = new WC_Admin_Menus();
$menus->register_default_nav_menu_meta_boxes_filter();
@@ -240,6 +241,7 @@ class WC_Admin_Menus_Test extends WC_Unit_Test_Case {
public function test_filter_keeps_late_registered_third_party_box_out_of_snapshot() {
$GLOBALS['wp_meta_boxes'] = $this->get_nav_meta_boxes(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$user_id = $this->factory->user->create();
+ wp_set_current_user( $user_id );
$menus = new WC_Admin_Menus();
$menus->register_default_nav_menu_meta_boxes_filter();
@@ -267,6 +269,7 @@ class WC_Admin_Menus_Test extends WC_Unit_Test_Case {
public function test_filter_persists_hidden_snapshot_on_first_visit() {
$GLOBALS['wp_meta_boxes'] = $this->get_nav_meta_boxes(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$user_id = $this->factory->user->create();
+ wp_set_current_user( $user_id );
$menus = new WC_Admin_Menus();
$menus->register_default_nav_menu_meta_boxes_filter();
@@ -289,6 +292,7 @@ class WC_Admin_Menus_Test extends WC_Unit_Test_Case {
public function test_snapshot_persists_so_later_registered_third_party_box_stays_visible() {
$GLOBALS['wp_meta_boxes'] = $this->get_nav_meta_boxes(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$user_id = $this->factory->user->create();
+ wp_set_current_user( $user_id );
// First request: user visits the nav-menus screen for the first time.
$first_visit = new WC_Admin_Menus();
@@ -313,4 +317,33 @@ class WC_Admin_Menus_Test extends WC_Unit_Test_Case {
$this->assertIsArray( $hidden );
$this->assertNotContains( 'add-later-plugin-box', $hidden );
}
+
+ /**
+ * A read performed on behalf of another user (e.g. user-switching or admin
+ * tooling) during the nav-menus request must not persist a snapshot for that
+ * user. Core's wp_initial_nav_menu_meta_boxes() only writes for the current
+ * user, so the other user's genuine first-visit snapshot stays intact and
+ * self-healing. The computed default is still returned for display.
+ */
+ public function test_filter_does_not_persist_snapshot_for_a_non_current_user() {
+ $GLOBALS['wp_meta_boxes'] = $this->get_nav_meta_boxes(); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
+ $current_user_id = $this->factory->user->create();
+ $other_user_id = $this->factory->user->create();
+ wp_set_current_user( $current_user_id );
+
+ $menus = new WC_Admin_Menus();
+ $menus->register_default_nav_menu_meta_boxes_filter();
+
+ // The current user reads another user's option (the pathological path).
+ $hidden = get_user_option( 'metaboxhidden_nav-menus', $other_user_id );
+
+ // The computed default is still returned for display...
+ $this->assertIsArray( $hidden );
+ $this->assertContains( 'add-post-type-news', $hidden );
+
+ // ...but nothing is persisted for either user, so the other user's genuine
+ // first-visit snapshot is never consumed by this request.
+ $this->assertSame( '', get_user_meta( $other_user_id, 'metaboxhidden_nav-menus', true ) );
+ $this->assertSame( '', get_user_meta( $current_user_id, 'metaboxhidden_nav-menus', true ) );
+ }
}