From b9850d00fefffd4451a214a9ee82ff6a8017a894 Mon Sep 17 00:00:00 2001 From: Felipe Elia Date: Tue, 10 Dec 2024 19:20:30 -0300 Subject: [PATCH] Initial exploration of compatibility with WC HPOS --- .../classes/Feature/WooCommerce/Orders.php | 40 +++++- .../Feature/WooCommerce/OrdersHPOS.php | 131 ++++++++++++++++++ .../WooCommerce/TestWooCommerceOrders.php | 34 ++++- 3 files changed, 193 insertions(+), 12 deletions(-) create mode 100644 includes/classes/Feature/WooCommerce/OrdersHPOS.php diff --git a/includes/classes/Feature/WooCommerce/Orders.php b/includes/classes/Feature/WooCommerce/Orders.php index 8c705acdc..f6ee9f4fc 100644 --- a/includes/classes/Feature/WooCommerce/Orders.php +++ b/includes/classes/Feature/WooCommerce/Orders.php @@ -25,6 +25,13 @@ class Orders { */ protected $woocommerce; + /** + * Receive the OrdersHPOS object instance + * + * @var OrdersHPOS + */ + protected $orders_hpos; + /** * Class constructor * @@ -32,6 +39,7 @@ class Orders { */ public function __construct( WooCommerce $woocommerce ) { $this->woocommerce = $woocommerce; + $this->orders_hpos = new OrdersHPOS( $this ); } /** @@ -46,6 +54,10 @@ public function setup() { add_action( 'parse_query', [ $this, 'search_order' ], 11 ); add_action( 'pre_get_posts', [ $this, 'translate_args' ], 11, 1 ); add_filter( 'ep_admin_notices', [ $this, 'hpos_compatibility_notice' ] ); + + if ( $this->is_hpos_enabled() ) { + $this->orders_hpos->setup(); + } } /** @@ -61,6 +73,10 @@ public function tear_down() { remove_action( 'parse_query', [ $this, 'maybe_hook_woocommerce_search_fields' ], 1 ); remove_action( 'parse_query', [ $this, 'search_order' ], 11 ); remove_action( 'pre_get_posts', [ $this, 'translate_args' ], 11 ); + + if ( $this->is_hpos_enabled() ) { + $this->orders_hpos->tear_down(); + } } /** @@ -359,13 +375,7 @@ public function hpos_compatibility_notice( array $notices ) : array { return $notices; } - if ( - ! class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) - || ! method_exists( '\Automattic\WooCommerce\Utilities\OrderUtil', 'custom_orders_table_usage_is_enabled' ) ) { - return $notices; - } - - if ( ! \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) { + if ( ! $this->is_hpos_enabled() ) { return $notices; } @@ -480,6 +490,22 @@ public function translate_args( $query ) { $this->maybe_set_search_fields( $query ); } + /** + * Whether WooCommerce HPOS is enabled or not + * + * @since 5.3.0 + * @return boolean Whether WooCommerce HPOS is enabled or not + */ + public function is_hpos_enabled() : bool { + if ( + ! class_exists( '\Automattic\WooCommerce\Utilities\OrderUtil' ) + || ! method_exists( '\Automattic\WooCommerce\Utilities\OrderUtil', 'custom_orders_table_usage_is_enabled' ) ) { + return false; + } + + return \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); + } + /** * Handle calls to OrdersAutosuggest methods * diff --git a/includes/classes/Feature/WooCommerce/OrdersHPOS.php b/includes/classes/Feature/WooCommerce/OrdersHPOS.php new file mode 100644 index 000000000..8c2404c60 --- /dev/null +++ b/includes/classes/Feature/WooCommerce/OrdersHPOS.php @@ -0,0 +1,131 @@ +orders = $orders; + } + + /** + * Setup order HPOS related hooks + */ + public function setup() { + add_action( 'woocommerce_new_order', [ $this, 'sync_order' ] ); + add_action( 'woocommerce_refund_created', [ $this, 'sync_order' ] ); + add_action( 'woocommerce_update_order', [ $this, 'sync_order' ] ); + add_filter( 'ep_post_sync_args_post_prepare_meta', [ $this, 'set_order_data' ], 10, 2 ); + } + + /** + * Unsetup order HPOS related hooks + */ + public function tear_down() { + remove_action( 'woocommerce_new_order', [ $this, 'sync_order' ], 10, 2 ); + remove_action( 'woocommerce_refund_created', [ $this, 'sync_order' ], 10, 2 ); + remove_action( 'woocommerce_update_order', [ $this, 'sync_order' ], 10, 2 ); + remove_filter( 'ep_post_sync_args_post_prepare_meta', [ $this, 'set_order_data' ], 10, 2 ); + } + + /** + * Add orders to the sync queue + * + * @param int $order_id Order ID. + */ + public function sync_order( $order_id ) { + Indexables::factory()->get( 'post' )->sync_manager->add_to_queue( $order_id ); + } + + /** + * Add order data to ES document args + * + * @param array $post_args Post arguments + * @param int $post_id Post ID + * @return array + */ + public function set_order_data( $post_args, $post_id ) { + if ( \Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer::PLACEHOLDER_ORDER_POST_TYPE !== $post_args['post_type'] ) { + return $post_args; + } + + /** + * Post Indexable instance. + * + * @var \ElasticPress\Indexable\Post\Post + */ + $post_indexable = Indexables::factory()->get( 'post' ); + $order = wc_get_order( $post_id ); + $order_class = get_class( $order ); + $post_order = new $order_class(); + + $post_args['post_type'] = $order->get_type(); + $post_args['post_status'] = $order->get_status( 'edit' ); + $post_args['post_parent'] = $order->get_changes()['parent_id'] ?? $order->get_data()['parent_id'] ?? 0; + $post_args['post_date'] = gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ); + $post_args['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ); + $post_args['edit_date'] = true; + $post_args['post_excerpt'] = method_exists( $order, 'get_customer_note' ) ? $order->get_customer_note() : ''; + + $post_order = new $order_class(); + $post_order->set_id( $order->get_id() ); + $post_order->set_props( $order->get_data() ); + + error_log( var_export( $post_order, true ) ); + + add_filter( 'ep_prepare_meta_data', [ $this, 'prepare_meta_data' ], 10, 2 ); + $post_args['meta'] = $post_indexable->prepare_meta_types( $post_indexable->prepare_meta( $post_order ) ); + remove_filter( 'ep_prepare_meta_data', [ $this, 'prepare_meta_data' ] ); + + return $post_args; + } + + /** + * Format meta data + * + * @param array $order_meta Meta data + * @param WP_Post $order_post Order object + * @return array + */ + public function prepare_meta_data( $order_meta, $order_post ) { + $order = wc_get_order( $order_post->get_id() ); + + if ( is_null( $order->get_meta() ) ) { + return $order_meta; + } + + foreach ( $order->get_meta_data() as $meta_data ) { + $order_meta[ $meta_data->key ] = ( is_object( $meta_data->value ) && '__PHP_Incomplete_Class' === get_class( $meta_data->value ) ) + ? maybe_serialize( $meta_data->value ) + : $meta_data->value; + } + + return $order_meta; + } +} diff --git a/tests/php/features/WooCommerce/TestWooCommerceOrders.php b/tests/php/features/WooCommerce/TestWooCommerceOrders.php index 2f5a3257c..fbcd65fcc 100644 --- a/tests/php/features/WooCommerce/TestWooCommerceOrders.php +++ b/tests/php/features/WooCommerce/TestWooCommerceOrders.php @@ -348,11 +348,7 @@ public function test_hpos_compatibility_notice() { ElasticPress\Features::factory()->activate_feature( 'protected_content' ); $this->assertCount( 1, $this->orders->hpos_compatibility_notice( $notices ) ); - $option_name = \Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION; - $change_value = function() { - return 'yes'; - }; - add_filter( 'pre_option_' . $option_name, $change_value ); + $this->enable_hpos(); $new_notices = $this->orders->hpos_compatibility_notice( $notices ); $this->assertCount( 2, $new_notices ); @@ -370,4 +366,32 @@ public function test_hpos_compatibility_notice() { $this->assertCount( 1, $new_notices ); $this->assertArrayNotHasKey( 'wc_orders_incompatible', $new_notices ); } + + /** + * Test the `is_hpos_enabled` method + * + * @since 5.3.0 + * @group woocommerce + * @group woocommerce-orders + */ + public function test_is_hpos_enabled() { + $this->assertFalse( $this->orders->is_hpos_enabled() ); + + $this->enable_hpos(); + + $this->assertTrue( $this->orders->is_hpos_enabled() ); + } + + /** + * Utilitary function to enable WooCommerce HPOS + * + * @since 5.3.0 + */ + protected function enable_hpos() { + $option_name = \Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION; + $change_value = function() { + return 'yes'; + }; + add_filter( 'pre_option_' . $option_name, $change_value ); + } }