diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php index f62dff69b4e..7ab112fa06d 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableQuery.php @@ -270,15 +270,6 @@ 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'] ]; - } - } - } } /** @@ -333,29 +324,15 @@ class OrdersTableQuery { '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 ); + + $gmt_date_keys = array_values( $local_to_gmt_date_keys ); + $local_date_keys = array_keys( $local_to_gmt_date_keys ); $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' ); - } - } - } + $this->args['date_query'] = $this->map_gmt_and_post_keys_to_hpos_keys( $this->args['date_query'] ); foreach ( $date_keys as $date_key ) { $date_value = $this->args[ $date_key ]; @@ -430,6 +407,63 @@ class OrdersTableQuery { $this->process_date_query_columns(); } + /** + * Helper function to map posts and gmt based keys to HPOS keys. + * + * @param array $query Date query argument. + * + * @return array|mixed Date query argument with modified keys. + */ + private function map_gmt_and_post_keys_to_hpos_keys( $query ) { + if ( ! is_array( $query ) ) { + return $query; + } + + $post_to_hpos_mappings = array( + 'post_date' => 'date_created', + 'post_date_gmt' => 'date_created_gmt', + 'post_modified' => 'date_updated', + 'post_modified_gmt' => 'date_updated_gmt', + '_date_completed' => 'date_completed', + '_date_paid' => 'date_paid', + 'date_modified' => 'date_updated', + 'date_modified_gmt' => 'date_updated_gmt', + ); + + $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', + ); + + array_walk( + $query, + function ( &$sub_query ) { + $sub_query = $this->map_gmt_and_post_keys_to_hpos_keys( $sub_query ); + } + ); + + if ( ! isset( $query['column'] ) ) { + return $query; + } + + if ( isset( $post_to_hpos_mappings[ $query['column'] ] ) ) { + $query['column'] = $post_to_hpos_mappings[ $query['column'] ]; + } + + // Convert any local dates to GMT. + if ( isset( $local_to_gmt_date_keys[ $query['column'] ] ) ) { + $query['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 ) ) ); + $query[ $op ] = $this->date_to_date_query_arg( $date_value_gmt, 'UTC' ); + } + + return $query; + } + /** * Makes sure all 'date_query' columns are correctly prefixed and their respective tables are being JOIN'ed. * diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableQueryTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableQueryTests.php new file mode 100644 index 00000000000..a9344c9f315 --- /dev/null +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableQueryTests.php @@ -0,0 +1,136 @@ +setup_cot(); + $this->cot_state = OrderUtil::custom_orders_table_usage_is_enabled(); + $this->toggle_cot( true ); + } + + /** + * Restore the original COT state. + */ + public function tearDown(): void { + $this->toggle_cot( $this->cot_state ); + parent::tearDown(); + } + + /** + * Helper function to create different orders with different dates for testing. + * + * @return array Array of WC_Order objects. + */ + private function create_orders_with_different_dates() { + $order1 = OrderHelper::create_order(); + $order2 = OrderHelper::create_order(); + $order3 = OrderHelper::create_order(); + + $order1->set_date_created( '2000-01-01T10:00:00' ); + $order1->set_date_modified( '2001-02-01T10:00:00' ); + $order1->set_date_paid( '2002-03-01T10:00:00' ); + $order1->save(); + + $order2->set_date_created( '2000-02-01T10:00:00' ); + $order2->set_date_modified( '2001-01-01T10:00:00' ); + $order2->set_date_paid( '2002-03-01T10:00:00' ); + $order2->save(); + + $order3->set_date_created( '2001-01-01T10:00:00' ); + $order3->set_date_modified( '2001-02-01T10:00:00' ); + $order3->set_date_paid( '2002-03-01T10:00:00' ); + $order3->save(); + + return array( $order1, $order2, $order3 ); + } + + /** + * @testDox Nested date queries works as expected. + */ + public function test_nested_date_queries_single() { + $orders = $this->create_orders_with_different_dates(); + + $date_query_created_in_2000 = array( + array( + 'relation' => 'AND', + array( + 'column' => 'date_created', + 'inclusive' => true, + 'after' => '2000-01-01T00:00:00', + ), + array( + 'column' => 'date_created', + 'inclusive' => false, + 'before' => '2001-01-01T10:00:00', + ), + ), + ); + + $queried_orders = wc_get_orders( + array( + 'return' => 'ids', + 'date_query' => $date_query_created_in_2000, + ) + ); + + $this->assertEquals( 2, count( $queried_orders ) ); + $this->assertContains( $orders[0]->get_id(), $queried_orders ); + $this->assertContains( $orders[1]->get_id(), $queried_orders ); + } + + /** + * @testDox Multiple nested date queries works as expected. + */ + public function test_nested_date_queries_multi() { + $orders = $this->create_orders_with_different_dates(); + + $date_query_created_in_2000_and_modified_in_2001 = array( + array( + 'relation' => 'AND', + array( + 'column' => 'date_created', + 'inclusive' => true, + 'after' => '2000-01-01T00:00:00', + ), + array( + 'column' => 'post_date', + 'inclusive' => false, + 'before' => '2001-01-01T10:00:00', + ), + ), + array( + 'column' => 'date_modified', + 'before' => '2001-01-02T10:00:00', + ), + ); + + $queried_orders = wc_get_orders( + array( + 'return' => 'ids', + 'date_query' => $date_query_created_in_2000_and_modified_in_2001, + ) + ); + + $this->assertEquals( 1, count( $queried_orders ) ); + $this->assertContains( $orders[1]->get_id(), $queried_orders ); + } +}