Add meta boxes for custom taxonomies in order edit screens (#38676)
This commit is contained in:
parent
960fa1035a
commit
e4f3273fb5
|
@ -0,0 +1,4 @@
|
||||||
|
Significance: patch
|
||||||
|
Type: fix
|
||||||
|
|
||||||
|
Add support for taxonomy meta boxes in HPOS order edit screen.
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 );
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue