[COT] Bulk actions for trashing etc (#34425)

Implement trash/untrash/delete permanently bulk actions for COT admin list table.

Co-authored-by: Nestor Soriano <konamiman@konamiman.com>
This commit is contained in:
Barry Hughes 2022-09-09 12:04:42 -07:00 committed by GitHub
parent 657dceaba1
commit d375c3b609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 184 additions and 15 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Adds bulk action support to the COT admin list table for deletion, trashing, and restoration.

View File

@ -112,7 +112,7 @@ class ListTable extends WP_List_Table {
* @return mixed
*/
public function set_items_per_page( $default, string $option, int $value ) {
return $option === 'edit_orders_per_page' ? absint( $value ) : $default;
return 'edit_orders_per_page' === $option ? absint( $value ) : $default;
}
/**
@ -184,12 +184,22 @@ class ListTable extends WP_List_Table {
* @return array
*/
protected function get_bulk_actions() {
$actions = array(
'mark_processing' => __( 'Change status to processing', 'woocommerce' ),
'mark_on-hold' => __( 'Change status to on-hold', 'woocommerce' ),
'mark_completed' => __( 'Change status to completed', 'woocommerce' ),
'mark_cancelled' => __( 'Change status to cancelled', 'woocommerce' ),
);
$selected_status = $this->order_query_args['status'] ?? false;
if ( array( 'trash' ) === $selected_status ) {
$actions = array(
'untrash' => __( 'Restore', 'woocommerce' ),
'delete' => __( 'Delete permanently', 'woocommerce' ),
);
} else {
$actions = array(
'mark_processing' => __( 'Change status to processing', 'woocommerce' ),
'mark_on-hold' => __( 'Change status to on-hold', 'woocommerce' ),
'mark_completed' => __( 'Change status to completed', 'woocommerce' ),
'mark_cancelled' => __( 'Change status to cancelled', 'woocommerce' ),
'trash' => __( 'Move to Trash', 'woocommerce' ),
);
}
if ( wc_string_to_bool( get_option( 'woocommerce_allow_bulk_remove_personal_data', 'no' ) ) ) {
$actions['remove_personal_data'] = __( 'Remove personal data', 'woocommerce' );
@ -404,7 +414,7 @@ class ListTable extends WP_List_Table {
protected function extra_tablenav( $which ) {
echo '<div class="alignleft actions">';
if ( $which === 'top' ) {
if ( 'top' === $which ) {
$this->months_filter();
$this->customers_filter();
@ -672,7 +682,7 @@ class ListTable extends WP_List_Table {
$latest_note = current( $latest_notes );
if ( isset( $latest_note->content ) && $approved_comments_count === 1 ) {
if ( isset( $latest_note->content ) && 1 === $approved_comments_count ) {
$tooltip = wc_sanitize_tooltip( $latest_note->content );
} elseif ( isset( $latest_note->content ) ) {
/* translators: %d: notes count */
@ -877,6 +887,15 @@ class ListTable extends WP_List_Table {
if ( 'remove_personal_data' === $action ) {
$report_action = 'removed_personal_data';
$changed = $this->do_bulk_action_remove_personal_data( $ids );
} elseif ( 'trash' === $action ) {
$changed = $this->do_delete( $ids );
$report_action = 'trashed';
} elseif ( 'delete' === $action ) {
$changed = $this->do_delete( $ids, true );
$report_action = 'deleted';
} elseif ( 'untrash' === $action ) {
$changed = $this->do_untrash( $ids );
$report_action = 'untrashed';
} elseif ( false !== strpos( $action, 'mark_' ) ) {
$order_statuses = wc_get_order_statuses();
$new_status = substr( $action, 5 );
@ -953,6 +972,52 @@ class ListTable extends WP_List_Table {
return $changed;
}
/**
* Handles bulk trashing of orders.
*
* @param int[] $ids Order IDs to be trashed.
* @param bool $force_delete When set, the order will be completed deleted. Otherwise, it will be trashed.
*
* @return int Number of orders that were trashed.
*/
private function do_delete( array $ids, bool $force_delete = false ): int {
$orders_store = wc_get_container()->get( OrdersTableDataStore::class );
$delete_args = $force_delete ? array( 'force_delete' => true ) : array();
$changed = 0;
foreach ( $ids as $id ) {
$order = wc_get_order( $id );
$orders_store->delete( $order, $delete_args );
$updated_order = wc_get_order( $id );
if ( ( $force_delete && false === $updated_order ) || ( ! $force_delete && $updated_order->get_status() === 'trash' ) ) {
$changed++;
}
}
return $changed;
}
/**
* Handles bulk restoration of trashed orders.
*
* @param array $ids Order IDs to be restored to their previous status.
*
* @return int Number of orders that were restored from the trash.
*/
private function do_untrash( array $ids ): int {
$orders_store = wc_get_container()->get( OrdersTableDataStore::class );
$changed = 0;
foreach ( $ids as $id ) {
if ( $orders_store->untrash_order( wc_get_order( $id ) ) ) {
$changed++;
}
}
return $changed;
}
/**
* Show confirmation message that order status changed for number of orders.
*/
@ -964,20 +1029,41 @@ class ListTable extends WP_List_Table {
$order_statuses = wc_get_order_statuses();
$number = absint( $_REQUEST['changed'] ?? 0 );
$bulk_action = wc_clean( wp_unslash( $_REQUEST['bulk_action'] ) );
$message = '';
// Check if any status changes happened.
foreach ( $order_statuses as $slug => $name ) {
if ( 'marked_' . str_replace( 'wc-', '', $slug ) === $bulk_action ) { // WPCS: input var ok, CSRF ok.
/* translators: %s: orders count */
$message = sprintf( _n( '%s order status changed.', '%s order statuses changed.', $number, 'woocommerce' ), number_format_i18n( $number ) );
echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
break;
}
}
if ( 'removed_personal_data' === $bulk_action ) { // WPCS: input var ok, CSRF ok.
/* translators: %s: orders count */
$message = sprintf( _n( 'Removed personal data from %s order.', 'Removed personal data from %s orders.', $number, 'woocommerce' ), number_format_i18n( $number ) );
switch ( $bulk_action ) {
case 'removed_personal_data':
/* translators: %s: orders count */
$message = sprintf( _n( 'Removed personal data from %s order.', 'Removed personal data from %s orders.', $number, 'woocommerce' ), number_format_i18n( $number ) );
echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
break;
case 'trashed':
/* translators: %s: orders count */
$message = sprintf( _n( '%s order moved to the Trash.', '%s orders moved to the Trash.', $number, 'woocommerce' ), number_format_i18n( $number ) );
break;
case 'untrashed':
/* translators: %s: orders count */
$message = sprintf( _n( '%s order restored from the Trash.', '%s orders restored from the Trash.', $number, 'woocommerce' ), number_format_i18n( $number ) );
break;
case 'deleted':
/* translators: %s: orders count */
$message = sprintf( _n( '%s order permanently deleted.', '%s orders permanently deleted.', $number, 'woocommerce' ), number_format_i18n( $number ) );
break;
}
if ( ! empty( $message ) ) {
echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
}
}

View File

@ -7,6 +7,8 @@ namespace Automattic\WooCommerce\Internal\DataStores\Orders;
use Automattic\Jetpack\Constants;
use Automattic\WooCommerce\Internal\Utilities\DatabaseUtil;
use WC_Data;
use WC_Order;
defined( 'ABSPATH' ) || exit;
@ -1246,10 +1248,10 @@ LEFT JOIN {$operational_data_clauses['join']}
/**
* Trashes an order.
*
* @param \WC_Order $order The order object
* @param WC_Order $order The order object
* @return void
*/
public function trash_order( &$order ) {
public function trash_order( $order ) {
global $wpdb;
if ( 'trash' === $order->get_status( 'edit' ) ) {
@ -1282,6 +1284,83 @@ LEFT JOIN {$operational_data_clauses['join']}
$order->set_status( 'trash' );
}
/**
* Attempts to restore the specified order back to its original status (after having been trashed).
*
* @param WC_Order $order The order to be untrashed.
*
* @return bool If the operation was successful.
*/
public function untrash_order( WC_Order $order ): bool {
$id = $order->get_id();
$status = $order->get_status();
if ( 'trash' !== $status ) {
wc_get_logger()->warning(
sprintf(
/* translators: 1: order ID, 2: order status */
__( 'Order %1$d cannot be restored from the trash: it has already been restored to status "%2$s".', 'woocommerce' ),
$id,
$status
)
);
return false;
}
$previous_status = $order->get_meta( '_wp_trash_meta_status' );
$valid_statuses = wc_get_order_statuses();
$previous_state_is_invalid = ! array_key_exists( 'wc-' . $previous_status, $valid_statuses );
$pending_is_valid_status = array_key_exists( 'wc-pending', $valid_statuses );
if ( $previous_state_is_invalid && $pending_is_valid_status ) {
// If the previous status is no longer valid, let's try to restore it to "pending" instead.
wc_get_logger()->warning(
sprintf(
/* translators: 1: order ID, 2: order status */
__( 'The previous status of order %1$d ("%2$s") is invalid. It has been restored to "pending" status instead.', 'woocommerce' ),
$id,
$previous_status
)
);
$previous_status = 'pending';
} elseif ( $previous_state_is_invalid ) {
// If we cannot restore to pending, we should probably stand back and let the merchant intervene some other way.
wc_get_logger()->warning(
sprintf(
/* translators: 1: order ID, 2: order status */
__( 'The previous status of order %1$d ("%2$s") is invalid. It could not be restored.', 'woocommerce' ),
$id,
$previous_status
)
);
return false;
}
$order->set_status( $previous_status );
$order->save();
// Was the status successfully restored? Let's clean up the meta and indicate success...
if ( $previous_status === $order->get_status() ) {
$order->delete_meta_data( '_wp_trash_meta_status' );
$order->delete_meta_data( '_wp_trash_meta_time' );
return true;
}
// ...Or log a warning and bail.
wc_get_logger()->warning(
sprintf(
/* translators: 1: order ID, 2: order status */
__( 'Something went wrong when trying to restore order %d from the trash. It could not be restored.', 'woocommerce' ),
$id
)
);
return false;
}
/**
* Deletes order data from custom order tables.
*