diff --git a/includes/admin/list-tables/class-wc-admin-list-table-orders.php b/includes/admin/list-tables/class-wc-admin-list-table-orders.php index 1b562292c2a..f9babc8825b 100644 --- a/includes/admin/list-tables/class-wc-admin-list-table-orders.php +++ b/includes/admin/list-tables/class-wc-admin-list-table-orders.php @@ -772,15 +772,22 @@ class WC_Admin_List_Table_Orders extends WC_Admin_List_Table { // Filter the orders by the posted customer. if ( ! empty( $_GET['_customer_user'] ) ) { // WPCS: input var ok. - // @codingStandardsIgnoreStart - $query_vars['meta_query'] = array( - array( - 'key' => '_customer_user', - 'value' => (int) $_GET['_customer_user'], // WPCS: input var ok, sanitization ok. - 'compare' => '=', - ), - ); - // @codingStandardsIgnoreEnd + $customer_id = (int) $_GET['_customer_user']; // WPCS: input var ok, sanitization ok. + + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $query_vars['author'] = $customer_id; + } else { + // @codingStandardsIgnoreStart + $query_vars['meta_query'] = array( + array( + 'key' => '_customer_user', + 'value' => $customer_id, + 'compare' => '=', + ), + ); + // @codingStandardsIgnoreEnd + } } // Sorting. diff --git a/includes/admin/reports/class-wc-report-customers.php b/includes/admin/reports/class-wc-report-customers.php index 503f470992f..326ad5334f5 100644 --- a/includes/admin/reports/class-wc-report-customers.php +++ b/includes/admin/reports/class-wc-report-customers.php @@ -69,46 +69,56 @@ class WC_Report_Customers extends WC_Admin_Report { * Output customers vs guests chart. */ public function customers_vs_guests() { - - $customer_order_totals = $this->get_order_report_data( - array( - 'data' => array( - 'ID' => array( - 'type' => 'post_data', - 'function' => 'COUNT', - 'name' => 'total_orders', - ), + $customer_args = array( + 'data' => array( + 'ID' => array( + 'type' => 'post_data', + 'function' => 'COUNT', + 'name' => 'total_orders', ), - 'where_meta' => array( - array( - 'meta_key' => '_customer_user', - 'meta_value' => '0', - 'operator' => '>', - ), - ), - 'filter_range' => true, - ) + ), + 'filter_range' => true, ); + $guest_args = $customer_args; - $guest_order_totals = $this->get_order_report_data( - array( - 'data' => array( - 'ID' => array( - 'type' => 'post_data', - 'function' => 'COUNT', - 'name' => 'total_orders', - ), + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $customer_args['where'] = array( + array( + 'key' => 'post_author', + 'value' => '0', + 'operator' => '>', ), - 'where_meta' => array( - array( - 'meta_key' => '_customer_user', - 'meta_value' => '0', - 'operator' => '=', - ), + ); + + $guest_args['where'] = array( + array( + 'key' => 'post_author', + 'value' => '0', + 'operator' => '=', ), - 'filter_range' => true, - ) - ); + ); + } else { + $customer_args['where_meta'] = array( + array( + 'meta_key' => '_customer_user', + 'meta_value' => '0', + 'operator' => '>', + ), + ); + + $guest_args['where_meta'] = array( + array( + 'meta_key' => '_customer_user', + 'meta_value' => '0', + 'operator' => '=', + ), + ); + } + + $customer_order_totals = $this->get_order_report_data( $customer_args ); + $guest_order_totals = $this->get_order_report_data( $guest_args ); + ?>
@@ -246,61 +256,63 @@ class WC_Report_Customers extends WC_Admin_Report { public function get_main_chart() { global $wp_locale; - $customer_orders = $this->get_order_report_data( - array( - 'data' => array( - 'ID' => array( - 'type' => 'post_data', - 'function' => 'COUNT', - 'name' => 'total_orders', - ), - 'post_date' => array( - 'type' => 'post_data', - 'function' => '', - 'name' => 'post_date', - ), + $customer_args = array( + 'data' => array( + 'ID' => array( + 'type' => 'post_data', + 'function' => 'COUNT', + 'name' => 'total_orders', ), - 'where_meta' => array( - array( - 'meta_key' => '_customer_user', - 'meta_value' => '0', - 'operator' => '>', - ), + 'post_date' => array( + 'type' => 'post_data', + 'function' => '', + 'name' => 'post_date', ), - 'group_by' => $this->group_by_query, - 'order_by' => 'post_date ASC', - 'query_type' => 'get_results', - 'filter_range' => true, - ) + ), + 'group_by' => $this->group_by_query, + 'order_by' => 'post_date ASC', + 'query_type' => 'get_results', + 'filter_range' => true, ); + $guest_args = $customer_args; - $guest_orders = $this->get_order_report_data( - array( - 'data' => array( - 'ID' => array( - 'type' => 'post_data', - 'function' => 'COUNT', - 'name' => 'total_orders', - ), - 'post_date' => array( - 'type' => 'post_data', - 'function' => '', - 'name' => 'post_date', - ), + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $customer_args['where'] = array( + array( + 'key' => 'post_author', + 'value' => '0', + 'operator' => '>', ), - 'where_meta' => array( - array( - 'meta_key' => '_customer_user', - 'meta_value' => '0', - 'operator' => '=', - ), + ); + + $guest_args['where'] = array( + array( + 'key' => 'post_author', + 'value' => '0', + 'operator' => '=', ), - 'group_by' => $this->group_by_query, - 'order_by' => 'post_date ASC', - 'query_type' => 'get_results', - 'filter_range' => true, - ) - ); + ); + } else { + $customer_args['where_meta'] = array( + array( + 'meta_key' => '_customer_user', + 'meta_value' => '0', + 'operator' => '>', + ), + ); + + $guest_args['where_meta'] = array( + array( + 'meta_key' => '_customer_user', + 'meta_value' => '0', + 'operator' => '=', + ), + ); + } + + $customer_orders = $this->get_order_report_data( $customer_args ); + $guest_orders = $this->get_order_report_data( $guest_args ); $signups = $this->prepare_chart_data( $this->customers, 'user_registered', '', $this->chart_interval, $this->start_date, $this->chart_groupby ); $customer_orders = $this->prepare_chart_data( $customer_orders, 'post_date', 'total_orders', $this->chart_interval, $this->start_date, $this->chart_groupby ); diff --git a/includes/api/class-wc-rest-orders-controller.php b/includes/api/class-wc-rest-orders-controller.php index 02957242ac0..c9121bb4465 100644 --- a/includes/api/class-wc-rest-orders-controller.php +++ b/includes/api/class-wc-rest-orders-controller.php @@ -365,15 +365,20 @@ class WC_REST_Orders_Controller extends WC_REST_Legacy_Orders_Controller { } if ( isset( $request['customer'] ) ) { - if ( ! empty( $args['meta_query'] ) ) { - $args['meta_query'] = array(); // WPCS: slow query ok. - } + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $args['author'] = $request['customer']; + } else { + if ( ! empty( $args['meta_query'] ) ) { + $args['meta_query'] = array(); // WPCS: slow query ok. + } - $args['meta_query'][] = array( - 'key' => '_customer_user', - 'value' => $request['customer'], - 'type' => 'NUMERIC', - ); + $args['meta_query'][] = array( + 'key' => '_customer_user', + 'value' => $request['customer'], + 'type' => 'NUMERIC', + ); + } } // Search by product. diff --git a/includes/api/v1/class-wc-rest-orders-controller.php b/includes/api/v1/class-wc-rest-orders-controller.php index 804e3fb571e..f617192c0bb 100644 --- a/includes/api/v1/class-wc-rest-orders-controller.php +++ b/includes/api/v1/class-wc-rest-orders-controller.php @@ -404,15 +404,20 @@ class WC_REST_Orders_V1_Controller extends WC_REST_Posts_Controller { } if ( isset( $request['customer'] ) ) { - if ( ! empty( $args['meta_query'] ) ) { - $args['meta_query'] = array(); - } + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $args['author'] = $request['customer']; + } else { + if ( ! empty( $args['meta_query'] ) ) { + $args['meta_query'] = array(); // WPCS: slow query ok. + } - $args['meta_query'][] = array( - 'key' => '_customer_user', - 'value' => $request['customer'], - 'type' => 'NUMERIC', - ); + $args['meta_query'][] = array( + 'key' => '_customer_user', + 'value' => $request['customer'], + 'type' => 'NUMERIC', + ); + } } // Search by product. diff --git a/includes/class-wc-background-updater.php b/includes/class-wc-background-updater.php index 3f80666a767..1c28f7f26da 100644 --- a/includes/class-wc-background-updater.php +++ b/includes/class-wc-background-updater.php @@ -132,4 +132,13 @@ class WC_Background_Updater extends WC_Background_Process { WC_Install::update_db_version(); parent::complete(); } + + /** + * See if the batch limit has been exceeded. + * + * @return bool + */ + public function is_memory_exceeded() { + return $this->memory_exceeded(); + } } diff --git a/includes/class-wc-install.php b/includes/class-wc-install.php index b0bcb358020..e07100c8262 100644 --- a/includes/class-wc-install.php +++ b/includes/class-wc-install.php @@ -109,6 +109,10 @@ class WC_Install { 'wc_update_343_cleanup_foreign_keys', 'wc_update_343_db_version', ), + '3.5.0' => array( + 'wc_update_350_order_customer_id', + 'wc_update_350_db_version', + ), ); /** diff --git a/includes/class-woocommerce.php b/includes/class-woocommerce.php index a98d4076c85..9f7e1380f85 100644 --- a/includes/class-woocommerce.php +++ b/includes/class-woocommerce.php @@ -20,7 +20,7 @@ final class WooCommerce { * * @var string */ - public $version = '3.4.0'; + public $version = '3.5.0'; /** * The single instance of the class. diff --git a/includes/data-stores/abstract-wc-order-data-store-cpt.php b/includes/data-stores/abstract-wc-order-data-store-cpt.php index 73ff2844cb0..0d6208887da 100644 --- a/includes/data-stores/abstract-wc-order-data-store-cpt.php +++ b/includes/data-stores/abstract-wc-order-data-store-cpt.php @@ -67,7 +67,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme 'post_type' => $order->get_type( 'edit' ), 'post_status' => 'wc-' . ( $order->get_status( 'edit' ) ? $order->get_status( 'edit' ) : apply_filters( 'woocommerce_default_order_status', 'pending' ) ), 'ping_status' => 'closed', - 'post_author' => 1, + 'post_author' => is_callable( array( $order, 'get_customer_id' ) ) ? $order->get_customer_id() : 0, 'post_title' => $this->get_post_title(), 'post_password' => uniqid( 'order_' ), 'post_parent' => $order->get_parent_id( 'edit' ), @@ -139,7 +139,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme $changes = $order->get_changes(); // Only update the post when the post data changes. - if ( array_intersect( array( 'date_created', 'date_modified', 'status', 'parent_id', 'post_excerpt' ), array_keys( $changes ) ) ) { + if ( array_intersect( array( 'date_created', 'date_modified', 'status', 'parent_id', 'post_excerpt', 'customer_id' ), array_keys( $changes ) ) ) { $post_data = array( 'post_date' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getOffsetTimestamp() ), 'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $order->get_date_created( 'edit' )->getTimestamp() ), @@ -148,6 +148,7 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme 'post_excerpt' => $this->get_post_excerpt( $order ), 'post_modified' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $order->get_date_modified( 'edit' )->getOffsetTimestamp() ) : current_time( 'mysql' ), 'post_modified_gmt' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $order->get_date_modified( 'edit' )->getTimestamp() ) : current_time( 'mysql', 1 ), + 'post_author' => is_callable( array( $order, 'get_customer_id' ) ) ? $order->get_customer_id() : 0, ); /** @@ -165,12 +166,27 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme wp_update_post( array_merge( array( 'ID' => $order->get_id() ), $post_data ) ); } $order->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook. + + // If customer changed, update any downloadable permissions. + if ( in_array( 'customer_id', $changes ) ) { + $this->update_downloadable_permissions( $order ); + } } $this->update_post_meta( $order ); $order->apply_changes(); $this->clear_caches( $order ); } + /** + * Update downloadable permissions for a given order. + * + * @param WC_Order $order Order object. + */ + protected function update_downloadable_permissions( $order ) { + $data_store = WC_Data_Store::load( 'customer-download' ); + $data_store->update_user_by_order_id( $order->get_id(), $order->get_customer_id(), $order->get_billing_email() ); + } + /** * Method to delete an order from the database. * diff --git a/includes/data-stores/class-wc-customer-data-store.php b/includes/data-stores/class-wc-customer-data-store.php index d907e61893c..8ad38340b3f 100644 --- a/includes/data-stores/class-wc-customer-data-store.php +++ b/includes/data-stores/class-wc-customer-data-store.php @@ -325,18 +325,27 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat public function get_last_order( &$customer ) { global $wpdb; - $last_order = $wpdb->get_var( - // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared - "SELECT posts.ID + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $query = "SELECT ID + FROM $wpdb->posts + WHERE post_author = '" . esc_sql( $customer->get_id() ) . "' + AND post_type = 'shop_order' + AND post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' ) + ORDER BY ID DESC"; + } else { + $query = "SELECT posts.ID FROM $wpdb->posts AS posts LEFT JOIN {$wpdb->postmeta} AS meta on posts.ID = meta.post_id WHERE meta.meta_key = '_customer_user' AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "' AND posts.post_type = 'shop_order' AND posts.post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' ) - ORDER BY posts.ID DESC" - // phpcs:enable - ); + ORDER BY posts.ID DESC"; + } + + // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared + $last_order = $wpdb->get_var( $query ); if ( ! $last_order ) { return false; @@ -358,17 +367,25 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat if ( '' === $count ) { global $wpdb; - $count = $wpdb->get_var( - // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared - "SELECT COUNT(*) + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $query = "SELECT COUNT(*) + FROM $wpdb->posts + WHERE post_type = 'shop_order' + AND post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' ) + AND post_author = " . esc_sql( $customer->get_id() ); + } else { + $query = "SELECT COUNT(*) FROM $wpdb->posts as posts LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id WHERE meta.meta_key = '_customer_user' AND posts.post_type = 'shop_order' AND posts.post_status IN ( '" . implode( "','", array_map( 'esc_sql', array_keys( wc_get_order_statuses() ) ) ) . "' ) - AND meta_value = '" . esc_sql( $customer->get_id() ) . "'" - // phpcs:enable - ); + AND meta_value = '" . esc_sql( $customer->get_id() ) . "'"; + } + + // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared + $count = $wpdb->get_var( $query ); update_user_meta( $customer->get_id(), '_order_count', $count ); } @@ -393,11 +410,18 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat global $wpdb; $statuses = array_map( 'esc_sql', wc_get_is_paid_statuses() ); - $spent = $wpdb->get_var( - // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared - apply_filters( - 'woocommerce_customer_get_total_spent_query', - "SELECT SUM(meta2.meta_value) + + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $query = "SELECT SUM(meta.meta_value) + FROM $wpdb->posts as posts + LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id + WHERE posts.post_author = '" . esc_sql( $customer->get_id() ) . "' + AND posts.post_type = 'shop_order' + AND posts.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' ) + AND meta.meta_key = '_order_total'"; + } else { + $query = "SELECT SUM(meta2.meta_value) FROM $wpdb->posts as posts LEFT JOIN {$wpdb->postmeta} AS meta ON posts.ID = meta.post_id LEFT JOIN {$wpdb->postmeta} AS meta2 ON posts.ID = meta2.post_id @@ -405,11 +429,11 @@ class WC_Customer_Data_Store extends WC_Data_Store_WP implements WC_Customer_Dat AND meta.meta_value = '" . esc_sql( $customer->get_id() ) . "' AND posts.post_type = 'shop_order' AND posts.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' ) - AND meta2.meta_key = '_order_total'", - $customer - ) - // phpcs:enable - ); + AND meta2.meta_key = '_order_total'"; + } + + // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared + $spent = $wpdb->get_var( apply_filters( 'woocommerce_customer_get_total_spent_query', $query, $customer ) ); if ( ! $spent ) { $spent = 0; diff --git a/includes/data-stores/class-wc-order-data-store-cpt.php b/includes/data-stores/class-wc-order-data-store-cpt.php index 2cc5b7e1fcd..316aa4b8373 100644 --- a/includes/data-stores/class-wc-order-data-store-cpt.php +++ b/includes/data-stores/class-wc-order-data-store-cpt.php @@ -107,10 +107,17 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement $date_paid = get_post_meta( $id, '_paid_date', true ); } + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $customer_id = $post_object->post_author; + } else { + $customer_id = get_post_meta( $id, '_customer_user', true ); + } + $order->set_props( array( 'order_key' => get_post_meta( $id, '_order_key', true ), - 'customer_id' => get_post_meta( $id, '_customer_user', true ), + 'customer_id' => $customer_id, 'billing_first_name' => get_post_meta( $id, '_billing_first_name', true ), 'billing_last_name' => get_post_meta( $id, '_billing_last_name', true ), 'billing_company' => get_post_meta( $id, '_billing_company', true ), @@ -258,10 +265,9 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement update_post_meta( $id, '_shipping_address_index', implode( ' ', $order->get_address( 'shipping' ) ) ); } - // If customer changed, update any downloadable permissions. - if ( in_array( 'customer_id', $updated_props, true ) || in_array( 'billing_email', $updated_props, true ) ) { - $data_store = WC_Data_Store::load( 'customer-download' ); - $data_store->update_user_by_order_id( $id, $order->get_customer_id(), $order->get_billing_email() ); + // If customer email changed, update any downloadable permissions. + if ( in_array( 'billing_email', $updated_props ) ) { + $this->update_downloadable_permissions( $order ); } // Mark user account as active. @@ -645,6 +651,11 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement 'page' => 'paged', ); + // On WC 3.5.0 the ID of the user that placed the order was moved from the post meta _customer_user to the post_author field in the wp_posts table. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) ) { + $key_mapping['customer_id'] = 'author'; + } + foreach ( $key_mapping as $query_key => $db_key ) { if ( isset( $query_vars[ $query_key ] ) ) { $query_vars[ $db_key ] = $query_vars[ $query_key ]; diff --git a/includes/wc-update-functions.php b/includes/wc-update-functions.php index ba56e596ab2..c564831bbdf 100644 --- a/includes/wc-update-functions.php +++ b/includes/wc-update-functions.php @@ -1835,3 +1835,130 @@ function wc_update_343_cleanup_foreign_keys() { function wc_update_343_db_version() { WC_Install::update_db_version( '3.4.3' ); } + +/** + * Copy order customer_id from post meta to post_author and set post_author to 0 for refunds. + * + * Two different strategies are used to copy data depending if the update is being executed from + * the command line or not. If `wp wc update` is used to update the database, this function + * copies data in a single go that is faster but uses more resources. If the databse update was + * triggered from the wp-admin, this function copies data in batches which is slower but uses + * few resources and thus is less likely to fail on smaller servers. + * + * @param WC_Background_Updater|false $updater Background updater instance or false if function is called from `wp wc update` WP-CLI command. + * @return true|void Return true if near memory limit and needs to restart. Return void if update completed. + */ +function wc_update_350_order_customer_id( $updater = false ) { + global $wpdb; + + $post_types = (array) apply_filters( 'woocommerce_update_350_order_customer_id_post_types', array( 'shop_order' ) ); + $post_types_placeholders = implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ); + + if ( defined( 'WP_CLI' ) && WP_CLI ) { + // If running the update from the command-line, copy data in a single go which is faster but uses more resources. + $wpdb->query( + 'CREATE TEMPORARY TABLE customers_map (post_id BIGINT(20), customer_id BIGINT(20), PRIMARY KEY(post_id))' + ); + + $wpdb->query( + "INSERT IGNORE INTO customers_map (SELECT post_id, meta_value FROM wp_postmeta WHERE meta_key = '_customer_user')" + ); + + $wpdb->query( 'SET sql_safe_updates=1' ); + + $wpdb->query( + $wpdb->prepare( + "UPDATE wp_posts JOIN customers_map ON wp_posts.ID = customers_map.post_id SET wp_posts.post_author = customers_map.customer_id WHERE post_type IN ({$post_types_placeholders})", // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared + $post_types + ) + ); + + $wpdb->update( $wpdb->posts, array( 'post_author' => 0 ), array( 'post_type' => 'shop_order_refund' ) ); + } else { + // If running the update from the wp-admin, copy data in batches being careful not to use more memory than allowed. + $admin_orders_sql = ''; + $admin_orders = get_transient( 'wc_update_350_admin_orders' ); + + if ( false === $admin_orders ) { + // Get the list of orders that we don't want to change as they belong to user ID 1. + $admin_orders = $wpdb->get_col( + $wpdb->prepare( + "SELECT ID FROM wp_posts p + INNER JOIN wp_postmeta pm ON p.ID = pm.post_id + WHERE post_type IN ({$post_types_placeholders}) AND meta_key = '_customer_user' AND meta_value = 1", // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared + $post_types + ) + ); + + // Cache the list of orders placed by the user ID 1 as to large stores this query can be slow. + set_transient( 'wc_update_350_admin_orders', $admin_orders, DAY_IN_SECONDS ); + } + + if ( ! empty( $admin_orders ) ) { + $admin_orders_sql .= ' AND ID NOT IN (' . implode( ', ', $admin_orders ) . ') '; + } + + // Query to get a batch of orders IDs to change. + $query = $wpdb->prepare( + // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared + "SELECT ID FROM {$wpdb->posts} + WHERE post_author = 1 AND post_type IN ({$post_types_placeholders}) $admin_orders_sql + LIMIT 1000", + $post_types + // phpcs:enable + ); + + while ( true ) { + // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared + $orders_to_update = $wpdb->get_col( $query ); + + // Exit loop if no more orders to update. + if ( ! $orders_to_update ) { + break; + } + + $orders_meta_data = $wpdb->get_results( + // phpcs:ignore WordPress.WP.PreparedSQL.NotPrepared + "SELECT post_id, meta_value as customer_id FROM {$wpdb->postmeta} WHERE meta_key = '_customer_user' AND post_id IN (" . implode( ', ', $orders_to_update ) . ')' + ); + + // Exit loop if no _customer_user metas exist for the list of orders to update. + if ( ! $orders_meta_data ) { + break; + } + + // Update post_author for a batch of orders. + foreach ( $orders_meta_data as $order_meta ) { + // Stop update execution and re-enqueue it if near memory limit. + if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) { + return true; + } + + $wpdb->update( $wpdb->posts, array( 'post_author' => $order_meta->customer_id ), array( 'ID' => $order_meta->post_id ) ); + } + } + + // Set post_author to 0 instead of 1 on all shop_order_refunds. + while ( true ) { + // Stop update execution and re-enqueue it if near memory limit. + if ( $updater instanceof WC_Background_Updater && $updater->is_memory_exceeded() ) { + return true; + } + + $updated_rows = $wpdb->query( "UPDATE {$wpdb->posts} SET post_author = 0 WHERE post_type = 'shop_order_refund' LIMIT 1000" ); + + if ( ! $updated_rows ) { + break; + } + } + } + + wp_cache_flush(); +} + +/** + * Update DB Version. + */ +function wc_update_350_db_version() { + WC_Install::update_db_version( '3.5.0' ); +} diff --git a/includes/wc-user-functions.php b/includes/wc-user-functions.php index a302ce008bf..b8f62fa7bc8 100644 --- a/includes/wc-user-functions.php +++ b/includes/wc-user-functions.php @@ -219,11 +219,15 @@ function wc_customer_bought_product( $customer_email, $user_id, $product_id ) { $result = get_transient( $transient_name ); if ( false === $result ) { - $customer_data = array( $user_id ); + $customer_data = array(); if ( $user_id ) { $user = get_user_by( 'id', $user_id ); + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '<' ) ) { + $customer_data[] = $user_id; + } + if ( isset( $user->user_email ) ) { $customer_data[] = $user->user_email; } @@ -240,19 +244,31 @@ function wc_customer_bought_product( $customer_email, $user_id, $product_id ) { return false; } - $result = $wpdb->get_col( - " - SELECT im.meta_value FROM {$wpdb->posts} AS p - INNER JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id - INNER JOIN {$wpdb->prefix}woocommerce_order_items AS i ON p.ID = i.order_id - INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS im ON i.order_item_id = im.order_item_id - WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' ) - AND pm.meta_key IN ( '_billing_email', '_customer_user' ) - AND im.meta_key IN ( '_product_id', '_variation_id' ) - AND im.meta_value != 0 - AND pm.meta_value IN ( '" . implode( "','", $customer_data ) . "' ) - " - ); // WPCS: unprepared SQL ok. + if ( version_compare( get_option( 'woocommerce_db_version' ), '3.5.0', '>=' ) && $user_id ) { + // Since WC 3.5 wp_posts.post_author is used to store the ID of the customer who placed an order. + $query = "SELECT im.meta_value FROM {$wpdb->posts} AS p + INNER JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id + INNER JOIN {$wpdb->prefix}woocommerce_order_items AS i ON p.ID = i.order_id + INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS im ON i.order_item_id = im.order_item_id + WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' ) + AND p.post_author = {$user_id} + AND pm.meta_key = '_billing_email' + AND im.meta_key IN ( '_product_id', '_variation_id' ) + AND im.meta_value != 0 + AND pm.meta_value IN ( '" . implode( "','", $customer_data ) . "' )"; + } else { + $query = "SELECT im.meta_value FROM {$wpdb->posts} AS p + INNER JOIN {$wpdb->postmeta} AS pm ON p.ID = pm.post_id + INNER JOIN {$wpdb->prefix}woocommerce_order_items AS i ON p.ID = i.order_id + INNER JOIN {$wpdb->prefix}woocommerce_order_itemmeta AS im ON i.order_item_id = im.order_item_id + WHERE p.post_status IN ( 'wc-" . implode( "','wc-", $statuses ) . "' ) + AND pm.meta_key IN ( '_billing_email', '_customer_user' ) + AND im.meta_key IN ( '_product_id', '_variation_id' ) + AND im.meta_value != 0 + AND pm.meta_value IN ( '" . implode( "','", $customer_data ) . "' )"; + } + + $result = $wpdb->get_col( $query ); // WPCS: unprepared SQL ok. $result = array_map( 'absint', $result ); set_transient( $transient_name, $result, DAY_IN_SECONDS * 30 ); @@ -495,7 +511,7 @@ function wc_get_customer_order_count( $user_id ) { } /** - * Reset _customer_user on orders when a user is deleted. + * Reset customer ID on orders when a user is deleted. * * @param int $user_id User ID. */ @@ -508,6 +524,22 @@ function wc_reset_order_customer_id_on_deleted_user( $user_id ) { 'meta_value' => $user_id, ) ); // WPCS: slow query ok. + + $post_types = (array) apply_filters( 'woocommerce_reset_order_customer_id_post_types', array( 'shop_order' ) ); + $post_types_placeholders = implode( ', ', array_fill( 0, count( $post_types ), '%s' ) ); + $query_args = array_merge( $post_types, array( $user_id ) ); + + // Since WC 3.5, the customer ID is stored both in the _customer_user postmeta and in the post_author field. + // In future versions of WC, the plan is to use only post_author and stop using _customer_user, but for now + // we have to update both places. + $wpdb->query( + // phpcs:disable WordPress.WP.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.UnfinishedPrepare + $wpdb->prepare( + "UPDATE {$wpdb->posts} SET `post_author` = 0 WHERE post_type IN ({$post_types_placeholders}) AND post_author = %d", + $query_args + ) + // phpcs:enable + ); } add_action( 'deleted_user', 'wc_reset_order_customer_id_on_deleted_user' );