From 628417ec8bea19534b62d9752e54bcd1e2bc3137 Mon Sep 17 00:00:00 2001 From: Sainath Poojary Date: Tue, 29 Jul 2025 15:24:04 +0530 Subject: [PATCH 1/2] Menus: Fix `nav_menu_item_args` filter side effects between menu items --- src/wp-includes/class-walker-nav-menu.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wp-includes/class-walker-nav-menu.php b/src/wp-includes/class-walker-nav-menu.php index 3d9f442bb8e87..bcb553fd2038b 100644 --- a/src/wp-includes/class-walker-nav-menu.php +++ b/src/wp-includes/class-walker-nav-menu.php @@ -157,7 +157,8 @@ public function start_el( &$output, $data_object, $depth = 0, $args = null, $cur // Restores the more descriptive, specific name for use within this method. $menu_item = $data_object; - if ( isset( $args->item_spacing ) && 'discard' === $args->item_spacing ) { + $args = (array) $args; + if ( isset( $args['item_spacing'] ) && 'discard' === $args['item_spacing'] ) { $t = ''; $n = ''; } else { @@ -178,7 +179,7 @@ public function start_el( &$output, $data_object, $depth = 0, $args = null, $cur * @param WP_Post $menu_item Menu item data object. * @param int $depth Depth of menu item. Used for padding. */ - $args = apply_filters( 'nav_menu_item_args', $args, $menu_item, $depth ); + $args = apply_filters( 'nav_menu_item_args', (object) $args, $menu_item, $depth ); /** * Filters the CSS classes applied to a menu item's list item element. From cae145a15938f5eb6d733dbae5a8152c1913b0a3 Mon Sep 17 00:00:00 2001 From: Sainath Poojary Date: Wed, 30 Jul 2025 17:59:00 +0530 Subject: [PATCH 2/2] Menus: Add tests to prevent nav_menu_item_args filter leakage between items --- tests/phpunit/tests/menu/walker-nav-menu.php | 68 ++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/phpunit/tests/menu/walker-nav-menu.php b/tests/phpunit/tests/menu/walker-nav-menu.php index 3ff725cbb0228..660ffa37b9e24 100644 --- a/tests/phpunit/tests/menu/walker-nav-menu.php +++ b/tests/phpunit/tests/menu/walker-nav-menu.php @@ -435,4 +435,72 @@ public function data_build_atts_should_build_attributes() { ), ); } + + /** + * Tests that modifications to `$args` in the `nav_menu_item_args` filter do not leak between menu items. + * + * @ticket 37344 + * + * @covers Walker_Nav_Menu::start_el + */ + public function test_walker_nav_menu_start_el_should_not_leak_nav_menu_item_args_filter_changes_between_items() { + add_filter( 'nav_menu_item_args', array( $this, 'filter_nav_menu_item_args_append_marker' ), 10, 3 ); + + $item1 = array( + 'ID' => 1, + 'title' => 'Item 1', + 'classes' => array(), + 'current' => false, + 'target' => '', + 'xfn' => '', + 'url' => 'http://example.com/item1', + ); + + $item2 = array( + 'ID' => 2, + 'title' => 'Item 2', + 'classes' => array(), + 'current' => false, + 'target' => '', + 'xfn' => '', + 'url' => 'http://example.com/item2', + ); + + $args = array( + 'before' => '', + 'after' => '', + 'link_before' => '', + 'link_after' => '', + ); + + $output1 = ''; + $this->walker->start_el( $output1, (object) $item1, 0, (object) $args ); + + $output2 = ''; + $this->walker->start_el( $output2, (object) $item2, 0, (object) $args ); + + remove_filter( 'nav_menu_item_args', array( $this, 'filter_nav_menu_item_args_append_marker' ), 10 ); + + // Both menu items should have exactly one 'X' marker, not accumulating. + $this->assertStringContainsString( '>XItem 1', $output1 ); + $this->assertStringContainsString( '>XItem 2', $output2 ); + + // Ensure no accumulation - Item 2 should not contain 'XX'. + $this->assertStringNotContainsString( '>XXItem 2', $output2 ); + } + + /** + * Filter callback for testing nav_menu_item_args filter side effects. + * + * Appends a marker character to the 'before' argument to test for accumulation. + * + * @param stdClass $args An object of wp_nav_menu() arguments. + * @param WP_Post $menu_item Menu item data object. + * @param int $depth Depth of menu item. Used for padding. + * @return stdClass Modified arguments object. + */ + public function filter_nav_menu_item_args_append_marker( $args, $menu_item, $depth ) { + $args->before .= 'X'; + return $args; + } }