@@ -643,58 +643,54 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) {
643643 // Fetch all menu items sorted by their raw menu_order to determine
644644 // normalized (1-indexed) ranks, since wp_get_nav_menu_items(ARRAY_A)
645645 // normalises menu_order to 1,2,3… which may differ from the raw DB values.
646- $ sorted_items = get_posts (
647- [
648- 'post_type ' => 'nav_menu_item ' ,
649- 'numberposts ' => -1 ,
650- 'orderby ' => 'menu_order ' ,
651- 'order ' => 'ASC ' ,
652- 'post_status ' => 'any ' ,
653- 'tax_query ' => [
654- [
655- 'taxonomy ' => 'nav_menu ' ,
656- 'field ' => 'term_taxonomy_id ' ,
657- 'terms ' => $ menu ->term_taxonomy_id ,
658- ],
659- ],
660- ]
661- );
662-
663- // Clamp the requested position to the valid range of menu items.
664- $ max_position = count ( $ sorted_items );
665- if ( $ max_position > 0 && $ new_position > $ max_position ) {
666- // Treat out-of-range positions as "move to end", consistent with core behavior.
667- $ new_position = $ max_position ;
668- }
669-
670- // Find the 1-indexed normalized rank of the item being moved.
671- $ old_position_normalized = 0 ;
672- foreach ( $ sorted_items as $ idx => $ sorted_item ) {
673- if ( (int ) $ sorted_item ->ID === (int ) $ menu_item_db_id ) {
674- $ old_position_normalized = $ idx + 1 ;
675- break ;
676- }
677- }
678-
679- if ( $ old_position_normalized > 0 && $ new_position !== $ old_position_normalized ) {
680- if ( $ new_position < $ old_position_normalized ) {
681- // Moving up: items at 0-indexed [new_pos-1, old_pos-2] shift down by +1.
682- for ( $ i = $ new_position - 1 ; $ i <= $ old_position_normalized - 2 ; $ i ++ ) {
683- $ pending_menu_order_updates [] = [
684- 'ID ' => $ sorted_items [ $ i ]->ID ,
685- 'menu_order ' => $ i + 2 ,
686- ];
687- }
688- } else {
689- // Moving down: items at 0-indexed [old_pos, new_pos-1] shift up by -1.
690- for ( $ i = $ old_position_normalized ; $ i <= $ new_position - 1 ; $ i ++ ) {
691- $ pending_menu_order_updates [] = [
692- 'ID ' => $ sorted_items [ $ i ]->ID ,
693- 'menu_order ' => $ i ,
694- ];
695- }
696- }
697- }
646+ $ sorted_item_ids = get_posts (
647+ [
648+ 'post_type ' => 'nav_menu_item ' ,
649+ 'numberposts ' => -1 ,
650+ 'orderby ' => 'menu_order ' ,
651+ 'order ' => 'ASC ' ,
652+ 'post_status ' => 'any ' ,
653+ 'tax_query ' => [
654+ [
655+ 'taxonomy ' => 'nav_menu ' ,
656+ 'field ' => 'term_taxonomy_id ' ,
657+ 'terms ' => $ menu ->term_taxonomy_id ,
658+ ],
659+ ],
660+ 'fields ' => 'ids ' ,
661+ ]
662+ );
663+
664+ // Clamp the requested position to the valid range of menu items.
665+ $ max_position = count ( $ sorted_item_ids );
666+ if ( $ max_position > 0 && $ new_position > $ max_position ) {
667+ // Treat out-of-range positions as "move to end", consistent with core behavior.
668+ $ new_position = $ max_position ;
669+ }
670+
671+ // Find the 1-indexed normalized rank of the item being moved.
672+ $ item_idx = array_search ( (string ) $ menu_item_db_id , $ sorted_item_ids , true );
673+ $ old_position_normalized = ( false !== $ item_idx ) ? $ item_idx + 1 : 0 ;
674+
675+ if ( $ old_position_normalized > 0 && $ new_position !== $ old_position_normalized ) {
676+ if ( $ new_position < $ old_position_normalized ) {
677+ // Moving up: items at 0-indexed [new_pos-1, old_pos-2] shift down by +1.
678+ for ( $ i = $ new_position - 1 ; $ i <= $ old_position_normalized - 2 ; $ i ++ ) {
679+ $ pending_menu_order_updates [] = [
680+ 'ID ' => $ sorted_item_ids [ $ i ],
681+ 'menu_order ' => $ i + 2 ,
682+ ];
683+ }
684+ } else {
685+ // Moving down: items at 0-indexed [old_pos, new_pos-1] shift up by -1.
686+ for ( $ i = $ old_position_normalized ; $ i <= $ new_position - 1 ; $ i ++ ) {
687+ $ pending_menu_order_updates [] = [
688+ 'ID ' => $ sorted_item_ids [ $ i ],
689+ 'menu_order ' => $ i ,
690+ ];
691+ }
692+ }
693+ }
698694 }
699695 }
700696
@@ -710,9 +706,25 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) {
710706 }
711707 } else {
712708 // Apply deferred reordering of other menu items only after a successful update.
713- foreach ( $ pending_menu_order_updates as $ update_args ) {
714- wp_update_post ( $ update_args );
715- }
709+ if ( ! empty ( $ pending_menu_order_updates ) ) {
710+ global $ wpdb ;
711+
712+ $ ids_to_update = [];
713+ $ case_clauses = '' ;
714+ foreach ( $ pending_menu_order_updates as $ update_args ) {
715+ $ item_id = (int ) $ update_args ['ID ' ];
716+ $ ids_to_update [] = $ item_id ;
717+ $ case_clauses .= $ wpdb ->prepare ( ' WHEN %d THEN %d ' , $ item_id , $ update_args ['menu_order ' ] );
718+ }
719+
720+ $ ids_sql = implode ( ', ' , $ ids_to_update );
721+ // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $case_clauses and $ids_sql are constructed from prepared/safe values.
722+ $ wpdb ->query ( "UPDATE {$ wpdb ->posts } SET menu_order = CASE ID {$ case_clauses } END WHERE ID IN ( {$ ids_sql }) " );
723+
724+ foreach ( $ ids_to_update as $ id ) {
725+ clean_post_cache ( $ id );
726+ }
727+ }
716728
717729 if ( ( 'add ' === $ method ) && $ menu_item_args ['menu-item-position ' ] ) {
718730 $ this ->reorder_menu_items ( $ menu ->term_id , $ menu_item_args ['menu-item-position ' ], +1 , $ result );
0 commit comments