Skip to content

Recalculate menu order on insertion or deletion #275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jan 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
},
"config": {
"process-timeout": 7200,
"sort-packages": true
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": true
}
},
"extra": {
"branch-alias": {
Expand Down
77 changes: 70 additions & 7 deletions features/menu-item.feature
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ Feature: Manage WordPress menu items
When I run `wp menu item add-term sidebar-menu post_tag {TERM_ID} --porcelain`
Then save STDOUT as {TERM_ITEM_ID}

When I run `wp menu item add-custom sidebar-menu Apple http://apple.com --parent-id={POST_ITEM_ID} --porcelain`
When I run `wp menu item add-custom sidebar-menu Apple https://apple.com --parent-id={POST_ITEM_ID} --porcelain`
Then save STDOUT as {CUSTOM_ITEM_ID}

When I run `wp menu item update {CUSTOM_ITEM_ID} --title=WordPress --link='http://wordpress.org' --target=_blank --position=2`
When I run `wp menu item update {CUSTOM_ITEM_ID} --title=WordPress --link='https://wordpress.org' --target=_blank --position=2`
Then STDOUT should be:
"""
Success: Menu item updated.
Expand All @@ -51,10 +51,10 @@ Feature: Manage WordPress menu items

When I run `wp menu item list sidebar-menu --fields=type,title,description,position,link,menu_item_parent`
Then STDOUT should be a table containing rows:
| type | title | description | position | link | menu_item_parent |
| post_type | Custom Test Post | Washington Apples | 1 | {POST_LINK} | 0 |
| custom | WordPress | | 2 | http://wordpress.org | {POST_ITEM_ID} |
| taxonomy | Test term | | 3 | {TERM_LINK} | 0 |
| type | title | description | position | link | menu_item_parent |
| post_type | Custom Test Post | Washington Apples | 1 | {POST_LINK} | 0 |
| custom | WordPress | | 2 | https://wordpress.org | {POST_ITEM_ID} |
| taxonomy | Test term | | 3 | {TERM_LINK} | 0 |

When I run `wp menu item list sidebar-menu --format=ids`
Then STDOUT should not be empty
Expand Down Expand Up @@ -122,7 +122,7 @@ Feature: Manage WordPress menu items
"""
And the return code should be 1

When I run `wp menu item add-custom sidebar-menu Apple http://apple.com --porcelain`
When I run `wp menu item add-custom sidebar-menu Apple https://apple.com --porcelain`
Then save STDOUT as {CUSTOM_ITEM_ID}

When I try `wp menu item delete {CUSTOM_ITEM_ID} 99999999`
Expand All @@ -132,3 +132,66 @@ Feature: Manage WordPress menu items
Error: Only deleted 1 of 2 menu items.
"""
And the return code should be 1

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

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

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

When I run `wp menu item add-custom sidebar-menu Third https://third.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 | First | 1 | https://first.com |
| custom | Second | 2 | https://second.com |
| custom | Third | 3 | https://third.com |

When I run `wp menu item add-custom sidebar-menu Fourth https://fourth.com --position=2 --porcelain`
Then save STDOUT as {ITEM_ID_4}

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 | First | 1 | https://first.com |
| custom | Fourth | 2 | https://fourth.com |
| custom | Second | 3 | https://second.com |
| custom | Third | 4 | https://third.com |

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

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

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

When I run `wp menu item add-custom sidebar-menu Third https://third.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 | First | 1 | https://first.com |
| custom | Second | 2 | https://second.com |
| custom | Third | 3 | https://third.com |

When I run `wp menu item delete {ITEM_ID_2}`
Then STDOUT should be:
"""
Success: Deleted 1 of 1 menu items.
"""

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 | First | 1 | https://first.com |
| custom | Third | 2 | https://third.com |
44 changes: 36 additions & 8 deletions src/Menu_Item_Command.php
Original file line number Diff line number Diff line change
Expand Up @@ -369,19 +369,28 @@ public function delete( $args, $assoc_args ) {

foreach ( $args as $arg ) {

$post = get_post( $arg );
$menu_term = get_the_terms( $arg, 'nav_menu' );
$parent_menu_id = (int) get_post_meta( $arg, '_menu_item_menu_item_parent', true );
$result = wp_delete_post( $arg, true );
if ( ! $result ) {
WP_CLI::warning( "Couldn't delete menu item {$arg}." );
$errors++;
} elseif ( $parent_menu_id ) {
$children = $wpdb->get_results( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_menu_item_menu_item_parent' AND meta_value=%s", (int) $arg ) );
if ( $children ) {
$children_query = $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = %d WHERE meta_key = '_menu_item_menu_item_parent' AND meta_value=%s", $parent_menu_id, (int) $arg );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $children_query is already prepared above.
$wpdb->query( $children_query );
foreach ( $children as $child ) {
clean_post_cache( $child );
} else {

if ( is_array( $menu_term ) && ! empty( $menu_term ) && $post ) {
$this->reorder_menu_items( $menu_term[0]->term_id, $post->menu_order, -1, 0 );
}

if ( $parent_menu_id ) {
$children = $wpdb->get_results( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_menu_item_menu_item_parent' AND meta_value=%s", (int) $arg ) );
if ( $children ) {
$children_query = $wpdb->prepare( "UPDATE $wpdb->postmeta SET meta_value = %d WHERE meta_key = '_menu_item_menu_item_parent' AND meta_value=%s", $parent_menu_id, (int) $arg );
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- $children_query is already prepared above.
$wpdb->query( $children_query );
foreach ( $children as $child ) {
clean_post_cache( $child );
}
}
}
}
Expand Down Expand Up @@ -477,6 +486,10 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) {
}
} else {

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

/**
* Set the menu
*
Expand All @@ -503,6 +516,21 @@ private function add_or_update_item( $method, $type, $args, $assoc_args ) {

}

/**
* Move block of items in one nav_menu up or down by incrementing/decrementing their menu_order field.
* Expects the menu items to have proper menu_orders (i.e. doesn't fix errors from previous incorrect operations).
*
* @param int $menu_id ID of the nav_menu
* @param int $min_position minimal menu_order to touch
* @param int $increment how much to change menu_order: +1 to move down, -1 to move up
* @param int $ignore_item_id menu item that should be ignored by the change (e.g. newly created menu item)
* @return int number of rows affected
*/
private function reorder_menu_items( $menu_id, $min_position, $increment, $ignore_item_id = 0 ) {
global $wpdb;
return $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->posts SET `menu_order`=`menu_order`+(%d) WHERE `menu_order`>=%d AND ID IN (SELECT object_id FROM $wpdb->term_relationships WHERE term_taxonomy_id=%d) AND ID<>%d", (int) $increment, (int) $min_position, (int) $menu_id, (int) $ignore_item_id ) );
}

protected function get_formatter( &$assoc_args ) {
return new Formatter( $assoc_args, $this->obj_fields );
}
Expand Down