diff --git a/plugins/woocommerce/changelog/fix-36678 b/plugins/woocommerce/changelog/fix-36678 new file mode 100644 index 00000000000..c784cf1db18 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36678 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Add HPOS compat for admin report functions. diff --git a/plugins/woocommerce/changelog/fix-36679 b/plugins/woocommerce/changelog/fix-36679 new file mode 100644 index 00000000000..ba4a88ad52d --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36679 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Add support for `after`, `before`, `modified_after` and `modified_before` params in local timezone. diff --git a/plugins/woocommerce/changelog/fix-36680 b/plugins/woocommerce/changelog/fix-36680 new file mode 100644 index 00000000000..1f0244c8d51 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36680 @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Changes are only in unit tests (for HPOS compat). + + diff --git a/plugins/woocommerce/changelog/fix-36681 b/plugins/woocommerce/changelog/fix-36681 new file mode 100644 index 00000000000..861800cd5f3 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36681 @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Skipping unit test for legacy widgets when in HPOS context. + + diff --git a/plugins/woocommerce/changelog/fix-36682 b/plugins/woocommerce/changelog/fix-36682 new file mode 100644 index 00000000000..57e3e2c4011 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36682 @@ -0,0 +1,5 @@ +Significance: patch +Type: fix +Comment: Changes are only in unit tests, no functionality is affected. + + diff --git a/plugins/woocommerce/changelog/fix-36684 b/plugins/woocommerce/changelog/fix-36684 new file mode 100644 index 00000000000..46751fa0216 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36684 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Add HPOS compat for wc-user-functions.php. diff --git a/plugins/woocommerce/changelog/fix-36685 b/plugins/woocommerce/changelog/fix-36685 new file mode 100644 index 00000000000..6aba52e9087 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36685 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Handle date arguments in OrderTableQuery correctly by adjusting their timezones before running. diff --git a/plugins/woocommerce/changelog/fix-36685-2 b/plugins/woocommerce/changelog/fix-36685-2 new file mode 100644 index 00000000000..9a8084aeee3 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36685-2 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Fetch order first to refresh cache before returning prop. diff --git a/plugins/woocommerce/changelog/fix-36686 b/plugins/woocommerce/changelog/fix-36686 new file mode 100644 index 00000000000..8ee9da198b9 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-36686 @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Treat order as seperate resource when validating for webhook since it's not necessarily a CPT anymore. diff --git a/plugins/woocommerce/includes/class-wc-post-data.php b/plugins/woocommerce/includes/class-wc-post-data.php index b27cfe47db8..c1e3aa6b5f0 100644 --- a/plugins/woocommerce/includes/class-wc-post-data.php +++ b/plugins/woocommerce/includes/class-wc-post-data.php @@ -54,6 +54,7 @@ class WC_Post_Data { add_action( 'wp_trash_post', array( __CLASS__, 'trash_post' ) ); add_action( 'untrashed_post', array( __CLASS__, 'untrash_post' ) ); add_action( 'before_delete_post', array( __CLASS__, 'before_delete_order' ) ); + add_action( 'woocommerce_before_delete_order', array( __CLASS__, 'before_delete_order' ) ); // Meta cache flushing. add_action( 'updated_post_meta', array( __CLASS__, 'flush_object_meta_cache' ), 10, 4 ); diff --git a/plugins/woocommerce/includes/class-wc-webhook.php b/plugins/woocommerce/includes/class-wc-webhook.php index 16d98af9ad3..3200b989057 100644 --- a/plugins/woocommerce/includes/class-wc-webhook.php +++ b/plugins/woocommerce/includes/class-wc-webhook.php @@ -274,21 +274,25 @@ class WC_Webhook extends WC_Legacy_Webhook { private function is_valid_resource( $arg ) { $resource = $this->get_resource(); - if ( in_array( $resource, array( 'order', 'product', 'coupon' ), true ) ) { + if ( in_array( $resource, array( 'product', 'coupon' ), true ) ) { $status = get_post_status( absint( $arg ) ); // Ignore auto drafts for all resources. if ( in_array( $status, array( 'auto-draft', 'new' ), true ) ) { return false; } + } - // Ignore standard drafts for orders. - if ( 'order' === $resource && 'draft' === $status ) { + if ( 'order' === $resource ) { + // Check registered order types for order types args. + if ( ! OrderUtil::is_order( absint( $arg ), wc_get_order_types( 'order-webhooks' ) ) ) { return false; } - // Check registered order types for order types args. - if ( 'order' === $resource && ! OrderUtil::is_order( absint( $arg ), wc_get_order_types( 'order-webhooks' ) ) ) { + $order = wc_get_order( absint( $arg ) ); + + // Ignore standard drafts for orders. + if ( in_array( $order->get_status(), array( 'draft', 'auto-draft', 'new' ), true ) ) { return false; } } diff --git a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php index fad3a54eca4..b26a0875462 100644 --- a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php +++ b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php @@ -413,6 +413,89 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme return $items; } + /** + * Return the order type of a given item which belongs to WC_Order. + * + * @since 3.2.0 + * @param WC_Order $order Order Object. + * @param int $order_item_id Order item id. + * @return string Order Item type + */ + public function get_order_item_type( $order, $order_item_id ) { + global $wpdb; + return $wpdb->get_var( $wpdb->prepare( "SELECT DISTINCT order_item_type FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d and order_item_id = %d;", $order->get_id(), $order_item_id ) ); + } + + /** + * Prime following caches: + * 1. item-$order_item_id For individual items. + * 2. order-items-$order-id For fetching items associated with an order. + * 3. order-item meta. + * + * @param array $order_ids Order Ids to prime cache for. + * @param array $query_vars Query vars for the query. + */ + protected function prime_order_item_caches_for_orders( $order_ids, $query_vars ) { + global $wpdb; + if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] ) { + $line_items = array( + 'line_items', + 'shipping_lines', + 'fee_lines', + 'coupon_lines', + ); + + if ( is_array( $query_vars['fields'] ) && 0 === count( array_intersect( $line_items, $query_vars['fields'] ) ) ) { + return; + } + } + $cache_keys = array_map( + function ( $order_id ) { + return 'order-items-' . $order_id; + }, + $order_ids + ); + $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 ] ) { + $non_cached_ids[] = $order_id; + } + } + if ( empty( $non_cached_ids ) ) { + return; + } + + $non_cached_ids = esc_sql( $non_cached_ids ); + $non_cached_ids_string = implode( ',', $non_cached_ids ); + $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;" + ); + if ( empty( $order_items ) ) { + return; + } + + $order_items_for_all_orders = array_reduce( + $order_items, + function ( $order_items_collection, $order_item ) { + if ( ! isset( $order_items_collection[ $order_item->order_id ] ) ) { + $order_items_collection[ $order_item->order_id ] = array(); + } + $order_items_collection[ $order_item->order_id ][] = $order_item; + return $order_items_collection; + } + ); + foreach ( $order_items_for_all_orders as $order_id => $items ) { + wp_cache_set( 'order-items-' . $order_id, $items, 'orders' ); + } + foreach ( $order_items as $item ) { + wp_cache_set( 'item-' . $item->order_item_id, $item, 'order-items' ); + } + $order_item_ids = wp_list_pluck( $order_items, 'order_item_id' ); + update_meta_cache( 'order_item', $order_item_ids ); + } + /** * Remove all line items (products, coupons, shipping, taxes) from the order. * 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 b8e26142e44..a8982de92ee 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 @@ -1118,76 +1118,6 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement } } - /** - * Prime following caches: - * 1. item-$order_item_id For individual items. - * 2. order-items-$order-id For fetching items associated with an order. - * 3. order-item meta. - * - * @param array $order_ids Order Ids to prime cache for. - * @param array $query_vars Query vars for the query. - */ - private function prime_order_item_caches_for_orders( $order_ids, $query_vars ) { - global $wpdb; - if ( isset( $query_vars['fields'] ) && 'all' !== $query_vars['fields'] ) { - $line_items = array( - 'line_items', - 'shipping_lines', - 'fee_lines', - 'coupon_lines', - ); - - if ( is_array( $query_vars['fields'] ) && 0 === count( array_intersect( $line_items, $query_vars['fields'] ) ) ) { - return; - } - } - $cache_keys = array_map( - function ( $order_id ) { - return 'order-items-' . $order_id; - }, - $order_ids - ); - $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 ] ) { - $non_cached_ids[] = $order_id; - } - } - if ( empty( $non_cached_ids ) ) { - return; - } - - $non_cached_ids = esc_sql( $non_cached_ids ); - $non_cached_ids_string = implode( ',', $non_cached_ids ); - $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;" - ); - if ( empty( $order_items ) ) { - return; - } - - $order_items_for_all_orders = array_reduce( - $order_items, - function ( $order_items_collection, $order_item ) { - if ( ! isset( $order_items_collection[ $order_item->order_id ] ) ) { - $order_items_collection[ $order_item->order_id ] = array(); - } - $order_items_collection[ $order_item->order_id ][] = $order_item; - return $order_items_collection; - } - ); - foreach ( $order_items_for_all_orders as $order_id => $items ) { - wp_cache_set( 'order-items-' . $order_id, $items, 'orders' ); - } - foreach ( $order_items as $item ) { - wp_cache_set( 'item-' . $item->order_item_id, $item, 'order-items' ); - } - $order_item_ids = wp_list_pluck( $order_items, 'order_item_id' ); - update_meta_cache( 'order_item', $order_item_ids ); - } - /** * Prime cache for raw meta data for orders in bulk. Difference between this and WP built-in metadata is that this method also fetches `meta_id` field which we use and cache it. * @@ -1240,17 +1170,4 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement ); WC_Order::prime_raw_meta_data_cache( $raw_meta_data_collection, 'orders' ); } - - /** - * Return the order type of a given item which belongs to WC_Order. - * - * @since 3.2.0 - * @param WC_Order $order Order Object. - * @param int $order_item_id Order item id. - * @return string Order Item type - */ - public function get_order_item_type( $order, $order_item_id ) { - global $wpdb; - return $wpdb->get_var( $wpdb->prepare( "SELECT DISTINCT order_item_type FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d and order_item_id = %d;", $order->get_id(), $order_item_id ) ); - } } diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php index 9f49bb546c9..b646c36a2e3 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version2/class-wc-rest-orders-v2-controller.php @@ -325,6 +325,29 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { return $result; } + /** + * With HPOS, few internal meta keys such as _billing_address_index, _shipping_address_index are not considered internal anymore (since most internal keys were flattened into dedicated columns). + * + * This function helps in filtering out any remaining internal meta keys with HPOS is enabled. + * + * @param array $meta_data Order meta data. + * + * @return array Filtered order meta data. + */ + private function filter_internal_meta_keys( $meta_data ) { + if ( ! OrderUtil::custom_orders_table_usage_is_enabled() ) { + return $meta_data; + } + $cpt_hidden_keys = ( new \WC_Order_Data_Store_CPT() )->get_internal_meta_keys(); + $meta_data = array_filter( + $meta_data, + function ( $meta ) use ( $cpt_hidden_keys ) { + return ! in_array( $meta->key, $cpt_hidden_keys, true ); + } + ); + return array_values( $meta_data ); + } + /** * Get formatted item data. * @@ -369,6 +392,7 @@ class WC_REST_Orders_V2_Controller extends WC_REST_CRUD_Controller { case 'meta_data': $meta_data = $order->get_meta_data(); $data['meta_data'] = $this->get_meta_data_for_response( $this->request, $meta_data ); + $data['meta_data'] = $this->filter_internal_meta_keys( $data['meta_data'] ); break; case 'line_items': $data['line_items'] = $order->get_items( 'line_item' ); diff --git a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php index b594de2284f..ab11869e49e 100644 --- a/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php +++ b/plugins/woocommerce/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller.php @@ -235,33 +235,6 @@ class WC_REST_Orders_Controller extends WC_REST_Orders_V2_Controller { } } - /** - * Get formatted item data. - * - * @param WC_Order $order WC_Data instance. - * @return array - */ - protected function get_formatted_item_data( $order ) { - $item_data = parent::get_formatted_item_data( $order ); - $cpt_hidden_keys = array(); - - if ( OrderUtil::custom_orders_table_usage_is_enabled() ) { - $cpt_hidden_keys = ( new \WC_Order_Data_Store_CPT() )->get_internal_meta_keys(); - } - - // XXX: This might be removed once we finalize the design for internal keys vs meta vs props in COT. - if ( ! empty( $item_data['meta_data'] ) ) { - $item_data['meta_data'] = array_filter( - $item_data['meta_data'], - function( $meta ) use ( $cpt_hidden_keys ) { - return ! in_array( $meta->key, $cpt_hidden_keys, true ); - } - ); - } - - return $item_data; - } - /** * Prepare objects query. * diff --git a/plugins/woocommerce/includes/wc-user-functions.php b/plugins/woocommerce/includes/wc-user-functions.php index 6b98a8a8df0..7b53871b454 100644 --- a/plugins/woocommerce/includes/wc-user-functions.php +++ b/plugins/woocommerce/includes/wc-user-functions.php @@ -714,16 +714,40 @@ function wc_get_customer_order_count( $user_id ) { function wc_reset_order_customer_id_on_deleted_user( $user_id ) { global $wpdb; - $wpdb->update( - $wpdb->postmeta, - array( - 'meta_value' => 0, - ), - array( - 'meta_key' => '_customer_user', - 'meta_value' => $user_id, - ) - ); // WPCS: slow query ok. + if ( OrderUtil::custom_orders_table_usage_is_enabled() ) { + $order_table_ds = wc_get_container()->get( OrdersTableDataStore::class ); + $order_table = $order_table_ds::get_orders_table_name(); + $wpdb->update( + $order_table, + array( + 'customer_id' => 0, + 'date_updated_gmt' => current_time( 'mysql', true ), + ), + array( + 'customer_id' => $user_id, + ), + array( + '%d', + '%s', + ), + array( + '%d', + ) + ); + } + + if ( ! OrderUtil::custom_orders_table_usage_is_enabled() || OrderUtil::is_custom_order_tables_in_sync() ) { + $wpdb->update( + $wpdb->postmeta, + array( + 'meta_value' => 0, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value + ), + array( + 'meta_key' => '_customer_user', //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key + 'meta_value' => $user_id, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value + ) + ); + } } add_action( 'deleted_user', 'wc_reset_order_customer_id_on_deleted_user' ); diff --git a/plugins/woocommerce/src/Admin/API/Orders.php b/plugins/woocommerce/src/Admin/API/Orders.php index e85bec04c10..64138466a65 100644 --- a/plugins/woocommerce/src/Admin/API/Orders.php +++ b/plugins/woocommerce/src/Admin/API/Orders.php @@ -10,6 +10,8 @@ namespace Automattic\WooCommerce\Admin\API; defined( 'ABSPATH' ) || exit; use Automattic\WooCommerce\Admin\API\Reports\Controller as ReportsController; +use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; +use Automattic\WooCommerce\Utilities\OrderUtil; /** * Orders controller. @@ -54,30 +56,61 @@ class Orders extends \WC_REST_Orders_Controller { * @return array */ protected function prepare_objects_query( $request ) { - global $wpdb; $args = parent::prepare_objects_query( $request ); - // Search by partial order number. if ( ! empty( $request['number'] ) ) { - $partial_number = trim( $request['number'] ); - $limit = intval( $args['posts_per_page'] ); - $order_ids = $wpdb->get_col( + $args = $this->search_partial_order_number( $request['number'], $args ); + } + + return $args; + } + + /** + * Helper method to allow searching by partial order number. + * + * @param int $number Partial order number match. + * @param array $args List of arguments for the request. + * + * @return array Modified args with partial order search included. + */ + private function search_partial_order_number( $number, $args ) { + global $wpdb; + + $partial_number = trim( $number ); + $limit = intval( $args['posts_per_page'] ); + if ( OrderUtil::custom_orders_table_usage_is_enabled() ) { + $order_table_name = OrdersTableDataStore::get_orders_table_name(); + // phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- $orders_table_name is hardcoded. + $order_ids = $wpdb->get_col( $wpdb->prepare( - "SELECT ID - FROM {$wpdb->prefix}posts - WHERE post_type = 'shop_order' - AND ID LIKE %s + "SELECT id + FROM $order_table_name + WHERE type = 'shop_order' + AND id LIKE %s LIMIT %d", $wpdb->esc_like( absint( $partial_number ) ) . '%', $limit ) ); - - // Force WP_Query return empty if don't found any order. - $order_ids = empty( $order_ids ) ? array( 0 ) : $order_ids; - $args['post__in'] = $order_ids; + // phpcs:enable + } else { + $order_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT ID + FROM {$wpdb->prefix}posts + WHERE post_type = 'shop_order' + AND ID LIKE %s + LIMIT %d", + $wpdb->esc_like( absint( $partial_number ) ) . '%', + $limit + ) + ); } + // Force WP_Query return empty if don't found any order. + $order_ids = empty( $order_ids ) ? array( 0 ) : $order_ids; + $args['post__in'] = $order_ids; + return $args; } diff --git a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php index b2ef6217df0..a7dc05948a7 100644 --- a/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php +++ b/plugins/woocommerce/src/Admin/API/Reports/Orders/Stats/DataStore.php @@ -105,6 +105,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { * Set up all the hooks for maintaining and populating table data. */ public static function init() { + add_action( 'woocommerce_before_delete_order', array( __CLASS__, 'delete_order' ) ); add_action( 'delete_post', array( __CLASS__, 'delete_order' ) ); } diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index 0fd369cf83c..8683d1c366e 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -180,6 +180,8 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements /** * Get the names of all the tables involved in the custom orders table feature. * + * See also : get_all_table_names_with_id. + * * @return string[] */ public function get_all_table_names() { @@ -191,6 +193,22 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements ); } + /** + * Similar to get_all_table_names, but also returns the table name along with the items table. + * + * @return array Names of the tables. + */ + public static function get_all_table_names_with_id() { + global $wpdb; + return array( + 'orders' => self::get_orders_table_name(), + 'addresses' => self::get_addresses_table_name(), + 'operational_data' => self::get_operational_data_table_name(), + 'meta' => self::get_meta_table_name(), + 'items' => $wpdb->prefix . 'woocommerce_order_items', + ); + } + /** * Table column to WC_Order mapping for wc_orders table. * @@ -554,7 +572,8 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * @return bool Whether permissions are granted. */ public function get_download_permissions_granted( $order ) { - $order = is_int( $order ) ? wc_get_order( $order ) : $order; + $order_id = is_int( $order ) ? $order : $order->get_id(); + $order = wc_get_order( $order_id ); return $order->get_download_permissions_granted(); } @@ -580,7 +599,8 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * @return bool Whether sales are recorded. */ public function get_recorded_sales( $order ) { - $order = is_int( $order ) ? wc_get_order( $order ) : $order; + $order_id = is_int( $order ) ? $order : $order->get_id(); + $order = wc_get_order( $order_id ); return $order->get_recorded_sales(); } @@ -606,7 +626,8 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * @return bool Whether coupon counts were updated. */ public function get_recorded_coupon_usage_counts( $order ) { - $order = is_int( $order ) ? wc_get_order( $order ) : $order; + $order_id = is_int( $order ) ? $order : $order->get_id(); + $order = wc_get_order( $order_id ); return $order->get_recorded_coupon_usage_counts(); } @@ -632,7 +653,8 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * @return bool Whether email is sent. */ public function get_email_sent( $order ) { - $order = is_int( $order ) ? wc_get_order( $order ) : $order; + $order_id = is_int( $order ) ? $order : $order->get_id(); + $order = wc_get_order( $order_id ); return $order->get_new_order_email_sent(); } @@ -658,8 +680,7 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * @return bool Whether email was sent. */ public function get_new_order_email_sent( $order ) { - $order = is_int( $order ) ? wc_get_order( $order ) : $order; - return $order->get_new_order_email_sent(); + return $this->get_email_sent( $order ); } /** @@ -684,7 +705,8 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * @return bool Whether stock was reduced. */ public function get_stock_reduced( $order ) { - $order = is_int( $order ) ? wc_get_order( $order ) : $order; + $order_id = is_int( $order ) ? $order : $order->get_id(); + $order = wc_get_order( $order_id ); return $order->get_order_stock_reduced(); } diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php index b6ff8f91be6..92148b218c6 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php @@ -164,6 +164,12 @@ class OrdersTableQuery { */ private $date_query = null; + /** + * Instance of the OrdersTableDataStore class. + * + * @var OrdersTableDataStore + */ + private $order_datastore = null; /** * Sets up and runs the query after processing arguments. @@ -171,19 +177,11 @@ class OrdersTableQuery { * @param array $args Array of query vars. */ public function __construct( $args = array() ) { - global $wpdb; + // Note that ideally we would inject this dependency via constructor, but that's not possible since this class needs to be backward compatible with WC_Order_Query class. + $this->order_datastore = wc_get_container()->get( OrdersTableDataStore::class ); - $datastore = wc_get_container()->get( OrdersTableDataStore::class ); - - // TODO: maybe OrdersTableDataStore::get_all_table_names() could return these keys/indices instead. - $this->tables = array( - 'orders' => $datastore::get_orders_table_name(), - 'addresses' => $datastore::get_addresses_table_name(), - 'operational_data' => $datastore::get_operational_data_table_name(), - 'meta' => $datastore::get_meta_table_name(), - 'items' => $wpdb->prefix . 'woocommerce_order_items', - ); - $this->mappings = $datastore->get_all_order_column_mappings(); + $this->tables = $this->order_datastore::get_all_table_names_with_id(); + $this->mappings = $this->order_datastore->get_all_order_column_mappings(); $this->args = $args; @@ -202,13 +200,13 @@ class OrdersTableQuery { private function maybe_remap_args(): void { $mapping = array( // WP_Query legacy. - 'post_date' => 'date_created_gmt', + 'post_date' => 'date_created', 'post_date_gmt' => 'date_created_gmt', - 'post_modified' => 'date_modified_gmt', + 'post_modified' => 'date_updated', 'post_modified_gmt' => 'date_updated_gmt', 'post_status' => 'status', - '_date_completed' => 'date_completed_gmt', - '_date_paid' => 'date_paid_gmt', + '_date_completed' => 'date_completed', + '_date_paid' => 'date_paid', 'paged' => 'page', 'post_parent' => 'parent_order_id', 'post_parent__in' => 'parent_order_id', @@ -231,12 +229,8 @@ class OrdersTableQuery { // Translate from WC_Order_Query to table structure. 'version' => 'woocommerce_version', - 'date_created' => 'date_created_gmt', - 'date_modified' => 'date_updated_gmt', + 'date_modified' => 'date_updated', 'date_modified_gmt' => 'date_updated_gmt', - 'date_completed' => 'date_completed_gmt', - 'date_completed_gmt' => 'date_completed_gmt', - 'date_paid' => 'date_paid_gmt', 'discount_total' => 'discount_total_amount', 'discount_tax' => 'discount_tax_amount', 'shipping_total' => 'shipping_total_amount', @@ -276,16 +270,26 @@ class OrdersTableQuery { $this->args['meta_query'] = array( $shortcut_meta_query ); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query } } + + // Date query. + if ( isset( $this->args['date_query'] ) && is_array( $this->args['date_query'] ) ) { + foreach ( $this->args['date_query'] as $index => $query ) { + if ( isset( $query['column'] ) && isset( $mapping[ $query['column'] ] ) ) { + $this->args['date_query'][ $index ]['column'] = $mapping[ $query['column'] ]; + } + } + } } /** * Generates a `WP_Date_Query` compatible query from a given date. * YYYY-MM-DD queries have 'day' precision for backwards compatibility. * - * @param mixed $date The date. Can be a {@see \WC_DateTime}, a timestamp or a string. + * @param mixed $date The date. Can be a {@see \WC_DateTime}, a timestamp or a string. + * @param string $timezone The timezone to use for the date. * @return array An array with keys 'year', 'month', 'day' and possibly 'hour', 'minute' and 'second'. */ - private function date_to_date_query_arg( $date ): array { + private function date_to_date_query_arg( $date, $timezone ): array { $result = array( 'year' => '', 'month' => '', @@ -294,7 +298,7 @@ class OrdersTableQuery { $precision = 'second'; if ( is_numeric( $date ) ) { - $date = new \WC_DateTime( "@{$date}", new \DateTimeZone( 'UTC' ) ); + $date = new \WC_DateTime( "@{$date}", new \DateTimeZone( $timezone ) ); } elseif ( ! is_a( $date, 'WC_DateTime' ) ) { // YYYY-MM-DD queries have 'day' precision for backwards compat. $date = wc_string_to_datetime( $date ); @@ -321,30 +325,54 @@ class OrdersTableQuery { * @throws \Exception When date args are invalid. */ private function process_date_args(): void { - $valid_operators = array( '>', '>=', '=', '<=', '<', '...' ); - $date_queries = array(); - $gmt_date_keys = array( - 'date_created_gmt', - 'date_updated_gmt', - 'date_paid_gmt', - 'date_completed_gmt', + $valid_operators = array( '>', '>=', '=', '<=', '<', '...' ); + $date_queries = array(); + $local_to_gmt_date_keys = array( + 'date_created' => 'date_created_gmt', + 'date_updated' => 'date_updated_gmt', + 'date_paid' => 'date_paid_gmt', + 'date_completed' => 'date_completed_gmt', ); + $gmt_date_keys = array_values( $local_to_gmt_date_keys ); + $local_date_keys = array_keys( $local_to_gmt_date_keys ); - foreach ( array_filter( $gmt_date_keys, array( $this, 'arg_isset' ) ) as $date_key ) { + $valid_date_keys = array_merge( $gmt_date_keys, $local_date_keys ); + $date_keys = array_filter( $valid_date_keys, array( $this, 'arg_isset' ) ); + + // Process already passed date queries args. + if ( $this->arg_isset( 'date_query' ) && is_array( $this->args['date_query'] ) ) { + foreach ( $this->args['date_query'] as $index => $query ) { + if ( ! isset( $query['column'] ) || ! in_array( $query['column'], $valid_date_keys, true ) ) { + unset( $this->args['date_query'][ $index ] ); + continue; + } + // Convert any local dates to GMT. + if ( isset( $local_to_gmt_date_keys[ $query['column'] ] ) ) { + $this->args['date_query'][ $index ]['column'] = $local_to_gmt_date_keys[ $query['column'] ]; + $op = isset( $query['after'] ) ? 'after' : 'before'; + $date_value_local = $query[ $op ]; + $date_value_gmt = wc_string_to_timestamp( get_gmt_from_date( wc_string_to_datetime( $date_value_local ) ) ); + $this->args['date_query'][ $index ][ $op ] = $this->date_to_date_query_arg( $date_value_gmt, 'UTC' ); + } + } + } + + foreach ( $date_keys as $date_key ) { $date_value = $this->args[ $date_key ]; $operator = '='; $dates = array(); + $timezone = in_array( $date_key, $gmt_date_keys, true ) ? '+0000' : wc_timezone_string(); if ( is_string( $date_value ) && preg_match( self::REGEX_SHORTHAND_DATES, $date_value, $matches ) ) { $operator = in_array( $matches[2], $valid_operators, true ) ? $matches[2] : ''; if ( ! empty( $matches[1] ) ) { - $dates[] = $this->date_to_date_query_arg( $matches[1] ); + $dates[] = $this->date_to_date_query_arg( $matches[1], $timezone ); } - $dates[] = $this->date_to_date_query_arg( $matches[3] ); + $dates[] = $this->date_to_date_query_arg( $matches[3], $timezone ); } else { - $dates[] = $this->date_to_date_query_arg( $date_value ); + $dates[] = $this->date_to_date_query_arg( $date_value, $timezone ); } if ( empty( $dates ) || ! $operator || ( '...' === $operator && count( $dates ) < 2 ) ) { @@ -361,6 +389,7 @@ class OrdersTableQuery { $operator_to_keys[] = 'before'; } + $date_key = in_array( $date_key, $local_date_keys, true ) ? $local_to_gmt_date_keys[ $date_key ] : $date_key; $date_queries[] = array_merge( array( 'column' => $date_key, @@ -874,6 +903,9 @@ class OrdersTableQuery { $ids[] = absint( $value ); } elseif ( is_string( $value ) && is_email( $value ) ) { $emails[] = sanitize_email( $value ); + } else { + // Invalid query. + $pieces[] = '1=0'; } } diff --git a/plugins/woocommerce/tests/legacy/framework/class-wc-unit-test-case.php b/plugins/woocommerce/tests/legacy/framework/class-wc-unit-test-case.php index a7f00cb42e7..488cea9e120 100644 --- a/plugins/woocommerce/tests/legacy/framework/class-wc-unit-test-case.php +++ b/plugins/woocommerce/tests/legacy/framework/class-wc-unit-test-case.php @@ -7,6 +7,7 @@ use Automattic\WooCommerce\Proxies\LegacyProxy; use Automattic\WooCommerce\Testing\Tools\CodeHacking\CodeHacker; +use Automattic\WooCommerce\Utilities\OrderUtil; use PHPUnit\Framework\Constraint\IsType; /** @@ -388,4 +389,15 @@ class WC_Unit_Test_Case extends WP_HTTP_TestCase { $events = self::get_tracks_events( $event_name ); $this->assertEmpty( $events ); } + + /** + * Mark test skipped when HPOS is enabled. + * + * @param string $message Message to display when test is skipped. + */ + protected function skip_if_hpos_enabled( $message ) { + if ( OrderUtil::custom_orders_table_usage_is_enabled() ) { + $this->markTestSkipped( $message ); + } + } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php b/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php index 56b0e4cd6ca..535869c41b0 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/admin/class-wc-tests-admin-dashboard.php @@ -37,6 +37,7 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case { * Test: get_status_widget */ public function test_status_widget() { + $this->skip_if_hpos_enabled( 'We don\'t support legacy reports on HPOS' ); wp_set_current_user( $this->user ); $order = WC_Helper_Order::create_order(); $order->set_status( 'completed' ); @@ -58,6 +59,7 @@ class WC_Tests_Admin_Dashboard extends WC_Unit_Test_Case { * Test: get_status_widget with woo admin disabled. */ public function test_status_widget_with_woo_admin_disabled() { + $this->skip_if_hpos_enabled( 'We don\'t support legacy reports on HPOS' ); wp_set_current_user( $this->user ); $order = WC_Helper_Order::create_order(); $order->set_status( 'completed' ); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-admin-report.php b/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-admin-report.php index 480af399bdc..4b21d058eb3 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-admin-report.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-admin-report.php @@ -18,6 +18,16 @@ class WC_Tests_Admin_Report extends WC_Unit_Test_Case { include_once WC_Unit_Tests_Bootstrap::instance()->plugin_dir . '/includes/admin/reports/class-wc-admin-report.php'; } + /** + * Set up the test. + */ + public function setUp(): void { + parent::setUp(); + if ( \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) { + $this->markTestSkipped( 'This test is not compatible with the custom orders table.' ); + } + } + /** * Clear cached report data. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-report-sales-by-date.php b/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-report-sales-by-date.php index 8f3364deb11..b7e602e0c56 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-report-sales-by-date.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/admin/reports/class-wc-tests-report-sales-by-date.php @@ -18,6 +18,16 @@ class WC_Tests_Report_Sales_By_Date extends WC_Unit_Test_Case { include_once WC_Unit_Tests_Bootstrap::instance()->plugin_dir . '/includes/admin/reports/class-wc-report-sales-by-date.php'; } + /** + * Set up the test. + */ + public function setUp(): void { + parent::setUp(); + if ( \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled() ) { + $this->markTestSkipped( 'This test is not compatible with the custom orders table.' ); + } + } + /** * Clear cached report data. * diff --git a/plugins/woocommerce/tests/legacy/unit-tests/crud/meta.php b/plugins/woocommerce/tests/legacy/unit-tests/crud/meta.php index 3bcc8da38d8..00c34fd5eac 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/crud/meta.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/crud/meta.php @@ -1,4 +1,7 @@ assertCount( 1, $order->get_meta_data() ); $this->assertTrue( in_array( 'random', wp_list_pluck( $order->get_meta_data(), 'key' ) ) ); - // The new $order should have 3 items of meta since it's freshly loaded. - $this->assertCount( 3, $new_order->get_meta_data() ); + $expected_count = OrderUtil::custom_orders_table_usage_is_enabled() ? 2 : 3; + // The new $order should have 3 items (or 2 in case of HPOS since direct post updates are not read) of meta since it's freshly loaded. + $this->assertCount( $expected_count, $new_order->get_meta_data() ); $this->assertTrue( in_array( 'random', wp_list_pluck( $new_order->get_meta_data(), 'key' ) ) ); $this->assertTrue( in_array( 'random_other', wp_list_pluck( $new_order->get_meta_data(), 'key' ) ) ); - $this->assertTrue( in_array( 'random_other_pre_crud', wp_list_pluck( $new_order->get_meta_data(), 'key' ) ) ); + if ( ! OrderUtil::custom_orders_table_usage_is_enabled() ) { + $this->assertTrue( in_array( 'random_other_pre_crud', wp_list_pluck( $new_order->get_meta_data(), 'key' ), true ) ); + } } /** diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php index 1d6a917bc02..e85e8f0fe50 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-crud-orders.php @@ -5,6 +5,8 @@ * @package WooCommerce\Tests\CRUD */ +use Automattic\WooCommerce\Utilities\OrderUtil; + /** * Meta * @@ -882,10 +884,10 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case { $object = new WC_Order(); // Save + create. - $save_id = $object->save(); - $post = get_post( $save_id ); - $this->assertEquals( 'shop_order', $post->post_type ); - $this->assertEquals( 'shop_order', $post->post_type ); + $save_id = $object->save(); + $post = get_post( $save_id ); + $expected_post_type = OrderUtil::custom_orders_table_usage_is_enabled() ? 'shop_order_placehold' : 'shop_order'; + $this->assertEquals( $expected_post_type, $post->post_type ); // Update. $update_id = $object->save(); diff --git a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-order-functions.php b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-order-functions.php index 4df224a5e58..696668decd9 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-order-functions.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/order/class-wc-tests-order-functions.php @@ -109,15 +109,19 @@ class WC_Tests_Order_Functions extends WC_Unit_Test_Case { ->will( $this->returnValueMap( $test_counts[ $order_type ] ) ); } - $add_mock_datastores = function( $stores ) use ( $mock_datastores ) { + $add_mock_datastores = function ( $stores ) use ( $mock_datastores ) { return array_merge( $stores, $mock_datastores ); }; - $add_mock_order_type = function( $order_types ) use ( $mock_datastores ) { + $add_mock_order_type = function ( $order_types ) use ( $mock_datastores ) { return array( 'shop_order', 'order-fake-type' ); }; + $return_mock_order_data_store = function ( $stores ) use ( $mock_datastores ) { + return $mock_datastores['order']; + }; add_filter( 'woocommerce_data_stores', $add_mock_datastores ); add_filter( 'wc_order_types', $add_mock_order_type ); + add_filter( 'woocommerce_order_data_store', $return_mock_order_data_store, 1000, 2 ); // Check counts for specific order types. $this->assertEquals( 2, wc_orders_count( 'on-hold', 'shop_order' ) ); @@ -131,6 +135,7 @@ class WC_Tests_Order_Functions extends WC_Unit_Test_Case { remove_filter( 'woocommerce_data_stores', $add_mock_datastores ); remove_filter( 'wc_order_types', $add_mock_order_type ); + remove_filter( 'woocommerce_order_data_store', $return_mock_order_data_store, 1000 ); // Confirm that everything's back to normal. wp_cache_flush(); @@ -190,7 +195,8 @@ class WC_Tests_Order_Functions extends WC_Unit_Test_Case { // Assert the return when $the_order args is false. $this->assertFalse( wc_get_order( false ) ); - $post = get_post( $order->get_id() ); + $post = get_post( $order->get_id() ); + $theorder = $order; $this->assertInstanceOf( 'WC_Order', wc_get_order(), 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 1dfed4fe425..8955f0368ae 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 @@ -14,6 +14,7 @@ use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableControlle use Automattic\WooCommerce\Internal\DataStores\Orders\DataSynchronizer; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; use Automattic\WooCommerce\Internal\Features\FeaturesController; +use Automattic\WooCommerce\Utilities\OrderUtil; use WC_Data_Store; use WC_Mock_Payment_Gateway; use WC_Order; @@ -187,6 +188,8 @@ class OrderHelper { * @return int Order ID */ public static function create_complex_wp_post_order() { + $current_cot_state = OrderUtil::custom_orders_table_usage_is_enabled(); + self::toggle_cot( false ); update_option( 'woocommerce_prices_include_tax', 'yes' ); update_option( 'woocommerce_calc_taxes', 'yes' ); $uniq_cust_id = wp_generate_password( 10, false ); @@ -256,6 +259,8 @@ class OrderHelper { $order->save(); $order->save_meta_data(); + self::toggle_cot( $current_cot_state ); + return $order->get_id(); } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/date-filtering.php b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/date-filtering.php index 2a513e41dc4..b18c201cb44 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/date-filtering.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/rest-api/Tests/Version3/date-filtering.php @@ -10,6 +10,9 @@ * @package WooCommerce\Tests\API */ +use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; +use Automattic\WooCommerce\Utilities\OrderUtil; + /** * Trait for testing the date filtering on controllers that inherit from WC_REST_CRUD_Controller. */ @@ -34,18 +37,34 @@ trait DateFilteringForCrudControllers { public function test_filter_by_creation_or_modification_date( $param_name, $filter_by_gmt, $expected_to_be_returned ) { global $wpdb; + $timezone_string_option = get_option( 'timezone_string' ); + update_option( 'timezone_string', 'Africa/Blantyre', true ); // +02:00 wp_set_current_user( $this->user ); - $item_id = $this->get_item_for_date_filtering_tests()->get_id(); + $item = $this->get_item_for_date_filtering_tests(); + $item_id = $item->get_id(); - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared - $wpdb->query( - 'UPDATE ' . $wpdb->prefix . "posts SET + if ( $item instanceof WC_Abstract_Order && OrderUtil::custom_orders_table_usage_is_enabled() ) { + $wpdb->update( + OrdersTableDataStore::get_orders_table_name(), + array( + 'date_created_gmt' => '2000-01-01T10:00:00', + 'date_updated_gmt' => '2000-02-01T10:00:00', + ), + array( + 'id' => $item->get_id(), + ) + ); + } else { + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( + 'UPDATE ' . $wpdb->prefix . "posts SET post_date = '2000-01-01T12:00:00', post_date_gmt = '2000-01-01T10:00:00', post_modified = '2000-02-01T12:00:00', post_modified_gmt = '2000-02-01T10:00:00' WHERE ID = " . $item_id - ); + ); + } // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared $request = new WP_REST_Request( 'GET', $this->get_endpoint_for_date_filtering_tests() ); @@ -60,6 +79,7 @@ trait DateFilteringForCrudControllers { $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( $expected_to_be_returned ? 1 : 0, count( $response_items ) ); + update_option( 'timezone_string', $timezone_string_option ); } /** @@ -80,19 +100,35 @@ trait DateFilteringForCrudControllers { public function test_can_filter_by_more_than_one_date( $first_param_name, $first_param_value, $second_param_name, $second_param_value, $filter_by_gmt, $expected_to_be_returned ) { global $wpdb; + $timezone_string_option = get_option( 'timezone_string' ); + update_option( 'timezone_string', 'Africa/Blantyre', true ); // +02:00 wp_set_current_user( $this->user ); - $item_id = $this->get_item_for_date_filtering_tests()->get_id(); + $item = $this->get_item_for_date_filtering_tests(); + $item_id = $item->get_id(); - // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared - $wpdb->query( - 'UPDATE ' . $wpdb->prefix . "posts SET + if ( $item instanceof WC_Abstract_Order && OrderUtil::custom_orders_table_usage_is_enabled() ) { + $wpdb->update( + OrdersTableDataStore::get_orders_table_name(), + array( + 'date_created_gmt' => '2000-01-01T10:00:00', + 'date_updated_gmt' => '2000-02-01T10:00:00', + ), + array( + 'id' => $item->get_id(), + ) + ); + } else { + // phpcs:disable WordPress.DB.PreparedSQL.NotPrepared + $wpdb->query( + 'UPDATE ' . $wpdb->prefix . "posts SET post_date = '2000-01-01T12:00:00', post_date_gmt = '2000-01-01T10:00:00', post_modified = '2000-02-01T12:00:00', post_modified_gmt = '2000-02-01T10:00:00' WHERE ID = " . $item_id - ); - // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared + ); + // phpcs:enable WordPress.DB.PreparedSQL.NotPrepared + } $request = new WP_REST_Request( 'GET', $this->get_endpoint_for_date_filtering_tests() ); $request->set_query_params( @@ -107,5 +143,6 @@ trait DateFilteringForCrudControllers { $this->assertEquals( 200, $response->get_status() ); $this->assertEquals( $expected_to_be_returned ? 1 : 0, count( $response_items ) ); + update_option( 'timezone_string', $timezone_string_option ); } } diff --git a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php index b4a78652004..18e00664fd4 100644 --- a/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php +++ b/plugins/woocommerce/tests/legacy/unit-tests/woocommerce-admin/api/reports-import.php @@ -323,13 +323,8 @@ class WC_Admin_Tests_API_Reports_Import extends WC_REST_Unit_Test_Case { // Create 1 draft order - to be excluded from totals. $order = WC_Helper_Order::create_order( $this->customer, $product ); $order->set_date_created( time() - ( 5 * DAY_IN_SECONDS ) ); + $order->set_status( 'auto-draft' ); $order->save(); - wp_update_post( - array( - 'ID' => $order->get_id(), - 'post_status' => 'auto-draft', - ) - ); // Test totals and total params. $request = new WP_REST_Request( 'GET', $this->endpoint . '/totals' ); diff --git a/plugins/woocommerce/tests/php/includes/class-wc-order-factory-test.php b/plugins/woocommerce/tests/php/includes/class-wc-order-factory-test.php index e3af7d32052..22f4839dfa0 100644 --- a/plugins/woocommerce/tests/php/includes/class-wc-order-factory-test.php +++ b/plugins/woocommerce/tests/php/includes/class-wc-order-factory-test.php @@ -1,16 +1,47 @@ cot_state = \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled(); + OrderHelper::toggle_cot( false ); + } + + /** + * Restore COT state after the test. + * + * @return void + */ + public function tearDown(): void { + parent::tearDown(); + wp_cache_flush(); + OrderHelper::toggle_cot( $this->cot_state ); + } + /** * @testDox get_orders should be able to return multiple orders of different types. */ public function test_get_orders_with_multiple_order_type() { - $order1 = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_complex_wp_post_order(); - $order2 = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_complex_wp_post_order(); + $order1 = OrderHelper::create_complex_wp_post_order(); + $order2 = OrderHelper::create_complex_wp_post_order(); assert( $order1 > 0 ); assert( $order2 > 0 ); diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-customer-data-store-session-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-customer-data-store-session-test.php index 75ef103b8e0..126d1b055a5 100644 --- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-customer-data-store-session-test.php +++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-customer-data-store-session-test.php @@ -11,11 +11,12 @@ class WC_Customer_Data_Store_Session_Test extends WC_Unit_Test_Case { * @see https://github.com/woocommerce/woocommerce/issues/28759 * @dataProvider provide_customers_with_different_addresses * - * @param WC_Customer $customer The customer object being tested. - * @param bool $states_should_match If the billing and shipping states should match. - * @param bool $countries_should_match If the billing and shipping countries should match. + * @param Closure $customer_closure The customer object being tested. + * @param bool $states_should_match If the billing and shipping states should match. + * @param bool $countries_should_match If the billing and shipping countries should match. */ - public function test_setting_default_address_fields( WC_Customer $customer, bool $states_should_match, bool $countries_should_match ) { + public function test_setting_default_address_fields( Closure $customer_closure, bool $states_should_match, bool $countries_should_match ) { + $customer = $customer_closure(); $session_data = new WC_Customer_Data_Store_Session(); $session_data->read( $customer ); @@ -46,66 +47,78 @@ class WC_Customer_Data_Store_Session_Test extends WC_Unit_Test_Case { * @return array[] */ public function provide_customers_with_different_addresses() { - $has_billing_address_only = new WC_Customer(); - $has_billing_address_only->set_email( 'wc-customer-test-01@test.user' ); - $has_billing_address_only->set_billing_address( '1234 Quality Lane' ); - $has_billing_address_only->set_billing_city( 'Testville' ); - $has_billing_address_only->set_billing_country( 'US' ); - $has_billing_address_only->set_billing_state( 'CA' ); - $has_billing_address_only->set_billing_postcode( '90123' ); - $has_billing_address_only->save(); + $cust1_closure = function () { + $has_billing_address_only = new WC_Customer(); + $has_billing_address_only->set_email( 'wc-customer-test-01@test.user' ); + $has_billing_address_only->set_billing_address( '1234 Quality Lane' ); + $has_billing_address_only->set_billing_city( 'Testville' ); + $has_billing_address_only->set_billing_country( 'US' ); + $has_billing_address_only->set_billing_state( 'CA' ); + $has_billing_address_only->set_billing_postcode( '90123' ); + $has_billing_address_only->save(); + return $has_billing_address_only; + }; - $separate_billing_and_shipping_state_and_country = new WC_Customer(); - $separate_billing_and_shipping_state_and_country->set_email( 'wc-customer-test-02@test.user' ); - $separate_billing_and_shipping_state_and_country->set_billing_address( '4567 Scenario Street' ); - $separate_billing_and_shipping_state_and_country->set_billing_city( 'Unitly' ); - $separate_billing_and_shipping_state_and_country->set_billing_country( 'UK' ); - $separate_billing_and_shipping_state_and_country->set_billing_state( 'Computershire' ); - $separate_billing_and_shipping_state_and_country->set_billing_postcode( 'ZX1 2PQ' ); - $separate_billing_and_shipping_state_and_country->set_shipping_address( '8901 Situation Court' ); - $separate_billing_and_shipping_state_and_country->set_shipping_city( 'Endtoendly' ); - $separate_billing_and_shipping_state_and_country->set_shipping_country( 'CA' ); - $separate_billing_and_shipping_state_and_country->set_shipping_state( 'BC' ); - $separate_billing_and_shipping_state_and_country->set_shipping_postcode( 'A1B 2C3' ); - $separate_billing_and_shipping_state_and_country->save(); + $cust2_closure = function () { + $separate_billing_and_shipping_state_and_country = new WC_Customer(); + $separate_billing_and_shipping_state_and_country->set_email( 'wc-customer-test-02@test.user' ); + $separate_billing_and_shipping_state_and_country->set_billing_address( '4567 Scenario Street' ); + $separate_billing_and_shipping_state_and_country->set_billing_city( 'Unitly' ); + $separate_billing_and_shipping_state_and_country->set_billing_country( 'UK' ); + $separate_billing_and_shipping_state_and_country->set_billing_state( 'Computershire' ); + $separate_billing_and_shipping_state_and_country->set_billing_postcode( 'ZX1 2PQ' ); + $separate_billing_and_shipping_state_and_country->set_shipping_address( '8901 Situation Court' ); + $separate_billing_and_shipping_state_and_country->set_shipping_city( 'Endtoendly' ); + $separate_billing_and_shipping_state_and_country->set_shipping_country( 'CA' ); + $separate_billing_and_shipping_state_and_country->set_shipping_state( 'BC' ); + $separate_billing_and_shipping_state_and_country->set_shipping_postcode( 'A1B 2C3' ); + $separate_billing_and_shipping_state_and_country->save(); + return $separate_billing_and_shipping_state_and_country; + }; - $separate_billing_state_same_country = new WC_Customer(); - $separate_billing_state_same_country->set_email( 'wc-customer-test-03@test.user' ); - $separate_billing_state_same_country->set_billing_address( '4567 Scenario Street' ); - $separate_billing_state_same_country->set_billing_city( 'Unitly' ); - $separate_billing_state_same_country->set_billing_country( 'UK' ); - $separate_billing_state_same_country->set_billing_state( 'Computershire' ); - $separate_billing_state_same_country->set_billing_postcode( 'ZX1 2PQ' ); - $separate_billing_state_same_country->set_shipping_address( '8901 Situation Court' ); - $separate_billing_state_same_country->set_shipping_city( 'Endtoendly' ); - $separate_billing_state_same_country->set_shipping_country( 'UK' ); - $separate_billing_state_same_country->set_shipping_state( 'Byteshire' ); - $separate_billing_state_same_country->set_shipping_postcode( 'RS1 2TU' ); - $separate_billing_state_same_country->save(); + $cust3_closure = function () { + $separate_billing_state_same_country = new WC_Customer(); + $separate_billing_state_same_country->set_email( 'wc-customer-test-03@test.user' ); + $separate_billing_state_same_country->set_billing_address( '4567 Scenario Street' ); + $separate_billing_state_same_country->set_billing_city( 'Unitly' ); + $separate_billing_state_same_country->set_billing_country( 'UK' ); + $separate_billing_state_same_country->set_billing_state( 'Computershire' ); + $separate_billing_state_same_country->set_billing_postcode( 'ZX1 2PQ' ); + $separate_billing_state_same_country->set_shipping_address( '8901 Situation Court' ); + $separate_billing_state_same_country->set_shipping_city( 'Endtoendly' ); + $separate_billing_state_same_country->set_shipping_country( 'UK' ); + $separate_billing_state_same_country->set_shipping_state( 'Byteshire' ); + $separate_billing_state_same_country->set_shipping_postcode( 'RS1 2TU' ); + $separate_billing_state_same_country->save(); + return $separate_billing_state_same_country; + }; - $shipping_address_is_effectively_empty = new WC_Customer(); - $shipping_address_is_effectively_empty->set_email( 'wc-customer-test-04@test.user' ); - $shipping_address_is_effectively_empty->set_shipping_address( ' ' ); - $shipping_address_is_effectively_empty->save(); + $cust4_closure = function () { + $shipping_address_is_effectively_empty = new WC_Customer(); + $shipping_address_is_effectively_empty->set_email( 'wc-customer-test-04@test.user' ); + $shipping_address_is_effectively_empty->set_shipping_address( ' ' ); + $shipping_address_is_effectively_empty->save(); + return $shipping_address_is_effectively_empty; + }; return array( 'has_billing_address_only' => array( - $has_billing_address_only, + $cust1_closure, true, true, ), 'separate_billing_and_shipping_state_and_country' => array( - $separate_billing_and_shipping_state_and_country, + $cust2_closure, false, false, ), 'separate_billing_state_same_country' => array( - $separate_billing_state_same_country, + $cust3_closure, false, true, ), 'shipping_address_is_effectively_empty' => array( - $shipping_address_is_effectively_empty, + $cust4_closure, true, true, ), diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php index 011a0bdf776..4fb6bd7132c 100644 --- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php +++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php @@ -1,9 +1,40 @@ prev_cot_state = OrderUtil::custom_orders_table_usage_is_enabled(); + OrderHelper::toggle_cot( false ); + } + + /** + * Restore the COT state after the test. + * + * @return void + */ + public function tearDown(): void { + OrderHelper::toggle_cot( $this->prev_cot_state ); + parent::tearDown(); + } /** * Test that refund cache are invalidated correctly when refund is deleted. @@ -131,7 +162,7 @@ class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case { * Legacy getters and setters for props migrated from data stores should be set/reset properly. */ public function test_legacy_getters_setters() { - $order_id = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_complex_wp_post_order(); + $order_id = OrderHelper::create_complex_wp_post_order(); $order = wc_get_order( $order_id ); $bool_props = array( '_download_permissions_granted' => 'download_permissions_granted', diff --git a/plugins/woocommerce/tests/php/includes/gateways/paypal/class-wc-gateway-paypal-test.php b/plugins/woocommerce/tests/php/includes/gateways/paypal/class-wc-gateway-paypal-test.php index 7f319dc4181..4f26bdfad79 100644 --- a/plugins/woocommerce/tests/php/includes/gateways/paypal/class-wc-gateway-paypal-test.php +++ b/plugins/woocommerce/tests/php/includes/gateways/paypal/class-wc-gateway-paypal-test.php @@ -29,8 +29,8 @@ class WC_Gateway_Paypal_Test extends \WC_Unit_Test_Case { $order = WC_Helper_Order::create_order(); $order->save(); - update_post_meta( $order->get_id(), '_paypal_status', 'pending' ); - update_post_meta( $order->get_id(), '_transaction_id', $this->transaction_id_26960 ); + $order->update_meta_data( '_paypal_status', 'pending' ); + $order->set_transaction_id( $this->transaction_id_26960 ); $order->set_payment_method( 'paypal' ); $order->save(); @@ -56,8 +56,8 @@ class WC_Gateway_Paypal_Test extends \WC_Unit_Test_Case { $order = WC_Helper_Order::create_order(); $order->save(); - update_post_meta( $order->get_id(), '_paypal_status', 'pending' ); - update_post_meta( $order->get_id(), '_transaction_id', $this->transaction_id_26960 ); + $order->update_meta_data( '_paypal_status', 'pending' ); + $order->set_transaction_id( $this->transaction_id_26960 ); $order->set_payment_method( 'paypal' ); $order->save(); 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 2d7b5685c55..db284beec3e 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php @@ -7,6 +7,7 @@ use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore; use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableQuery; use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper; use Automattic\WooCommerce\RestApi\UnitTests\HPOSToggleTrait; +use Automattic\WooCommerce\Utilities\OrderUtil; /** * Class OrdersTableDataStoreTests. @@ -37,6 +38,12 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { */ private $cpt_data_store; + /** + * Whether COT was enabled before the test. + * @var bool + */ + private $cot_state; + /** * Initializes system under test. */ @@ -47,6 +54,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { parent::setUp(); // Remove the Test Suiteā€™s use of temporary tables https://wordpress.stackexchange.com/a/220308. $this->setup_cot(); + $this->cot_state = OrderUtil::custom_orders_table_usage_is_enabled(); $this->toggle_cot( false ); $this->sut = wc_get_container()->get( OrdersTableDataStore::class ); $this->migrator = wc_get_container()->get( PostsToOrdersMigrationController::class ); @@ -59,6 +67,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { public function tearDown(): void { //phpcs:ignore WordPress.DateTime.RestrictedFunctions.timezone_change_date_default_timezone_set -- We need to change the timezone to test the date sync fields. update_option( 'timezone_string', $this->original_time_zone ); + $this->toggle_cot( $this->cot_state ); $this->clean_up_cot_setup(); parent::tearDown(); } @@ -205,6 +214,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { wp_cache_flush(); $order = new WC_Order(); $order->set_id( $post_order->get_id() ); + $this->toggle_cot( true ); $this->switch_data_store( $order, $this->sut ); $this->sut->read( $order ); @@ -239,6 +249,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { foreach ( $datastore_updates as $prop => $value ) { $this->assertEquals( $value, $this->sut->{"get_$prop"}( $order ), "Unable to match prop $prop" ); } + $this->toggle_cot( false ); } /** @@ -1767,6 +1778,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { * Ideally, this should be possible only from getters and setters for objects, but for backward compatibility, earlier ways are also supported. */ public function test_internal_ds_getters_and_setters() { + $this->toggle_cot( true ); $props_to_test = array( '_download_permissions_granted', '_recorded_sales', @@ -1813,6 +1825,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { $order->save(); } $this->assert_get_prop_via_ds_object_and_metadata( $props_to_test, $order, false, $ds_getter_setter_names ); + $this->toggle_cot( false ); } /** @@ -1855,7 +1868,8 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { * @testDox Legacy getters and setters for props migrated from data stores should be set/reset properly. */ public function test_legacy_getters_setters() { - $order_id = \Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper::create_complex_wp_post_order(); + $this->toggle_cot( true ); + $order_id = OrderHelper::create_complex_data_store_order( $this->sut ); $order = wc_get_order( $order_id ); $this->switch_data_store( $order, $this->sut ); $bool_props = array( @@ -1887,7 +1901,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { $this->assert_props_value_via_data_store( $order, $bool_props, true ); $this->assert_props_value_via_order_object( $order, $bool_props, true ); - + $this->toggle_cot( false ); } /** @@ -1984,8 +1998,9 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { */ public function test_read_multiple_dont_sync_again_for_same_order() { $this->toggle_cot( true ); - $this->enable_cot_sync(); $order = $this->create_complex_cot_order(); + $this->sut->backfill_post_record( $order ); + $this->enable_cot_sync(); $order_id = $order->get_id(); @@ -1999,6 +2014,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { $this->assertTrue( $should_sync_callable->call( $this->sut, $order ) ); $this->sut->read_multiple( $orders ); $this->assertFalse( $should_sync_callable->call( $this->sut, $order ) ); + $this->toggle_cot( false ); } /**