wp/wp-includes/nav-menu.php
changeset 21 48c4eec2b7e6
parent 19 3d72ae0968f4
child 22 8c2e4d02f4ef
--- a/wp/wp-includes/nav-menu.php	Thu Sep 29 08:06:27 2022 +0200
+++ b/wp/wp-includes/nav-menu.php	Fri Sep 05 18:40:08 2025 +0200
@@ -143,7 +143,7 @@
  *
  * @global array $_wp_registered_nav_menus
  *
- * @return string[] Associative array of egistered navigation menu descriptions keyed
+ * @return string[] Associative array of registered navigation menu descriptions keyed
  *                  by their location. If none are registered, an empty array.
  */
 function get_registered_nav_menus() {
@@ -280,7 +280,7 @@
 	// Remove this menu from any locations.
 	$locations = get_nav_menu_locations();
 	foreach ( $locations as $location => $menu_id ) {
-		if ( $menu_id == $menu->term_id ) {
+		if ( $menu_id === $menu->term_id ) {
 			$locations[ $location ] = 0;
 		}
 	}
@@ -331,7 +331,7 @@
 		$_possible_existing &&
 		! is_wp_error( $_possible_existing ) &&
 		isset( $_possible_existing->term_id ) &&
-		$_possible_existing->term_id != $menu_id
+		$_possible_existing->term_id !== $menu_id
 	) {
 		return new WP_Error(
 			'menu_exists',
@@ -458,12 +458,22 @@
 
 	$args = wp_parse_args( $menu_item_data, $defaults );
 
-	if ( 0 == $menu_id ) {
+	if ( 0 === $menu_id ) {
 		$args['menu-item-position'] = 1;
-	} elseif ( 0 == (int) $args['menu-item-position'] ) {
-		$menu_items                 = 0 == $menu_id ? array() : (array) wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) );
-		$last_item                  = array_pop( $menu_items );
-		$args['menu-item-position'] = ( $last_item && isset( $last_item->menu_order ) ) ? 1 + $last_item->menu_order : count( $menu_items );
+	} elseif ( 0 === (int) $args['menu-item-position'] ) {
+		$menu_items = array();
+
+		if ( 0 !== $menu_id ) {
+			$menu_items = (array) wp_get_nav_menu_items( $menu_id, array( 'post_status' => 'publish,draft' ) );
+		}
+
+		$last_item = array_pop( $menu_items );
+
+		if ( $last_item && isset( $last_item->menu_order ) ) {
+			$args['menu-item-position'] = 1 + $last_item->menu_order;
+		} else {
+			$args['menu-item-position'] = count( $menu_items );
+		}
 	}
 
 	$original_parent = 0 < $menu_item_db_id ? get_post_field( 'post_parent', $menu_item_db_id ) : 0;
@@ -522,7 +532,7 @@
 		$post['post_date'] = $post_date;
 	}
 
-	$update = 0 != $menu_item_db_id;
+	$update = 0 !== $menu_item_db_id;
 
 	// New menu item. Default is draft status.
 	if ( ! $update ) {
@@ -547,8 +557,10 @@
 		do_action( 'wp_add_nav_menu_item', $menu_id, $menu_item_db_id, $args );
 	}
 
-	// Associate the menu item with the menu term.
-	// Only set the menu term if it isn't set to avoid unnecessary wp_get_object_terms().
+	/*
+	 * Associate the menu item with the menu term.
+	 * Only set the menu term if it isn't set to avoid unnecessary wp_get_object_terms().
+	 */
 	if ( $menu_id && ( ! $update || ! is_object_in_term( $menu_item_db_id, 'nav_menu', (int) $menu->term_id ) ) ) {
 		$update_terms = wp_set_object_terms( $menu_item_db_id, array( $menu->term_id ), 'nav_menu' );
 		if ( is_wp_error( $update_terms ) ) {
@@ -563,6 +575,11 @@
 
 	$menu_item_db_id = (int) $menu_item_db_id;
 
+	// Reset invalid `menu_item_parent`.
+	if ( (int) $args['menu-item-parent-id'] === $menu_item_db_id ) {
+		$args['menu-item-parent-id'] = 0;
+	}
+
 	update_post_meta( $menu_item_db_id, '_menu_item_type', sanitize_key( $args['menu-item-type'] ) );
 	update_post_meta( $menu_item_db_id, '_menu_item_menu_item_parent', (string) ( (int) $args['menu-item-parent-id'] ) );
 	update_post_meta( $menu_item_db_id, '_menu_item_object_id', (string) ( (int) $args['menu-item-object-id'] ) );
@@ -573,9 +590,9 @@
 	$args['menu-item-xfn']     = implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $args['menu-item-xfn'] ) ) );
 	update_post_meta( $menu_item_db_id, '_menu_item_classes', $args['menu-item-classes'] );
 	update_post_meta( $menu_item_db_id, '_menu_item_xfn', $args['menu-item-xfn'] );
