first commit
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* When Custom Order Tables are not the default order store (ie, posts are authoritative), we should take care of
|
||||
* redirecting requests for the order editor and order admin list table to the equivalent posts-table screens.
|
||||
*
|
||||
* If the redirect logic is problematic, it can be unhooked using code like the following example:
|
||||
*
|
||||
* remove_action(
|
||||
* 'admin_page_access_denied',
|
||||
* array( wc_get_container()->get( COTRedirectionController::class ), 'handle_hpos_admin_requests' )
|
||||
* );
|
||||
*/
|
||||
class COTRedirectionController {
|
||||
use AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* Add hooks needed to perform our magic.
|
||||
*/
|
||||
public function setup(): void {
|
||||
// Only take action in cases where access to the admin screen would otherwise be denied.
|
||||
self::add_action( 'admin_page_access_denied', array( $this, 'handle_hpos_admin_requests' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for denied admin requests and, if they appear to relate to HPOS admin screens, potentially
|
||||
* redirect the user to the equivalent CPT-driven screens.
|
||||
*
|
||||
* @param array|null $query_params The query parameters to use when determining the redirect. If not provided, the $_GET superglobal will be used.
|
||||
*/
|
||||
private function handle_hpos_admin_requests( $query_params = null ) {
|
||||
$query_params = is_array( $query_params ) ? $query_params : $_GET;
|
||||
|
||||
if ( ! isset( $query_params['page'] ) || 'wc-orders' !== $query_params['page'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$params = wp_unslash( $query_params );
|
||||
$action = $params['action'] ?? '';
|
||||
unset( $params['page'] );
|
||||
|
||||
if ( 'edit' === $action && isset( $params['id'] ) ) {
|
||||
$params['post'] = $params['id'];
|
||||
unset( $params['id'] );
|
||||
$new_url = add_query_arg( $params, get_admin_url( null, 'post.php' ) );
|
||||
} elseif ( 'new' === $action ) {
|
||||
unset( $params['action'] );
|
||||
$params['post_type'] = 'shop_order';
|
||||
$new_url = add_query_arg( $params, get_admin_url( null, 'post-new.php' ) );
|
||||
} else {
|
||||
// If nonce parameters are present and valid, rebuild them for the CPT admin list table.
|
||||
if ( isset( $params['_wpnonce'] ) && check_admin_referer( 'bulk-orders' ) ) {
|
||||
$params['_wp_http_referer'] = get_admin_url( null, 'edit.php?post_type=shop_order' );
|
||||
$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'] );
|
||||
}
|
||||
|
||||
$params['post_type'] = 'shop_order';
|
||||
$new_url = add_query_arg( $params, get_admin_url( null, 'edit.php' ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $new_url ) && wp_safe_redirect( $new_url, 301 ) ) {
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
/**
|
||||
* Renders order edit page, works with both post and order object.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\CustomMetaBox;
|
||||
|
||||
/**
|
||||
* Class Edit.
|
||||
*/
|
||||
class Edit {
|
||||
|
||||
/**
|
||||
* Screen ID for the edit order screen.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $screen_id;
|
||||
|
||||
/**
|
||||
* Instance of the CustomMetaBox class. Used to render meta box for custom meta.
|
||||
*
|
||||
* @var CustomMetaBox
|
||||
*/
|
||||
private $custom_meta_box;
|
||||
|
||||
/**
|
||||
* Instance of WC_Order to be used in metaboxes.
|
||||
*
|
||||
* @var \WC_Order
|
||||
*/
|
||||
private $order;
|
||||
|
||||
/**
|
||||
* Action name that the form is currently handling. Could be new_order or edit_order.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $current_action;
|
||||
|
||||
/**
|
||||
* Message to be displayed to the user. Index of message from the messages array registered when declaring shop_order post type.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $message;
|
||||
|
||||
/**
|
||||
* Hooks all meta-boxes for order edit page. This is static since this may be called by post edit form rendering.
|
||||
*
|
||||
* @param string $screen_id Screen ID.
|
||||
* @param string $title Title of the page.
|
||||
*/
|
||||
public static function add_order_meta_boxes( string $screen_id, string $title ) {
|
||||
/* Translators: %s order type name. */
|
||||
add_meta_box( 'woocommerce-order-data', sprintf( __( '%s data', 'woocommerce' ), $title ), 'WC_Meta_Box_Order_Data::output', $screen_id, 'normal', 'high' );
|
||||
add_meta_box( 'woocommerce-order-items', __( 'Items', 'woocommerce' ), 'WC_Meta_Box_Order_Items::output', $screen_id, 'normal', 'high' );
|
||||
/* Translators: %s order type name. */
|
||||
add_meta_box( 'woocommerce-order-notes', sprintf( __( '%s notes', 'woocommerce' ), $title ), 'WC_Meta_Box_Order_Notes::output', $screen_id, 'side', 'default' );
|
||||
add_meta_box( 'woocommerce-order-downloads', __( 'Downloadable product permissions', 'woocommerce' ) . wc_help_tip( __( 'Note: Permissions for order items will automatically be granted when the order status changes to processing/completed.', 'woocommerce' ) ), 'WC_Meta_Box_Order_Downloads::output', $screen_id, 'normal', 'default' );
|
||||
/* Translators: %s order type name. */
|
||||
add_meta_box( 'woocommerce-order-actions', sprintf( __( '%s actions', 'woocommerce' ), $title ), 'WC_Meta_Box_Order_Actions::output', $screen_id, 'side', 'high' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks metabox save functions for order edit page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function add_save_meta_boxes() {
|
||||
/**
|
||||
* Save Order Meta Boxes.
|
||||
*
|
||||
* In order:
|
||||
* Save the order items.
|
||||
* Save the order totals.
|
||||
* Save the order downloads.
|
||||
* Save order data - also updates status and sends out admin emails if needed. Last to show latest data.
|
||||
* Save actions - sends out other emails. Last to show latest data.
|
||||
*/
|
||||
add_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Items::save', 10 );
|
||||
add_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Downloads::save', 30, 2 );
|
||||
add_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Data::save', 40 );
|
||||
add_action( 'woocommerce_process_shop_order_meta', 'WC_Meta_Box_Order_Actions::save', 50, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue necessary scripts for order edit page.
|
||||
*/
|
||||
private function enqueue_scripts() {
|
||||
if ( wp_is_mobile() ) {
|
||||
wp_enqueue_script( 'jquery-touch-punch' );
|
||||
}
|
||||
wp_enqueue_script( 'post' ); // Ensure existing JS libraries are still available for backward compat.
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup hooks, actions and variables needed to render order edit page.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
$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();
|
||||
|
||||
/**
|
||||
* From wp-admin/includes/meta-boxes.php.
|
||||
*
|
||||
* Fires after all built-in meta boxes have been added. Custom metaboxes may be enqueued here.
|
||||
*
|
||||
* @since 3.8.0.
|
||||
*/
|
||||
do_action( 'add_meta_boxes', $wc_screen_id, $this->order );
|
||||
|
||||
/**
|
||||
* Provides an opportunity to inject custom meta boxes into the order editor screen. This
|
||||
* hook is an analog of `add_meta_boxes_<POST_TYPE>` as provided by WordPress core.
|
||||
*
|
||||
* @since 7.4.0
|
||||
*
|
||||
* @oaram WC_Order $order The order being edited.
|
||||
*/
|
||||
do_action( 'add_meta_boxes_' . $wc_screen_id, $this->order );
|
||||
|
||||
$this->enqueue_scripts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current action for the form.
|
||||
*
|
||||
* @param string $action Action name.
|
||||
*/
|
||||
public function set_current_action( string $action ) {
|
||||
$this->current_action = $action;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hooks meta box for order specific meta.
|
||||
*/
|
||||
private function add_order_specific_meta_box() {
|
||||
add_meta_box(
|
||||
'order_custom',
|
||||
__( 'Custom Fields', 'woocommerce' ),
|
||||
array( $this, 'render_custom_meta_box' ),
|
||||
$this->screen_id,
|
||||
'normal'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes care of updating order data. Fires action that metaboxes can hook to for order data updating.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_order_update() {
|
||||
global $theorder;
|
||||
if ( ! isset( $this->order ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'edit_order' !== sanitize_text_field( wp_unslash( $_POST['action'] ?? '' ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
check_admin_referer( $this->get_order_edit_nonce_action() );
|
||||
|
||||
/**
|
||||
* Save meta for shop order.
|
||||
*
|
||||
* @param int Order ID.
|
||||
* @param \WC_Order Post object.
|
||||
*
|
||||
* @since 2.1.0
|
||||
*/
|
||||
do_action( 'woocommerce_process_shop_order_meta', $this->order->get_id(), $this->order );
|
||||
|
||||
// Order updated message.
|
||||
$this->message = 1;
|
||||
|
||||
// Refresh the order from DB.
|
||||
$this->order = wc_get_order( $this->order->get_id() );
|
||||
$theorder = $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to get the name of order edit nonce.
|
||||
*
|
||||
* @return string Nonce action name.
|
||||
*/
|
||||
private function get_order_edit_nonce_action() {
|
||||
return 'update-order_' . $this->order->get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render meta box for order specific meta.
|
||||
*/
|
||||
public function render_custom_meta_box() {
|
||||
$this->custom_meta_box->output( $this->order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render order edit page.
|
||||
*/
|
||||
public function display() {
|
||||
/**
|
||||
* This is used by the order edit page to show messages in the notice fields.
|
||||
* It should be similar to post_updated_messages filter, i.e.:
|
||||
* array(
|
||||
* {order_type} => array(
|
||||
* 1 => 'Order updated.',
|
||||
* 2 => 'Custom field updated.',
|
||||
* ...
|
||||
* ).
|
||||
*
|
||||
* The index to be displayed is computed from the $_GET['message'] variable.
|
||||
*
|
||||
* @since 7.4.0.
|
||||
*/
|
||||
$messages = apply_filters( 'woocommerce_order_updated_messages', array() );
|
||||
|
||||
$message = $this->message;
|
||||
if ( isset( $_GET['message'] ) ) {
|
||||
$message = absint( $_GET['message'] );
|
||||
}
|
||||
|
||||
if ( isset( $message ) ) {
|
||||
$message = $messages[ $this->order->get_type() ][ $message ] ?? false;
|
||||
}
|
||||
|
||||
$this->render_wrapper_start( '', $message );
|
||||
$this->render_meta_boxes();
|
||||
$this->render_wrapper_end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to render wrapper start.
|
||||
*
|
||||
* @param string $notice Notice to display, if any.
|
||||
* @param string $message Message to display, if any.
|
||||
*/
|
||||
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() );
|
||||
$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() );
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1 class="wp-heading-inline">
|
||||
<?php
|
||||
echo 'new_order' === $this->current_action ? esc_html( $post_type->labels->add_new_item ) : esc_html( $post_type->labels->edit_item );
|
||||
?>
|
||||
</h1>
|
||||
<?php
|
||||
if ( 'edit_order' === $this->current_action ) {
|
||||
echo ' <a href="' . esc_url( $new_page_url ) . '" class="page-title-action">' . esc_html( $post_type->labels->add_new ) . '</a>';
|
||||
}
|
||||
?>
|
||||
<hr class="wp-header-end">
|
||||
|
||||
<?php
|
||||
if ( $notice ) :
|
||||
?>
|
||||
<div id="notice" class="notice notice-warning"><p
|
||||
id="has-newer-autosave"><?php echo wp_kses_post( $notice ); ?></p></div>
|
||||
<?php endif; ?>
|
||||
<?php if ( $message ) : ?>
|
||||
<div id="message" class="updated notice notice-success is-dismissible">
|
||||
<p><?php echo wp_kses_post( $message ); ?></p></div>
|
||||
<?php
|
||||
endif;
|
||||
?>
|
||||
|
||||
<form name="order" action="<?php echo esc_url( $edit_page_url ); ?>" method="post" id="order"
|
||||
<?php
|
||||
/**
|
||||
* Fires inside the order edit form tag.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
*
|
||||
* @since 6.9.0
|
||||
*/
|
||||
do_action( 'order_edit_form_tag', $this->order );
|
||||
?>
|
||||
>
|
||||
<?php wp_nonce_field( $this->get_order_edit_nonce_action() ); ?>
|
||||
<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 ) : ''; ?>"/>
|
||||
<div id="poststuff">
|
||||
<div id="post-body"
|
||||
class="metabox-holder columns-<?php echo ( 1 === get_current_screen()->get_columns() ) ? '1' : '2'; ?>">
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to render meta boxes.
|
||||
*/
|
||||
private function render_meta_boxes() {
|
||||
?>
|
||||
<div id="postbox-container-1" class="postbox-container">
|
||||
<?php do_meta_boxes( $this->screen_id, 'side', $this->order ); ?>
|
||||
</div>
|
||||
<div id="postbox-container-2" class="postbox-container">
|
||||
<?php
|
||||
do_meta_boxes( $this->screen_id, 'normal', $this->order );
|
||||
do_meta_boxes( $this->screen_id, 'advanced', $this->order );
|
||||
?>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to render wrapper end.
|
||||
*/
|
||||
private function render_wrapper_end() {
|
||||
?>
|
||||
</div> <!-- /post-body -->
|
||||
</div> <!-- /poststuff -->
|
||||
</form>
|
||||
</div> <!-- /wrap -->
|
||||
<?php
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,418 @@
|
||||
<?php
|
||||
/**
|
||||
* Meta box to edit and add custom meta values for an order.
|
||||
*/
|
||||
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes;
|
||||
|
||||
use WC_Data_Store;
|
||||
use WC_Meta_Data;
|
||||
use WC_Order;
|
||||
use WP_Ajax_Response;
|
||||
|
||||
/**
|
||||
* Class CustomMetaBox.
|
||||
*/
|
||||
class CustomMetaBox {
|
||||
|
||||
/**
|
||||
* Update nonce shared among different meta rows.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $update_nonce;
|
||||
|
||||
/**
|
||||
* Helper method to get formatted meta data array with proper keys. This can be directly fed to `list_meta()` method.
|
||||
*
|
||||
* @param \WC_Order $order Order object.
|
||||
*
|
||||
* @return array Meta data.
|
||||
*/
|
||||
private function get_formatted_order_meta_data( \WC_Order $order ) {
|
||||
$metadata = $order->get_meta_data();
|
||||
$metadata_to_list = array();
|
||||
foreach ( $metadata as $meta ) {
|
||||
$data = $meta->get_data();
|
||||
if ( is_protected_meta( $data['key'], 'order' ) ) {
|
||||
continue;
|
||||
}
|
||||
$metadata_to_list[] = array(
|
||||
'meta_id' => $data['id'],
|
||||
'meta_key' => $data['key'], // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- False positive, not a meta query.
|
||||
'meta_value' => maybe_serialize( $data['value'] ), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- False positive, not a meta query.
|
||||
);
|
||||
}
|
||||
return $metadata_to_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the meta box to manage custom meta.
|
||||
*
|
||||
* @param \WP_Post|\WC_Order $order_or_post Post or order object that we are rendering for.
|
||||
*/
|
||||
public function output( $order_or_post ) {
|
||||
if ( is_a( $order_or_post, \WP_Post::class ) ) {
|
||||
$order = wc_get_order( $order_or_post );
|
||||
} else {
|
||||
$order = $order_or_post;
|
||||
}
|
||||
$this->render_custom_meta_form( $this->get_formatted_order_meta_data( $order ), $order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to render layout and actual HTML
|
||||
*
|
||||
* @param array $metadata_to_list List of metadata to render.
|
||||
* @param \WC_Order $order Order object.
|
||||
*/
|
||||
private function render_custom_meta_form( array $metadata_to_list, \WC_Order $order ) {
|
||||
?>
|
||||
<div id="postcustomstuff">
|
||||
<div id="ajax-response"></div>
|
||||
<?php
|
||||
list_meta( $metadata_to_list );
|
||||
$this->render_meta_form( $order );
|
||||
?>
|
||||
</div>
|
||||
<p>
|
||||
<?php
|
||||
printf(
|
||||
/* translators: 1: opening documentation tag 2: closing documentation tag. */
|
||||
esc_html( __( 'Custom fields can be used to add extra metadata to an order that you can %1$suse in your theme%2$s.', 'woocommerce' ) ),
|
||||
'<a href="' . esc_attr__( 'https://wordpress.org/support/article/custom-fields/', 'woocommerce' ) . '">',
|
||||
'</a>'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute keys to display in autofill when adding new meta key entry in custom meta box.
|
||||
* Currently, returns empty keys, will be implemented after caching is merged.
|
||||
*
|
||||
* @param array|null $keys Keys to display in autofill.
|
||||
* @param \WP_Post|\WC_Order $order Order object.
|
||||
*
|
||||
* @return array|mixed Array of keys to display in autofill.
|
||||
*/
|
||||
public function order_meta_keys_autofill( $keys, $order ) {
|
||||
if ( is_a( $order, \WC_Order::class ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reimplementation of WP core's `meta_form` function. Renders meta form box.
|
||||
*
|
||||
* @param \WC_Order $order WC_Order object.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render_meta_form( \WC_Order $order ) : void {
|
||||
$meta_key_input_id = 'metakeyselect';
|
||||
|
||||
$keys = $this->order_meta_keys_autofill( null, $order );
|
||||
/**
|
||||
* Filters values for the meta key dropdown in the Custom Fields meta box.
|
||||
*
|
||||
* Compatibility filter for `postmeta_form_keys` filter.
|
||||
*
|
||||
* @since 6.9.0
|
||||
*
|
||||
* @param array|null $keys Pre-defined meta keys to be used in place of a postmeta query. Default null.
|
||||
* @param \WC_Order $order The current post object.
|
||||
*/
|
||||
$keys = apply_filters( 'postmeta_form_keys', $keys, $order );
|
||||
?>
|
||||
<p><strong><?php esc_html_e( 'Add New Custom Field:', 'woocommerce' ); ?></strong></p>
|
||||
<table id="newmeta">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="left"><label for="<?php echo esc_attr( $meta_key_input_id ); ?>"><?php esc_html_e( 'Name', 'woocommerce' ); ?></label></th>
|
||||
<th><label for="metavalue"><?php esc_html_e( 'Value', 'woocommerce' ); ?></label></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr>
|
||||
<td id="newmetaleft" class="left">
|
||||
<?php if ( $keys ) { ?>
|
||||
<select id="metakeyselect" name="metakeyselect">
|
||||
<option value="#NONE#"><?php esc_html_e( '— Select —', 'woocommerce' ); ?></option>
|
||||
<?php
|
||||
foreach ( $keys as $key ) {
|
||||
if ( is_protected_meta( $key, 'post' ) || ! current_user_can( 'edit_others_shop_order', $order->get_id() ) ) {
|
||||
continue;
|
||||
}
|
||||
echo "\n<option value='" . esc_attr( $key ) . "'>" . esc_html( $key ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<input class="hide-if-js" type="text" id="metakeyinput" name="metakeyinput" value="" />
|
||||
<a href="#postcustomstuff" class="hide-if-no-js" onclick="jQuery('#metakeyinput, #metakeyselect, #enternew, #cancelnew').toggle();return false;">
|
||||
<span id="enternew"><?php esc_html_e( 'Enter new', 'woocommerce' ); ?></span>
|
||||
<span id="cancelnew" class="hidden"><?php esc_html_e( 'Cancel', 'woocommerce' ); ?></span></a>
|
||||
<?php } else { ?>
|
||||
<input type="text" id="metakeyinput" name="metakeyinput" value="" />
|
||||
<?php } ?>
|
||||
</td>
|
||||
<td><textarea id="metavalue" name="metavalue" rows="2" cols="25"></textarea></td>
|
||||
</tr>
|
||||
|
||||
<tr><td colspan="2">
|
||||
<div class="submit">
|
||||
<?php
|
||||
submit_button(
|
||||
__( 'Add Custom Field', 'woocommerce' ),
|
||||
'',
|
||||
'addmeta',
|
||||
false,
|
||||
array(
|
||||
'id' => 'newmeta-submit',
|
||||
'data-wp-lists' => 'add:the-list:newmeta',
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div>
|
||||
<?php wp_nonce_field( 'add-meta', '_ajax_nonce-add-meta', false ); ?>
|
||||
</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to verify order edit permissions.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
*
|
||||
* @return ?WC_Order WC_Order object if the user can edit the order, die otherwise.
|
||||
*/
|
||||
private function verify_order_edit_permission_for_ajax( int $order_id ): ?WC_Order {
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) || ! current_user_can( 'edit_others_shop_orders' ) ) {
|
||||
wp_send_json_error( 'missing_capabilities' );
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$order = wc_get_order( $order_id );
|
||||
if ( ! $order ) {
|
||||
wp_send_json_error( 'invalid_order_id' );
|
||||
wp_die();
|
||||
}
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reimplementation of WP core's `wp_ajax_add_meta` method to support order custom meta updates with custom tables.
|
||||
*/
|
||||
public function add_meta_ajax() {
|
||||
if ( ! check_ajax_referer( 'add-meta', '_ajax_nonce-add-meta' ) ) {
|
||||
wp_send_json_error( 'invalid_nonce' );
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$order_id = (int) $_POST['order_id'] ?? 0;
|
||||
$order = $this->verify_order_edit_permission_for_ajax( $order_id );
|
||||
|
||||
if ( isset( $_POST['metakeyselect'] ) && '#NONE#' === $_POST['metakeyselect'] && empty( $_POST['metakeyinput'] ) ) {
|
||||
wp_die( 1 );
|
||||
}
|
||||
|
||||
if ( isset( $_POST['metakeyinput'] ) ) { // add meta.
|
||||
$meta_key = sanitize_text_field( wp_unslash( $_POST['metakeyinput'] ) );
|
||||
$meta_value = sanitize_text_field( wp_unslash( $_POST['metavalue'] ?? '' ) );
|
||||
$this->handle_add_meta( $order, $meta_key, $meta_value );
|
||||
} else { // update.
|
||||
$meta = wp_unslash( $_POST['meta'] ?? array() ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- sanitization done below in array_walk.
|
||||
$this->handle_update_meta( $order, $meta );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Part of WP Core's `wp_ajax_add_meta`. This is re-implemented to support updating meta for custom tables.
|
||||
*
|
||||
* @param WC_Order $order Order object.
|
||||
* @param string $meta_key Meta key.
|
||||
* @param string $meta_value Meta value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handle_add_meta( WC_Order $order, string $meta_key, string $meta_value ) {
|
||||
$order_data_store = WC_Data_Store::load( 'order' );
|
||||
$count = 0;
|
||||
if ( is_protected_meta( $meta_key ) ) {
|
||||
wp_send_json_error( 'protected_meta' );
|
||||
wp_die();
|
||||
}
|
||||
$meta_id = $order_data_store->add_meta(
|
||||
$order,
|
||||
new WC_Meta_Data(
|
||||
array(
|
||||
'key' => $meta_key,
|
||||
'value' => $meta_value,
|
||||
)
|
||||
)
|
||||
);
|
||||
$response = new WP_Ajax_Response(
|
||||
array(
|
||||
'what' => 'meta',
|
||||
'id' => $meta_id,
|
||||
'data' => $this->list_meta_row(
|
||||
array(
|
||||
'meta_id' => $meta_id,
|
||||
'meta_key' => $meta_key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- false positive, not a meta query.
|
||||
'meta_value' => $meta_value, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- false positive, not a meta query.
|
||||
),
|
||||
$count
|
||||
),
|
||||
'position' => 1,
|
||||
)
|
||||
);
|
||||
$response->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles updating metadata.
|
||||
*
|
||||
* @param WC_Order $order Order object.
|
||||
* @param array $meta Meta object to update.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handle_update_meta( WC_Order $order, array $meta ) {
|
||||
if ( ! is_array( $meta ) ) {
|
||||
wp_send_json_error( 'invalid_meta' );
|
||||
wp_die();
|
||||
}
|
||||
array_walk( $meta, 'sanitize_text_field' );
|
||||
$mid = (int) key( $meta );
|
||||
if ( ! $mid ) {
|
||||
wp_send_json_error( 'invalid_meta_id' );
|
||||
wp_die();
|
||||
}
|
||||
$key = $meta[ $mid ]['key'];
|
||||
$value = $meta[ $mid ]['value'];
|
||||
if ( is_protected_meta( $key ) ) {
|
||||
wp_send_json_error( 'protected_meta' );
|
||||
wp_die();
|
||||
}
|
||||
if ( '' === trim( $key ) ) {
|
||||
wp_send_json_error( 'invalid_meta_key' );
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$order_data_store = WC_Data_Store::load( 'order' );
|
||||
$count = 0;
|
||||
$meta_object = new WC_Meta_Data(
|
||||
array(
|
||||
'id' => $mid,
|
||||
'key' => $key,
|
||||
'value' => $value,
|
||||
)
|
||||
);
|
||||
$order_data_store->update_meta( $order, $meta_object );
|
||||
$response = new WP_Ajax_Response(
|
||||
array(
|
||||
'what' => 'meta',
|
||||
'id' => $mid,
|
||||
'old_id' => $mid,
|
||||
'data' => $this->list_meta_row(
|
||||
array(
|
||||
'meta_key' => $key, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- false positive, not a meta query.
|
||||
'meta_value' => $value, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- false positive, not a meta query.
|
||||
'meta_id' => $mid,
|
||||
),
|
||||
$count
|
||||
),
|
||||
'position' => 0,
|
||||
)
|
||||
);
|
||||
$response->send();
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a single row of public meta data in the Custom Fields meta box.
|
||||
*
|
||||
* @since 2.5.0
|
||||
*
|
||||
* @param array $entry Meta entry.
|
||||
* @param int $count Sequence number of meta entries.
|
||||
* @return string
|
||||
*/
|
||||
private function list_meta_row( array $entry, int &$count ) : string {
|
||||
if ( is_protected_meta( $entry['meta_key'], 'post' ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( ! $this->update_nonce ) {
|
||||
$this->update_nonce = wp_create_nonce( 'add-meta' );
|
||||
}
|
||||
|
||||
$r = '';
|
||||
++ $count;
|
||||
|
||||
if ( is_serialized( $entry['meta_value'] ) ) {
|
||||
if ( is_serialized_string( $entry['meta_value'] ) ) {
|
||||
// This is a serialized string, so we should display it.
|
||||
$entry['meta_value'] = maybe_unserialize( $entry['meta_value'] ); // // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- false positive, not a meta query.
|
||||
} else {
|
||||
// This is a serialized array/object so we should NOT display it.
|
||||
--$count;
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
$entry['meta_key'] = esc_attr( $entry['meta_key'] ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- false positive, not a meta query.
|
||||
$entry['meta_value'] = esc_textarea( $entry['meta_value'] ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value -- false positive, not a meta query.
|
||||
$entry['meta_id'] = (int) $entry['meta_id'];
|
||||
|
||||
$delete_nonce = wp_create_nonce( 'delete-meta_' . $entry['meta_id'] );
|
||||
|
||||
$r .= "\n\t<tr id='meta-{$entry['meta_id']}'>";
|
||||
$r .= "\n\t\t<td class='left'><label class='screen-reader-text' for='meta-{$entry['meta_id']}-key'>" . __( 'Key', 'woocommerce' ) . "</label><input name='meta[{$entry['meta_id']}][key]' id='meta-{$entry['meta_id']}-key' type='text' size='20' value='{$entry['meta_key']}' />";
|
||||
|
||||
$r .= "\n\t\t<div class='submit'>";
|
||||
$r .= get_submit_button( __( 'Delete', 'woocommerce' ), 'deletemeta small', "deletemeta[{$entry['meta_id']}]", false, array( 'data-wp-lists' => "delete:the-list:meta-{$entry['meta_id']}::_ajax_nonce:$delete_nonce" ) );
|
||||
$r .= "\n\t\t";
|
||||
$r .= get_submit_button( __( 'Update', 'woocommerce' ), 'updatemeta small', "meta-{$entry['meta_id']}-submit", false, array( 'data-wp-lists' => "add:the-list:meta-{$entry['meta_id']}::_ajax_nonce-add-meta={$this->update_nonce}" ) );
|
||||
$r .= '</div>';
|
||||
$r .= wp_nonce_field( 'change-meta', '_ajax_nonce', false, false );
|
||||
$r .= '</td>';
|
||||
|
||||
$r .= "\n\t\t<td><label class='screen-reader-text' for='meta-{$entry['meta_id']}-value'>" . __( 'Value', 'woocommerce' ) . "</label><textarea name='meta[{$entry['meta_id']}][value]' id='meta-{$entry['meta_id']}-value' rows='2' cols='30'>{$entry['meta_value']}</textarea></td>\n\t</tr>";
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reimplementation of WP core's `wp_ajax_delete_meta` method to support order custom meta updates with custom tables.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete_meta_ajax() {
|
||||
$meta_id = (int) $_POST['id'] ?? 0;
|
||||
$order_id = (int) $_POST['order_id'] ?? 0;
|
||||
if ( ! $meta_id || ! $order_id ) {
|
||||
wp_send_json_error( 'invalid_meta_id' );
|
||||
wp_die();
|
||||
}
|
||||
check_ajax_referer( "delete-meta_$meta_id" );
|
||||
|
||||
$order = $this->verify_order_edit_permission_for_ajax( $order_id );
|
||||
$meta_to_delete = wp_list_filter( $order->get_meta_data(), array( 'id' => $meta_id ) );
|
||||
|
||||
if ( empty( $meta_to_delete ) ) {
|
||||
wp_send_json_error( 'invalid_meta_id' );
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$order->delete_meta_data_by_mid( $meta_id );
|
||||
if ( $order->save() ) {
|
||||
wp_die( 1 );
|
||||
}
|
||||
wp_die( 0 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Automattic\WooCommerce\Internal\Traits\AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* Controls the different pages/screens associated to the "Orders" menu page.
|
||||
*/
|
||||
class PageController {
|
||||
|
||||
use AccessiblePrivateMethods;
|
||||
|
||||
/**
|
||||
* The order type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $order_type = '';
|
||||
|
||||
/**
|
||||
* Instance of the posts redirection controller.
|
||||
*
|
||||
* @var PostsRedirectionController
|
||||
*/
|
||||
private $redirection_controller;
|
||||
|
||||
/**
|
||||
* Instance of the orders list table.
|
||||
*
|
||||
* @var ListTable
|
||||
*/
|
||||
private $orders_table;
|
||||
|
||||
/**
|
||||
* Instance of orders edit form.
|
||||
*
|
||||
* @var Edit
|
||||
*/
|
||||
private $order_edit_form;
|
||||
|
||||
/**
|
||||
* Current action.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $current_action = '';
|
||||
|
||||
/**
|
||||
* Order object to be used in edit/new form.
|
||||
*
|
||||
* @var \WC_Order
|
||||
*/
|
||||
private $order;
|
||||
|
||||
/**
|
||||
* Verify that user has permission to edit orders.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function verify_edit_permission() {
|
||||
if ( 'edit_order' === $this->current_action && ( ! isset( $this->order ) || ! $this->order ) ) {
|
||||
wp_die( esc_html__( 'You attempted to edit an order that does not exist. Perhaps it was deleted?', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( $this->order->get_type() !== $this->order_type ) {
|
||||
wp_die( esc_html__( 'Order type mismatch.', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( ! current_user_can( get_post_type_object( $this->order_type )->cap->edit_post, $this->order->get_id() ) && ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_die( esc_html__( 'You do not have permission to edit this order', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( 'trash' === $this->order->get_status() ) {
|
||||
wp_die( esc_html__( 'You cannot edit this item because it is in the Trash. Please restore it and try again.', 'woocommerce' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that user has permission to create order.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function verify_create_permission() {
|
||||
if ( ! current_user_can( get_post_type_object( $this->order_type )->cap->publish_posts ) && ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_die( esc_html__( 'You don\'t have permission to create a new order', 'woocommerce' ) );
|
||||
}
|
||||
|
||||
if ( isset( $this->order ) ) {
|
||||
$this->verify_edit_permission();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the page controller, including registering the menu item.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup(): void {
|
||||
$this->redirection_controller = new PostsRedirectionController( $this );
|
||||
|
||||
// Register menu.
|
||||
if ( 'admin_menu' === current_action() ) {
|
||||
$this->register_menu();
|
||||
} else {
|
||||
add_action( 'admin_menu', 'register_menu', 9 );
|
||||
}
|
||||
|
||||
$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' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform initialization for the current action.
|
||||
*/
|
||||
private function handle_load_page_action() {
|
||||
if ( method_exists( $this, 'setup_action_' . $this->current_action ) ) {
|
||||
$this->{"setup_action_{$this->current_action}"}();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$this->order_type = str_replace( array( 'wc-orders--', 'wc-orders' ), '', $plugin_page );
|
||||
$this->order_type = empty( $this->order_type ) ? 'shop_order' : $this->order_type;
|
||||
|
||||
$wc_order_type = wc_get_order_type( $this->order_type );
|
||||
$wp_order_type = get_post_type_object( $this->order_type );
|
||||
|
||||
if ( ! $wc_order_type || ! $wp_order_type || ! $wp_order_type->show_ui || ! current_user_can( $wp_order_type->cap->edit_posts ) ) {
|
||||
wp_die();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current action based on querystring arguments. Defaults to 'list_orders'.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function set_action(): void {
|
||||
switch ( isset( $_GET['action'] ) ? sanitize_text_field( wp_unslash( $_GET['action'] ) ) : '' ) {
|
||||
case 'edit':
|
||||
$this->current_action = 'edit_order';
|
||||
break;
|
||||
case 'new':
|
||||
$this->current_action = 'new_order';
|
||||
break;
|
||||
default:
|
||||
$this->current_action = 'list_orders';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the "Orders" menu.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_menu(): void {
|
||||
$order_types = wc_get_order_types( 'admin-menu' );
|
||||
|
||||
foreach ( $order_types as $order_type ) {
|
||||
$post_type = get_post_type_object( $order_type );
|
||||
|
||||
add_submenu_page(
|
||||
'woocommerce',
|
||||
$post_type->labels->name,
|
||||
$post_type->labels->menu_name,
|
||||
$post_type->cap->edit_posts,
|
||||
'wc-orders' . ( 'shop_order' === $order_type ? '' : '--' . $order_type ),
|
||||
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() use ( $order_types ) {
|
||||
foreach ( $order_types as $order_type ) {
|
||||
remove_submenu_page( 'woocommerce', 'edit.php?post_type=' . $order_type );
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs content for the current orders screen.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function output(): void {
|
||||
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':
|
||||
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 = wc_get_container()->get( ListTable::class );
|
||||
$this->orders_table->setup(
|
||||
array(
|
||||
'order_type' => $this->order_type,
|
||||
)
|
||||
);
|
||||
|
||||
if ( $this->orders_table->current_action() ) {
|
||||
$this->orders_table->handle_bulk_actions();
|
||||
}
|
||||
|
||||
$this->strip_http_referer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a redirect to remove the `_wp_http_referer` and `_wpnonce` strings if present in the URL (see also
|
||||
* wp-admin/edit.php where a similar process takes place), otherwise the size of this field builds to an
|
||||
* unmanageable length over time.
|
||||
*/
|
||||
private function strip_http_referer(): void {
|
||||
$current_url = esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ?? '' ) );
|
||||
$stripped_url = remove_query_arg( array( '_wp_http_referer', '_wpnonce' ), $current_url );
|
||||
|
||||
if ( $stripped_url !== $current_url ) {
|
||||
wp_safe_redirect( $stripped_url );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles initialization of the orders edit form.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setup_action_edit_order(): void {
|
||||
global $theorder;
|
||||
$this->order = wc_get_order( absint( isset( $_GET['id'] ) ? $_GET['id'] : 0 ) );
|
||||
$this->verify_edit_permission();
|
||||
$theorder = $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles initialization of the orders edit form with a new order.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function setup_action_new_order(): void {
|
||||
global $theorder;
|
||||
|
||||
$this->verify_create_permission();
|
||||
|
||||
$order_class_name = wc_get_order_type( $this->order_type )['class_name'];
|
||||
if ( ! $order_class_name || ! class_exists( $order_class_name ) ) {
|
||||
wp_die();
|
||||
}
|
||||
|
||||
$this->order = new $order_class_name();
|
||||
$this->order->set_object_read( false );
|
||||
$this->order->set_status( 'pending' );
|
||||
$this->order->save();
|
||||
|
||||
$theorder = $this->order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current order type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_order_type() {
|
||||
return $this->order_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate a link to the main orders screen.
|
||||
*
|
||||
* @return string Orders screen URL.
|
||||
*/
|
||||
public function get_orders_url(): string {
|
||||
return wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ?
|
||||
admin_url( 'admin.php?page=wc-orders' ) :
|
||||
admin_url( 'edit.php?post_type=shop_order' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate edit link for an order.
|
||||
*
|
||||
* @param int $order_id Order ID.
|
||||
*
|
||||
* @return string Edit link.
|
||||
*/
|
||||
public function get_edit_url( int $order_id ) : string {
|
||||
if ( ! wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
|
||||
return admin_url( 'post.php?post=' . absint( $order_id ) ) . '&action=edit';
|
||||
}
|
||||
|
||||
$order = wc_get_order( $order_id );
|
||||
|
||||
// Confirm we could obtain the order object (since it's possible it will not exist, due to a sync issue, or may
|
||||
// have been deleted in a separate concurrent request).
|
||||
if ( false === $order ) {
|
||||
wc_get_logger()->debug(
|
||||
sprintf(
|
||||
/* translators: %d order ID. */
|
||||
__( 'Attempted to determine the edit URL for order %d, however the order does not exist.', 'woocommerce' ),
|
||||
$order_id
|
||||
)
|
||||
);
|
||||
$order_type = 'shop_order';
|
||||
} else {
|
||||
$order_type = $order->get_type();
|
||||
}
|
||||
|
||||
return add_query_arg(
|
||||
array(
|
||||
'action' => 'edit',
|
||||
'id' => absint( $order_id ),
|
||||
),
|
||||
$this->get_base_page_url( $order_type )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate a link for creating order.
|
||||
*
|
||||
* @param string $order_type The order type. Defaults to 'shop_order'.
|
||||
* @return string
|
||||
*/
|
||||
public function get_new_page_url( $order_type = 'shop_order' ) : string {
|
||||
$url = wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ?
|
||||
add_query_arg( 'action', 'new', $this->get_base_page_url( $order_type ) ) :
|
||||
admin_url( 'post-new.php?post_type=' . $order_type );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to generate a link to the main screen for a custom order type.
|
||||
*
|
||||
* @param string $order_type The order type.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* @throws \Exception When an invalid order type is passed.
|
||||
*/
|
||||
public function get_base_page_url( $order_type ): string {
|
||||
$order_types_with_ui = wc_get_order_types( 'admin-menu' );
|
||||
|
||||
if ( ! in_array( $order_type, $order_types_with_ui, true ) ) {
|
||||
// translators: %s is a custom order type.
|
||||
throw new \Exception( sprintf( __( 'Invalid order type: %s.', 'woocommerce' ), esc_html( $order_type ) ) );
|
||||
}
|
||||
|
||||
return admin_url( 'admin.php?page=wc-orders' . ( 'shop_order' === $order_type ? '' : '--' . $order_type ) );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
namespace Automattic\WooCommerce\Internal\Admin\Orders;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
|
||||
/**
|
||||
* When {@see OrdersTableDataStore} is in use, this class takes care of redirecting admins from CPT-based URLs
|
||||
* to the new ones.
|
||||
*/
|
||||
class PostsRedirectionController {
|
||||
|
||||
/**
|
||||
* Instance of the PageController class.
|
||||
*
|
||||
* @var PageController
|
||||
*/
|
||||
private $page_controller;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param PageController $page_controller Page controller instance. Used to generate links/URLs.
|
||||
*/
|
||||
public function __construct( PageController $page_controller ) {
|
||||
$this->page_controller = $page_controller;
|
||||
|
||||
if ( ! wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action(
|
||||
'load-edit.php',
|
||||
function() {
|
||||
$this->maybe_redirect_to_orders_page();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'load-post-new.php',
|
||||
function() {
|
||||
$this->maybe_redirect_to_new_order_page();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'load-post.php',
|
||||
function() {
|
||||
$this->maybe_redirect_to_edit_order_page();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If needed, performs a redirection to the main orders page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function maybe_redirect_to_orders_page(): void {
|
||||
$post_type = $_GET['post_type'] ?? ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
if ( ! $post_type || ! in_array( $post_type, wc_get_order_types( 'admin-menu' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Respect query args, except for 'post_type'.
|
||||
$query_args = wp_unslash( $_GET );
|
||||
$action = $query_args['action'] ?? '';
|
||||
$posts = $query_args['post'] ?? array();
|
||||
unset( $query_args['post_type'], $query_args['post'], $query_args['_wpnonce'], $query_args['_wp_http_referer'], $query_args['action'] );
|
||||
|
||||
// Remap 'post_status' arg.
|
||||
if ( isset( $query_args['post_status'] ) ) {
|
||||
$query_args['status'] = $query_args['post_status'];
|
||||
unset( $query_args['post_status'] );
|
||||
}
|
||||
|
||||
$new_url = $this->page_controller->get_base_page_url( $post_type );
|
||||
$new_url = add_query_arg( $query_args, $new_url );
|
||||
|
||||
// Handle bulk actions.
|
||||
if ( $action && in_array( $action, array( 'trash', 'untrash', 'delete', 'mark_processing', 'mark_on-hold', 'mark_completed', 'mark_cancelled' ), true ) ) {
|
||||
check_admin_referer( 'bulk-posts' );
|
||||
|
||||
$new_url = add_query_arg(
|
||||
array(
|
||||
'action' => $action,
|
||||
'order' => $posts,
|
||||
'_wp_http_referer' => $this->page_controller->get_orders_url(),
|
||||
'_wpnonce' => wp_create_nonce( 'bulk-orders' ),
|
||||
),
|
||||
$new_url
|
||||
);
|
||||
}
|
||||
|
||||
wp_safe_redirect( $new_url, 301 );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* If needed, performs a redirection to the new order page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function maybe_redirect_to_new_order_page(): void {
|
||||
$post_type = $_GET['post_type'] ?? ''; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
if ( ! $post_type || ! in_array( $post_type, wc_get_order_types( 'admin-menu' ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Respect query args, except for 'post_type'.
|
||||
$query_args = wp_unslash( $_GET );
|
||||
unset( $query_args['post_type'] );
|
||||
|
||||
$new_url = $this->page_controller->get_new_page_url( $post_type );
|
||||
$new_url = add_query_arg( $query_args, $new_url );
|
||||
|
||||
wp_safe_redirect( $new_url, 301 );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* If needed, performs a redirection to the edit order page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function maybe_redirect_to_edit_order_page(): void {
|
||||
$post_id = absint( $_GET['post'] ?? 0 );
|
||||
|
||||
$redirect_from_types = wc_get_order_types( 'admin-menu' );
|
||||
$redirect_from_types[] = 'shop_order_placehold';
|
||||
|
||||
if ( ! $post_id || ! in_array( get_post_type( $post_id ), $redirect_from_types, true ) || ! isset( $_GET['action'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Respect query args, except for 'post'.
|
||||
$query_args = wp_unslash( $_GET );
|
||||
$action = $query_args['action'];
|
||||
unset( $query_args['post'], $query_args['_wpnonce'], $query_args['_wp_http_referer'], $query_args['action'] );
|
||||
|
||||
$new_url = '';
|
||||
|
||||
switch ( $action ) {
|
||||
case 'edit':
|
||||
$new_url = $this->page_controller->get_edit_url( $post_id );
|
||||
break;
|
||||
|
||||
case 'trash':
|
||||
case 'untrash':
|
||||
case 'delete':
|
||||
// Re-generate nonce if validation passes.
|
||||
check_admin_referer( $action . '-post_' . $post_id );
|
||||
|
||||
$new_url = add_query_arg(
|
||||
array(
|
||||
'action' => $action,
|
||||
'order' => array( $post_id ),
|
||||
'_wp_http_referer' => $this->page_controller->get_orders_url(),
|
||||
'_wpnonce' => wp_create_nonce( 'bulk-orders' ),
|
||||
),
|
||||
$this->page_controller->get_orders_url()
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if ( ! $new_url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$new_url = add_query_arg( $query_args, $new_url );
|
||||
|
||||
wp_safe_redirect( $new_url, 301 );
|
||||
exit;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user