wp/wp-includes/nav-menu-template.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
--- a/wp/wp-includes/nav-menu-template.php	Thu Sep 29 08:06:27 2022 +0200
+++ b/wp/wp-includes/nav-menu-template.php	Fri Sep 05 18:40:08 2025 +0200
@@ -198,6 +198,14 @@
 	$sorted_menu_items        = array();
 	$menu_items_with_children = array();
 	foreach ( (array) $menu_items as $menu_item ) {
+		/*
+		 * Fix invalid `menu_item_parent`. See: https://core.trac.wordpress.org/ticket/56926.
+		 * Compare as strings. Plugins may change the ID to a string.
+		 */
+		if ( (string) $menu_item->ID === (string) $menu_item->menu_item_parent ) {
+			$menu_item->menu_item_parent = 0;
+		}
+
 		$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
 		if ( $menu_item->menu_item_parent ) {
 			$menu_items_with_children[ $menu_item->menu_item_parent ] = true;
@@ -333,9 +341,9 @@
 				if ( is_array( $terms ) ) {
 					$possible_object_parents = array_merge( $possible_object_parents, $terms );
 					$term_to_ancestor        = array();
-					foreach ( (array) $term_hierarchy as $anc => $descs ) {
-						foreach ( (array) $descs as $desc ) {
-							$term_to_ancestor[ $desc ] = $anc;
+					foreach ( (array) $term_hierarchy as $ancestor => $descendents ) {
+						foreach ( (array) $descendents as $desc ) {
+							$term_to_ancestor[ $desc ] = $ancestor;
 						}
 					}
 
@@ -357,9 +365,9 @@
 	} elseif ( ! empty( $queried_object->taxonomy ) && is_taxonomy_hierarchical( $queried_object->taxonomy ) ) {
 		$term_hierarchy   = _get_term_hierarchy( $queried_object->taxonomy );
 		$term_to_ancestor = array();
-		foreach ( (array) $term_hierarchy as $anc => $descs ) {
-			foreach ( (array) $descs as $desc ) {
-				$term_to_ancestor[ $desc ] = $anc;
+		foreach ( (array) $term_hierarchy as $ancestor => $descendents ) {
+			foreach ( (array) $descendents as $desc ) {
+				$term_to_ancestor[ $desc ] = $ancestor;
 			}
 		}
 		$desc = $queried_object->term_id;
@@ -410,25 +418,25 @@
 
 			// If the menu item corresponds to the currently queried post or taxonomy object.
 		} elseif (
-			$menu_item->object_id == $queried_object_id
+			(int) $menu_item->object_id === $queried_object_id
 			&& (
 				( ! empty( $home_page_id ) && 'post_type' === $menu_item->type
-					&& $wp_query->is_home && $home_page_id == $menu_item->object_id )
+					&& $wp_query->is_home && $home_page_id === (int) $menu_item->object_id )
 				|| ( 'post_type' === $menu_item->type && $wp_query->is_singular )
 				|| ( 'taxonomy' === $menu_item->type
 					&& ( $wp_query->is_category || $wp_query->is_tag || $wp_query->is_tax )
-					&& $queried_object->taxonomy == $menu_item->object )
+					&& $queried_object->taxonomy === $menu_item->object )
 			)
 		) {
 			$classes[]                   = 'current-menu-item';
 			$menu_items[ $key ]->current = true;
-			$_anc_id                     = (int) $menu_item->db_id;
+			$ancestor_id                 = (int) $menu_item->db_id;
 
 			while (
-				( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) )
-				&& ! in_array( $_anc_id, $active_ancestor_item_ids, true )
+				( $ancestor_id = (int) get_post_meta( $ancestor_id, '_menu_item_menu_item_parent', true ) )
+				&& ! in_array( $ancestor_id, $active_ancestor_item_ids, true )
 			) {
-				$active_ancestor_item_ids[] = $_anc_id;
+				$active_ancestor_item_ids[] = $ancestor_id;
 			}
 
 			if ( 'post_type' === $menu_item->type && 'page' === $menu_item->object ) {
@@ -449,13 +457,13 @@
 		) {
 			$classes[]                   = 'current-menu-item';
 			$menu_items[ $key ]->current = true;
-			$_anc_id                     = (int) $menu_item->db_id;
+			$ancestor_id                 = (int) $menu_item->db_id;
 
 			while (
-				( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) )
-				&& ! in_array( $_anc_id, $active_ancestor_item_ids, true )
+				( $ancestor_id = (int) get_post_meta( $ancestor_id, '_menu_item_menu_item_parent', true ) )
+				&& ! in_array( $ancestor_id, $active_ancestor_item_ids, true )
 			) {
-				$active_ancestor_item_ids[] = $_anc_id;
+				$active_ancestor_item_ids[] = $ancestor_id;
 			}
 
 			$active_parent_item_ids[] = (int) $menu_item->menu_item_parent;
@@ -486,13 +494,13 @@
 			if ( $raw_item_url && in_array( $item_url, $matches, true ) ) {
 				$classes[]                   = 'current-menu-item';
 				$menu_items[ $key ]->current = true;
-				$_anc_id                     = (int) $menu_item->db_id;
+				$ancestor_id                 = (int) $menu_item->db_id;
 
 				while (
-					( $_anc_id = (int) get_post_meta( $_anc_id, '_menu_item_menu_item_parent', true ) )
-					&& ! in_array( $_anc_id, $active_ancestor_item_ids, true )
+					( $ancestor_id = (int) get_post_meta( $ancestor_id, '_menu_item_menu_item_parent', true ) )
+					&& ! in_array( $ancestor_id, $active_ancestor_item_ids, true )
 				) {
-					$active_ancestor_item_ids[] = $_anc_id;
+					$active_ancestor_item_ids[] = $ancestor_id;
 				}
 
 				if ( in_array( home_url(), array( untrailingslashit( $current_url ), untrailingslashit( $_indexless_current ) ), true ) ) {
@@ -504,18 +512,18 @@
 				$active_object              = $menu_item->object;
 
 				// Give front page item the 'current-menu-item' class when extra query arguments are involved.
-			} elseif ( $item_url == $front_page_url && is_front_page() ) {
+			} elseif ( $item_url === $front_page_url && is_front_page() ) {
 				$classes[] = 'current-menu-item';
 			}
 
-			if ( untrailingslashit( $item_url ) == home_url() ) {
+			if ( untrailingslashit( $item_url ) === home_url() ) {
 				$classes[] = 'menu-item-home';
 			}
 		}
 
 		// Back-compat with wp_page_menu(): add "current_page_parent" to static home page link for any non-page query.
 		if ( ! empty( $home_page_id ) && 'post_type' === $menu_item->type
-			&& empty( $wp_query->is_page ) && $home_page_id == $menu_item->object_id
+			&& empty( $wp_query->is_page ) && $home_page_id === (int) $menu_item->object_id
 		) {
 			$classes[] = 'current_page_parent';
 		}
@@ -541,7 +549,7 @@
 					&& ! empty( $queried_object->post_type )
 					&& is_post_type_hierarchical( $queried_object->post_type )
 					&& in_array( (int) $parent_item->object_id, $queried_object->ancestors, true )
-					&& $parent_item->object != $queried_object->ID
+					&& (int) $parent_item->object_id !== $queried_object->ID
 				) ||
 
 				// Ancestral term.
@@ -551,7 +559,7 @@
 					&& in_array( (int) $parent_item->object_id, $possible_taxonomy_ancestors[ $parent_item->object ], true )
 					&& (
 						! isset( $queried_object->term_id ) ||
-						$parent_item->object_id != $queried_object->term_id
+						(int) $parent_item->object_id !== $queried_object->term_id
 					)
 				)
 			)
@@ -603,7 +611,7 @@
  * @return string The HTML list content for the menu items.
  */
 function walk_nav_menu_tree( $items, $depth, $args ) {
-	$walker = ( empty( $args->walker ) ) ? new Walker_Nav_Menu : $args->walker;
+	$walker = ( empty( $args->walker ) ) ? new Walker_Nav_Menu() : $args->walker;
 
 	return $walker->walk( $items, $depth, $args );
 }
@@ -629,3 +637,60 @@
 
 	return $id;
 }
+
+/**
+ * Remove the `menu-item-has-children` class from bottom level menu items.
+ *
+ * This runs on the {@see 'nav_menu_css_class'} filter. The $args and $depth
+ * parameters were added after the filter was originally introduced in
+ * WordPress 3.0.0 so this needs to allow for cases in which the filter is
+ * called without them.
+ *
+ * @see https://core.trac.wordpress.org/ticket/56926
+ *
+ * @since 6.2.0
+ *
+ * @param string[]       $classes   Array of the CSS classes that are applied to the menu item's `<li>` element.
+ * @param WP_Post        $menu_item The current menu item object.
+ * @param stdClass|false $args      An object of wp_nav_menu() arguments. Default false ($args unspecified when filter is called).
+ * @param int|false      $depth     Depth of menu item. Default false ($depth unspecified when filter is called).
+ * @return string[] Modified nav menu classes.
+ */
+function wp_nav_menu_remove_menu_item_has_children_class( $classes, $menu_item, $args = false, $depth = false ) {
+	/*
+	 * Account for the filter being called without the $args or $depth parameters.
+	 *
+	 * This occurs when a theme uses a custom walker calling the `nav_menu_css_class`
+	 * filter using the legacy formats prior to the introduction of the $args and
+	 * $depth parameters.
+	 *
+	 * As both of these parameters are required for this function to determine
+	 * both the current and maximum depth of the menu tree, the function does not
+	 * attempt to remove the `menu-item-has-children` class if these parameters
+	 * are not set.
+	 */
+	if ( false === $depth || false === $args ) {
+		return $classes;
+	}
+
+	// Max-depth is 1-based.
+	$max_depth = isset( $args->depth ) ? (int) $args->depth : 0;
+	// Depth is 0-based so needs to be increased by one.
+	$depth = $depth + 1;
+
+	// Complete menu tree is displayed.
+	if ( 0 === $max_depth ) {
+		return $classes;
+	}
+
+	/*
+	 * Remove the `menu-item-has-children` class from bottom level menu items.
+	 * -1 is used to display all menu items in one level so the class should
+	 * be removed from all menu items.
+	 */
+	if ( -1 === $max_depth || $depth >= $max_depth ) {
+		$classes = array_diff( $classes, array( 'menu-item-has-children' ) );
+	}
+
+	return $classes;
+}