From aef11ef5864f7ab9d36b3f72e1c898d0c7015c5a Mon Sep 17 00:00:00 2001 From: Joshua T Flowers Date: Wed, 20 Feb 2019 10:30:00 +0800 Subject: [PATCH] Refactor customer ID creation (https://github.com/woocommerce/woocommerce-admin/pull/1619) * Fix indendtation for table creations * Refactor how customers are created on order update * Check if user has a role of customer or previous orders before storing * Add tests for user creation * Allow null emails for customers * Only select customer_id in guest ID query --- .../includes/class-wc-admin-install.php | 114 ++++++------ ...-wc-admin-reports-customers-data-store.php | 163 ++++++++++-------- ...-admin-reports-orders-stats-data-store.php | 26 +-- .../tests/api/reports-customers.php | 69 ++++++++ 4 files changed, 217 insertions(+), 155 deletions(-) diff --git a/plugins/woocommerce-admin/includes/class-wc-admin-install.php b/plugins/woocommerce-admin/includes/class-wc-admin-install.php index 24d7444ef1d..bb63ffeb4b6 100644 --- a/plugins/woocommerce-admin/includes/class-wc-admin-install.php +++ b/plugins/woocommerce-admin/includes/class-wc-admin-install.php @@ -112,8 +112,8 @@ class WC_Admin_Install { KEY date_created (date_created), KEY customer_id (customer_id), KEY status (status) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_product_lookup ( + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_order_product_lookup ( order_item_id BIGINT UNSIGNED NOT NULL, order_id BIGINT UNSIGNED NOT NULL, product_id BIGINT UNSIGNED NOT NULL, @@ -133,19 +133,19 @@ class WC_Admin_Install { KEY product_id (product_id), KEY customer_id (customer_id), KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup ( - order_id BIGINT UNSIGNED NOT NULL, - tax_rate_id BIGINT UNSIGNED NOT NULL, - date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, - shipping_tax double DEFAULT 0 NOT NULL, - order_tax double DEFAULT 0 NOT NULL, - total_tax double DEFAULT 0 NOT NULL, - PRIMARY KEY (order_id, tax_rate_id), - KEY tax_rate_id (tax_rate_id), - KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup ( + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_order_tax_lookup ( + order_id BIGINT UNSIGNED NOT NULL, + tax_rate_id BIGINT UNSIGNED NOT NULL, + date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, + shipping_tax double DEFAULT 0 NOT NULL, + order_tax double DEFAULT 0 NOT NULL, + total_tax double DEFAULT 0 NOT NULL, + PRIMARY KEY (order_id, tax_rate_id), + KEY tax_rate_id (tax_rate_id), + KEY date_created (date_created) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_order_coupon_lookup ( order_id BIGINT UNSIGNED NOT NULL, coupon_id BIGINT UNSIGNED NOT NULL, date_created datetime DEFAULT '0000-00-00 00:00:00' NOT NULL, @@ -153,48 +153,48 @@ class WC_Admin_Install { PRIMARY KEY (order_id, coupon_id), KEY coupon_id (coupon_id), KEY date_created (date_created) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_admin_notes ( - note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - name varchar(255) NOT NULL, - type varchar(20) NOT NULL, - locale varchar(20) NOT NULL, - title longtext NOT NULL, - content longtext NOT NULL, - icon varchar(200) NOT NULL, - content_data longtext NULL default null, - status varchar(200) NOT NULL, - source varchar(200) NOT NULL, - date_created datetime NOT NULL default '0000-00-00 00:00:00', - date_reminder datetime NULL default null, - PRIMARY KEY (note_id) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_admin_note_actions ( - action_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - note_id BIGINT UNSIGNED NOT NULL, - name varchar(255) NOT NULL, - label varchar(255) NOT NULL, - query longtext NOT NULL, - PRIMARY KEY (action_id), - KEY note_id (note_id) - ) $collate; - CREATE TABLE {$wpdb->prefix}wc_customer_lookup ( - customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, - user_id BIGINT UNSIGNED DEFAULT NULL, - username varchar(60) DEFAULT '' NOT NULL, - first_name varchar(255) NOT NULL, - last_name varchar(255) NOT NULL, - email varchar(100) NOT NULL, - date_last_active timestamp NULL default null, - date_registered timestamp NULL default null, - country char(2) DEFAULT '' NOT NULL, - postcode varchar(20) DEFAULT '' NOT NULL, - city varchar(100) DEFAULT '' NOT NULL, - PRIMARY KEY (customer_id), - UNIQUE KEY user_id (user_id), - KEY email (email) - ) $collate; - "; + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_admin_notes ( + note_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + name varchar(255) NOT NULL, + type varchar(20) NOT NULL, + locale varchar(20) NOT NULL, + title longtext NOT NULL, + content longtext NOT NULL, + icon varchar(200) NOT NULL, + content_data longtext NULL default null, + status varchar(200) NOT NULL, + source varchar(200) NOT NULL, + date_created datetime NOT NULL default '0000-00-00 00:00:00', + date_reminder datetime NULL default null, + PRIMARY KEY (note_id) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_admin_note_actions ( + action_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + note_id BIGINT UNSIGNED NOT NULL, + name varchar(255) NOT NULL, + label varchar(255) NOT NULL, + query longtext NOT NULL, + PRIMARY KEY (action_id), + KEY note_id (note_id) + ) $collate; + CREATE TABLE {$wpdb->prefix}wc_customer_lookup ( + customer_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + user_id BIGINT UNSIGNED DEFAULT NULL, + username varchar(60) DEFAULT '' NOT NULL, + first_name varchar(255) NOT NULL, + last_name varchar(255) NOT NULL, + email varchar(100) NULL default NULL, + date_last_active timestamp NULL default null, + date_registered timestamp NULL default null, + country char(2) DEFAULT '' NOT NULL, + postcode varchar(20) DEFAULT '' NOT NULL, + city varchar(100) DEFAULT '' NOT NULL, + PRIMARY KEY (customer_id), + UNIQUE KEY user_id (user_id), + KEY email (email) + ) $collate; + "; return $tables; } diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-customers-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-customers-data-store.php index 8c6e284a4bf..1550a48230f 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-customers-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-customers-data-store.php @@ -414,52 +414,77 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store } /** - * Gets the guest (no user_id) customer ID or creates a new one for - * the corresponding billing email in the provided WC_Order + * Returns an existing customer ID for an order if one exists. * - * @param WC_Order $order Order to get/create guest customer data with. - * @return int|false The ID of the retrieved/created customer, or false on error. + * @param object $order WC Order. + * @return int|bool */ - public function get_or_create_guest_customer_from_order( $order ) { + public static function get_existing_customer_id_from_order( $order ) { + $user_id = $order->get_customer_id(); + + if ( 0 === $user_id ) { + $email = $order->get_billing_email( 'edit' ); + + if ( $email ) { + return self::get_guest_id_by_email( $email ); + } else { + return false; + } + } else { + return self::get_customer_id_by_user_id( $user_id ); + } + } + + /** + * Get or create a customer from a given order. + * + * @param object $order WC Order. + * @return int|bool + */ + public static function get_or_create_customer_from_order( $order ) { global $wpdb; + $returning_customer_id = self::get_existing_customer_id_from_order( $order ); - $email = $order->get_billing_email( 'edit' ); - - if ( empty( $email ) ) { - return false; + if ( $returning_customer_id ) { + return $returning_customer_id; } - $existing_guest = $this->get_guest_by_email( $email ); - - if ( $existing_guest ) { - return $existing_guest['customer_id']; - } - - $result = $wpdb->insert( - $wpdb->prefix . self::TABLE_NAME, - array( - 'first_name' => $order->get_billing_first_name( 'edit' ), - 'last_name' => $order->get_billing_last_name( 'edit' ), - 'email' => $email, - 'city' => $order->get_billing_city( 'edit' ), - 'postcode' => $order->get_billing_postcode( 'edit' ), - 'country' => $order->get_billing_country( 'edit' ), - 'date_last_active' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), - ), - array( - '%s', - '%s', - '%s', - '%s', - '%s', - '%s', - '%s', - ) + $data = array( + 'first_name' => $order->get_billing_first_name( 'edit' ), + 'last_name' => $order->get_billing_last_name( 'edit' ), + 'email' => $order->get_billing_email( 'edit' ), + 'city' => $order->get_billing_city( 'edit' ), + 'postcode' => $order->get_billing_postcode( 'edit' ), + 'country' => $order->get_billing_country( 'edit' ), + 'date_last_active' => date( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), ); + $format = array( + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + ); + + // Add registered customer data. + if ( 0 !== $order->get_user_id() ) { + $user_id = $order->get_user_id(); + $customer = new WC_Customer( $user_id ); + $data['user_id'] = $user_id; + $data['username'] = $customer->get_username( 'edit' ); + $data['date_registered'] = $customer->get_date_created( 'edit' )->date( WC_Admin_Reports_Interval::$sql_datetime_format ); + $format[] = '%d'; + $format[] = '%s'; + $format[] = '%s'; + } + + $result = $wpdb->insert( $wpdb->prefix . self::TABLE_NAME, $data, $format ); $customer_id = $wpdb->insert_id; /** - * Fires when customser's reports are created. + * Fires when a new report customer is created. * * @param int $customer_id Customer ID. */ @@ -469,53 +494,23 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store } /** - * Retrieve a guest (no user_id) customer row by email. + * Retrieve a guest ID (when user_id is null) by email. * * @param string $email Email address. * @return false|array Customer array if found, boolean false if not. */ - public function get_guest_by_email( $email ) { + public static function get_guest_id_by_email( $email ) { global $wpdb; - $table_name = $wpdb->prefix . self::TABLE_NAME; - $guest_row = $wpdb->get_row( + $table_name = $wpdb->prefix . self::TABLE_NAME; + $customer_id = $wpdb->get_var( $wpdb->prepare( - "SELECT * FROM {$table_name} WHERE email = %s AND user_id IS NULL LIMIT 1", + "SELECT customer_id FROM {$table_name} WHERE email = %s AND user_id IS NULL LIMIT 1", $email - ), - ARRAY_A + ) ); // WPCS: unprepared SQL ok. - if ( $guest_row ) { - return $this->cast_numbers( $guest_row ); - } - - return false; - } - - /** - * Retrieve a registered customer row by user_id. - * - * @param string|int $user_id User ID. - * @return false|array Customer array if found, boolean false if not. - */ - public function get_customer_by_user_id( $user_id ) { - global $wpdb; - - $table_name = $wpdb->prefix . self::TABLE_NAME; - $customer = $wpdb->get_row( - $wpdb->prepare( - "SELECT * FROM {$table_name} WHERE user_id = %d LIMIT 1", - $user_id - ), - ARRAY_A - ); // WPCS: unprepared SQL ok. - - if ( $customer ) { - return $this->cast_numbers( $customer ); - } - - return false; + return $customer_id ? (int) $customer_id : false; } /** @@ -573,7 +568,7 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store $customer = new WC_Customer( $user_id ); - if ( $customer->get_id() != $user_id ) { + if ( ! self::is_valid_customer( $user_id ) ) { return false; } @@ -622,6 +617,26 @@ class WC_Admin_Reports_Customers_Data_Store extends WC_Admin_Reports_Data_Store return $results; } + /** + * Check if a user ID is a valid customer or other user role with past orders. + * + * @param int $user_id User ID. + * @return bool + */ + protected static function is_valid_customer( $user_id ) { + $customer = new WC_Customer( $user_id ); + + if ( $customer->get_id() !== $user_id ) { + return false; + } + + if ( $customer->get_order_count() < 1 && 'customer' !== $customer->get_role() ) { + return false; + } + + return true; + } + /** * Returns string to be used as cache key for the data. * diff --git a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php index 7b7a5ba4433..54e1d059738 100644 --- a/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php +++ b/plugins/woocommerce-admin/includes/data-stores/class-wc-admin-reports-orders-stats-data-store.php @@ -404,6 +404,7 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto 'net_total' => self::get_net_total( $order ), 'returning_customer' => self::is_returning_customer( $order ), 'status' => self::normalize_order_status( $order->get_status() ), + 'customer_id' => WC_Admin_Reports_Customers_Data_Store::get_or_create_customer_from_order( $order ), ); $format = array( '%d', @@ -417,32 +418,9 @@ class WC_Admin_Reports_Orders_Stats_Data_Store extends WC_Admin_Reports_Data_Sto '%f', '%d', '%s', + '%d', ); - // Ensure we're associating this order with a Customer in the lookup table. - $order_user_id = $order->get_customer_id(); - $customers_data_store = new WC_Admin_Reports_Customers_Data_Store(); - - if ( 0 === $order_user_id ) { - $email = $order->get_billing_email( 'edit' ); - - if ( $email ) { - $customer_id = $customers_data_store->get_or_create_guest_customer_from_order( $order ); - - if ( $customer_id ) { - $data['customer_id'] = $customer_id; - $format[] = '%d'; - } - } - } else { - $customer = $customers_data_store->get_customer_by_user_id( $order_user_id ); - - if ( $customer && $customer['customer_id'] ) { - $data['customer_id'] = $customer['customer_id']; - $format[] = '%d'; - } - } - // Update or add the information to the DB. $result = $wpdb->replace( $table_name, $data, $format ); diff --git a/plugins/woocommerce-admin/tests/api/reports-customers.php b/plugins/woocommerce-admin/tests/api/reports-customers.php index 14e9f8d336e..56512297a08 100644 --- a/plugins/woocommerce-admin/tests/api/reports-customers.php +++ b/plugins/woocommerce-admin/tests/api/reports-customers.php @@ -106,6 +106,75 @@ class WC_Tests_API_Reports_Customers extends WC_REST_Unit_Test_Case { $this->assertFalse( $result ); } + /** + * Test creation of various user roles + * + * @since 3.5.0 + */ + public function test_user_creation() { + wp_set_current_user( $this->user ); + $admin_id = wp_insert_user( + array( + 'user_login' => 'testadmin', + 'user_pass' => null, + 'role' => 'administrator', + ) + ); + + // Admin user without orders should not be shown. + $request = new WP_REST_Request( 'GET', $this->endpoint ); + $request->set_query_params( array( 'per_page' => 10 ) ); + $response = $this->server->dispatch( $request ); + $reports = $response->get_data(); + $headers = $response->get_headers(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertCount( 0, $reports ); + + // Creating an order with admin should return the admin. + $product = new WC_Product_Simple(); + $product->set_name( 'Test Product' ); + $product->set_regular_price( 25 ); + $product->save(); + + $order = WC_Helper_Order::create_order( $admin_id, $product ); + $order->set_status( 'processing' ); + $order->set_total( 100 ); + $order->save(); + + WC_Helper_Queue::run_all_pending(); + + $request = new WP_REST_Request( 'GET', $this->endpoint ); + $request->set_query_params( array( 'per_page' => 10 ) ); + $response = $this->server->dispatch( $request ); + $reports = $response->get_data(); + $headers = $response->get_headers(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertCount( 1, $reports ); + $this->assertEquals( $admin_id, $reports[0]['user_id'] ); + + // Creating a customer should show up regardless of orders. + $customer = WC_Helper_Customer::create_customer( 'customer', 'password', 'customer@example.com' ); + + $request = new WP_REST_Request( 'GET', $this->endpoint ); + $request->set_query_params( + array( + 'per_page' => 10, + 'order' => 'asc', + 'orderby' => 'username', + ) + ); + $response = $this->server->dispatch( $request ); + $reports = $response->get_data(); + $headers = $response->get_headers(); + + $this->assertEquals( 200, $response->get_status() ); + $this->assertCount( 2, $reports ); + $this->assertEquals( $customer->get_id(), $reports[0]['user_id'] ); + $this->assertEquals( $admin_id, $reports[1]['user_id'] ); + } + /** * Test getting reports. *