diff --git a/includes/class-wc-data-store.php b/includes/class-wc-data-store.php index 8c7a26657d8..a74c8efeb4e 100644 --- a/includes/class-wc-data-store.php +++ b/includes/class-wc-data-store.php @@ -30,6 +30,7 @@ class WC_Data_Store { private $stores = array( 'coupon' => 'WC_Coupon_Data_Store_CPT', 'customer' => 'WC_Customer_Data_Store', + 'customer-download' => 'WC_Customer_Download_Data_Store_Session', 'customer-session' => 'WC_Customer_Data_Store_Session', 'order' => 'WC_Order_Data_Store_CPT', 'order-refund' => 'WC_Order_Refund_Data_Store_CPT', diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index d6eeba61d9d..148f7baa92a 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -68,6 +68,8 @@ class WC_Order extends WC_Abstract_Order { '_date_completed', '_date_paid', '_payment_tokens', + '_billing_address_index', + '_shipping_address_index' ); /** diff --git a/includes/class-wc-payment-tokens.php b/includes/class-wc-payment-tokens.php index c0e6178c0a3..c933019e6c5 100644 --- a/includes/class-wc-payment-tokens.php +++ b/includes/class-wc-payment-tokens.php @@ -105,8 +105,8 @@ class WC_Payment_Tokens { return array(); } - // @todo Order Data Store should handle this one. - $token_ids = get_post_meta( $order_id, '_payment_tokens', true ); + $token_ids = $order->get_payment_tokens(); + if ( empty( $token_ids ) ) { return array(); } diff --git a/includes/data-stores/class-wc-customer-download-data-store.php b/includes/data-stores/class-wc-customer-download-data-store.php new file mode 100644 index 00000000000..a865350dd29 --- /dev/null +++ b/includes/data-stores/class-wc-customer-download-data-store.php @@ -0,0 +1,83 @@ + 0, + 'product' => false, + 'order' => false, + 'qty' => 1, + ) ); + + extract( $args ); + + if ( ! $download_id || ! $product || ! $order ) { + return false; + } + + $data = array( + 'download_id' => $download_id, + 'product_id' => $product->get_id(), + 'user_id' => $order->get_customer_id(), + 'user_email' => $order->get_billing_email(), + 'order_id' => $order->get_id(), + 'order_key' => $order->get_order_key(), + 'downloads_remaining' => 0 > $product->get_download_limit() ? '' : $product->get_download_limit() * $qty, + 'access_granted' => current_time( 'mysql' ), + 'download_count' => 0, + ); + + $format = array( + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + '%s', + '%d', + ); + + $expiry = $product->get_download_expiry(); + + if ( $expiry > 0 ) { + $order_completed_date = date_i18n( "Y-m-d", $order->get_date_completed() ); + $expiry = date_i18n( "Y-m-d", strtotime( $order_completed_date . ' + ' . $expiry . ' DAY' ) ); + $data['access_expires'] = $expiry; + $format[] = '%s'; + } + + $result = $wpdb->insert( + $wpdb->prefix . 'woocommerce_downloadable_product_permissions', + apply_filters( 'woocommerce_downloadable_file_permission_data', $data ), + apply_filters( 'woocommerce_downloadable_file_permission_format', $format, $data ) + ); + + do_action( 'woocommerce_grant_product_download_access', $data ); + + return $result ? $wpdb->insert_id : false; + } +} 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 26221350054..a9cda33f8a9 100644 --- a/includes/data-stores/class-wc-order-data-store-cpt.php +++ b/includes/data-stores/class-wc-order-data-store-cpt.php @@ -128,6 +128,7 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement if ( '' !== $value ? update_post_meta( $order->get_id(), $meta_key, $value ) : delete_post_meta( $order->get_id(), $meta_key ) ) { $updated_props[] = $prop; + $updated_props[] = 'billing'; } } @@ -152,11 +153,20 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement if ( '' !== $value ? update_post_meta( $order->get_id(), $meta_key, $value ) : delete_post_meta( $order->get_id(), $meta_key ) ) { $updated_props[] = $prop; + $updated_props[] = 'shipping'; } } parent::update_post_meta( $order ); + // If address changed, store concatinated version to make searches faster. + if ( in_array( 'billing', $updated_props ) || ! metadata_exists( 'post', $order->get_id(), '_billing_address_index' ) ) { + update_post_meta( $order->get_id(), '_billing_address_index', implode( ' ', $order->get_address( 'billing' ) ) ); + } + if ( in_array( 'shipping', $updated_props ) || ! metadata_exists( 'post', $order->get_id(), '_shipping_address_index' ) ) { + update_post_meta( $order->get_id(), '_shipping_address_index', implode( ' ', $order->get_address( 'shipping' ) ) ); + } + // If customer changed, update any downloadable permissions. if ( in_array( 'customer_user', $updated_props ) || in_array( 'billing_email', $updated_props ) ) { global $wpdb; @@ -241,4 +251,253 @@ class WC_Order_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT implement return abs( $total ); } + + /** + * Finds an Order ID based on an order key. + * + * @param string $order_key An order key has generated by + * @return int The ID of an order, or 0 if the order could not be found + */ + public function get_order_id_by_order_key() { + global $wpdb; + return $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_order_key' AND meta_value = %s", $order_key ) ); + } + + /** + * Return count of orders with type. + * @param string $type + * @return int + */ + public function get_order_count( $type ) { + global $wpdb; + return absint( $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( * ) FROM {$wpdb->posts} WHERE post_type = 'shop_order' AND post_status = %s", $type ) ) ); + } + + /** + * Get all orders matching the passed in args. + * + * @see wc_get_orders() + * @param array $args + * @return array of orders + */ + public function get_orders( $args = array() ) { + /** + * Generate WP_Query args. This logic will change if orders are moved to + * custom tables in the future. + */ + $wp_query_args = array( + 'post_type' => $args['type'] ? $args['type'] : 'shop_order', + 'post_status' => $args['status'], + 'posts_per_page' => $args['limit'], + 'meta_query' => array(), + 'fields' => 'ids', + 'orderby' => $args['orderby'], + 'order' => $args['order'], + ); + + if ( ! is_null( $args['parent'] ) ) { + $wp_query_args['post_parent'] = absint( $args['parent'] ); + } + + if ( ! is_null( $args['offset'] ) ) { + $wp_query_args['offset'] = absint( $args['offset'] ); + } else { + $wp_query_args['paged'] = absint( $args['page'] ); + } + + if ( ! empty( $args['customer'] ) ) { + $values = is_array( $args['customer'] ) ? $args['customer'] : array( $args['customer'] ); + $wp_query_args['meta_query'][] = $this->get_orders_generate_customer_meta_query( $values ); + } + + if ( ! empty( $args['exclude'] ) ) { + $wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] ); + } + + if ( ! $args['paginate'] ) { + $wp_query_args['no_found_rows'] = true; + } + + // Get results. + $orders = new WP_Query( $wp_query_args ); + + if ( 'objects' === $args['return'] ) { + $return = array_map( 'wc_get_order', $orders->posts ); + } else { + $return = $orders->posts; + } + + if ( $args['paginate'] ) { + return (object) array( + 'orders' => $return, + 'total' => $orders->found_posts, + 'max_num_pages' => $orders->max_num_pages, + ); + } else { + return $return; + } + } + + /** + * Generate meta query for wc_get_orders. + * @param array $values + * @param string $relation + * @return array + */ + private function get_orders_generate_customer_meta_query( $values, $relation = 'or' ) { + $meta_query = array( + 'relation' => strtoupper( $relation ), + 'customer_emails' => array( + 'key' => '_billing_email', + 'value' => array(), + 'compare' => 'IN', + ), + 'customer_ids' => array( + 'key' => '_customer_user', + 'value' => array(), + 'compare' => 'IN', + ), + ); + foreach ( $values as $value ) { + if ( is_array( $value ) ) { + $meta_query[] = $this->get_orders_generate_customer_meta_query( $value, 'and' ); + } elseif ( is_email( $value ) ) { + $meta_query['customer_emails']['value'][] = sanitize_email( $value ); + } else { + $meta_query['customer_ids']['value'][] = strval( absint( $value ) ); + } + } + + if ( empty( $meta_query['customer_emails']['value'] ) ) { + unset( $meta_query['customer_emails'] ); + unset( $meta_query['relation'] ); + } + + if ( empty( $meta_query['customer_ids']['value'] ) ) { + unset( $meta_query['customer_ids'] ); + unset( $meta_query['relation'] ); + } + + return $meta_query; + } + + /** + * Get unpaid orders after a certain date, + * @param int timestamp $date + * @return array + */ + public function get_unpaid_orders( $date ) { + global $wpdb; + + $unpaid_orders = $wpdb->get_col( $wpdb->prepare( " + SELECT posts.ID + FROM {$wpdb->posts} AS posts + WHERE posts.post_type IN ('" . implode( "','", wc_get_order_types() ) . "') + AND posts.post_status = 'wc-pending' + AND posts.post_modified < %s + ", date( "Y-m-d H:i:s", absint( $date ) ) ) ); + + return $unpaid_orders; + } + + /** + * Search order data for a term and return ids. + * @param string $term + * @return array of ids + */ + public function search_orders( $term ) { + global $wpdb; + + /** + * Searches on meta data can be slow - this lets you choose what fields to search. + * 2.7.0 added _billing_address and _shipping_address meta which contains all address data to make this faster. + * This however won't work on older orders unless updated, so search a few others (expand this using the filter if needed). + * @var array + */ + $search_fields = array_map( 'wc_clean', apply_filters( 'woocommerce_shop_order_search_fields', array( + '_billing_address_index', + '_shipping_address_index', + '_billing_last_name', + '_billing_email', + ) ) ); + $order_ids = array(); + + if ( is_numeric( $term ) ) { + $order_ids[] = absint( $term ); + } + + if ( ! empty( $search_fields ) ) { + $order_ids = array_unique( array_merge( + $order_ids, + $wpdb->get_col( + $wpdb->prepare( "SELECT DISTINCT p1.post_id FROM {$wpdb->postmeta} p1 WHERE p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "') AND p1.meta_value LIKE '%%%s%%';", wc_clean( $term ) ) + ), + $wpdb->get_col( + $wpdb->prepare( " + SELECT order_id + FROM {$wpdb->prefix}woocommerce_order_items as order_items + WHERE order_item_name LIKE '%%%s%%' + ", + $term + ) + ) + ) ); + } + + return $order_ids; + } + + /** + * Gets information about whether permissions were generated yet. + * @param WC_Order $order + * @return boolet + */ + public function get_download_permissions_granted( $order ) { + return wc_string_to_bool( get_post_meta( $order->get_id(), '_download_permissions_granted', true ) ); + } + + /** + * Stores information about whether permissions were generated yet. + * @param WC_Order $order + * @param bool $set + */ + public function set_download_permissions_granted( $order, $set ) { + update_post_meta( $order->get_id(), '_download_permissions_granted', wc_bool_to_string( $set ) ); + } + + /** + * Gets information about whether sales were recorded. + * @param WC_Order $order + * @return boolet + */ + public function get_recorded_sales( $order ) { + return wc_string_to_bool( get_post_meta( $order->get_id(), '_recorded_sales', true ) ); + } + + /** + * Stores information about whether sales were recorded. + * @param WC_Order $order + * @param bool $set + */ + public function set_recorded_sales( $order, $set ) { + update_post_meta( $order->get_id(), '_recorded_sales', wc_bool_to_string( $set ) ); + } + + /** + * Gets information about whether coupon counts were updated. + * @param WC_Order $order + * @return boolet + */ + public function get_recorded_coupon_usage_counts( $order ) { + return wc_string_to_bool( get_post_meta( $order->get_id(), '_recorded_sales', true ) ); + } + + /** + * Stores information about whether coupon counts were updated. + * @param WC_Order $order + * @param bool $set + */ + public function set_recorded_coupon_usage_counts( $order, $set ) { + update_post_meta( $order->get_id(), '_recorded_sales', wc_bool_to_string( $set ) ); + } } diff --git a/includes/data-stores/interfaces/wc-customer-download-data-store-interface.php b/includes/data-stores/interfaces/wc-customer-download-data-store-interface.php new file mode 100644 index 00000000000..6c402281da2 --- /dev/null +++ b/includes/data-stores/interfaces/wc-customer-download-data-store-interface.php @@ -0,0 +1,14 @@ + $args['type'] ? $args['type'] : 'shop_order', - 'post_status' => $args['status'], - 'posts_per_page' => $args['limit'], - 'meta_query' => array(), - 'fields' => 'ids', - 'orderby' => $args['orderby'], - 'order' => $args['order'], - ); - - if ( ! is_null( $args['parent'] ) ) { - $wp_query_args['post_parent'] = absint( $args['parent'] ); - } - - if ( ! is_null( $args['offset'] ) ) { - $wp_query_args['offset'] = absint( $args['offset'] ); - } else { - $wp_query_args['paged'] = absint( $args['page'] ); - } - - if ( ! empty( $args['customer'] ) ) { - $values = is_array( $args['customer'] ) ? $args['customer'] : array( $args['customer'] ); - $wp_query_args['meta_query'][] = _wc_get_orders_generate_customer_meta_query( $values ); - } - - if ( ! empty( $args['exclude'] ) ) { - $wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] ); - } - - if ( ! $args['paginate'] ) { - $wp_query_args['no_found_rows'] = true; - } - - // Get results. - $orders = new WP_Query( $wp_query_args ); - - if ( 'objects' === $args['return'] ) { - $return = array_map( 'wc_get_order', $orders->posts ); - } else { - $return = $orders->posts; - } - - if ( $args['paginate'] ) { - return (object) array( - 'orders' => $return, - 'total' => $orders->found_posts, - 'max_num_pages' => $orders->max_num_pages, - ); - } else { - return $return; - } + return WC_Data_Store::load( 'order' )->get_orders( $args ); } /** - * Generate meta query for wc_get_orders. Used internally only. - * @since 2.6.0 - * @param array $values - * @param string $relation - * @return array + * Main function for returning orders, uses the WC_Order_Factory class. + * + * @since 2.2 + * @param mixed $the_order Post object or post ID of the order. + * @return WC_Order|WC_Refund */ -function _wc_get_orders_generate_customer_meta_query( $values, $relation = 'or' ) { - $meta_query = array( - 'relation' => strtoupper( $relation ), - 'customer_emails' => array( - 'key' => '_billing_email', - 'value' => array(), - 'compare' => 'IN', - ), - 'customer_ids' => array( - 'key' => '_customer_user', - 'value' => array(), - 'compare' => 'IN', - ), - ); - foreach ( $values as $value ) { - if ( is_array( $value ) ) { - $meta_query[] = _wc_get_orders_generate_customer_meta_query( $value, 'and' ); - } elseif ( is_email( $value ) ) { - $meta_query['customer_emails']['value'][] = sanitize_email( $value ); - } else { - $meta_query['customer_ids']['value'][] = strval( absint( $value ) ); - } +function wc_get_order( $the_order = false ) { + if ( ! did_action( 'woocommerce_init' ) ) { + _doing_it_wrong( __FUNCTION__, __( 'wc_get_order should not be called before the woocommerce_init action.', 'woocommerce' ), '2.5' ); + return false; } - - if ( empty( $meta_query['customer_emails']['value'] ) ) { - unset( $meta_query['customer_emails'] ); - unset( $meta_query['relation'] ); - } - - if ( empty( $meta_query['customer_ids']['value'] ) ) { - unset( $meta_query['customer_ids'] ); - unset( $meta_query['relation'] ); - } - - return $meta_query; + return WC()->order_factory->get_order( $the_order ); } /** @@ -217,21 +134,6 @@ function wc_get_is_paid_statuses() { return apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ); } -/** - * Main function for returning orders, uses the WC_Order_Factory class. - * - * @since 2.2 - * @param mixed $the_order Post object or post ID of the order. - * @return WC_Order|WC_Refund - */ -function wc_get_order( $the_order = false ) { - if ( ! did_action( 'woocommerce_init' ) ) { - _doing_it_wrong( __FUNCTION__, __( 'wc_get_order should not be called before the woocommerce_init action.', 'woocommerce' ), '2.5' ); - return false; - } - return WC()->order_factory->get_order( $the_order ); -} - /** * Get the nice name for an order status. * @@ -243,24 +145,18 @@ function wc_get_order_status_name( $status ) { $statuses = wc_get_order_statuses(); $status = 'wc-' === substr( $status, 0, 3 ) ? substr( $status, 3 ) : $status; $status = isset( $statuses[ 'wc-' . $status ] ) ? $statuses[ 'wc-' . $status ] : $status; - return $status; } /** * Finds an Order ID based on an order key. * - * @access public * @param string $order_key An order key has generated by * @return int The ID of an order, or 0 if the order could not be found */ function wc_get_order_id_by_order_key( $order_key ) { - global $wpdb; - - // Faster than get_posts() - $order_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_order_key' AND meta_value = %s", $order_key ) ); - - return $order_id; + $data_store = WC_Data_Store::load( 'order' ); + return $data_store->get_order_id_by_order_key(); } /** @@ -406,272 +302,6 @@ function wc_register_order_type( $type, $args = array() ) { return true; } -/** - * Grant downloadable product access to the file identified by $download_id. - * - * @access public - * @param string $download_id file identifier - * @param int $product_id product identifier - * @param WC_Order $order the order - * @param int $qty purchased - * @return int|bool insert id or false on failure - */ -function wc_downloadable_file_permission( $download_id, $product_id, $order, $qty = 1 ) { - global $wpdb; - - $user_email = sanitize_email( $order->get_billing_email() ); - $limit = trim( get_post_meta( $product_id, '_download_limit', true ) ); - $expiry = trim( get_post_meta( $product_id, '_download_expiry', true ) ); - - $limit = empty( $limit ) ? '' : absint( $limit ) * $qty; - - // Default value is NULL in the table schema - $expiry = empty( $expiry ) ? null : absint( $expiry ); - - if ( $expiry ) { - $order_completed_date = date_i18n( "Y-m-d", strtotime( $order->completed_date ) ); - $expiry = date_i18n( "Y-m-d", strtotime( $order_completed_date . ' + ' . $expiry . ' DAY' ) ); - } - - $data = apply_filters( 'woocommerce_downloadable_file_permission_data', array( - 'download_id' => $download_id, - 'product_id' => $product_id, - 'user_id' => absint( $order->get_user_id() ), - 'user_email' => $user_email, - 'order_id' => $order->get_id(), - 'order_key' => $order->get_order_key(), - 'downloads_remaining' => $limit, - 'access_granted' => current_time( 'mysql' ), - 'download_count' => 0, - )); - - $format = apply_filters( 'woocommerce_downloadable_file_permission_format', array( - '%s', - '%s', - '%s', - '%s', - '%s', - '%s', - '%s', - '%s', - '%d', - ), $data); - - if ( ! is_null( $expiry ) ) { - $data['access_expires'] = $expiry; - $format[] = '%s'; - } - - // Downloadable product - give access to the customer - $result = $wpdb->insert( $wpdb->prefix . 'woocommerce_downloadable_product_permissions', - $data, - $format - ); - - do_action( 'woocommerce_grant_product_download_access', $data ); - - return $result ? $wpdb->insert_id : false; -} - -/** - * Order Status completed - GIVE DOWNLOADABLE PRODUCT ACCESS TO CUSTOMER. - * - * @access public - * @param int $order_id - */ -function wc_downloadable_product_permissions( $order_id ) { - if ( get_post_meta( $order_id, '_download_permissions_granted', true ) == 1 ) { - return; // Only do this once - } - - $order = wc_get_order( $order_id ); - - if ( $order && $order->has_status( 'processing' ) && get_option( 'woocommerce_downloads_grant_access_after_payment' ) == 'no' ) { - return; - } - - if ( sizeof( $order->get_items() ) > 0 ) { - foreach ( $order->get_items() as $item ) { - $_product = $item->get_product(); - - if ( $_product && $_product->exists() && $_product->is_downloadable() ) { - $downloads = $_product->get_downloads(); - - foreach ( array_keys( $downloads ) as $download_id ) { - wc_downloadable_file_permission( $download_id, $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'], $order, $item['qty'] ); - } - } - } - } - - update_post_meta( $order_id, '_download_permissions_granted', 1 ); - - do_action( 'woocommerce_grant_product_download_permissions', $order_id ); -} -add_action( 'woocommerce_order_status_completed', 'wc_downloadable_product_permissions' ); -add_action( 'woocommerce_order_status_processing', 'wc_downloadable_product_permissions' ); - - -/** - * Add a item to an order (for example a line item). - * - * @access public - * @param int $order_id - * @return mixed - */ -function wc_add_order_item( $order_id, $item ) { - global $wpdb; - - $order_id = absint( $order_id ); - - if ( ! $order_id ) - return false; - - $defaults = array( - 'order_item_name' => '', - 'order_item_type' => 'line_item', - ); - - $item = wp_parse_args( $item, $defaults ); - - $wpdb->insert( - $wpdb->prefix . "woocommerce_order_items", - array( - 'order_item_name' => $item['order_item_name'], - 'order_item_type' => $item['order_item_type'], - 'order_id' => $order_id, - ), - array( - '%s', - '%s', - '%d', - ) - ); - - $item_id = absint( $wpdb->insert_id ); - - do_action( 'woocommerce_new_order_item', $item_id, $item, $order_id ); - - return $item_id; -} - -/** - * Update an item for an order. - * - * @since 2.2 - * @param int $item_id - * @param array $args either `order_item_type` or `order_item_name` - * @return bool true if successfully updated, false otherwise - */ -function wc_update_order_item( $item_id, $args ) { - global $wpdb; - - $update = $wpdb->update( $wpdb->prefix . 'woocommerce_order_items', $args, array( 'order_item_id' => $item_id ) ); - - if ( false === $update ) { - return false; - } - - do_action( 'woocommerce_update_order_item', $item_id, $args ); - - return true; -} - -/** - * Delete an item from the order it belongs to based on item id. - * - * @access public - * @param int $item_id - * @return bool - */ -function wc_delete_order_item( $item_id ) { - global $wpdb; - - $item_id = absint( $item_id ); - - if ( ! $item_id ) - return false; - - do_action( 'woocommerce_before_delete_order_item', $item_id ); - - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d", $item_id ) ); - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d", $item_id ) ); - - do_action( 'woocommerce_delete_order_item', $item_id ); - - return true; -} - -/** - * WooCommerce Order Item Meta API - Update term meta. - * - * @access public - * @param mixed $item_id - * @param mixed $meta_key - * @param mixed $meta_value - * @param string $prev_value (default: '') - * @return bool - */ -function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) { - if ( update_metadata( 'order_item', $item_id, $meta_key, $meta_value, $prev_value ) ) { - $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; - wp_cache_delete( $cache_key, 'orders' ); - return true; - } - return false; -} - -/** - * WooCommerce Order Item Meta API - Add term meta. - * - * @access public - * @param mixed $item_id - * @param mixed $meta_key - * @param mixed $meta_value - * @param bool $unique (default: false) - * @return int New row ID or 0 - */ -function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = false ) { - if ( $meta_id = add_metadata( 'order_item', $item_id, $meta_key, $meta_value, $unique ) ) { - $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; - wp_cache_delete( $cache_key, 'orders' ); - return $meta_id; - } - return 0; -} - -/** - * WooCommerce Order Item Meta API - Delete term meta. - * - * @access public - * @param mixed $item_id - * @param mixed $meta_key - * @param string $meta_value (default: '') - * @param bool $delete_all (default: false) - * @return bool - */ -function wc_delete_order_item_meta( $item_id, $meta_key, $meta_value = '', $delete_all = false ) { - if ( delete_metadata( 'order_item', $item_id, $meta_key, $meta_value, $delete_all ) ) { - $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; - wp_cache_delete( $cache_key, 'orders' ); - return true; - } - return false; -} - -/** - * WooCommerce Order Item Meta API - Get term meta. - * - * @access public - * @param mixed $item_id - * @param mixed $key - * @param bool $single (default: true) - * @return mixed - */ -function wc_get_order_item_meta( $item_id, $key, $single = true ) { - return get_metadata( 'order_item', $item_id, $key, $single ); -} - /** * Return the count of processing orders. * @@ -685,15 +315,12 @@ function wc_processing_order_count() { /** * Return the orders count of a specific order status. * - * @access public * @param string $status * @return int */ function wc_orders_count( $status ) { - global $wpdb; - - $count = 0; - $status = 'wc-' . $status; + $count = 0; + $status = 'wc-' . $status; $order_statuses = array_keys( wc_get_order_statuses() ); if ( ! in_array( $status, $order_statuses ) ) { @@ -708,8 +335,10 @@ function wc_orders_count( $status ) { } foreach ( wc_get_order_types( 'order-count' ) as $type ) { - $query = "SELECT COUNT( * ) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s"; - $count += $wpdb->get_var( $wpdb->prepare( $query, $type, $status ) ); + $data_store = WC_Data_Store::load( 'shop_order' === $type ? 'order' : $type ); + if ( $data_store ) { + $count += $data_store->get_order_count( $status ); + } } wp_cache_set( $cache_key, $count, 'counts' ); @@ -717,6 +346,64 @@ function wc_orders_count( $status ) { return $count; } +/** + * Grant downloadable product access to the file identified by $download_id. + * + * @param string $download_id file identifier + * @param int|WC_Product $product + * @param WC_Order $order the order + * @param int $qty purchased + * @return int|bool insert id or false on failure + */ +function wc_downloadable_file_permission( $download_id, $product, $order, $qty = 1 ) { + if ( is_numeric( $product ) ) { + $product = wc_get_product( $product ); + } + $data_store = WC_Data_Store::load( 'customer-download' ); + return $data_store->create( array( + 'download_id' => $download_id, + 'product' => $product, + 'order' => $order, + 'qty' => $qty + ) ); +} + +/** + * Order Status completed - GIVE DOWNLOADABLE PRODUCT ACCESS TO CUSTOMER. + * + * @param int $order_id + */ +function wc_downloadable_product_permissions( $order_id ) { + $order = wc_get_order( $order_id ); + + if ( ! $order || $order->get_data_store()->get_download_permissions_granted( $order ) ) { + return; + } + + if ( $order->has_status( 'processing' ) && 'no' === get_option( 'woocommerce_downloads_grant_access_after_payment' ) ) { + return; + } + + if ( sizeof( $order->get_items() ) > 0 ) { + foreach ( $order->get_items() as $item ) { + $product = $item->get_product(); + + if ( $product && $product->exists() && $product->is_downloadable() ) { + $downloads = $product->get_downloads(); + + foreach ( array_keys( $downloads ) as $download_id ) { + wc_downloadable_file_permission( $download_id, $product, $order, $item->get_quantity() ); + } + } + } + } + + $order->get_data_store()->set_download_permissions_granted( $order, true ); + do_action( 'woocommerce_grant_product_download_permissions', $order_id ); +} +add_action( 'woocommerce_order_status_completed', 'wc_downloadable_product_permissions' ); +add_action( 'woocommerce_order_status_processing', 'wc_downloadable_product_permissions' ); + /** * Clear all transients cache for order data. * @@ -863,10 +550,7 @@ function wc_create_refund( $args = array() ) { */ function wc_get_tax_class_by_tax_id( $tax_id ) { global $wpdb; - - $tax_class = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) ); - - return wc_clean( $tax_class ); + return $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) ); } /** @@ -920,79 +604,17 @@ function wc_order_fully_refunded( $order_id ) { add_action( 'woocommerce_order_status_refunded', 'wc_order_fully_refunded' ); /** - * Search in orders. + * Search orders. * * @since 2.6.0 * @param string $term Term to search. * @return array List of orders ID. */ function wc_order_search( $term ) { - global $wpdb; - - $term = str_replace( 'Order #', '', wc_clean( $term ) ); - $post_ids = array(); - - // Search fields. - $search_fields = array_map( 'wc_clean', apply_filters( 'woocommerce_shop_order_search_fields', array( - '_order_key', - '_billing_company', - '_billing_address_1', - '_billing_address_2', - '_billing_city', - '_billing_postcode', - '_billing_country', - '_billing_state', - '_billing_email', - '_billing_phone', - '_shipping_address_1', - '_shipping_address_2', - '_shipping_city', - '_shipping_postcode', - '_shipping_country', - '_shipping_state', - ) ) ); - - // Search orders. - if ( is_numeric( $term ) ) { - $post_ids = array_unique( array_merge( - $wpdb->get_col( - $wpdb->prepare( "SELECT DISTINCT p1.post_id FROM {$wpdb->postmeta} p1 WHERE p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "') AND p1.meta_value LIKE '%%%s%%';", wc_clean( $term ) ) - ), - array( absint( $term ) ) - ) ); - } elseif ( ! empty( $search_fields ) ) { - $post_ids = array_unique( array_merge( - $wpdb->get_col( - $wpdb->prepare( " - SELECT DISTINCT p1.post_id - FROM {$wpdb->postmeta} p1 - INNER JOIN {$wpdb->postmeta} p2 ON p1.post_id = p2.post_id - WHERE - ( p1.meta_key = '_billing_first_name' AND p2.meta_key = '_billing_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' ) - OR - ( p1.meta_key = '_shipping_first_name' AND p2.meta_key = '_shipping_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' ) - OR - ( p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "') AND p1.meta_value LIKE '%%%s%%' ) - ", - $term, $term, $term - ) - ), - $wpdb->get_col( - $wpdb->prepare( " - SELECT order_id - FROM {$wpdb->prefix}woocommerce_order_items as order_items - WHERE order_item_name LIKE '%%%s%%' - ", - $term - ) - ) - ) ); - } - - return $post_ids; + $data_store = WC_Data_Store::load( 'order' ); + return $data_store->search_orders( str_replace( 'Order #', '', wc_clean( $term ) ) ); } - /** * Update total sales amount for each product within a paid order. * @@ -1002,19 +624,20 @@ function wc_order_search( $term ) { function wc_update_total_sales_counts( $order_id ) { $order = wc_get_order( $order_id ); - if ( ! $order || 'yes' === get_post_meta( $order_id, '_recorded_sales', true ) ) { + if ( ! $order || $order->get_data_store()->get_recorded_sales( $order ) ) { return; } if ( sizeof( $order->get_items() ) > 0 ) { foreach ( $order->get_items() as $item ) { - if ( $item['product_id'] > 0 ) { - update_post_meta( $item['product_id'], 'total_sales', absint( get_post_meta( $item['product_id'], 'total_sales', true ) ) + absint( $item['qty'] ) ); + if ( $product = $item->get_product() ) { + $product->set_total_sales( $product->get_total_sales() + absint( $item['qty'] ) ); + $product->save(); } } } - update_post_meta( $order_id, '_recorded_sales', 'yes' ); + $order->get_data_store()->set_recorded_sales( $order, true ); /** * Called when sales for an order are recorded @@ -1034,19 +657,18 @@ add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' ) * @param int $order_id */ function wc_update_coupon_usage_counts( $order_id ) { - $order = wc_get_order( $order_id ); - $has_recorded = get_post_meta( $order_id, '_recorded_coupon_usage_counts', true ); - - if ( ! $order ) { + if ( ! $order = wc_get_order( $order_id ) ) { return; } - if ( $order->has_status( 'cancelled' ) && 'yes' === $has_recorded ) { + $has_recorded = $order->get_data_store()->get_recorded_coupon_usage_counts( $order ); + + if ( $order->has_status( 'cancelled' ) && $has_recorded ) { $action = 'reduce'; - delete_post_meta( $order_id, '_recorded_coupon_usage_counts' ); - } elseif ( ! $order->has_status( 'cancelled' ) && 'yes' !== $has_recorded ) { + $order->get_data_store()->set_recorded_coupon_usage_counts( $order, false ); + } elseif ( ! $order->has_status( 'cancelled' ) && ! $has_recorded ) { $action = 'increase'; - update_post_meta( $order_id, '_recorded_coupon_usage_counts', 'yes' ); + $order->get_data_store()->set_recorded_coupon_usage_counts( $order, true ); } else { return; } @@ -1082,38 +704,26 @@ add_action( 'woocommerce_order_status_cancelled', 'wc_update_coupon_usage_counts /** * Cancel all unpaid orders after held duration to prevent stock lock for those products. - * - * @access public */ function wc_cancel_unpaid_orders() { - global $wpdb; - $held_duration = get_option( 'woocommerce_hold_stock_minutes' ); if ( $held_duration < 1 || 'yes' !== get_option( 'woocommerce_manage_stock' ) ) { return; } - $date = date( "Y-m-d H:i:s", strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) ); - - $unpaid_orders = $wpdb->get_col( $wpdb->prepare( " - SELECT posts.ID - FROM {$wpdb->posts} AS posts - WHERE posts.post_type IN ('" . implode( "','", wc_get_order_types() ) . "') - AND posts.post_status = 'wc-pending' - AND posts.post_modified < %s - ", $date ) ); + $data_store = WC_Data_Store::load( 'order' ); + $unpaid_orders = $data_store->get_unpaid_orders( strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) ); if ( $unpaid_orders ) { foreach ( $unpaid_orders as $unpaid_order ) { $order = wc_get_order( $unpaid_order ); - if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === get_post_meta( $unpaid_order, '_created_via', true ), $order ) ) { + if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === $order->get_created_via(), $order ) ) { $order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) ); } } } - wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' ); wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' ); } diff --git a/includes/wc-order-item-functions.php b/includes/wc-order-item-functions.php new file mode 100644 index 00000000000..1516b4a8ba1 --- /dev/null +++ b/includes/wc-order-item-functions.php @@ -0,0 +1,173 @@ + '', + 'order_item_type' => 'line_item', + ); + + $item = wp_parse_args( $item, $defaults ); + + $wpdb->insert( + $wpdb->prefix . "woocommerce_order_items", + array( + 'order_item_name' => $item['order_item_name'], + 'order_item_type' => $item['order_item_type'], + 'order_id' => $order_id, + ), + array( + '%s', + '%s', + '%d', + ) + ); + + $item_id = absint( $wpdb->insert_id ); + + do_action( 'woocommerce_new_order_item', $item_id, $item, $order_id ); + + return $item_id; +} + +/** + * Update an item for an order. + * + * @since 2.2 + * @param int $item_id + * @param array $args either `order_item_type` or `order_item_name` + * @return bool true if successfully updated, false otherwise + */ +function wc_update_order_item( $item_id, $args ) { + global $wpdb; + + $update = $wpdb->update( $wpdb->prefix . 'woocommerce_order_items', $args, array( 'order_item_id' => $item_id ) ); + + if ( false === $update ) { + return false; + } + + do_action( 'woocommerce_update_order_item', $item_id, $args ); + + return true; +} + +/** + * Delete an item from the order it belongs to based on item id. + * + * @access public + * @param int $item_id + * @return bool + */ +function wc_delete_order_item( $item_id ) { + global $wpdb; + + $item_id = absint( $item_id ); + + if ( ! $item_id ) + return false; + + do_action( 'woocommerce_before_delete_order_item', $item_id ); + + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d", $item_id ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d", $item_id ) ); + + do_action( 'woocommerce_delete_order_item', $item_id ); + + return true; +} + +/** + * WooCommerce Order Item Meta API - Update term meta. + * + * @access public + * @param mixed $item_id + * @param mixed $meta_key + * @param mixed $meta_value + * @param string $prev_value (default: '') + * @return bool + */ +function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) { + if ( update_metadata( 'order_item', $item_id, $meta_key, $meta_value, $prev_value ) ) { + $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; + wp_cache_delete( $cache_key, 'orders' ); + return true; + } + return false; +} + +/** + * WooCommerce Order Item Meta API - Add term meta. + * + * @access public + * @param mixed $item_id + * @param mixed $meta_key + * @param mixed $meta_value + * @param bool $unique (default: false) + * @return int New row ID or 0 + */ +function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = false ) { + if ( $meta_id = add_metadata( 'order_item', $item_id, $meta_key, $meta_value, $unique ) ) { + $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; + wp_cache_delete( $cache_key, 'orders' ); + return $meta_id; + } + return 0; +} + +/** + * WooCommerce Order Item Meta API - Delete term meta. + * + * @access public + * @param mixed $item_id + * @param mixed $meta_key + * @param string $meta_value (default: '') + * @param bool $delete_all (default: false) + * @return bool + */ +function wc_delete_order_item_meta( $item_id, $meta_key, $meta_value = '', $delete_all = false ) { + if ( delete_metadata( 'order_item', $item_id, $meta_key, $meta_value, $delete_all ) ) { + $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id; + wp_cache_delete( $cache_key, 'orders' ); + return true; + } + return false; +} + +/** + * WooCommerce Order Item Meta API - Get term meta. + * + * @access public + * @param mixed $item_id + * @param mixed $key + * @param bool $single (default: true) + * @return mixed + */ +function wc_get_order_item_meta( $item_id, $key, $single = true ) { + return get_metadata( 'order_item', $item_id, $key, $single ); +} diff --git a/woocommerce.php b/woocommerce.php index cfdd4b36736..51012477291 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -286,16 +286,19 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/class-wc-https.php' ); // https Helper include_once( WC_ABSPATH . 'includes/class-wc-data-store.php' ); // WC_Data_Store for CRUD + include_once( WC_ABSPATH . 'includes/data-stores/interfaces/interface-wc-object-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/interface-wc-coupon-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-product-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-product-variable-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-customer-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-customer-download-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/class-wc-payment-token-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/class-wc-shipping-zone-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-abstract-order-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-order-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-order-refund-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-coupon-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-data-store-cpt.php' ); @@ -305,6 +308,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/data-stores/class-wc-payment-token-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store-session.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-download-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-shipping-zone-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/abstract-wc-order-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-order-data-store-cpt.php' );