Skip to content

Commit

Permalink
Add meta boxes for custom taxonomies in order edit screens (#38676)
Browse files Browse the repository at this point in the history
  • Loading branch information
vedanshujain committed Jun 14, 2023
1 parent 960fa10 commit e4f3273
Show file tree
Hide file tree
Showing 6 changed files with 288 additions and 3 deletions.
4 changes: 4 additions & 0 deletions plugins/woocommerce/changelog/fix-38560
@@ -0,0 +1,4 @@
Significance: patch
Type: fix

Add support for taxonomy meta boxes in HPOS order edit screen.
27 changes: 27 additions & 0 deletions plugins/woocommerce/src/Internal/Admin/Orders/Edit.php
Expand Up @@ -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.
Expand All @@ -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.
*
Expand Down Expand Up @@ -110,10 +118,16 @@ public function setup( \WC_Order $order ) {
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.
Expand Down Expand Up @@ -159,6 +173,15 @@ private function add_order_specific_meta_box() {
);
}

/**
* 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.
*
Expand All @@ -176,6 +199,10 @@ public function handle_order_update() {

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.
*
Expand Down
@@ -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 );
}
}
Expand Up @@ -1641,6 +1641,84 @@ protected function persist_order_to_db( &$order, bool $force_all_fields = false

$changes = $order->get_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 );
}
}
}

/**
Expand Down
Expand Up @@ -9,7 +9,9 @@
use Automattic\WooCommerce\Internal\Admin\Orders\Edit;
use Automattic\WooCommerce\Internal\Admin\Orders\EditLock;
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\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\Internal\DependencyManagement\AbstractServiceProvider;

/**
Expand All @@ -28,6 +30,7 @@ class OrderAdminServiceProvider extends AbstractServiceProvider {
Edit::class,
ListTable::class,
EditLock::class,
TaxonomiesMetaBox::class,
);

/**
Expand All @@ -41,5 +44,6 @@ public function register() {
$this->share( Edit::class )->addArgument( PageController::class );
$this->share( ListTable::class )->addArgument( PageController::class );
$this->share( EditLock::class );
$this->share( TaxonomiesMetaBox::class )->addArgument( OrdersTableDataStore::class );
}
}
Expand Up @@ -190,7 +190,7 @@ public function set_props( $args, $context = 'set' ) {
*/
public function test_apply_coupon_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() );

$order = WC_Helper_Order::create_order();
Expand Down Expand Up @@ -253,8 +253,8 @@ public function test_apply_coupon_multiple_across_status() {
*/
public function test_apply_coupon_stores_meta_data() {
$coupon_code = 'coupon_test_meta_data';
$coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
$order = WC_Helper_Order::create_order();
$coupon = WC_Helper_Coupon::create_coupon( $coupon_code );
$order = WC_Helper_Order::create_order();
$order->set_status( 'processing' );
$order->save();
$order->apply_coupon( $coupon_code );
Expand Down Expand Up @@ -324,4 +324,29 @@ function( $order_id ) {
$order = wc_get_order( $order->get_id() );
$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 );
}
}

0 comments on commit e4f3273

Please sign in to comment.