-	update_post_meta( $menu_item_db_id, '_menu_item_url', esc_url_raw( $args['menu-item-url'] ) );
+	update_post_meta( $menu_item_db_id, '_menu_item_url', sanitize_url( $args['menu-item-url'] ) );
 
-	if ( 0 == $menu_id ) {
+	if ( 0 === $menu_id ) {
 		update_post_meta( $menu_item_db_id, '_menu_item_orphaned', (string) time() );
 	} elseif ( get_post_meta( $menu_item_db_id, '_menu_item_orphaned' ) ) {
 		delete_post_meta( $menu_item_db_id, '_menu_item_orphaned' );
@@ -669,18 +686,21 @@
  * @param array              $args {
  *     Optional. Arguments to pass to get_posts().
  *
- *     @type string $order       How to order nav menu items as queried with get_posts(). Will be ignored
- *                               if 'output' is ARRAY_A. Default 'ASC'.
- *     @type string $orderby     Field to order menu items by as retrieved from get_posts(). Supply an orderby
- *                               field via 'output_key' to affect the output order of nav menu items.
- *                               Default 'menu_order'.
- *     @type string $post_type   Menu items post type. Default 'nav_menu_item'.
- *     @type string $post_status Menu items post status. Default 'publish'.
- *     @type string $output      How to order outputted menu items. Default ARRAY_A.
- *     @type string $output_key  Key to use for ordering the actual menu items that get returned. Note that
- *                               that is not a get_posts() argument and will only affect output of menu items
- *                               processed in this function. Default 'menu_order'.
- *     @type bool   $nopaging    Whether to retrieve all menu items (true) or paginate (false). Default true.
+ *     @type string $order                  How to order nav menu items as queried with get_posts().
+ *                                          Will be ignored if 'output' is ARRAY_A. Default 'ASC'.
+ *     @type string $orderby                Field to order menu items by as retrieved from get_posts().
+ *                                          Supply an orderby field via 'output_key' to affect the
+ *                                          output order of nav menu items. Default 'menu_order'.
+ *     @type string $post_type              Menu items post type. Default 'nav_menu_item'.
+ *     @type string $post_status            Menu items post status. Default 'publish'.
+ *     @type string $output                 How to order outputted menu items. Default ARRAY_A.
+ *     @type string $output_key             Key to use for ordering the actual menu items that get
+ *                                          returned. Note that that is not a get_posts() argument
+ *                                          and will only affect output of menu items processed in
+ *                                          this function. Default 'menu_order'.
+ *     @type bool   $nopaging               Whether to retrieve all menu items (true) or paginate
+ *                                          (false). Default true.
+ *     @type bool   $update_menu_item_cache Whether to update the menu item cache. Default true.
  * }
  * @return array|false Array of menu items, otherwise false.
  */
@@ -691,21 +711,20 @@
 		return false;
 	}
 
-	static $fetched = array();
-
 	if ( ! taxonomy_exists( 'nav_menu' ) ) {
 		return false;
 	}
 
 	$defaults = array(
-		'order'       => 'ASC',
-		'orderby'     => 'menu_order',
-		'post_type'   => 'nav_menu_item',
-		'post_status' => 'publish',
-		'output'      => ARRAY_A,
-		'output_key'  => 'menu_order',
-		'nopaging'    => true,
-		'tax_query'   => array(
+		'order'                  => 'ASC',
+		'orderby'                => 'menu_order',
+		'post_type'              => 'nav_menu_item',
+		'post_status'            => 'publish',
+		'output'                 => ARRAY_A,
+		'output_key'             => 'menu_order',
+		'nopaging'               => true,
+		'update_menu_item_cache' => true,
+		'tax_query'              => array(
 			array(
 				'taxonomy' => 'nav_menu',
 				'field'    => 'term_taxonomy_id',
@@ -720,33 +739,6 @@
 		$items = array();
 	}
 
-	// Prime posts and terms caches.
-	if ( empty( $fetched[ $menu->term_id ] ) ) {
-		$fetched[ $menu->term_id ] = true;
-		$post_ids                  = array();
-		$term_ids                  = array();
-		foreach ( $items as $item ) {
-			$object_id = get_post_meta( $item->ID, '_menu_item_object_id', true );
-			$type      = get_post_meta( $item->ID, '_menu_item_type', true );
-
-			if ( 'post_type' === $type ) {
-				$post_ids[] = (int) $object_id;
-			} elseif ( 'taxonomy' === $type ) {
-				$term_ids[] = (int) $object_id;
-			}
-		}
-
-		if ( ! empty( $post_ids ) ) {
-			_prime_post_caches( $post_ids, false );
-		}
-		unset( $post_ids );
-
-		if ( ! empty( $term_ids ) ) {
-			_prime_term_caches( $term_ids );
-		}
-		unset( $term_ids );
-	}
-
 	$items = array_map( 'wp_setup_nav_menu_item', $items );
 
 	if ( ! is_admin() ) { // Remove invalid items only on front end.
@@ -781,6 +773,41 @@
 }
 
 /**
+ * Updates post and term caches for all linked objects for a list of menu items.
+ *
+ * @since 6.1.0
+ *
+ * @param WP_Post[] $menu_items Array of menu item post objects.
+ */
+function update_menu_item_cache( $menu_items ) {
+	$post_ids = array();
+	$term_ids = array();
+
+	foreach ( $menu_items as $menu_item ) {
+		if ( 'nav_menu_item' !== $menu_item->post_type ) {
+			continue;
+		}
+
+		$object_id = get_post_meta( $menu_item->ID, '_menu_item_object_id', true );
+		$type      = get_post_meta( $menu_item->ID, '_menu_item_type', true );
+
+		if ( 'post_type' === $type ) {
+			$post_ids[] = (int) $object_id;
+		} elseif ( 'taxonomy' === $type ) {
+			$term_ids[] = (int) $object_id;
+		}
+	}
+
+	if ( ! empty( $post_ids ) ) {
+		_prime_post_caches( $post_ids, false );
+	}
+
+	if ( ! empty( $term_ids ) ) {
+		_prime_term_caches( $term_ids );
+	}
+}
+
+/**
  * Decorates a menu item object with the shared navigation menu item properties.
  *
  * Properties:
@@ -808,6 +835,24 @@
  * @return object The menu item with standard menu item properties.
  */
 function wp_setup_nav_menu_item( $menu_item ) {
+
+	/**
+	 * Filters whether to short-circuit the wp_setup_nav_menu_item() output.
+	 *
+	 * Returning a non-null value from the filter will short-circuit wp_setup_nav_menu_item(),
+	 * returning that value instead.
+	 *
+	 * @since 6.3.0
+	 *
+	 * @param object|null $modified_menu_item Modified menu item. Default null.
+	 * @param object      $menu_item          The menu item to modify.
+	 */
+	$pre_menu_item = apply_filters( 'pre_wp_setup_nav_menu_item', null, $menu_item );
+
+	if ( null !== $pre_menu_item ) {
+		return $pre_menu_item;
+	}
+
 	if ( isset( $menu_item->post_type ) ) {
 		if ( 'nav_menu_item' === $menu_item->post_type ) {
 			$menu_item->db_id            = (int) $menu_item->ID;
@@ -1003,7 +1048,7 @@
 	$object_id     = (int) $object_id;
 	$menu_item_ids = array();
 
-	$query      = new WP_Query;
+	$query      = new WP_Query();
 	$menu_items = $query->query(
 		array(
 			'meta_key'       => '_menu_item_object_id',
@@ -1024,7 +1069,7 @@
 			} elseif (
 				'taxonomy' === $object_type &&
 				'taxonomy' === $menu_item_type &&
-				get_post_meta( $menu_item->ID, '_menu_item_object', true ) == $taxonomy
+				get_post_meta( $menu_item->ID, '_menu_item_object', true ) === $taxonomy
 			) {
 				$menu_item_ids[] = (int) $menu_item->ID;
 			}
@@ -1111,7 +1156,7 @@
 			continue;
 		}
 		foreach ( $items as $item ) {
-			if ( $post->ID == $item->object_id ) {
+			if ( $post->ID === (int) $item->object_id ) {
 				continue 2;
 			}
 		}
@@ -1266,3 +1311,31 @@
 
 	return $new_nav_menu_locations;
 }
+
+/**
+ * Prevents menu items from being their own parent.
+ *
+ * Resets menu_item_parent to 0 when the parent is set to the item itself.
+ * For use before saving `_menu_item_menu_item_parent` in nav-menus.php.
+ *
+ * @since 6.2.0
+ * @access private
+ *
+ * @param array $menu_item_data The menu item data array.
+ * @return array The menu item data with reset menu_item_parent.
+ */
+function _wp_reset_invalid_menu_item_parent( $menu_item_data ) {
+	if ( ! is_array( $menu_item_data ) ) {
+		return $menu_item_data;
+	}
+
+	if (
+		! empty( $menu_item_data['ID'] ) &&
+		! empty( $menu_item_data['menu_item_parent'] ) &&
+		(int) $menu_item_data['ID'] === (int) $menu_item_data['menu_item_parent']
+	) {
+		$menu_item_data['menu_item_parent'] = 0;
+	}
+
+	return $menu_item_data;
+}