diff --git a/composer.json b/composer.json index 7fe0c9ca1..641f46015 100644 --- a/composer.json +++ b/composer.json @@ -24,7 +24,10 @@ }, "config": { "process-timeout": 7200, - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } }, "extra": { "branch-alias": { diff --git a/features/menu-item.feature b/features/menu-item.feature index 01da75572..6aa1d33ae 100644 --- a/features/menu-item.feature +++ b/features/menu-item.feature @@ -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. @@ -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 @@ -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` @@ -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 | diff --git a/src/Menu_Item_Command.php b/src/Menu_Item_Command.php index 4f4cdeba9..75ee1ce33 100644 --- a/src/Menu_Item_Command.php +++ b/src/Menu_Item_Command.php @@ -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 ); + } } } } @@ -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 * @@ -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 ); }