Add meta boxes for custom taxonomies in order edit screens (#38676)

This commit is contained in:
Vedanshu Jain 2023-06-14 19:17:22 +05:30 committed by GitHub
parent 960fa1035a
commit e4f3273fb5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 288 additions and 3 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: fix
Add support for taxonomy meta boxes in HPOS order edit screen.

View File

@ -6,6 +6,7 @@
namespace Automattic\WooCommerce\Internal\Admin\Orders; namespace Automattic\WooCommerce\Internal\Admin\Orders;
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\CustomMetaBox; use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\CustomMetaBox;
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\TaxonomiesMetaBox;
/** /**
* Class Edit. * Class Edit.
@ -26,6 +27,13 @@ class Edit {
*/ */
private $custom_meta_box; 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. * Instance of WC_Order to be used in metaboxes.
* *
@ -110,10 +118,16 @@ class Edit {
if ( ! isset( $this->custom_meta_box ) ) { if ( ! isset( $this->custom_meta_box ) ) {
$this->custom_meta_box = wc_get_container()->get( CustomMetaBox::class ); $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->add_save_meta_boxes();
$this->handle_order_update(); $this->handle_order_update();
$this->add_order_meta_boxes( $this->screen_id, __( 'Order', 'woocommerce' ) ); $this->add_order_meta_boxes( $this->screen_id, __( 'Order', 'woocommerce' ) );
$this->add_order_specific_meta_box(); $this->add_order_specific_meta_box();
$this->add_order_taxonomies_meta_box();
/** /**
* From wp-admin/includes/meta-boxes.php. * From wp-admin/includes/meta-boxes.php.
@ -159,6 +173,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. * Takes care of updating order data. Fires action that metaboxes can hook to for order data updating.
* *
@ -176,6 +199,10 @@ class Edit {
check_admin_referer( $this->get_order_edit_nonce_action() ); 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. * Save meta for shop order.
* *

View File

@ -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 );
}
}

View File

@ -1641,6 +1641,84 @@ FROM $order_meta_table
$changes = $order->get_changes(); $changes = $order->get_changes();
$this->update_address_index_meta( $order, $changes ); $this->update_address_index_meta( $order, $changes );
$default_taxonomies = $this->init_default_taxonomies( $order, array() );
$this->set_custom_taxonomies( $order, $default_taxonomies );
}
/**
* Set default taxonomies for the order.
*
* Note: This is re-implementation of part of WP core's `wp_insert_post` function. Since the code block that set default taxonomies is not filterable, we have to re-implement it.
*
* @param \WC_Abstract_Order $order Order object.
* @param array $sanitized_tax_input Sanitized taxonomy input.
*
* @return array Sanitized tax input with default taxonomies.
*/
public function init_default_taxonomies( \WC_Abstract_Order $order, array $sanitized_tax_input ) {
if ( 'auto-draft' === $order->get_status() ) {
return $sanitized_tax_input;
}
foreach ( get_object_taxonomies( $order->get_type(), 'object' ) as $taxonomy => $tax_object ) {
if ( empty( $tax_object->default_term ) ) {
return $sanitized_tax_input;
}
// Filter out empty terms.
if ( isset( $sanitized_tax_input[ $taxonomy ] ) && is_array( $sanitized_tax_input[ $taxonomy ] ) ) {
$sanitized_tax_input[ $taxonomy ] = array_filter( $sanitized_tax_input[ $taxonomy ] );
}
// Passed custom taxonomy list overwrites the existing list if not empty.
$terms = wp_get_object_terms( $order->get_id(), $taxonomy, array( 'fields' => 'ids' ) );
if ( ! empty( $terms ) && empty( $sanitized_tax_input[ $taxonomy ] ) ) {
$sanitized_tax_input[ $taxonomy ] = $terms;
}
if ( empty( $sanitized_tax_input[ $taxonomy ] ) ) {
$default_term_id = get_option( 'default_term_' . $taxonomy );
if ( ! empty( $default_term_id ) ) {
$sanitized_tax_input[ $taxonomy ] = array( (int) $default_term_id );
}
}
}
return $sanitized_tax_input;
}
/**
* Set custom taxonomies for the order.
*
* Note: This is re-implementation of part of WP core's `wp_insert_post` function. Since the code block that set custom taxonomies is not filterable, we have to re-implement it.
*
* @param \WC_Abstract_Order $order Order object.
* @param array $sanitized_tax_input Sanitized taxonomy input.
*
* @return void
*/
public function set_custom_taxonomies( \WC_Abstract_Order $order, array $sanitized_tax_input ) {
if ( empty( $sanitized_tax_input ) ) {
return;
}
foreach ( $sanitized_tax_input as $taxonomy => $tags ) {
$taxonomy_obj = get_taxonomy( $taxonomy );
if ( ! $taxonomy_obj ) {
/* translators: %s: Taxonomy name. */
_doing_it_wrong( __FUNCTION__, esc_html( sprintf( __( 'Invalid taxonomy: %s.', 'woocommerce' ), $taxonomy ) ), '7.9.0' );
continue;
}
// array = hierarchical, string = non-hierarchical.
if ( is_array( $tags ) ) {
$tags = array_filter( $tags );
}
if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
wp_set_post_terms( $order->get_id(), $tags, $taxonomy );
}
}
} }
/** /**

View File

@ -9,7 +9,9 @@ use Automattic\WooCommerce\Internal\Admin\Orders\COTRedirectionController;
use Automattic\WooCommerce\Internal\Admin\Orders\Edit; use Automattic\WooCommerce\Internal\Admin\Orders\Edit;
use Automattic\WooCommerce\Internal\Admin\Orders\EditLock; use Automattic\WooCommerce\Internal\Admin\Orders\EditLock;
use Automattic\WooCommerce\Internal\Admin\Orders\ListTable; use Automattic\WooCommerce\Internal\Admin\Orders\ListTable;
use Automattic\WooCommerce\Internal\Admin\Orders\MetaBoxes\TaxonomiesMetaBox;
use Automattic\WooCommerce\Internal\Admin\Orders\PageController; use Automattic\WooCommerce\Internal\Admin\Orders\PageController;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider; use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;
/** /**
@ -28,6 +30,7 @@ class OrderAdminServiceProvider extends AbstractServiceProvider {
Edit::class, Edit::class,
ListTable::class, ListTable::class,
EditLock::class, EditLock::class,
TaxonomiesMetaBox::class,
); );
/** /**
@ -41,5 +44,6 @@ class OrderAdminServiceProvider extends AbstractServiceProvider {
$this->share( Edit::class )->addArgument( PageController::class ); $this->share( Edit::class )->addArgument( PageController::class );
$this->share( ListTable::class )->addArgument( PageController::class ); $this->share( ListTable::class )->addArgument( PageController::class );
$this->share( EditLock::class ); $this->share( EditLock::class );
$this->share( TaxonomiesMetaBox::class )->addArgument( OrdersTableDataStore::class );
} }
} }

View File

@ -190,7 +190,7 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
*/ */
public function test_apply_coupon_across_status() { public function test_apply_coupon_across_status() {
$coupon_code = 'coupon_test_count_across_status'; $coupon_code = 'coupon_test_count_across_status';
$coupon = WC_Helper_Coupon::create_coupon( $coupon_code ); $coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
$this->assertEquals( 0, $coupon->get_usage_count() ); $this->assertEquals( 0, $coupon->get_usage_count() );
$order = WC_Helper_Order::create_order(); $order = WC_Helper_Order::create_order();
@ -253,8 +253,8 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
*/ */
public function test_apply_coupon_stores_meta_data() { public function test_apply_coupon_stores_meta_data() {
$coupon_code = 'coupon_test_meta_data'; $coupon_code = 'coupon_test_meta_data';
$coupon = WC_Helper_Coupon::create_coupon( $coupon_code ); $coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
$order = WC_Helper_Order::create_order(); $order = WC_Helper_Order::create_order();
$order->set_status( 'processing' ); $order->set_status( 'processing' );
$order->save(); $order->save();
$order->apply_coupon( $coupon_code ); $order->apply_coupon( $coupon_code );
@ -324,4 +324,29 @@ class WC_Abstract_Order_Test extends WC_Unit_Test_Case {
$order = wc_get_order( $order->get_id() ); $order = wc_get_order( $order->get_id() );
$this->assertInstanceOf( Automattic\WooCommerce\Admin\Overrides\Order::class, $order ); $this->assertInstanceOf( Automattic\WooCommerce\Admin\Overrides\Order::class, $order );
} }
/**
* @testDox When a taxonomy with a default term is set on the order, it's inserted when a new order is created.
*/
public function test_default_term_for_custom_taxonomy() {
$custom_taxonomy = register_taxonomy(
'custom_taxonomy',
'shop_order',
array(
'default_term' => 'new_term',
),
);
// Set user who has access to create term.
$current_user_id = get_current_user_id();
$user = new WP_User( wp_create_user( 'test', '' ) );
$user->set_role( 'administrator' );
wp_set_current_user( $user->ID );
$order = wc_create_order();
wp_set_current_user( $current_user_id );
$order_terms = wp_list_pluck( wp_get_object_terms( $order->get_id(), $custom_taxonomy->name ), 'name' );
$this->assertContains( 'new_term', $order_terms );
}
} }