[COT] Implement order bulk actions (#33687)

Introduce `Admin\Orders\PageController` to manage admin pages for orders

* Change orders menu registration & initialization to use `Admin\Orders\PageController`
* Implement bulk actions in orders ListTable
* Unnecessary import
* Make PHPCS happy
* Add changelog
* Address feedback
This commit is contained in:
Jorge A. Torres 2022-07-04 20:57:01 -03:00 committed by GitHub
parent 6c13fdf0ce
commit bddb6621ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 275 additions and 34 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
Implement bulk actions in the new orders admin list table.

View File

@ -7,6 +7,7 @@
*/
use Automattic\WooCommerce\Internal\Admin\Orders\ListTable as Custom_Orders_List_Table;
use Automattic\WooCommerce\Internal\Admin\Orders\PageController as Custom_Orders_PageController;
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
defined( 'ABSPATH' ) || exit;
@ -316,38 +317,9 @@ class WC_Admin_Menus {
*/
public function orders_menu(): void {
if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
add_submenu_page( 'woocommerce', __( 'Orders', 'woocommerce' ), __( 'Orders', 'woocommerce' ), 'edit_others_shop_orders', 'wc-orders', array( $this, 'orders_page' ) );
add_filter( 'manage_woocommerce_page_wc-orders_columns', array( $this, 'orders_table' ) );
// In some cases (such as if the authoritative order store was changed earlier in the current request) we
// need an extra step to remove the menu entry for the menu post type.
add_action(
'admin_init',
function () {
remove_submenu_page( 'woocommerce', 'edit.php?post_type=shop_order' );
$this->orders_page_controller = new Custom_Orders_PageController();
$this->orders_page_controller->setup();
}
);
}
}
/**
* Set-up the orders admin list table.
*
* @return void
*/
public function orders_table(): void {
$this->orders_list_table = new Custom_Orders_List_Table();
$this->orders_list_table->setup();
}
/**
* Render the orders admin list table.
*
* @return void
*/
public function orders_page(): void {
$this->orders_list_table->prepare_items();
$this->orders_list_table->display();
}
/**

View File

@ -4,7 +4,6 @@ namespace Automattic\WooCommerce\Internal\Admin\Orders;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use WC_Order;
use WC_Order_Data_Store_Interface;
use WP_List_Table;
use WP_Screen;
@ -33,9 +32,12 @@ class ListTable extends WP_List_Table {
* @return void
*/
public function setup(): void {
add_action( 'admin_notices', array( $this, 'bulk_action_notices' ) );
add_filter( 'manage_woocommerce_page_wc-orders_columns', array( $this, 'get_columns' ) );
add_filter( 'set_screen_option_edit_orders_per_page', array( $this, 'set_items_per_page' ), 10, 3 );
add_filter( 'default_hidden_columns', array( $this, 'default_hidden_columns' ), 10, 2 );
$this->items_per_page();
set_screen_options();
}
@ -118,7 +120,7 @@ class ListTable extends WP_List_Table {
/**
* Renders after the 'blank state' message for the order list table has rendered.
*/
do_action( 'wc_marketplace_suggestions_orders_empty_state' );
do_action( 'wc_marketplace_suggestions_orders_empty_state' ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingSinceComment
?>
</div>
@ -458,7 +460,7 @@ class ListTable extends WP_List_Table {
human_time_diff( $order->get_date_created()->getTimestamp(), time() )
);
} else {
$show_date = $order->get_date_created()->date_i18n( apply_filters( 'woocommerce_admin_order_date_format', __( 'M j, Y', 'woocommerce' ) ) );
$show_date = $order->get_date_created()->date_i18n( apply_filters( 'woocommerce_admin_order_date_format', __( 'M j, Y', 'woocommerce' ) ) ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
}
printf(
'<time datetime="%1$s" title="%2$s">%3$s</time>',
@ -583,6 +585,7 @@ class ListTable extends WP_List_Table {
* are registered.
*
* @param WC_Order $order Current order object.
* @since 6.7.0
*/
do_action( 'woocommerce_admin_order_actions_start', $order );
@ -609,6 +612,7 @@ class ListTable extends WP_List_Table {
*
* @param array $action Order actions.
* @param WC_Order $order Current order object.
* @since 6.7.0
*/
$actions = apply_filters( 'woocommerce_admin_order_actions', $actions, $order );
@ -620,6 +624,7 @@ class ListTable extends WP_List_Table {
* are rendered.
*
* @param WC_Order $order Current order object.
* @since 6.7.0
*/
do_action( 'woocommerce_admin_order_actions_end', $order );
@ -649,4 +654,148 @@ class ListTable extends WP_List_Table {
echo '<input type="hidden" name="status" value="' . esc_attr( sanitize_text_field( wp_unslash( $_GET[ $param ] ) ) ) . '" >';
}
}
/**
* Handle bulk actions.
*/
public function handle_bulk_actions() {
$action = $this->current_action();
if ( ! $action ) {
return;
}
check_admin_referer( 'bulk-orders' );
$redirect_to = remove_query_arg( array( 'deleted', 'ids' ), wp_get_referer() );
$redirect_to = add_query_arg( 'paged', $this->get_pagenum(), $redirect_to );
/**
* Allows 3rd parties to modify order IDs about to be affected by a bulk action.
*
* @param array Array of order IDs.
*/
$ids = apply_filters( // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingSinceComment
'woocommerce_bulk_action_ids',
isset( $_REQUEST['order'] ) ? array_reverse( array_map( 'absint', $_REQUEST['order'] ) ) : array(),
$action,
'order'
);
if ( ! $ids ) {
wp_safe_redirect( $redirect_to );
exit;
}
$report_action = '';
$changed = 0;
if ( 'remove_personal_data' === $action ) {
$report_action = 'removed_personal_data';
$changed = $this->do_bulk_action_remove_personal_data( $ids );
} elseif ( false !== strpos( $action, 'mark_' ) ) {
$order_statuses = wc_get_order_statuses();
$new_status = substr( $action, 5 );
$report_action = 'marked_' . $new_status;
if ( isset( $order_statuses[ 'wc-' . $new_status ] ) ) {
$changed = $this->do_bulk_action_mark_orders( $ids, $new_status );
}
}
if ( $changed ) {
$redirect_to = add_query_arg(
array(
'bulk_action' => $report_action,
'changed' => $changed,
'ids' => implode( ',', $ids ),
),
$redirect_to
);
}
wp_safe_redirect( $redirect_to );
exit;
}
/**
* Implements the "remove personal data" bulk action.
*
* @param array $order_ids The Order IDs.
* @return int Number of orders modified.
*/
private function do_bulk_action_remove_personal_data( $order_ids ): int {
$changed = 0;
foreach ( $order_ids as $id ) {
$order = wc_get_order( $id );
if ( ! $order ) {
continue;
}
do_action( 'woocommerce_remove_order_personal_data', $order ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
$changed++;
}
return $changed;
}
/**
* Implements the "mark <status>" bulk action.
*
* @param array $order_ids The order IDs to change.
* @param string $new_status The new order status.
* @return int Number of orders modified.
*/
private function do_bulk_action_mark_orders( $order_ids, $new_status ): int {
$changed = 0;
// Initialize payment gateways in case order has hooked status transition actions.
WC()->payment_gateways();
foreach ( $order_ids as $id ) {
$order = wc_get_order( $id );
if ( ! $order ) {
continue;
}
$order->update_status( $new_status, __( 'Order status changed by bulk edit.', 'woocommerce' ), true );
do_action( 'woocommerce_order_edit_status', $id, $new_status ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingHookComment
$changed++;
}
return $changed;
}
/**
* Show confirmation message that order status changed for number of orders.
*/
public function bulk_action_notices() {
if ( empty( $_REQUEST['bulk_action'] ) ) {
return;
}
$order_statuses = wc_get_order_statuses();
$number = absint( $_REQUEST['changed'] ?? 0 );
$bulk_action = wc_clean( wp_unslash( $_REQUEST['bulk_action'] ) );
// 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 ) );
echo '<div class="updated"><p>' . esc_html( $message ) . '</p></div>';
}
}
}

View File

@ -0,0 +1,116 @@
<?php
namespace Automattic\WooCommerce\Internal\Admin\Orders;
/**
* Controls the different pages/screens associated to the "Orders" menu page.
*/
class PageController {
/**
* Instance of the orders list table.
*
* @var Automattic\WooCommerce\Internal\Admin\Orders\ListTable
*/
private $orders_table;
/**
* Current action.
*
* @var string
*/
private $current_action = '';
/**
* Sets up the page controller, including registering the menu item.
*
* @return void
*/
public function setup(): void {
// Register menu.
if ( 'admin_menu' === current_action() ) {
$this->register_menu();
} else {
add_action( 'admin_menu', 'register_menu', 9 );
}
$this->set_action();
// Perform initialization for the current action.
add_action(
'load-woocommerce_page_wc-orders',
function() {
if ( method_exists( $this, 'setup_action_' . $this->current_action ) ) {
$this->{"setup_action_{$this->current_action}"}();
}
}
);
}
/**
* Sets the current action based on querystring arguments. Defaults to 'list_orders'.
*
* @return void
*/
private function set_action(): void {
$this->current_action = 'list_orders';
if ( ! empty( $_GET['action'] ) && 'edit' === $_GET['action'] ) {
$this->current_action = 'edit_order';
}
}
/**
* Registers the "Orders" menu.
*
* @return void
*/
public function register_menu(): void {
add_submenu_page(
'woocommerce',
__( 'Orders', 'woocommerce' ),
__( 'Orders', 'woocommerce' ),
'edit_others_shop_orders',
'wc-orders',
array( $this, 'output' )
);
// In some cases (such as if the authoritative order store was changed earlier in the current request) we
// need an extra step to remove the menu entry for the menu post type.
add_action(
'admin_init',
function() {
remove_submenu_page( 'woocommerce', 'edit.php?post_type=shop_order' );
}
);
}
/**
* Outputs content for the current orders screen.
*
* @return void
*/
public function output(): void {
switch ( $this->current_action ) {
case 'list_orders':
default:
$this->orders_table->prepare_items();
$this->orders_table->display();
break;
}
}
/**
* Handles initialization of the orders list table.
*
* @return void
*/
private function setup_action_list_orders(): void {
$this->orders_table = new ListTable();
$this->orders_table->setup();
if ( $this->orders_table->current_action() ) {
$this->orders_table->handle_bulk_actions();
}
}
}