diff --git a/plugins/woocommerce/changelog/cot-backfill b/plugins/woocommerce/changelog/cot-backfill new file mode 100644 index 00000000000..249f098c7f2 --- /dev/null +++ b/plugins/woocommerce/changelog/cot-backfill @@ -0,0 +1,4 @@ +Significance: minor +Type: add + +Implement backfill for wp_post and wp_postmeta table for custom tables. diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php index 83d59992a42..52f9087be61 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php @@ -635,7 +635,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_discount_total( $value ) { - $this->set_prop( 'discount_total', wc_format_decimal( $value ) ); + $this->set_prop( 'discount_total', wc_format_decimal( $value, false, true ) ); } /** @@ -645,7 +645,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_discount_tax( $value ) { - $this->set_prop( 'discount_tax', wc_format_decimal( $value ) ); + $this->set_prop( 'discount_tax', wc_format_decimal( $value, false, true ) ); } /** @@ -655,7 +655,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_shipping_total( $value ) { - $this->set_prop( 'shipping_total', wc_format_decimal( $value ) ); + $this->set_prop( 'shipping_total', wc_format_decimal( $value, false, true ) ); } /** @@ -665,7 +665,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_shipping_tax( $value ) { - $this->set_prop( 'shipping_tax', wc_format_decimal( $value ) ); + $this->set_prop( 'shipping_tax', wc_format_decimal( $value, false, true ) ); $this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() ); } @@ -676,7 +676,7 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { * @throws WC_Data_Exception Exception may be thrown if value is invalid. */ public function set_cart_tax( $value ) { - $this->set_prop( 'cart_tax', wc_format_decimal( $value ) ); + $this->set_prop( 'cart_tax', wc_format_decimal( $value, false, true ) ); $this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() ); } diff --git a/plugins/woocommerce/includes/data-stores/class-wc-order-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/class-wc-order-data-store-cpt.php index bc055291c45..4cc09195e9a 100644 --- a/plugins/woocommerce/includes/data-stores/class-wc-order-data-store-cpt.php +++ b/plugins/woocommerce/includes/data-stores/class-wc-order-data-store-cpt.php @@ -76,6 +76,28 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement '_order_stock_reduced', ); + /** + * Getters for internal key in data stores. + * + * @var string[] + */ + protected $internal_data_store_key_getters = array( + '_download_permissions_granted' => 'download_permissions_granted', + '_recorded_sales' => 'recorded_sales', + '_recorded_coupon_usage_counts' => 'recorded_coupon_usage_counts', + '_order_stock_reduced' => 'stock_reduced', + '_new_order_email_sent' => 'email_sent', + ); + + /** + * Return internal key getters name. + * + * @return string[] + */ + public function get_internal_data_store_key_getters() { + return $this->internal_data_store_key_getters; + } + /** * Method to create a new order in the database. * @@ -297,6 +319,57 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement do_action( 'woocommerce_order_object_updated_props', $order, $updated_props ); } + /** + * Given an initialized order object, update the post/postmeta records. + * + * @param WC_Order $order Order object. + * + * @return bool Whether the order was updated. + */ + public function update_order_from_object( $order ) { + if ( ! $order->get_id() ) { + return false; + } + $this->update_order_meta_from_object( $order ); + return wp_update_post( + array( + 'ID' => $order->get_id(), + 'post_date' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ), + 'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), + 'post_status' => $this->get_post_status( $order ), + 'post_parent' => $order->get_parent_id(), + 'post_excerpt' => $this->get_post_excerpt( $order ), + 'post_type' => 'shop_order', + ) + ); + } + + /** + * Helper method to update order metadata from intialized order object. + * + * @param WC_Order $order Order object. + */ + private function update_order_meta_from_object( $order ) { + if ( is_null( $order->get_meta() ) ) { + return; + } + + $existing_meta_data = get_post_meta( $order->get_id() ); + + foreach ( $order->get_meta_data() as $meta_data ) { + if ( isset( $existing_meta_data[ $meta_data->key ] ) ) { + if ( $existing_meta_data[ $meta_data->key ] === $meta_data->value ) { + continue; + } + delete_post_meta( $order->get_id(), $meta_data->key ); + unset( $existing_meta_data[ $meta_data->key ] ); + } + add_post_meta( $order->get_id(), $meta_data->key, $meta_data->value, false ); + } + + $this->update_post_meta( $order ); + } + /** * Excerpt for post. * @@ -630,6 +703,29 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement update_post_meta( $order_id, '_recorded_coupon_usage_counts', wc_bool_to_string( $set ) ); } + /** + * Whether email have been sent for this order. + * + * @param WC_Order|int $order Order ID or order object. + * + * @return bool Whether email is sent. + */ + public function get_email_sent( $order ) { + $order_id = WC_Order_Factory::get_order_id( $order ); + return wc_string_to_bool( get_post_meta( $order_id, '_new_order_email_sent', true ) ); + } + + /** + * Stores information about whether email was sent. + * + * @param WC_Order|int $order Order ID or order object. + * @param bool $set True or false. + */ + public function set_email_sent( $order, $set ) { + $order_id = WC_Order_Factory::get_order_id( $order ); + update_post_meta( $order_id, '_new_order_email_sent', wc_bool_to_string( $set ) ); + } + /** * Return array of coupon_code => meta_key for coupon which have usage limit and have tentative keys. * Pass $coupon_id if key for only one of the coupon is needed. @@ -874,7 +970,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement } else { update_post_caches( $query->posts ); // We already fetching posts, might as well hydrate some caches. $order_ids = wp_list_pluck( $query->posts, 'ID' ); - $orders = $this->compile_orders( $order_ids, $query_vars, $query ); + $orders = $this->compile_orders( $order_ids, $query_vars, $query ); } if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) { @@ -942,7 +1038,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement $cache_keys_mapping[ $order_id ] = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'refunds' . $order_id; } $non_cached_ids = array(); - $cache_values = wc_cache_get_multiple( array_values( $cache_keys_mapping ), 'orders' ); + $cache_values = wc_cache_get_multiple( array_values( $cache_keys_mapping ), 'orders' ); foreach ( $order_ids as $order_id ) { if ( false === $cache_values[ $cache_keys_mapping[ $order_id ] ] ) { $non_cached_ids[] = $order_id; @@ -952,11 +1048,11 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement return; } - $refunds = wc_get_orders( + $refunds = wc_get_orders( array( - 'type' => 'shop_order_refund', + 'type' => 'shop_order_refund', 'post_parent__in' => $non_cached_ids, - 'limit' => - 1, + 'limit' => - 1, ) ); $order_refunds = array_reduce( @@ -1002,13 +1098,13 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement return; } } - $cache_keys = array_map( + $cache_keys = array_map( function ( $order_id ) { return 'order-items-' . $order_id; }, $order_ids ); - $cache_values = wc_cache_get_multiple( $cache_keys, 'orders' ); + $cache_values = wc_cache_get_multiple( $cache_keys, 'orders' ); $non_cached_ids = array(); foreach ( $order_ids as $order_id ) { if ( false === $cache_values[ 'order-items-' . $order_id ] ) { @@ -1019,9 +1115,9 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement return; } - $non_cached_ids = esc_sql( $non_cached_ids ); + $non_cached_ids = esc_sql( $non_cached_ids ); $non_cached_ids_string = implode( ',', $non_cached_ids ); - $order_items = $wpdb->get_results( + $order_items = $wpdb->get_results( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared "SELECT order_item_type, order_item_id, order_id, order_item_name FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id in ( $non_cached_ids_string ) ORDER BY order_item_id;" ); @@ -1068,7 +1164,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement foreach ( $order_ids as $order_id ) { $cache_keys_mapping[ $order_id ] = WC_Order::generate_meta_cache_key( $order_id, 'orders' ); } - $cache_values = wc_cache_get_multiple( array_values( $cache_keys_mapping ), 'orders' ); + $cache_values = wc_cache_get_multiple( array_values( $cache_keys_mapping ), 'orders' ); $non_cached_ids = array(); foreach ( $order_ids as $order_id ) { if ( false === $cache_values[ $cache_keys_mapping[ $order_id ] ] ) { @@ -1078,8 +1174,8 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement if ( empty( $non_cached_ids ) ) { return; } - $order_ids = esc_sql( $non_cached_ids ); - $order_ids_in = "'" . implode( "', '", $order_ids ) . "'"; + $order_ids = esc_sql( $non_cached_ids ); + $order_ids_in = "'" . implode( "', '", $order_ids ) . "'"; $raw_meta_data_array = $wpdb->get_results( // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared "SELECT post_id as object_id, meta_id, meta_key, meta_value diff --git a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/PostToOrderOpTableMigrator.php b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/PostToOrderOpTableMigrator.php index 2cda34ad884..cb142df21a9 100644 --- a/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/PostToOrderOpTableMigrator.php +++ b/plugins/woocommerce/src/Database/Migrations/CustomOrderTable/PostToOrderOpTableMigrator.php @@ -115,10 +115,18 @@ class PostToOrderOpTableMigrator extends MetaToCustomTableMigrator { 'type' => 'date_epoch', 'destination' => 'date_paid_gmt', ), + '_paid_date' => array( // For compatibility with WC < 2.6. + 'type' => 'date', + 'destination' => 'date_paid_gmt', + ), '_date_completed' => array( 'type' => 'date_epoch', 'destination' => 'date_completed_gmt', ), + '_completed_date' => array( // For compatibility with WC < 2.6. + 'type' => 'date', + 'destination' => 'date_completed_gmt', + ), '_order_shipping_tax' => array( 'type' => 'decimal', 'destination' => 'shipping_tax_amount', diff --git a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php index 68cc2c33708..57d5d825988 100644 --- a/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php +++ b/plugins/woocommerce/src/Database/Migrations/MigrationHelper.php @@ -24,32 +24,6 @@ class MigrationHelper { 'bool' => '%d', ); - /** - * Get insert clause for appropriate switch. - * - * @param string $switch Name of the switch to use. - * - * @return string Insert clause. - */ - public static function get_insert_switch( string $switch ): string { - switch ( $switch ) { - case 'insert_ignore': - $insert_query = 'INSERT IGNORE'; - break; - case 'replace': // delete and then insert. - $insert_query = 'REPLACE'; - break; - case 'update': - $insert_query = 'UPDATE'; - break; - case 'insert': - default: - $insert_query = 'INSERT'; - } - - return $insert_query; - } - /** * Helper method to escape backtick in various schema fields. * diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index 29e0fcd735e..779ceaf4123 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -265,18 +265,30 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements 'type' => 'string', 'name' => 'prices_include_tax', ), - 'coupon_usages_are_counted' => array( 'type' => 'bool' ), - 'download_permission_granted' => array( 'type' => 'bool' ), + 'coupon_usages_are_counted' => array( + 'type' => 'bool', + 'name' => 'recorded_coupon_usage_counts', + ), + 'download_permission_granted' => array( + 'type' => 'bool', + 'name' => 'download_permissions_granted', + ), 'cart_hash' => array( 'type' => 'string', 'name' => 'cart_hash', ), - 'new_order_email_sent' => array( 'type' => 'string' ), + 'new_order_email_sent' => array( + 'type' => 'string', + 'name' => 'new_order_email_sent', + ), 'order_key' => array( 'type' => 'string', 'name' => 'order_key', ), - 'order_stock_reduced' => array( 'type' => 'bool' ), + 'order_stock_reduced' => array( + 'type' => 'bool', + 'name' => 'stock_reduced', + ), 'date_paid_gmt' => array( 'type' => 'date', 'name' => 'date_paid', @@ -299,7 +311,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements ), 'discount_total_amount' => array( 'type' => 'decimal', - 'name' => 'discount_total_amount', + 'name' => 'discount_total', ), 'recorded_sales' => array( 'type' => 'bool' ), ); @@ -329,6 +341,162 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements return $this->all_order_column_mapping; } + /** + * Backfills order details in to WP_Post DB. Uses WC_Order_Data_store_CPT. + * + * @param \WC_Order $order Order object to backfill. + */ + public function backfill_post_record( $order ) { + $cpt_data_store = new \WC_Order_Data_Store_CPT(); + $cpt_data_store->update_order_from_object( $order ); + foreach ( $cpt_data_store->get_internal_data_store_key_getters() as $key => $getter_name ) { + if ( + is_callable( array( $cpt_data_store, "set_$getter_name" ) ) && + is_callable( array( $this, "get_$getter_name" ) ) + ) { + call_user_func_array( + array( + $cpt_data_store, + "set_$getter_name", + ), + array( + $order, + $this->{"get_$getter_name"}( $order ), + ) + ); + } + } + } + + /** + * Get information about whether permissions are granted yet. + * + * @param \WC_Order $order Order object. + * + * @return bool Whether permissions are granted. + */ + public function get_download_permissions_granted( $order ) { + return wc_string_to_bool( $order->get_meta( '_download_permissions_granted', true ) ); + } + + /** + * Stores information about whether permissions were generated yet. + * + * @param \WC_Order $order Order ID or order object. + * @param bool $set True or false. + */ + public function set_download_permissions_granted( $order, $set ) { + return $order->update_meta_data( '_download_permissions_granted', wc_bool_to_string( $set ) ); + } + + /** + * Gets information about whether sales were recorded. + * + * @param \WC_Order $order Order object. + * + * @return bool Whether sales are recorded. + */ + public function get_recorded_sales( $order ) { + return wc_string_to_bool( $order->get_meta( '_recorded_sales', true ) ); + } + + /** + * Stores information about whether sales were recorded. + * + * @param \WC_Order $order Order object. + * @param bool $set True or false. + */ + public function set_recorded_sales( $order, $set ) { + return $order->update_meta_data( '_recorded_sales', wc_bool_to_string( $set ) ); + } + + /** + * Gets information about whether coupon counts were updated. + * + * @param \WC_Order $order Order object. + * + * @return bool Whether coupon counts were updated. + */ + public function get_recorded_coupon_usage_counts( $order ) { + return wc_string_to_bool( $order->get_meta( '_recorded_coupon_usage_counts', true ) ); + } + + /** + * Stores information about whether coupon counts were updated. + * + * @param \WC_Order $order Order object. + * @param bool $set True or false. + */ + public function set_recorded_coupon_usage_counts( $order, $set ) { + return $order->update_meta_data( '_recorded_coupon_usage_counts', wc_bool_to_string( $set ) ); + } + + /** + * Whether email have been sent for this order. + * + * @param \WC_Order|int $order Order object. + * + * @return bool Whether email is sent. + */ + public function get_email_sent( $order ) { + return wc_string_to_bool( $order->get_meta( '_new_order_email_sent', true ) ); + } + + /** + * Stores information about whether email was sent. + * + * @param \WC_Order $order Order object. + * + * @param bool $set True or false. + */ + public function set_email_sent( $order, $set ) { + return $order->update_meta_data( '_new_order_email_sent', wc_bool_to_string( $set ) ); + } + + /** + * Helper setter for email_sent. + * + * @param \WC_Order $order Order object. + * + * @return bool Whether email was sent. + */ + private function get_new_order_email_sent( $order ) { + return $this->get_email_sent( $order ); + } + + /** + * Helper setter for new order email sent. + * + * @param \WC_Order $order Order object. + * @param bool $set True or false. + * + * @return bool Whether email was sent. + */ + private function set_new_order_email_sent( $order, $set ) { + return $this->set_email_sent( $order, $set ); + } + + /** + * Gets information about whether stock was reduced. + * + * @param \WC_Order $order Order object. + * + * @return bool Whether stock was reduced. + */ + public function get_stock_reduced( $order ) { + return wc_string_to_bool( $order->get_meta( '_order_stock_reduced', true ) ); + } + + /** + * Stores information about whether stock was reduced. + * + * @param \WC_Order $order Order ID or order object. + * @param bool $set True or false. + */ + public function set_stock_reduced( $order, $set ) { + return $order->update_meta_data( '_order_stock_reduced', wc_string_to_bool( $set ) ); + } + //phpcs:disable Squiz.Commenting, Generic.Commenting // TODO: Add methods for other table names as appropriate. @@ -372,33 +540,6 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements return array(); } - public function get_download_permissions_granted( $order ) { - // TODO: Implement get_download_permissions_granted() method. - false; - } - - public function set_download_permissions_granted( $order, $set ) { - // TODO: Implement set_download_permissions_granted() method. - } - - public function get_recorded_sales( $order ) { - // TODO: Implement get_recorded_sales() method. - return false; - } - - public function set_recorded_sales( $order, $set ) { - // TODO: Implement set_recorded_sales() method. - } - - public function get_recorded_coupon_usage_counts( $order ) { - // TODO: Implement get_recorded_coupon_usage_counts() method. - return false; - } - - public function set_recorded_coupon_usage_counts( $order, $set ) { - // TODO: Implement set_recorded_coupon_usage_counts() method. - } - public function get_order_type( $order_id ) { // TODO: Implement get_order_type() method. return 'shop_order'; @@ -418,6 +559,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements if ( ! $order->get_id() ) { throw new \Exception( __( 'ID must be set for an order to be read', 'woocommerce' ) ); } + $order->read_meta_data(); $order_data = $this->get_order_data_for_id( $order->get_id() ); foreach ( $this->get_all_order_column_mappings() as $table_name => $column_mapping ) { foreach ( $column_mapping as $column_name => $prop_details ) { @@ -427,6 +569,8 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements $prop_setter_function_name = "set_{$prop_details['name']}"; if ( is_callable( array( $order, $prop_setter_function_name ) ) ) { $order->{$prop_setter_function_name}( $order_data->{$prop_details['name']} ); + } elseif ( is_callable( array( $this, $prop_setter_function_name ) ) ) { + $this->{$prop_setter_function_name}( $order, $order_data->{$prop_details['name']} ); } } } @@ -434,6 +578,33 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements $order->set_object_read(); } + /** + * Read metadata directly from database. + * + * @param \WC_Order $order Order object. + * + * @return array Metadata array. + */ + public function read_meta( &$order ) { + global $wpdb; + $meta_table = $this::get_meta_table_name(); + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $meta_table is hardcoded. + $raw_meta_data = $wpdb->get_results( + $wpdb->prepare( + " +SELECT id as meta_id, meta_key, meta_value +FROM $meta_table +WHERE order_id = %d +ORDER BY meta_id; +", + $order->get_id() + ) + ); + // phpcs:enable + + return $this->filter_raw_meta_data( $order, $raw_meta_data ); + } + /** * Return order data for a single order ID. * @@ -537,6 +708,7 @@ LEFT JOIN {$operational_data_clauses['join']} "{$clauses['join']} AND $address_table_alias.address_type = %s", $address_type ); + // phpcs:enable return array( 'select' => $clauses['select'], @@ -603,6 +775,7 @@ LEFT JOIN {$operational_data_clauses['join']} //phpcs:disable Squiz.Commenting, Generic.Commenting + /** * @param \WC_Order $order */ @@ -630,14 +803,6 @@ LEFT JOIN {$operational_data_clauses['join']} throw new \Exception( 'Unimplemented' ); } - public function get_stock_reduced( $order ) { - return false; - } - - public function set_stock_reduced( $order, $set ) { - throw new \Exception( 'Unimplemented' ); - } - public function query( $query_vars ) { return array(); } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php index 4d40bcc1fe6..0de8cef7f62 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Helpers/OrderHelper.php @@ -1,4 +1,5 @@ get( CustomOrdersTableController::class ); + $order_table_controller->show_feature(); + $synchronizer = wc_get_container() + ->get( DataSynchronizer::class ); + if ( $synchronizer->check_orders_table_exists() ) { + $synchronizer->delete_database_tables(); + } + } + /** * Helper method to create custom tables if not present. */ @@ -208,8 +225,8 @@ class OrderHelper { $order->get_data_store()->set_download_permissions_granted( $order, true ); $order->get_data_store()->set_recorded_sales( $order, true ); $order->set_cart_hash( '1234' ); - $order->update_meta_data( '_new_order_email_sent', 'true' ); - $order->update_meta_data( '_order_stock_reduced', 'true' ); + $order->get_data_store()->set_email_sent( $order, true ); + $order->get_data_store()->stock_reduced( $order, true ); $order->set_date_paid( time() ); $order->set_date_completed( time() ); $order->calculate_shipping(); diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php index 657ec3479b9..5a41d4e70cc 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php @@ -1,6 +1,7 @@ sut = wc_get_container()->get( OrdersTableDataStore::class ); $this->migrator = wc_get_container()->get( PostsToOrdersMigrationController::class ); $this->cpt_data_store = new WC_Order_Data_Store_CPT(); + } + + /** + * Destroys system under test. + */ + public function tearDown(): void { // Add back removed filter. add_filter( 'query', array( $this, '_create_temporary_tables' ) ); add_filter( 'query', array( $this, '_drop_temporary_tables' ) ); + parent::tearDown(); } /** @@ -50,26 +59,100 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { $post_order_id = OrderHelper::create_complex_wp_post_order(); $this->migrator->migrate_orders( array( $post_order_id ) ); + wp_cache_flush(); $cot_order = new WC_Order(); $cot_order->set_id( $post_order_id ); + $this->switch_data_store( $cot_order, $this->sut ); $this->sut->read( $cot_order ); + wp_cache_flush(); $post_order = new WC_Order(); $post_order->set_id( $post_order_id ); + $this->switch_data_store( $post_order, $this->cpt_data_store ); $this->cpt_data_store->read( $post_order ); - $post_order_data = $post_order->get_data(); - $string_to_num_keys = array( 'discount_total', 'discount_tax', 'shipping_total', 'shipping_tax', 'cart_tax' ); - array_walk( - $post_order_data, - function ( &$data, $key ) use ( $string_to_num_keys ) { - if ( in_array( $key, $string_to_num_keys, true ) ) { - $data = (float) $data; - } - } - ); - - $this->assertEquals( $post_order_data, $cot_order->get_data() ); + $this->assertEquals( $post_order->get_base_data(), $cot_order->get_base_data() ); + $post_order_meta_keys = wp_list_pluck( $post_order->get_meta_data(), 'key' ); + foreach ( $post_order_meta_keys as $meta_key ) { + $this->assertEquals( $post_order->get_meta( $meta_key ), $cot_order->get_meta( $meta_key ) ); + } } + /** + * Test whether backfill_post_record works as expected. + */ + public function test_backfill_post_record() { + $post_order_id = OrderHelper::create_complex_wp_post_order(); + $this->migrator->migrate_orders( array( $post_order_id ) ); + + $post_data = get_post( $post_order_id, ARRAY_A ); + $post_meta_data = get_post_meta( $post_order_id ); + // TODO: Remove `_recorded_sales` from exempted keys after https://github.com/woocommerce/woocommerce/issues/32843. + $exempted_keys = array( 'post_modified', 'post_modified_gmt', '_recorded_sales' ); + $convert_to_float_keys = array( '_cart_discount_tax', '_order_shipping', '_order_shipping_tax', '_order_tax', '_cart_discount', 'cart_tax' ); + $exempted_keys = array_flip( array_merge( $exempted_keys, $convert_to_float_keys ) ); + + $post_data_float = array_intersect_key( $post_data, array_flip( $convert_to_float_keys ) ); + $post_meta_data_float = array_intersect_key( $post_meta_data, array_flip( $convert_to_float_keys ) ); + $post_data = array_diff_key( $post_data, $exempted_keys ); + $post_meta_data = array_diff_key( $post_meta_data, $exempted_keys ); + + // Let's update post data. + wp_update_post( + array( + 'ID' => $post_order_id, + 'post_status' => 'migration_pending', + 'post_type' => DataSynchronizer::PLACEHOLDER_ORDER_POST_TYPE, + 'ping_status' => 'closed', + 'post_parent' => 0, + 'menu_order' => 0, + 'post_date' => '', + 'post_date_gmt' => '', + ) + ); + $this->delete_all_meta_for_post( $post_order_id ); + + $this->assertEquals( 'migration_pending', get_post_status( $post_order_id ) ); // assert post was updated. + $this->assertEquals( array(), get_post_meta( $post_order_id ) ); // assert postmeta was deleted. + + $cot_order = new WC_Order(); + $cot_order->set_id( $post_order_id ); + $this->switch_data_store( $cot_order, $this->sut ); + $this->sut->read( $cot_order ); + $this->sut->backfill_post_record( $cot_order ); + + $this->assertEquals( $post_data, array_diff_key( get_post( $post_order_id, ARRAY_A ), $exempted_keys ) ); + $this->assertEquals( $post_meta_data, array_diff_key( get_post_meta( $post_order_id ), $exempted_keys ) ); + + foreach ( $post_data_float as $float_key => $value ) { + $this->assertEquals( (float) get_post( $post_order_id, ARRAY_A )[ $float_key ], (float) $value, "Value for $float_key does not match." ); + } + + foreach ( $post_meta_data_float as $float_key => $value ) { + $this->assertEquals( (float) get_post_meta( $post_order_id )[ $float_key ], (float) $value, "Value for $float_key does not match." ); + } + } + + /** + * Helper function to delete all meta for post. + * + * @param int $post_id Post ID to delete data for. + */ + private function delete_all_meta_for_post( $post_id ) { + global $wpdb; + $wpdb->delete( $wpdb->postmeta, array( 'post_id' => $post_id ) ); + } + + /** + * Helper method to allow switching data stores. + * + * @param WC_Order $order Order object. + * @param WC_Data_Store $data_store Data store object to switch order to. + */ + private function switch_data_store( $order, $data_store ) { + $update_data_store_func = function ( $data_store ) { + $this->data_store = $data_store; + }; + $update_data_store_func->call( $order, $data_store ); + } }