[COT] Fix order related methods in customer data store (#34121)

This commit is contained in:
Néstor Soriano 2022-08-01 11:13:55 +02:00 committed by GitHub
parent 5459f7e8ef
commit e05697dfab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 290 additions and 33 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: dev
Fix order related methods in customer data store

View File

@ -5,6 +5,9 @@
* @package WooCommerce\DataStores * @package WooCommerce\DataStores
*/ */
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
if ( ! defined( 'ABSPATH' ) ) { if ( ! defined( 'ABSPATH' ) ) {
exit; exit;
} }
@ -320,6 +323,15 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
do_action( 'woocommerce_customer_object_updated_props', $customer, $updated_props ); do_action( 'woocommerce_customer_object_updated_props', $customer, $updated_props );
} }
/**
* Check if the usage of the custom orders table is enabled.
*
* @return bool
*/
private function is_cot_in_use(): bool {
return wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled();
}
/** /**
* Gets the customers last order. * Gets the customers last order.
* *
@ -328,35 +340,59 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
* @return WC_Order|false * @return WC_Order|false
*/ */
public function get_last_order( &$customer ) { public function get_last_order( &$customer ) {
$last_order = apply_filters( //phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
/**
* Filters the id of the last order from a given customer.
*
* @param string @last_order_id The last order id as retrieved from the database.
* @param WC_Customer The customer whose last order id is being retrieved.
* @return string The actual last order id to use.
*/
$last_order_id = apply_filters(
'woocommerce_customer_get_last_order', 'woocommerce_customer_get_last_order',
get_user_meta( $customer->get_id(), '_last_order', true ), get_user_meta( $customer->get_id(), '_last_order', true ),
$customer $customer
); );
//phpcs:enable WooCommerce.Commenting.CommentHooks.MissingSinceComment
if ( '' === $last_order ) { if ( '' === $last_order_id ) {
global $wpdb; global $wpdb;
$last_order = $wpdb->get_var( $order_statuses_sql = "( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' )";
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
"SELECT posts.ID //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( $this->is_cot_in_use() ) {
$sql = $wpdb->prepare(
'SELECT id FROM ' . OrdersTableDataStore::get_orders_table_name() . "
WHERE customer_id = %d
AND status in $order_statuses_sql
ORDER BY id DESC
LIMIT 1",
$customer->get_id()
);
$last_order_id = $wpdb->get_var( $sql );
} else {
$last_order_id = $wpdb->get_var(
"SELECT posts.ID
FROM $wpdb->posts AS posts FROM $wpdb->posts AS posts
LEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id LEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id
WHERE meta.meta_key = '_customer_user' WHERE meta.meta_key = '_customer_user'
AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "' AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "'
AND posts.post_type = 'shop_order' AND posts.post_type = 'shop_order'
AND posts.post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' ) AND posts.post_status IN $order_statuses_sql
ORDER BY posts.ID DESC" ORDER BY posts.ID DESC
// phpcs:enable LIMIT 1"
); );
update_user_meta( $customer->get_id(), '_last_order', $last_order ); }
//phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
update_user_meta( $customer->get_id(), '_last_order', $last_order_id );
} }
if ( ! $last_order ) { if ( ! $last_order_id ) {
return false; return false;
} }
return wc_get_order( absint( $last_order ) ); return wc_get_order( absint( $last_order_id ) );
} }
/** /**
@ -373,20 +409,33 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
$customer $customer
); );
$order_statuses_sql = "( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' )";
if ( '' === $count ) { if ( '' === $count ) {
global $wpdb; global $wpdb;
$count = $wpdb->get_var( //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared if ( $this->is_cot_in_use() ) {
"SELECT COUNT(*) $sql = $wpdb->prepare(
'SELECT COUNT(id) FROM ' . OrdersTableDataStore::get_orders_table_name() . "
WHERE customer_id = %d
AND status in $order_statuses_sql",
$customer->get_id()
);
$count = $wpdb->get_var( $sql );
} else {
$count = $wpdb->get_var(
"SELECT COUNT(*)
FROM $wpdb->posts as posts FROM $wpdb->posts as posts
LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id
WHERE meta.meta_key = '_customer_user' WHERE meta.meta_key = '_customer_user'
AND posts.post_type = 'shop_order' AND posts.post_type = 'shop_order'
AND posts.post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' ) AND posts.post_status IN $order_statuses_sql
AND meta_value = '" . esc_sql( $customer->get_id() ) . "'" AND meta_value = '" . esc_sql( $customer->get_id() ) . "'"
// phpcs:enable );
); }
//phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
update_user_meta( $customer->get_id(), '_order_count', $count ); update_user_meta( $customer->get_id(), '_order_count', $count );
} }
@ -410,24 +459,42 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
if ( '' === $spent ) { if ( '' === $spent ) {
global $wpdb; global $wpdb;
$statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() ); $statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() );
$spent = $wpdb->get_var( $statuses_sql = "( 'wc-" . implode( "','wc-", $statuses ) . "' )";
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
apply_filters( //phpcs:disable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
'woocommerce_customer_get_total_spent_query', if ( $this->is_cot_in_use() ) {
"SELECT SUM(meta2.meta_value) $sql = $wpdb->prepare(
'SELECT SUM(total_amount) FROM ' . OrdersTableDataStore::get_orders_table_name() . "
WHERE customer_id = %d
AND status in $statuses_sql",
$customer->get_id()
);
} else {
$sql = "SELECT SUM(meta2.meta_value)
FROM $wpdb->posts as posts FROM $wpdb->posts as posts
LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id
LEFT JOIN {$wpdb->postmeta} AS meta2 ON posts.ID = meta2.post_id LEFT JOIN {$wpdb->postmeta} AS meta2 ON posts.ID = meta2.post_id
WHERE meta.meta_key = '_customer_user' WHERE meta.meta_key = '_customer_user'
AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "' AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "'
AND posts.post_type = 'shop_order' AND posts.post_type = 'shop_order'
AND posts.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' ) AND posts.post_status IN $statuses_sql
AND meta2.meta_key = '_order_total'", AND meta2.meta_key = '_order_total'";
$customer }
)
// phpcs:enable //phpcs:disable WooCommerce.Commenting.CommentHooks.MissingSinceComment
); /**
* Filters the SQL query used to get the combined total of all the orders from a given customer.
*
* @param string The SQL query to use.
* @param WC_Customer The customer to get the total spent for.
* @return string The actual SQL query to use.
*/
$sql = apply_filters( 'woocommerce_customer_get_total_spent_query', $sql, $customer );
//phpcs:enable WooCommerce.Commenting.CommentHooks.MissingSinceComment
$spent = $wpdb->get_var( $sql );
//phpcs:enable WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
if ( ! $spent ) { if ( ! $spent ) {
$spent = 0; $spent = 0;
@ -511,7 +578,7 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat
* @return array * @return array
*/ */
public function get_user_ids_for_billing_email( $emails ) { public function get_user_ids_for_billing_email( $emails ) {
$emails = array_unique( array_map( 'strtolower', array_map( 'sanitize_email', $emails ) ) ); $emails = array_unique( array_map( 'strtolower', array_map( 'sanitize_email', $emails ) ) );
$users_query = new WP_User_Query( $users_query = new WP_User_Query(
array( array(
'fields' => 'ID', 'fields' => 'ID',

View File

@ -40,10 +40,11 @@ class WC_Helper_Order {
* *
* @param int $customer_id The ID of the customer the order is for. * @param int $customer_id The ID of the customer the order is for.
* @param WC_Product $product The product to add to the order. * @param WC_Product $product The product to add to the order.
* @param array $order_data Order data to be passed to wc_create_order.
* *
* @return WC_Order * @return WC_Order
*/ */
public static function create_order( $customer_id = 1, $product = null ) { public static function create_order( $customer_id = 1, $product = null, $order_data = array() ) {
if ( ! is_a( $product, 'WC_Product' ) ) { if ( ! is_a( $product, 'WC_Product' ) ) {
$product = WC_Helper_Product::create_simple_product(); $product = WC_Helper_Product::create_simple_product();
@ -51,12 +52,13 @@ class WC_Helper_Order {
WC_Helper_Shipping::create_simple_flat_rate(); WC_Helper_Shipping::create_simple_flat_rate();
$order_data = array( $default_order_data = array(
'status' => 'pending', 'status' => 'pending',
'customer_id' => $customer_id, 'customer_id' => $customer_id,
'customer_note' => '', 'customer_note' => '',
'total' => '', 'total' => '',
); );
$order_data = wp_parse_args( $order_data, $default_order_data );
$_SERVER['REMOTE_ADDR'] = '127.0.0.1'; // Required, else wc_create_order throws an exception. $_SERVER['REMOTE_ADDR'] = '127.0.0.1'; // Required, else wc_create_order throws an exception.
$order = wc_create_order( $order_data ); $order = wc_create_order( $order_data );

View File

@ -1,10 +1,22 @@
<?php <?php
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper;
/** /**
* Class WC_Customer_Data_Store_CPT_Test. * Class WC_Customer_Data_Store_CPT_Test.
*/ */
class WC_Customer_Data_Store_CPT_Test extends WC_Unit_Test_Case { class WC_Customer_Data_Store_CPT_Test extends WC_Unit_Test_Case {
/**
* Runs before each test.
*/
public function setUp(): void {
parent::setUp();
OrderHelper::create_order_custom_table_if_not_exist();
}
/** /**
* Test that metadata cannot overwrite customer's column data. * Test that metadata cannot overwrite customer's column data.
* *
@ -23,4 +35,176 @@ class WC_Customer_Data_Store_CPT_Test extends WC_Unit_Test_Case {
$this->assertEquals( $customer_id, $customer->get_id() ); $this->assertEquals( $customer_id, $customer->get_id() );
$this->assertEquals( $username, $customer->get_username() ); $this->assertEquals( $username, $customer->get_username() );
} }
/**
* Handler for the wc_order_statuses filter, returns just 'pending" as the valid order statuses list.
*
* @return string[]
*/
public function get_pending_only_as_order_statuses() {
return array( 'wc-pending' => 'pending' );
}
/**
* @testdox 'get_last_order' works when the posts table is used for storing orders.
*/
public function test_get_last_customer_order_not_using_cot() {
update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'no' );
$customer_1 = WC_Helper_Customer::create_customer( 'test1', 'pass1', 'test1@example.com' );
$customer_2 = WC_Helper_Customer::create_customer( 'test2', 'pass2', 'test2@example.com' );
WC_Helper_Order::create_order( $customer_1->get_id() );
$last_valid_order_of_1 = WC_Helper_Order::create_order( $customer_1->get_id() );
WC_Helper_Order::create_order( $customer_1->get_id(), null, array( 'status' => 'completed' ) );
WC_Helper_Order::create_order( $customer_2->get_id() );
WC_Helper_Order::create_order( $customer_2->get_id() );
$sut = new WC_Customer_Data_Store();
add_filter( 'wc_order_statuses', array( $this, 'get_pending_only_as_order_statuses' ), 10, 0 );
$actual_order = $sut->get_last_order( $customer_1 );
remove_filter( 'wc_order_statuses', array( $this, 'get_pending_only_as_order_statuses' ), 10 );
$this->assertEquals( $last_valid_order_of_1->get_id(), $actual_order->get_id() );
}
/**
* @testdox 'get_last_order' works when the custom orders table is used for storing orders.
*/
public function test_get_last_customer_order_using_cot() {
global $wpdb;
$customer_1 = WC_Helper_Customer::create_customer( 'test1', 'pass1', 'test1@example.com' );
$customer_2 = WC_Helper_Customer::create_customer( 'test2', 'pass2', 'test2@example.com' );
$last_valid_order = WC_Helper_Order::create_order( $customer_1->get_id() );
update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'yes' );
$sql =
'INSERT INTO ' . OrdersTableDataStore::get_orders_table_name() . "
( id, customer_id, status )
VALUES
( 1, %d, 'wc-completed' ), ( %d, %d, 'wc-completed' ), ( 3, %d, 'wc-invalid-status' ),
( 4, %d, 'wc-completed' ), ( 5, %d, 'wc-completed' )";
$customer_1_id = $customer_1->get_id();
$customer_2_id = $customer_2->get_id();
//phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$query = $wpdb->prepare( $sql, $customer_1_id, $last_valid_order->get_id(), $customer_1_id, $customer_1_id, $customer_2_id, $customer_2_id );
$wpdb->query( $query );
//phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
$sut = new WC_Customer_Data_Store();
$actual_order = $sut->get_last_order( $customer_1 );
$this->assertEquals( $last_valid_order->get_id(), $actual_order->get_id() );
}
/**
* @testdox 'get_order_count' works when the posts table is used for storing orders.
*/
public function test_order_count_not_using_cot() {
update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'no' );
$customer_1 = WC_Helper_Customer::create_customer( 'test1', 'pass1', 'test1@example.com' );
$customer_2 = WC_Helper_Customer::create_customer( 'test2', 'pass2', 'test2@example.com' );
WC_Helper_Order::create_order( $customer_1->get_id() );
WC_Helper_Order::create_order( $customer_1->get_id() );
WC_Helper_Order::create_order( $customer_1->get_id() );
WC_Helper_Order::create_order( $customer_1->get_id(), null, array( 'status' => 'completed' ) );
WC_Helper_Order::create_order( $customer_2->get_id() );
WC_Helper_Order::create_order( $customer_2->get_id() );
$sut = new WC_Customer_Data_Store();
add_filter( 'wc_order_statuses', array( $this, 'get_pending_only_as_order_statuses' ), 10, 0 );
$actual_count = $sut->get_order_count( $customer_1 );
remove_filter( 'wc_order_statuses', array( $this, 'get_pending_only_as_order_statuses' ), 10 );
$this->assertEquals( 3, $actual_count );
}
/**
* @testdox 'get_order_count' works when the custom orders table is used for storing orders.
*/
public function test_get_order_count_using_cot() {
global $wpdb;
update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'yes' );
$customer_1 = WC_Helper_Customer::create_customer( 'test1', 'pass1', 'test1@example.com' );
$customer_2 = WC_Helper_Customer::create_customer( 'test2', 'pass2', 'test2@example.com' );
$sql =
'INSERT INTO ' . OrdersTableDataStore::get_orders_table_name() . "
( id, customer_id, status )
VALUES
( 1, %d, 'wc-completed' ), ( 2, %d, 'wc-completed' ), ( 3, %d, 'wc-completed' ), ( 4, %d, 'wc-invalid-status' ),
( 5, %d, 'wc-completed' ), ( 6, %d, 'wc-completed' )";
$customer_1_id = $customer_1->get_id();
$customer_2_id = $customer_2->get_id();
//phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$query = $wpdb->prepare( $sql, $customer_1_id, $customer_1_id, $customer_1_id, $customer_1_id, $customer_2_id, $customer_2_id );
$wpdb->query( $query );
//phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
$sut = new WC_Customer_Data_Store();
$actual_count = $sut->get_order_count( $customer_1 );
$this->assertEquals( 3, $actual_count );
}
/**
* @testdox 'get_total_spent' works when the posts table is used for storing orders.
*/
public function test_get_total_spent_not_using_cot() {
update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'no' );
$customer_1 = WC_Helper_Customer::create_customer( 'test1', 'pass1', 'test1@example.com' );
$customer_2 = WC_Helper_Customer::create_customer( 'test2', 'pass2', 'test2@example.com' );
WC_Helper_Order::create_order( $customer_1->get_id(), null, array( 'status' => 'completed' ) );
WC_Helper_Order::create_order( $customer_1->get_id(), null, array( 'status' => 'completed' ) );
WC_Helper_Order::create_order( $customer_1->get_id(), null, array( 'status' => 'completed' ) );
WC_Helper_Order::create_order( $customer_1->get_id(), null, array( 'status' => 'pending' ) );
WC_Helper_Order::create_order( $customer_2->get_id() );
WC_Helper_Order::create_order( $customer_2->get_id() );
$sut = new WC_Customer_Data_Store();
add_filter( 'wc_order_statuses', array( $this, 'get_pending_only_as_order_statuses' ), 10, 0 );
$actual_amount = $sut->get_total_spent( $customer_1 );
remove_filter( 'wc_order_statuses', array( $this, 'get_pending_only_as_order_statuses' ), 10 );
// Each order created by WC_Helper_Order::create_order has a total amount of 50.
$this->assertEquals( '150.00', $actual_amount );
}
/**
* @testdox 'get_total_spent' works when the custom orders table is used for storing orders.
*/
public function test_get_total_spent_using_cot() {
global $wpdb;
update_option( CustomOrdersTableController::CUSTOM_ORDERS_TABLE_USAGE_ENABLED_OPTION, 'yes' );
$customer_1 = WC_Helper_Customer::create_customer( 'test1', 'pass1', 'test1@example.com' );
$customer_2 = WC_Helper_Customer::create_customer( 'test2', 'pass2', 'test2@example.com' );
$sql =
'INSERT INTO ' . OrdersTableDataStore::get_orders_table_name() . "
( id, customer_id, status, total_amount )
VALUES
( 1, %d, 'wc-completed', 10 ), ( 2, %d, 'wc-completed', 20 ), ( 3, %d, 'wc-completed', 30 ), ( 4, %d, 'wc-invalid-status', 40 ),
( 5, %d, 'wc-completed', 200 ), ( 6, %d, 'wc-completed', 300 )";
$customer_1_id = $customer_1->get_id();
$customer_2_id = $customer_2->get_id();
//phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
$query = $wpdb->prepare( $sql, $customer_1_id, $customer_1_id, $customer_1_id, $customer_1_id, $customer_2_id, $customer_2_id );
$wpdb->query( $query );
//phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
$sut = new WC_Customer_Data_Store();
$actual_spent = $sut->get_total_spent( $customer_1 );
$this->assertEquals( '60.00', $actual_spent );
}
} }