rebase on oct-10-2023
This commit is contained in:
@@ -58,10 +58,10 @@ class COTRedirectionController {
|
||||
$params['_wpnonce'] = wp_create_nonce( 'bulk-posts' );
|
||||
}
|
||||
|
||||
// If an `order` array parameter is present, rename as `post`.
|
||||
if ( isset( $params['order'] ) && is_array( $params['order'] ) ) {
|
||||
$params['post'] = $params['order'];
|
||||
unset( $params['order'] );
|
||||
// If an `id` array parameter is present, rename as `post`.
|
||||
if ( isset( $params['id'] ) && is_array( $params['id'] ) ) {
|
||||
$params['post'] = $params['id'];
|
||||
unset( $params['id'] );
|
||||
}
|
||||
|
||||
$params['post_type'] = 'shop_order';
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\CustomMetaBox;
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\TaxonomiesMetaBox;
|
||||
|
||||
/**
|
||||
* Class Edit.
|
||||
@@ -26,6 +27,13 @@ class Edit {
|
||||
*/
|
||||
private $custom_meta_box;
|
||||
|
||||
/**
|
||||
* Instance of the TaxonomiesMetaBox class. Used to render meta box for taxonomies.
|
||||
*
|
||||
* @var TaxonomiesMetaBox
|
||||
*/
|
||||
private $taxonomies_meta_box;
|
||||
|
||||
/**
|
||||
* Instance of WC_Order to be used in metaboxes.
|
||||
*
|
||||
@@ -47,6 +55,13 @@ class Edit {
|
||||
*/
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* Controller for orders page. Used to determine redirection URLs.
|
||||
*
|
||||
* @var PageController
|
||||
*/
|
||||
private $orders_page_controller;
|
||||
|
||||
/**
|
||||
* Hooks all meta-boxes for order edit page. This is static since this may be called by post edit form rendering.
|
||||
*
|
||||
@@ -96,6 +111,20 @@ class Edit {
|
||||
wp_enqueue_script( 'post' ); // Ensure existing JS libraries are still available for backward compat.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PageController for this edit form. This method is protected to allow child classes to overwrite the PageController object and return custom links.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @return PageController PageController object.
|
||||
*/
|
||||
protected function get_page_controller() {
|
||||
if ( ! isset( $this->orders_page_controller ) ) {
|
||||
$this->orders_page_controller = wc_get_container()->get( PageController::class );
|
||||
}
|
||||
return $this->orders_page_controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup hooks, actions and variables needed to render order edit page.
|
||||
*
|
||||
@@ -103,17 +132,22 @@ class Edit {
|
||||
*/
|
||||
public function setup( \WC_Order $order ) {
|
||||
$this->order = $order;
|
||||
$wc_screen_id = wc_get_page_screen_id( 'shop-order' );
|
||||
$current_screen = get_current_screen();
|
||||
$current_screen->is_block_editor( false );
|
||||
$this->screen_id = $current_screen->id;
|
||||
if ( ! isset( $this->custom_meta_box ) ) {
|
||||
$this->custom_meta_box = wc_get_container()->get( CustomMetaBox::class );
|
||||
}
|
||||
|
||||
if ( ! isset( $this->taxonomies_meta_box ) ) {
|
||||
$this->taxonomies_meta_box = wc_get_container()->get( TaxonomiesMetaBox::class );
|
||||
}
|
||||
|
||||
$this->add_save_meta_boxes();
|
||||
$this->handle_order_update();
|
||||
$this->add_order_meta_boxes( $this->screen_id, __( 'Order', 'woocommerce' ) );
|
||||
$this->add_order_specific_meta_box();
|
||||
$this->add_order_taxonomies_meta_box();
|
||||
|
||||
/**
|
||||
* From wp-admin/includes/meta-boxes.php.
|
||||
@@ -122,7 +156,7 @@ class Edit {
|
||||
*
|
||||
* @since 3.8.0.
|
||||
*/
|
||||
do_action( 'add_meta_boxes', $wc_screen_id, $this->order );
|
||||
do_action( 'add_meta_boxes', $this->screen_id, $this->order );
|
||||
|
||||
/**
|
||||
* Provides an opportunity to inject custom meta boxes into the order editor screen. This
|
||||
@@ -132,7 +166,7 @@ class Edit {
|
||||
*
|
||||
* @oaram WC_Order $order The order being edited.
|
||||
*/
|
||||
do_action( 'add_meta_boxes_' . $wc_screen_id, $this->order );
|
||||
do_action( 'add_meta_boxes_' . $this->screen_id, $this->order );
|
||||
|
||||
$this->enqueue_scripts();
|
||||
}
|
||||
@@ -159,6 +193,15 @@ class Edit {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render custom meta box.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function add_order_taxonomies_meta_box() {
|
||||
$this->taxonomies_meta_box->add_taxonomies_meta_boxes( $this->screen_id, $this->order->get_type() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of updating order data. Fires action that metaboxes can hook to for order data updating.
|
||||
*
|
||||
@@ -176,6 +219,10 @@ class Edit {
|
||||
|
||||
check_admin_referer( $this->get_order_edit_nonce_action() );
|
||||
|
||||
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitized later on by taxonomies_meta_box object.
|
||||
$taxonomy_input = isset( $_POST['tax_input'] ) ? wp_unslash( $_POST['tax_input'] ) : null;
|
||||
$this->taxonomies_meta_box->save_taxonomies( $this->order, $taxonomy_input );
|
||||
|
||||
/**
|
||||
* Save meta for shop order.
|
||||
*
|
||||
@@ -189,9 +236,39 @@ class Edit {
|
||||
// Order updated message.
|
||||
$this->message = 1;
|
||||
|
||||
// Refresh the order from DB.
|
||||
$this->order = wc_get_order( $this->order->get_id() );
|
||||
$theorder = $this->order;
|
||||
$this->redirect_order( $this->order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to redirect to order edit page.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
*/
|
||||
private function redirect_order( \WC_Order $order ) {
|
||||
$redirect_to = $this->get_page_controller()->get_edit_url( $order->get_id() );
|
||||
if ( isset( $this->message ) ) {
|
||||
$redirect_to = add_query_arg( 'message', $this->message, $redirect_to );
|
||||
}
|
||||
wp_safe_redirect(
|
||||
/**
|
||||
* Filter the URL used to redirect after an order is updated. Similar to the WP post's `redirect_post_location` filter.
|
||||
*
|
||||
* @param string $redirect_to The redirect destination URL.
|
||||
* @param int $order_id The order ID.
|
||||
* @param \WC_Order $order The order object.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
apply_filters(
|
||||
'woocommerce_redirect_order_location',
|
||||
$redirect_to,
|
||||
$order->get_id(),
|
||||
$order
|
||||
)
|
||||
);
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,10 +330,10 @@ class Edit {
|
||||
private function render_wrapper_start( $notice = '', $message = '' ) {
|
||||
$post_type = get_post_type_object( $this->order->get_type() );
|
||||
|
||||
$edit_page_url = wc_get_container()->get( PageController::class )->get_edit_url( $this->order->get_id() );
|
||||
$edit_page_url = $this->get_page_controller()->get_edit_url( $this->order->get_id() );
|
||||
$form_action = 'edit_order';
|
||||
$referer = wp_get_referer();
|
||||
$new_page_url = wc_get_container()->get( PageController::class )->get_new_page_url( $this->order->get_type() );
|
||||
$new_page_url = $this->get_page_controller()->get_new_page_url( $this->order->get_type() );
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
@@ -298,9 +375,20 @@ class Edit {
|
||||
?>
|
||||
>
|
||||
<?php wp_nonce_field( $this->get_order_edit_nonce_action() ); ?>
|
||||
<?php
|
||||
/**
|
||||
* Fires at the top of the order edit form. Can be used as a replacement for edit_form_top hook for HPOS.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
*
|
||||
* @since 8.0.0
|
||||
*/
|
||||
do_action( 'order_edit_form_top', $this->order );
|
||||
?>
|
||||
<input type="hidden" id="hiddenaction" name="action" value="<?php echo esc_attr( $form_action ); ?>"/>
|
||||
<input type="hidden" id="original_order_status" name="original_order_status" value="<?php echo esc_attr( $this->order->get_status() ); ?>"/>
|
||||
<input type="hidden" id="referredby" name="referredby" value="<?php echo $referer ? esc_url( $referer ) : ''; ?>"/>
|
||||
<input type="hidden" id="post_ID" name="post_ID" value="<?php echo esc_attr( $this->order->get_id() ); ?>"/>
|
||||
<div id="poststuff">
|
||||
<div id="post-body"
|
||||
class="metabox-holder columns-<?php echo ( 1 === get_current_screen()->get_columns() ) ? '1' : '2'; ?>">
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||
|
||||
/**
|
||||
* This class takes care of the edit lock logic when HPOS is enabled.
|
||||
* For better interoperability with WordPress, edit locks are stored in the same format as posts. That is, as a metadata
|
||||
* in the order object (key: '_edit_lock') in the format "timestamp:user_id".
|
||||
*
|
||||
* @since 7.8.0
|
||||
*/
|
||||
class EditLock {
|
||||
|
||||
const META_KEY_NAME = '_edit_lock';
|
||||
|
||||
/**
|
||||
* Obtains lock information for a given order. If the lock has expired or it's assigned to an invalid user,
|
||||
* the order is no longer considered locked.
|
||||
*
|
||||
* @param \WC_Order $order Order to check.
|
||||
* @return bool|array
|
||||
*/
|
||||
public function get_lock( \WC_Order $order ) {
|
||||
$lock = $order->get_meta( self::META_KEY_NAME, true, 'edit' );
|
||||
if ( ! $lock ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$lock = explode( ':', $lock );
|
||||
if ( 2 !== count( $lock ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time = absint( $lock[0] );
|
||||
$user_id = isset( $lock[1] ) ? absint( $lock[1] ) : 0;
|
||||
|
||||
if ( ! $time || ! get_user_by( 'id', $user_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** This filter is documented in WP's wp-admin/includes/ajax-actions.php */
|
||||
$time_window = apply_filters( 'wp_check_post_lock_window', 150 ); // phpcs:ignore WooCommerce.Commenting.CommentHooks.MissingSinceComment
|
||||
if ( time() >= ( $time + $time_window ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return compact( 'time', 'user_id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the order is being edited (i.e. locked) by another user.
|
||||
*
|
||||
* @param \WC_Order $order Order to check.
|
||||
* @return bool TRUE if order is locked and currently being edited by another user. FALSE otherwise.
|
||||
*/
|
||||
public function is_locked_by_another_user( \WC_Order $order ) : bool {
|
||||
$lock = $this->get_lock( $order );
|
||||
return $lock && ( get_current_user_id() !== $lock['user_id'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the order is being edited by any user.
|
||||
*
|
||||
* @param \WC_Order $order Order to check.
|
||||
* @return boolean TRUE if order is locked and currently being edited by a user. FALSE otherwise.
|
||||
*/
|
||||
public function is_locked( \WC_Order $order ) : bool {
|
||||
return (bool) $this->get_lock( $order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns an order's edit lock to the current user.
|
||||
*
|
||||
* @param \WC_Order $order The order to apply the lock to.
|
||||
* @return array|bool FALSE if no user is logged-in, an array in the same format as {@see get_lock()} otherwise.
|
||||
*/
|
||||
public function lock( \WC_Order $order ) {
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( ! $user_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order->update_meta_data( self::META_KEY_NAME, time() . ':' . $user_id );
|
||||
$order->save_meta_data();
|
||||
|
||||
return $order->get_meta( self::META_KEY_NAME, true, 'edit' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked to 'heartbeat_received' on the edit order page to refresh the lock on an order being edited by the current user.
|
||||
*
|
||||
* @param array $response The heartbeat response to be sent.
|
||||
* @param array $data Data sent through the heartbeat.
|
||||
* @return array Response to be sent.
|
||||
*/
|
||||
public function refresh_lock_ajax( $response, $data ) {
|
||||
$order_id = absint( $data['wc-refresh-order-lock'] ?? 0 );
|
||||
if ( ! $order_id ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( ! $order || ( ! current_user_can( get_post_type_object( $order->get_type() )->cap->edit_post, $order->get_id() ) && ! current_user_can( 'manage_woocommerce' ) ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response['wc-refresh-order-lock'] = array();
|
||||
|
||||
if ( ! $this->is_locked_by_another_user( $order ) ) {
|
||||
$response['wc-refresh-order-lock']['lock'] = $this->lock( $order );
|
||||
} else {
|
||||
$current_lock = $this->get_lock( $order );
|
||||
$user = get_user_by( 'id', $current_lock['user_id'] );
|
||||
|
||||
$response['wc-refresh-order-lock']['error'] = array(
|
||||
// translators: %s is a user's name.
|
||||
'message' => sprintf( __( '%s has taken over and is currently editing.', 'woocommerce' ), $user->display_name ),
|
||||
'user_name' => $user->display_name,
|
||||
'user_avatar_src' => get_option( 'show_avatars' ) ? get_avatar_url( $user->ID, array( 'size' => 64 ) ) : '',
|
||||
'user_avatar_src_2x' => get_option( 'show_avatars' ) ? get_avatar_url( $user->ID, array( 'size' => 128 ) ) : '',
|
||||
);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooked to 'heartbeat_received' on the orders screen to refresh the locked status of orders in the list table.
|
||||
*
|
||||
* @param array $response The heartbeat response to be sent.
|
||||
* @param array $data Data sent through the heartbeat.
|
||||
* @return array Response to be sent.
|
||||
*/
|
||||
public function check_locked_orders_ajax( $response, $data ) {
|
||||
if ( empty( $data['wc-check-locked-orders'] ) || ! is_array( $data['wc-check-locked-orders'] ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response['wc-check-locked-orders'] = array();
|
||||
|
||||
$order_ids = array_unique( array_map( 'absint', $data['wc-check-locked-orders'] ) );
|
||||
foreach ( $order_ids as $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( ! $order ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $this->is_locked_by_another_user( $order ) || ( ! current_user_can( get_post_type_object( $order->get_type() )->cap->edit_post, $order->get_id() ) && ! current_user_can( 'manage_woocommerce' ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$response['wc-check-locked-orders'][ $order_id ] = true;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs HTML for the lock dialog based on the status of the lock on the order (if any).
|
||||
* Depending on who owns the lock, this could be a message with the chance to take over or a message indicating that
|
||||
* someone else has taken over the order.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
* @return void
|
||||
*/
|
||||
public function render_dialog( $order ) {
|
||||
$locked = $this->is_locked_by_another_user( $order );
|
||||
$lock = $this->get_lock( $order );
|
||||
$user = get_user_by( 'id', $lock['user_id'] );
|
||||
|
||||
$edit_url = wc_get_container()->get( \Automattic\WooCommerce\Internal\Admin\Orders\PageController::class )->get_edit_url( $order->get_id() );
|
||||
|
||||
$sendback_url = wp_get_referer();
|
||||
if ( ! $sendback_url ) {
|
||||
$sendback_url = wc_get_container()->get( \Automattic\WooCommerce\Internal\Admin\Orders\PageController::class )->get_base_page_url( $order->get_type() );
|
||||
}
|
||||
|
||||
$sendback_text = __( 'Go back', 'woocommerce' );
|
||||
?>
|
||||
<div id="post-lock-dialog" class="notification-dialog-wrap <?php echo $locked ? '' : 'hidden'; ?> order-lock-dialog">
|
||||
<div class="notification-dialog-background"></div>
|
||||
<div class="notification-dialog">
|
||||
<?php if ( $locked ) : ?>
|
||||
<div class="post-locked-message">
|
||||
<div class="post-locked-avatar"><?php echo get_avatar( $user->ID, 64 ); ?></div>
|
||||
<p class="currently-editing wp-tab-first" tabindex="0">
|
||||
<?php
|
||||
// translators: %s is a user's name.
|
||||
echo esc_html( sprintf( __( '%s is currently editing this order. Do you want to take over?', 'woocommerce' ), esc_html( $user->display_name ) ) );
|
||||
?>
|
||||
</p>
|
||||
<p>
|
||||
<a class="button" href="<?php echo esc_url( $sendback_url ); ?>"><?php echo esc_html( $sendback_text ); ?></a>
|
||||
<a class="button button-primary wp-tab-last" href="<?php echo esc_url( add_query_arg( 'claim-lock', '1', wp_nonce_url( $edit_url, 'claim-lock-' . $order->get_id() ) ) ); ?>"><?php esc_html_e( 'Take over', 'woocommerce' ); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<div class="post-taken-over">
|
||||
<div class="post-locked-avatar"></div>
|
||||
<p class="wp-tab-first" tabindex="0">
|
||||
<span class="currently-editing"></span><br />
|
||||
</p>
|
||||
<p><a class="button button-primary wp-tab-last" href="<?php echo esc_url( $sendback_url ); ?>"><?php echo esc_html( $sendback_text ); ?></a></p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,6 +4,7 @@ namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
use WC_Order;
|
||||
use WP_List_Table;
|
||||
use WP_Screen;
|
||||
@@ -109,6 +110,44 @@ class ListTable extends WP_List_Table {
|
||||
add_action( 'manage_' . wc_get_page_screen_id( $this->order_type ) . '_custom_column', array( $this, 'render_column' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates content for a single row of the table.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*
|
||||
* @param \WC_Order $order The current order.
|
||||
*/
|
||||
public function single_row( $order ) {
|
||||
/**
|
||||
* Filters the list of CSS class names for a given order row in the orders list table.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*
|
||||
* @param string[] $classes An array of CSS class names.
|
||||
* @param \WC_Order $order The order object.
|
||||
*/
|
||||
$css_classes = apply_filters(
|
||||
'woocommerce_' . $this->order_type . '_list_table_order_css_classes',
|
||||
array(
|
||||
'order-' . $order->get_id(),
|
||||
'type-' . $order->get_type(),
|
||||
'status-' . $order->get_status(),
|
||||
),
|
||||
$order
|
||||
);
|
||||
$css_classes = array_unique( array_map( 'trim', $css_classes ) );
|
||||
|
||||
// Is locked?
|
||||
$edit_lock = wc_get_container()->get( EditLock::class );
|
||||
if ( $edit_lock->is_locked_by_another_user( $order ) ) {
|
||||
$css_classes[] = 'wp-locked';
|
||||
}
|
||||
|
||||
echo '<tr id="order-' . esc_attr( $order->get_id() ) . '" class="' . esc_attr( implode( ' ', $css_classes ) ) . '">';
|
||||
$this->single_row_columns( $order );
|
||||
echo '</tr>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Render individual column.
|
||||
*
|
||||
@@ -276,6 +315,37 @@ class ListTable extends WP_List_Table {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of CSS classes for the WP_List_Table table tag.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*
|
||||
* @return string[] Array of CSS classes for the table tag.
|
||||
*/
|
||||
protected function get_table_classes() {
|
||||
/**
|
||||
* Filters the list of CSS class names for the orders list table.
|
||||
*
|
||||
* @since 7.8.0
|
||||
*
|
||||
* @param string[] $classes An array of CSS class names.
|
||||
* @param string $order_type The order type.
|
||||
*/
|
||||
$css_classes = apply_filters(
|
||||
'woocommerce_' . $this->order_type . '_list_table_css_classes',
|
||||
array_merge(
|
||||
parent::get_table_classes(),
|
||||
array(
|
||||
'wc-orders-list-table',
|
||||
'wc-orders-list-table-' . $this->order_type,
|
||||
)
|
||||
),
|
||||
$this->order_type
|
||||
);
|
||||
|
||||
return array_unique( array_map( 'trim', $css_classes ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the list of items for displaying.
|
||||
*/
|
||||
@@ -471,7 +541,7 @@ class ListTable extends WP_List_Table {
|
||||
$view_counts[ $slug ] = $total_in_status;
|
||||
}
|
||||
|
||||
if ( ( get_post_status_object( $slug ) )->show_in_admin_all_list ) {
|
||||
if ( ( get_post_status_object( $slug ) )->show_in_admin_all_list && 'auto-draft' !== $slug ) {
|
||||
$all_count += $total_in_status;
|
||||
}
|
||||
}
|
||||
@@ -550,8 +620,9 @@ class ListTable extends WP_List_Table {
|
||||
array_merge(
|
||||
wc_get_order_statuses(),
|
||||
array(
|
||||
'trash' => ( get_post_status_object( 'trash' ) )->label,
|
||||
'draft' => ( get_post_status_object( 'draft' ) )->label,
|
||||
'trash' => ( get_post_status_object( 'trash' ) )->label,
|
||||
'draft' => ( get_post_status_object( 'draft' ) )->label,
|
||||
'auto-draft' => ( get_post_status_object( 'auto-draft' ) )->label,
|
||||
)
|
||||
),
|
||||
array_flip( get_post_stati( array( 'show_in_admin_status_list' => true ) ) )
|
||||
@@ -794,7 +865,21 @@ class ListTable extends WP_List_Table {
|
||||
* @return string
|
||||
*/
|
||||
public function column_cb( $item ) {
|
||||
return sprintf( '<input type="checkbox" name="%1$s[]" value="%2$s" />', esc_attr( $this->_args['singular'] ), esc_attr( $item->get_id() ) );
|
||||
ob_start();
|
||||
?>
|
||||
<input id="cb-select-<?php echo esc_attr( $item->get_id() ); ?>" type="checkbox" name="id[]" value="<?php echo esc_attr( $item->get_id() ); ?>" />
|
||||
|
||||
<div class="locked-indicator">
|
||||
<span class="locked-indicator-icon" aria-hidden="true"></span>
|
||||
<span class="screen-reader-text">
|
||||
<?php
|
||||
// translators: %s is an order ID.
|
||||
echo esc_html( sprintf( __( 'Order %s is locked.', 'woocommerce' ), $item->get_id() ) );
|
||||
?>
|
||||
</span>
|
||||
</div>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -916,7 +1001,7 @@ class ListTable extends WP_List_Table {
|
||||
}
|
||||
|
||||
// Gracefully handle legacy statuses.
|
||||
if ( in_array( $order->get_status(), array( 'trash', 'draft' ), true ) ) {
|
||||
if ( in_array( $order->get_status(), array( 'trash', 'draft', 'auto-draft' ), true ) ) {
|
||||
$status_name = ( get_post_status_object( $order->get_status() ) )->label;
|
||||
} else {
|
||||
$status_name = wc_get_order_status_name( $order->get_status() );
|
||||
@@ -1112,7 +1197,7 @@ class ListTable extends WP_List_Table {
|
||||
|
||||
$action = 'delete';
|
||||
} else {
|
||||
$ids = isset( $_REQUEST['order'] ) ? array_reverse( array_map( 'absint', $_REQUEST['order'] ) ) : array();
|
||||
$ids = isset( $_REQUEST['id'] ) ? array_reverse( array_map( 'absint', (array) $_REQUEST['id'] ) ) : array();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1132,8 +1217,9 @@ class ListTable extends WP_List_Table {
|
||||
exit;
|
||||
}
|
||||
|
||||
$report_action = '';
|
||||
$changed = 0;
|
||||
$report_action = '';
|
||||
$changed = 0;
|
||||
$action_handled = true;
|
||||
|
||||
if ( 'remove_personal_data' === $action ) {
|
||||
$report_action = 'removed_personal_data';
|
||||
@@ -1154,8 +1240,15 @@ class ListTable extends WP_List_Table {
|
||||
|
||||
if ( isset( $order_statuses[ 'wc-' . $new_status ] ) ) {
|
||||
$changed = $this->do_bulk_action_mark_orders( $ids, $new_status );
|
||||
} else {
|
||||
$action_handled = false;
|
||||
}
|
||||
} else {
|
||||
$action_handled = false;
|
||||
}
|
||||
|
||||
// Custom action.
|
||||
if ( ! $action_handled ) {
|
||||
$screen = get_current_screen()->id;
|
||||
|
||||
/**
|
||||
@@ -1247,13 +1340,11 @@ class ListTable extends WP_List_Table {
|
||||
* @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 );
|
||||
$order->delete( $force_delete );
|
||||
$updated_order = wc_get_order( $id );
|
||||
|
||||
if ( ( $force_delete && false === $updated_order ) || ( ! $force_delete && $updated_order->get_status() === 'trash' ) ) {
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
|
||||
/**
|
||||
* TaxonomiesMetaBox class, renders taxonomy sidebar widget on order edit screen.
|
||||
*/
|
||||
class TaxonomiesMetaBox {
|
||||
|
||||
/**
|
||||
* Order Table data store class.
|
||||
*
|
||||
* @var OrdersTableDataStore
|
||||
*/
|
||||
private $orders_table_data_store;
|
||||
|
||||
/**
|
||||
* Dependency injection init method.
|
||||
*
|
||||
* @param OrdersTableDataStore $orders_table_data_store Order Table data store class.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function init( OrdersTableDataStore $orders_table_data_store ) {
|
||||
$this->orders_table_data_store = $orders_table_data_store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers meta boxes to be rendered in order edit screen for taxonomies.
|
||||
*
|
||||
* Note: This is re-implementation of part of WP core's `register_and_do_post_meta_boxes` function. Since the code block that add meta box for taxonomies is not filterable, we have to re-implement it.
|
||||
*
|
||||
* @param string $screen_id Screen ID.
|
||||
* @param string $order_type Order type to register meta boxes for.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function add_taxonomies_meta_boxes( string $screen_id, string $order_type ) {
|
||||
include_once ABSPATH . 'wp-admin/includes/meta-boxes.php';
|
||||
$taxonomies = get_object_taxonomies( $order_type );
|
||||
// All taxonomies.
|
||||
foreach ( $taxonomies as $tax_name ) {
|
||||
$taxonomy = get_taxonomy( $tax_name );
|
||||
if ( ! $taxonomy->show_ui || false === $taxonomy->meta_box_cb ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 'post_categories_meta_box' === $taxonomy->meta_box_cb ) {
|
||||
$taxonomy->meta_box_cb = array( $this, 'order_categories_meta_box' );
|
||||
}
|
||||
|
||||
if ( 'post_tags_meta_box' === $taxonomy->meta_box_cb ) {
|
||||
$taxonomy->meta_box_cb = array( $this, 'order_tags_meta_box' );
|
||||
}
|
||||
|
||||
$label = $taxonomy->labels->name;
|
||||
|
||||
if ( ! is_taxonomy_hierarchical( $tax_name ) ) {
|
||||
$tax_meta_box_id = 'tagsdiv-' . $tax_name;
|
||||
} else {
|
||||
$tax_meta_box_id = $tax_name . 'div';
|
||||
}
|
||||
|
||||
add_meta_box(
|
||||
$tax_meta_box_id,
|
||||
$label,
|
||||
$taxonomy->meta_box_cb,
|
||||
$screen_id,
|
||||
'side',
|
||||
'core',
|
||||
array(
|
||||
'taxonomy' => $tax_name,
|
||||
'__back_compat_meta_box' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save handler for taxonomy data.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Order object.
|
||||
* @param array|null $taxonomy_input Taxonomy input passed from input.
|
||||
*/
|
||||
public function save_taxonomies( \WC_Abstract_Order $order, $taxonomy_input ) {
|
||||
if ( ! isset( $taxonomy_input ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$sanitized_tax_input = $this->sanitize_tax_input( $taxonomy_input );
|
||||
|
||||
$sanitized_tax_input = $this->orders_table_data_store->init_default_taxonomies( $order, $sanitized_tax_input );
|
||||
$this->orders_table_data_store->set_custom_taxonomies( $order, $sanitized_tax_input );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize taxonomy input by calling sanitize callbacks for each registered taxonomy.
|
||||
*
|
||||
* @param array|null $taxonomy_data Nonce verified taxonomy input.
|
||||
*
|
||||
* @return array Sanitized taxonomy input.
|
||||
*/
|
||||
private function sanitize_tax_input( $taxonomy_data ) : array {
|
||||
$sanitized_tax_input = array();
|
||||
if ( ! is_array( $taxonomy_data ) ) {
|
||||
return $sanitized_tax_input;
|
||||
}
|
||||
|
||||
// Convert taxonomy input to term IDs, to avoid ambiguity.
|
||||
foreach ( $taxonomy_data as $taxonomy => $terms ) {
|
||||
$tax_object = get_taxonomy( $taxonomy );
|
||||
if ( $tax_object && isset( $tax_object->meta_box_sanitize_cb ) ) {
|
||||
$sanitized_tax_input[ $taxonomy ] = call_user_func_array( $tax_object->meta_box_sanitize_cb, array( $taxonomy, $terms ) );
|
||||
}
|
||||
}
|
||||
|
||||
return $sanitized_tax_input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the categories meta box to the order screen. This is just a wrapper around the post_categories_meta_box.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Order object.
|
||||
* @param array $box Meta box args.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function order_categories_meta_box( $order, $box ) {
|
||||
$post = get_post( $order->get_id() );
|
||||
post_categories_meta_box( $post, $box );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the tags meta box to the order screen. This is just a wrapper around the post_tags_meta_box.
|
||||
*
|
||||
* @param \WC_Abstract_Order $order Order object.
|
||||
* @param array $box Meta box args.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function order_tags_meta_box( $order, $box ) {
|
||||
$post = get_post( $order->get_id() );
|
||||
post_tags_meta_box( $post, $box );
|
||||
}
|
||||
}
|
||||
@@ -91,12 +91,48 @@ class PageController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Claims the lock for the order being edited/created (unless it belongs to someone else).
|
||||
* Also handles the 'claim-lock' action which allows taking over the order forcefully.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handle_edit_lock() {
|
||||
if ( ! $this->order ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$edit_lock = wc_get_container()->get( EditLock::class );
|
||||
|
||||
$locked = $edit_lock->is_locked_by_another_user( $this->order );
|
||||
|
||||
// Take over order?
|
||||
if ( ! empty( $_GET['claim-lock'] ) && wp_verify_nonce( $_GET['_wpnonce'] ?? '', 'claim-lock-' . $this->order->get_id() ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized,WordPress.Security.ValidatedSanitizedInput.MissingUnslash
|
||||
$edit_lock->lock( $this->order );
|
||||
wp_safe_redirect( $this->get_edit_url( $this->order->get_id() ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( ! $locked ) {
|
||||
$edit_lock->lock( $this->order );
|
||||
}
|
||||
|
||||
add_action(
|
||||
'admin_footer',
|
||||
function() use ( $edit_lock ) {
|
||||
$edit_lock->render_dialog( $this->order );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the page controller, including registering the menu item.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup(): void {
|
||||
global $plugin_page, $pagenow;
|
||||
|
||||
$this->redirection_controller = new PostsRedirectionController( $this );
|
||||
|
||||
// Register menu.
|
||||
@@ -106,34 +142,81 @@ class PageController {
|
||||
add_action( 'admin_menu', 'register_menu', 9 );
|
||||
}
|
||||
|
||||
// Not on an Orders page.
|
||||
if ( 'admin.php' !== $pagenow || 0 !== strpos( $plugin_page, 'wc-orders' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->set_order_type();
|
||||
$this->set_action();
|
||||
|
||||
$page_suffix = ( 'shop_order' === $this->order_type ? '' : '--' . $this->order_type );
|
||||
|
||||
self::add_action( 'load-woocommerce_page_wc-orders' . $page_suffix, array( $this, 'handle_load_page_action' ) );
|
||||
self::add_action( 'admin_title', array( $this, 'set_page_title' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform initialization for the current action.
|
||||
*/
|
||||
private function handle_load_page_action() {
|
||||
$screen = get_current_screen();
|
||||
$screen->post_type = $this->order_type;
|
||||
|
||||
if ( method_exists( $this, 'setup_action_' . $this->current_action ) ) {
|
||||
$this->{"setup_action_{$this->current_action}"}();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the document title for Orders screens to match what it would be with the shop_order CPT.
|
||||
*
|
||||
* @param string $admin_title The admin screen title before it's filtered.
|
||||
*
|
||||
* @return string The filtered admin title.
|
||||
*/
|
||||
private function set_page_title( $admin_title ) {
|
||||
if ( ! $this->is_order_screen( $this->order_type ) ) {
|
||||
return $admin_title;
|
||||
}
|
||||
|
||||
$wp_order_type = get_post_type_object( $this->order_type );
|
||||
$labels = get_post_type_labels( $wp_order_type );
|
||||
|
||||
if ( $this->is_order_screen( $this->order_type, 'list' ) ) {
|
||||
$admin_title = sprintf(
|
||||
// translators: 1: The label for an order type 2: The name of the website.
|
||||
esc_html__( '%1$s ‹ %2$s — WordPress', 'woocommerce' ),
|
||||
esc_html( $labels->name ),
|
||||
esc_html( get_bloginfo( 'name' ) )
|
||||
);
|
||||
} elseif ( $this->is_order_screen( $this->order_type, 'edit' ) ) {
|
||||
$admin_title = sprintf(
|
||||
// translators: 1: The label for an order type 2: The title of the order 3: The name of the website.
|
||||
esc_html__( '%1$s #%2$s ‹ %3$s — WordPress', 'woocommerce' ),
|
||||
esc_html( $labels->edit_item ),
|
||||
absint( $this->order->get_id() ),
|
||||
esc_html( get_bloginfo( 'name' ) )
|
||||
);
|
||||
} elseif ( $this->is_order_screen( $this->order_type, 'new' ) ) {
|
||||
$admin_title = sprintf(
|
||||
// translators: 1: The label for an order type 2: The name of the website.
|
||||
esc_html__( '%1$s ‹ %2$s — WordPress', 'woocommerce' ),
|
||||
esc_html( $labels->add_new_item ),
|
||||
esc_html( get_bloginfo( 'name' ) )
|
||||
);
|
||||
}
|
||||
|
||||
return $admin_title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the order type for the current screen.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function set_order_type() {
|
||||
global $plugin_page, $pagenow;
|
||||
|
||||
if ( 'admin.php' !== $pagenow || 0 !== strpos( $plugin_page, 'wc-orders' ) ) {
|
||||
return;
|
||||
}
|
||||
global $plugin_page;
|
||||
|
||||
$this->order_type = str_replace( array( 'wc-orders--', 'wc-orders' ), '', $plugin_page );
|
||||
$this->order_type = empty( $this->order_type ) ? 'shop_order' : $this->order_type;
|
||||
@@ -207,11 +290,6 @@ class PageController {
|
||||
switch ( $this->current_action ) {
|
||||
case 'edit_order':
|
||||
case 'new_order':
|
||||
if ( ! isset( $this->order_edit_form ) ) {
|
||||
$this->order_edit_form = new Edit();
|
||||
$this->order_edit_form->setup( $this->order );
|
||||
}
|
||||
$this->order_edit_form->set_current_action( $this->current_action );
|
||||
$this->order_edit_form->display();
|
||||
break;
|
||||
case 'list_orders':
|
||||
@@ -257,6 +335,22 @@ class PageController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the order edit form for creating or editing an order.
|
||||
*
|
||||
* @see \Automattic\WooCommerce\Internal\Admin\Orders\Edit.
|
||||
* @since 8.1.0
|
||||
*/
|
||||
private function prepare_order_edit_form(): void {
|
||||
if ( ! $this->order || ! in_array( $this->current_action, array( 'new_order', 'edit_order' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->order_edit_form = $this->order_edit_form ?? new Edit();
|
||||
$this->order_edit_form->setup( $this->order );
|
||||
$this->order_edit_form->set_current_action( $this->current_action );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles initialization of the orders edit form.
|
||||
*
|
||||
@@ -266,7 +360,10 @@ class PageController {
|
||||
global $theorder;
|
||||
$this->order = wc_get_order( absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 ) );
|
||||
$this->verify_edit_permission();
|
||||
$this->handle_edit_lock();
|
||||
$theorder = $this->order;
|
||||
|
||||
$this->prepare_order_edit_form();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,10 +383,18 @@ class PageController {
|
||||
|
||||
$this->order = new $order_class_name();
|
||||
$this->order->set_object_read( false );
|
||||
$this->order->set_status( 'pending' );
|
||||
$this->order->set_status( 'auto-draft' );
|
||||
$this->order->save();
|
||||
$this->handle_edit_lock();
|
||||
|
||||
// Schedule auto-draft cleanup. We re-use the WP event here on purpose.
|
||||
if ( ! wp_next_scheduled( 'wp_scheduled_auto_draft_delete' ) ) {
|
||||
wp_schedule_event( time(), 'daily', 'wp_scheduled_auto_draft_delete' );
|
||||
}
|
||||
|
||||
$theorder = $this->order;
|
||||
|
||||
$this->prepare_order_edit_form();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -384,4 +489,89 @@ class PageController {
|
||||
return admin_url( 'admin.php?page=wc-orders' . ( 'shop_order' === $order_type ? '' : '--' . $order_type ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check if the current admin screen is related to orders.
|
||||
*
|
||||
* @param string $type Optional. The order type to check for. Default shop_order.
|
||||
* @param string $action Optional. The purpose of the screen to check for. 'list', 'edit', or 'new'.
|
||||
* Leave empty to check for any order screen.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_order_screen( $type = 'shop_order', $action = '' ) : bool {
|
||||
if ( ! did_action( 'current_screen' ) ) {
|
||||
wc_doing_it_wrong(
|
||||
__METHOD__,
|
||||
sprintf(
|
||||
// translators: %s is the name of a function.
|
||||
esc_html__( '%s must be called after the current_screen action.', 'woocommerce' ),
|
||||
esc_html( __METHOD__ )
|
||||
),
|
||||
'7.9.0'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$valid_types = wc_get_order_types( 'view-order' );
|
||||
if ( ! in_array( $type, $valid_types, true ) ) {
|
||||
wc_doing_it_wrong(
|
||||
__METHOD__,
|
||||
sprintf(
|
||||
// translators: %s is the name of an order type.
|
||||
esc_html__( '%s is not a valid order type.', 'woocommerce' ),
|
||||
esc_html( $type )
|
||||
),
|
||||
'7.9.0'
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
|
||||
if ( $action ) {
|
||||
switch ( $action ) {
|
||||
case 'edit':
|
||||
$is_action = 'edit_order' === $this->current_action;
|
||||
break;
|
||||
case 'list':
|
||||
$is_action = 'list_orders' === $this->current_action;
|
||||
break;
|
||||
case 'new':
|
||||
$is_action = 'new_order' === $this->current_action;
|
||||
break;
|
||||
default:
|
||||
$is_action = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$type_match = $type === $this->order_type;
|
||||
$action_match = ! $action || $is_action;
|
||||
} else {
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( $action ) {
|
||||
switch ( $action ) {
|
||||
case 'edit':
|
||||
$screen_match = 'post' === $screen->base && filter_input( INPUT_GET, 'post', FILTER_VALIDATE_INT );
|
||||
break;
|
||||
case 'list':
|
||||
$screen_match = 'edit' === $screen->base;
|
||||
break;
|
||||
case 'new':
|
||||
$screen_match = 'post' === $screen->base && 'add' === $screen->action;
|
||||
break;
|
||||
default:
|
||||
$screen_match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$type_match = $type === $screen->post_type;
|
||||
$action_match = ! $action || $screen_match;
|
||||
}
|
||||
|
||||
return $type_match && $action_match;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@ class PostsRedirectionController {
|
||||
$new_url = add_query_arg(
|
||||
array(
|
||||
'action' => $action,
|
||||
'order' => $posts,
|
||||
'id' => $posts,
|
||||
'_wp_http_referer' => $this->page_controller->get_orders_url(),
|
||||
'_wpnonce' => wp_create_nonce( 'bulk-orders' ),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user