Skip to content
Merged
46 changes: 46 additions & 0 deletions features/menu-item.feature
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,52 @@ Feature: Manage WordPress menu items
| custom | First | 1 | https://first.com |
| custom | Third | 2 | https://third.com |

Scenario: Menu order is recalculated on update
When I run `wp menu create "Sidebar Menu"`
Then STDOUT should not be empty

When I run `wp menu item add-custom sidebar-menu Alpha https://alpha.com --porcelain`
Then save STDOUT as {ITEM_ID_1}

When I run `wp menu item add-custom sidebar-menu Beta https://beta.com --porcelain`
Then save STDOUT as {ITEM_ID_2}

When I run `wp menu item add-custom sidebar-menu Gamma https://gamma.com --porcelain`
Then save STDOUT as {ITEM_ID_3}

When I run `wp menu item list sidebar-menu --fields=type,title,position,link`
Then STDOUT should be a table containing rows:
| type | title | position | link |
| custom | Alpha | 1 | https://alpha.com |
| custom | Beta | 2 | https://beta.com |
| custom | Gamma | 3 | https://gamma.com |

When I run `wp menu item update {ITEM_ID_3} --position=1`
Then STDOUT should be:
"""
Success: Menu item updated.
"""

When I run `wp menu item list sidebar-menu --fields=type,title,position,link`
Then STDOUT should be a table containing rows:
| type | title | position | link |
| custom | Gamma | 1 | https://gamma.com |
| custom | Alpha | 2 | https://alpha.com |
| custom | Beta | 3 | https://beta.com |

When I run `wp menu item update {ITEM_ID_1} --position=3`
Then STDOUT should be:
"""
Success: Menu item updated.
"""

When I run `wp menu item list sidebar-menu --fields=type,title,position,link`
Then STDOUT should be a table containing rows:
| type | title | position | link |
| custom | Gamma | 1 | https://gamma.com |
| custom | Beta | 2 | https://beta.com |
| custom | Alpha | 3 | https://alpha.com |

Scenario: Get menu item details
When I run `wp menu create "Sidebar Menu"`
Then STDOUT should not be empty
Expand Down
70 changes: 69 additions & 1 deletion src/Menu_Item_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,71 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) {
}

$menu_item_args['menu-item-type'] = $type;
$result = wp_update_nav_menu_item( $menu->term_id, $menu_item_db_id, $menu_item_args );
$pending_menu_order_updates = [];

// Reorder other menu items when the position changes on update.
if ( 'update' === $method ) {
$new_position = (int) $menu_item_args['menu-item-position'];
if ( $new_position > 0 ) {
// Fetch all menu items sorted by their raw menu_order to determine
// normalized (1-indexed) ranks, since wp_get_nav_menu_items(ARRAY_A)
// normalises menu_order to 1,2,3… which may differ from the raw DB values.
$sorted_items = get_posts(
[
'post_type' => 'nav_menu_item',
'numberposts' => -1,
'orderby' => 'menu_order',
'order' => 'ASC',
'post_status' => 'any',
'tax_query' => [
[
'taxonomy' => 'nav_menu',
'field' => 'term_taxonomy_id',
'terms' => $menu->term_taxonomy_id,
],
],
]
);

// Clamp the requested position to the valid range of menu items.
$max_position = count( $sorted_items );
if ( $max_position > 0 && $new_position > $max_position ) {
// Treat out-of-range positions as "move to end", consistent with core behavior.
$new_position = $max_position;
}

// Find the 1-indexed normalized rank of the item being moved.
$old_position_normalized = 0;
foreach ( $sorted_items as $idx => $sorted_item ) {
if ( (int) $sorted_item->ID === (int) $menu_item_db_id ) {
$old_position_normalized = $idx + 1;
break;
}
}

if ( $old_position_normalized > 0 && $new_position !== $old_position_normalized ) {
if ( $new_position < $old_position_normalized ) {
// Moving up: items at 0-indexed [new_pos-1, old_pos-2] shift down by +1.
for ( $i = $new_position - 1; $i <= $old_position_normalized - 2; $i++ ) {
$pending_menu_order_updates[] = [
'ID' => $sorted_items[ $i ]->ID,
'menu_order' => $i + 2,
];
}
} else {
// Moving down: items at 0-indexed [old_pos, new_pos-1] shift up by -1.
for ( $i = $old_position_normalized; $i <= $new_position - 1; $i++ ) {
$pending_menu_order_updates[] = [
'ID' => $sorted_items[ $i ]->ID,
'menu_order' => $i,
];
}
}
}
}
}

$result = wp_update_nav_menu_item( $menu->term_id, $menu_item_db_id, $menu_item_args );

if ( is_wp_error( $result ) ) {
WP_CLI::error( $result->get_error_message() );
Expand All @@ -645,6 +709,10 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) {
WP_CLI::error( "Couldn't update menu item." );
}
} else {
// Apply deferred reordering of other menu items only after a successful update.
foreach ( $pending_menu_order_updates as $update_args ) {
wp_update_post( $update_args );
}

if ( ( 'add' === $method ) && $menu_item_args['menu-item-position'] ) {
$this->reorder_menu_items( $menu->term_id, $menu_item_args['menu-item-position'], +1, $result );
Expand Down
Loading