diff --git a/includes/abstracts/abstract-wc-legacy-order.php b/includes/abstracts/abstract-wc-legacy-order.php new file mode 100644 index 00000000000..8fe96d3435a --- /dev/null +++ b/includes/abstracts/abstract-wc-legacy-order.php @@ -0,0 +1,595 @@ +get_item( $item ); + } + if ( ! is_object( $item ) || ! $item->is_type( 'line_item' ) ) { + return false; + } + if ( ! $this->get_id() ) { + $this->save(); // Order must exist + } + + // BW compatibility with old args + if ( isset( $args['totals'] ) ) { + foreach ( $args['totals'] as $key => $value ) { + if ( 'tax' === $key ) { + $args['total_tax'] = $value; + } elseif ( 'tax_data' === $key ) { + $args['taxes'] = $value; + } else { + $args[ $key ] = $value; + } + } + } + + // Handly qty if set + if ( isset( $args['qty'] ) ) { + if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) { + $item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ), true ); + } + $args['subtotal'] = $args['subtotal'] ? $args['subtotal'] : $product->get_price_excluding_tax( $args['qty'] ); + $args['total'] = $args['total'] ? $args['total'] : $product->get_price_excluding_tax( $args['qty'] ); + } + + $item->set_order_id( $this->get_id() ); + $item->set_all( $args ); + $item->save(); + do_action( 'woocommerce_order_edit_product', $this->get_id(), $item->get_id(), $args, $product ); + + return $item->get_id(); + } + + /** + * Update coupon for order. Note this does not update order totals. + * @param object|int $item + * @param array $args + * @return int updated order item ID + */ + public function update_coupon( $item, $args ) { + _deprecated_function( 'WC_Order::update_coupon', '2.7', 'Interact with WC_Order_Item_Coupon class' ); + if ( is_numeric( $item ) ) { + $item = $this->get_item( $item ); + } + if ( ! is_object( $item ) || ! $item->is_type( 'coupon' ) ) { + return false; + } + if ( ! $this->get_id() ) { + $this->save(); // Order must exist + } + + // BW compatibility for old args + if ( isset( $args['discount_amount'] ) ) { + $args['discount'] = $args['discount_amount']; + } + if ( isset( $args['discount_amount_tax'] ) ) { + $args['discount_tax'] = $args['discount_amount_tax']; + } + + $item->set_order_id( $this->get_id() ); + $item->set_all( $args ); + $item->save(); + + do_action( 'woocommerce_order_update_coupon', $this->get_id(), $item->get_id(), $args ); + + return $item->get_id(); + } + + /** + * Update shipping method for order. + * + * Note this does not update the order total. + * + * @param object|int $item + * @param array $args + * @return int updated order item ID + */ + public function update_shipping( $item, $args ) { + _deprecated_function( 'WC_Order::update_shipping', '2.7', 'Interact with WC_Order_Item_Shipping class' ); + if ( is_numeric( $item ) ) { + $item = $this->get_item( $item ); + } + if ( ! is_object( $item ) || ! $item->is_type( 'shipping' ) ) { + return false; + } + if ( ! $this->get_id() ) { + $this->save(); // Order must exist + } + + // BW compatibility for old args + if ( isset( $args['cost'] ) ) { + $args['total'] = $args['cost']; + } + + $item->set_order_id( $this->get_id() ); + $item->set_all( $args ); + $item->save(); + $this->calculate_shipping(); + + do_action( 'woocommerce_order_update_shipping', $this->get_id(), $item->get_id(), $args ); + + return $item->get_id(); + } + + /** + * Update fee for order. + * + * Note this does not update order totals. + * + * @param object|int $item + * @param array $args + * @return int updated order item ID + */ + public function update_fee( $item, $args ) { + _deprecated_function( 'WC_Order::update_fee', '2.7', 'Interact with WC_Order_Item_Fee class' ); + if ( is_numeric( $item ) ) { + $item = $this->get_item( $item ); + } + if ( ! is_object( $item ) || ! $item->is_type( 'fee' ) ) { + return false; + } + if ( ! $this->get_id() ) { + $this->save(); // Order must exist + } + + $item->set_order_id( $this->get_id() ); + $item->set_all( $args ); + $item->save(); + + do_action( 'woocommerce_order_update_fee', $this->get_id(), $item->get_id(), $args ); + + return $item->get_id(); + } + + /** + * Update tax line on order. + * Note this does not update order totals. + * + * @since 2.7 + * @param object|int $item + * @param array $args + * @return int updated order item ID + */ + public function update_tax( $item, $args ) { + _deprecated_function( 'WC_Order::update_tax', '2.7', 'Interact with WC_Order_Item_Tax class' ); + if ( is_numeric( $item ) ) { + $item = $this->get_item( $item ); + } + if ( ! is_object( $item ) || ! $item->is_type( 'tax' ) ) { + return false; + } + if ( ! $this->get_id() ) { + $this->save(); // Order must exist + } + + $item->set_order_id( $this->get_id() ); + $item->set_all( $args ); + $item->save(); + + do_action( 'woocommerce_order_update_tax', $this->get_id(), $item->get_id(), $args ); + + return $item->get_id(); + } + + /** + * Get a product (either product or variation). + * @deprecated Add deprecation notices in future release. Replaced with $item->get_product() + * @param object $item + * @return WC_Product|bool + */ + public function get_product_from_item( $item ) { + if ( is_callable( array( $item, 'get_product' ) ) ) { + $product = $item->get_product(); + } else { + $product = false; + } + return apply_filters( 'woocommerce_get_product_from_item', $product, $item, $this ); + } + + /** + * Set the customer address. + * @param array $address Address data. + * @param string $type billing or shipping. + */ + public function set_address( $address, $type = 'billing' ) { + foreach ( $address as $key => $value ) { + update_post_meta( $this->get_id(), "_{$type}_" . $key, $value ); + if ( is_callable( array( $this, "set_{$type}_{$key}" ) ) ) { + $this->{"set_{$type}_{$key}"}( $value ); + } + } + } + + /** + * Set an order total. + * @param float $amount + * @param string $total_type + * @return bool + */ + public function legacy_set_total( $amount, $total_type = 'total' ) { + if ( ! in_array( $total_type, array( 'shipping', 'tax', 'shipping_tax', 'total', 'cart_discount', 'cart_discount_tax' ) ) ) { + return false; + } + + switch ( $total_type ) { + case 'total' : + $amount = wc_format_decimal( $amount, wc_get_price_decimals() ); + $this->set_total( $amount ); + update_post_meta( $this->get_id(), '_order_total', $amount ); + break; + case 'cart_discount' : + $amount = wc_format_decimal( $amount ); + $this->set_discount_total( $amount ); + update_post_meta( $this->get_id(), '_cart_discount', $amount ); + break; + case 'cart_discount_tax' : + $amount = wc_format_decimal( $amount ); + $this->set_discount_tax( $amount ); + update_post_meta( $this->get_id(), '_cart_discount_tax', $amount ); + break; + case 'shipping' : + $amount = wc_format_decimal( $amount ); + $this->set_shipping_total( $amount ); + update_post_meta( $this->get_id(), '_order_shipping', $amount ); + break; + case 'shipping_tax' : + $amount = wc_format_decimal( $amount ); + $this->set_shipping_tax( $amount ); + update_post_meta( $this->get_id(), '_order_shipping_tax', $amount ); + break; + case 'tax' : + $amount = wc_format_decimal( $amount ); + $this->set_cart_tax( $amount ); + update_post_meta( $this->get_id(), '_order_tax', $amount ); + break; + } + + return true; + } + + /** + * Magic __isset method for backwards compatibility. + * @param string $key + * @return bool + */ + public function __isset( $key ) { + // Legacy properties which could be accessed directly in the past. + $legacy_props = array( 'completed_date', 'id', 'order_type', 'post', 'status', 'post_status', 'customer_note', 'customer_message', 'user_id', 'customer_user', 'prices_include_tax', 'tax_display_cart', 'display_totals_ex_tax', 'display_cart_ex_tax', 'order_date', 'modified_date', 'cart_discount', 'cart_discount_tax', 'order_shipping', 'order_shipping_tax', 'order_total', 'order_tax', 'billing_first_name', 'billing_last_name', 'billing_company', 'billing_address_1', 'billing_address_2', 'billing_city', 'billing_state', 'billing_postcode', 'billing_country', 'billing_phone', 'billing_email', 'shipping_first_name', 'shipping_last_name', 'shipping_company', 'shipping_address_1', 'shipping_address_2', 'shipping_city', 'shipping_state', 'shipping_postcode', 'shipping_country', 'customer_ip_address', 'customer_user_agent', 'payment_method_title', 'payment_method', 'order_currency' ); + return $this->get_id() ? ( in_array( $key, $legacy_props ) || metadata_exists( 'post', $this->get_id(), '_' . $key ) ) : false; + } + + /** + * Magic __get method for backwards compatibility. + * @param string $key + * @return mixed + */ + public function __get( $key ) { + _doing_it_wrong( $key, 'Order properties should not be accessed directly.', '2.7' ); + + if ( 'completed_date' === $key ) { + return $this->get_date_completed(); + } elseif ( 'paid_date' === $key ) { + return $this->get_date_paid(); + } elseif ( 'modified_date' === $key ) { + return $this->get_date_modified(); + } elseif ( 'order_date' === $key ) { + return $this->get_date_created(); + } elseif ( 'id' === $key ) { + return $this->get_id(); + } elseif ( 'post' === $key ) { + return get_post( $this->get_id() ); + } elseif ( 'status' === $key || 'post_status' === $key ) { + return $this->get_status(); + } elseif ( 'customer_message' === $key || 'customer_note' === $key ) { + return $this->get_customer_note(); + } elseif ( in_array( $key, array( 'user_id', 'customer_user' ) ) ) { + return $this->get_customer_id(); + } elseif ( 'tax_display_cart' === $key ) { + return get_option( 'woocommerce_tax_display_cart' ); + } elseif ( 'display_totals_ex_tax' === $key ) { + return 'excl' === get_option( 'woocommerce_tax_display_cart' ); + } elseif ( 'display_cart_ex_tax' === $key ) { + return 'excl' === get_option( 'woocommerce_tax_display_cart' ); + } elseif ( 'cart_discount' === $key ) { + return $this->get_discount(); + } elseif ( 'cart_discount_tax' === $key ) { + return $this->get_discount_tax(); + } elseif ( 'order_tax' === $key ) { + return $this->get_cart_tax(); + } elseif ( 'order_shipping_tax' === $key ) { + return $this->get_shipping_tax(); + } elseif ( 'order_shipping' === $key ) { + return $this->get_shipping_total(); + } elseif ( 'order_total' === $key ) { + return $this->get_total(); + } elseif ( 'order_type' === $key ) { + return $this->get_type(); + } elseif ( 'order_currency' === $key ) { + return $this->get_currency(); + } elseif ( 'order_version' === $key ) { + return $this->get_version(); + } elseif ( is_callable( array( $this, "get_{$key}" ) ) ) { + return $this->{"get_{$key}"}(); + } else { + return get_post_meta( $this->get_id(), '_' . $key, true ); + } + } + + /** + * has_meta function for order items. + * + * @param string $order_item_id + * @return array of meta data. + */ + public function has_meta( $order_item_id ) { + global $wpdb; + + _deprecated_function( 'has_meta', '2.7', 'WC_Order_item::get_meta_data' ); + + return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, order_item_id + FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d + ORDER BY meta_id", absint( $order_item_id ) ), ARRAY_A ); + } + + /** + * Display meta data belonging to an item. + * @param array $item + */ + public function display_item_meta( $item ) { + _deprecated_function( 'get_item_meta', '2.7', 'wc_display_item_meta' ); + $product = $item->get_product(); + $item_meta = new WC_Order_Item_Meta( $item, $product ); + $item_meta->display(); + } + + /** + * Display download links for an order item. + * @param array $item + */ + public function display_item_downloads( $item ) { + _deprecated_function( 'display_item_downloads', '2.7', 'wc_display_item_downloads' ); + $product = $item->get_product(); + + if ( $product && $product->exists() && $product->is_downloadable() && $this->is_download_permitted() ) { + $download_files = $this->get_item_downloads( $item ); + $i = 0; + $links = array(); + + foreach ( $download_files as $download_id => $file ) { + $i++; + $prefix = count( $download_files ) > 1 ? sprintf( __( 'Download %d', 'woocommerce' ), $i ) : __( 'Download', 'woocommerce' ); + $links[] = '' . $prefix . ': ' . esc_html( $file['name'] ) . '' . "\n"; + } + + echo '
' . implode( '
', $links ); + } + } + + /** + * Get the Download URL. + * + * @param int $product_id + * @param int $download_id + * @return string + */ + public function get_download_url( $product_id, $download_id ) { + _deprecated_function( 'get_download_url', '2.7', 'WC_Order_Item_Product::get_item_download_url' ); + return add_query_arg( array( + 'download_file' => $product_id, + 'order' => $this->get_order_key(), + 'email' => urlencode( $this->get_billing_email() ), + 'key' => $download_id, + ), trailingslashit( home_url() ) ); + } + + /** + * Get the downloadable files for an item in this order. + * + * @param array $item + * @return array + */ + public function get_item_downloads( $item ) { + _deprecated_function( 'get_item_downloads', '2.7', 'WC_Order_Item_Product::get_item_downloads' ); + return $item->get_item_downloads(); + } + + /** + * Gets shipping total. Alias of WC_Order::get_shipping_total(). + * @deprecated 2.7.0 since this is an alias only. + * @return float + */ + public function get_total_shipping() { + return $this->get_shipping_total(); + } + + /** + * Get order item meta. + * @deprecated 2.7.0 + * @param mixed $order_item_id + * @param string $key (default: '') + * @param bool $single (default: false) + * @return array|string + */ + public function get_item_meta( $order_item_id, $key = '', $single = false ) { + _deprecated_function( 'get_item_meta', '2.7', 'wc_get_order_item_meta' ); + return get_metadata( 'order_item', $order_item_id, $key, $single ); + } + + /** + * Get all item meta data in array format in the order it was saved. Does not group meta by key like get_item_meta(). + * + * @param mixed $order_item_id + * @return array of objects + */ + public function get_item_meta_array( $order_item_id ) { + _deprecated_function( 'get_item_meta_array', '2.7', 'WC_Order_Item::get_meta_data() (note the format has changed)' ); + $item = $this->get_item( $order_item_id ); + $meta_data = $item->get_meta_data(); + $item_meta_array = array(); + + foreach ( $meta_data as $meta ) { + $item_meta_array[ $meta->meta_id ] = $meta; + } + + return $item_meta_array; + } + + /** + * Expand item meta into the $item array. + * @deprecated 2.7.0 Item meta no longer expanded due to new order item + * classes. This function now does nothing to avoid data breakage. + * @param array $item before expansion. + * @return array + */ + public function expand_item_meta( $item ) { + _deprecated_function( 'expand_item_meta', '2.7', '' ); + return $item; + } + + /** + * Load the order object. Called from the constructor. + * @deprecated 2.7.0 Logic moved to constructor + * @param int|object|WC_Order $order Order to init. + */ + protected function init( $order ) { + _deprecated_function( 'init', '2.7', 'Logic moved to constructor' ); + if ( is_numeric( $order ) ) { + $this->read( $order ); + } elseif ( $order instanceof WC_Order ) { + $this->read( absint( $order->get_id() ) ); + } elseif ( isset( $order->ID ) ) { + $this->read( absint( $order->ID ) ); + } + } + + /** + * Gets an order from the database. + * @deprecated 2.7 + * @param int $id (default: 0). + * @return bool + */ + public function get_order( $id = 0 ) { + _deprecated_function( 'get_order', '2.7', 'read' ); + if ( ! $id ) { + return false; + } + if ( $result = get_post( $id ) ) { + $this->populate( $result ); + return true; + } + return false; + } + + /** + * Populates an order from the loaded post data. + * @deprecated 2.7 + * @param mixed $result + */ + public function populate( $result ) { + _deprecated_function( 'populate', '2.7', 'read' ); + $this->read( $result->ID ); + } + + /** + * Cancel the order and restore the cart (before payment). + * @deprecated 2.7.0 Moved to event handler. + * @param string $note (default: '') Optional note to add. + */ + public function cancel_order( $note = '' ) { + _deprecated_function( 'cancel_order', '2.7', 'update_status' ); + WC()->session->set( 'order_awaiting_payment', false ); + $this->update_status( 'cancelled', $note ); + } + + /** + * Record sales. + * @deprecated 2.7.0 + */ + public function record_product_sales() { + _deprecated_function( 'record_product_sales', '2.7', 'wc_update_total_sales_counts' ); + wc_update_total_sales_counts( $this->get_id() ); + } + + /** + * Increase applied coupon counts. + * @deprecated 2.7.0 + */ + public function increase_coupon_usage_counts() { + _deprecated_function( 'increase_coupon_usage_counts', '2.7', 'wc_update_coupon_usage_counts' ); + wc_update_coupon_usage_counts( $this->get_id() ); + } + + /** + * Decrease applied coupon counts. + * @deprecated 2.7.0 + */ + public function decrease_coupon_usage_counts() { + _deprecated_function( 'decrease_coupon_usage_counts', '2.7', 'wc_update_coupon_usage_counts' ); + wc_update_coupon_usage_counts( $this->get_id() ); + } + + /** + * Reduce stock levels for all line items in the order. + * @deprecated 2.7.0 + */ + public function reduce_order_stock() { + _deprecated_function( 'reduce_order_stock', '2.7', 'wc_reduce_stock_levels' ); + wc_reduce_stock_levels( $this->get_id() ); + } + + /** + * Send the stock notifications. + * @deprecated 2.7.0 No longer needs to be called directly. + */ + public function send_stock_notifications( $product, $new_stock, $qty_ordered ) { + _deprecated_function( 'send_stock_notifications', '2.7' ); + } + + /** + * Output items for display in html emails. + * @deprecated 2.7.0 Moved to template functions. + * @param array $args Items args. + * @return string + */ + public function email_order_items_table( $args = array() ) { + _deprecated_function( 'email_order_items_table', '2.7', 'wc_get_email_order_items' ); + return wc_get_email_order_items( $this, $args ); + } + + /** + * Get currency. + * @deprecated 2.7.0 + */ + public function get_order_currency() { + _deprecated_function( 'get_order_currency', '2.7', 'get_currency' ); + return apply_filters( 'woocommerce_get_order_currency', $this->get_currency(), $this ); + } +} diff --git a/includes/abstracts/abstract-wc-order.php b/includes/abstracts/abstract-wc-order.php index 527e1b503c3..fee786a16ad 100644 --- a/includes/abstracts/abstract-wc-order.php +++ b/includes/abstracts/abstract-wc-order.php @@ -1,94 +1,80 @@ 0, + 'parent_id' => 0, + 'status' => 'pending', + 'type' => 'shop_order', + 'order_key' => '', + 'currency' => '', + 'version' => '', + 'prices_include_tax' => false, + 'date_created' => '', + 'date_modified' => '', + 'customer_id' => 0, + 'discount_total' => 0, + 'discount_tax' => 0, + 'shipping_total' => 0, + 'shipping_tax' => 0, + 'cart_tax' => 0, + 'total' => 0, + 'total_tax' => 0, + ); - /** @var $post WP_Post. */ - public $post = null; + /** + * Data stored in meta keys, but not considered "meta" for an order. + * @since 2.7.0 + * @var array + */ + protected $_internal_meta_keys = array( + '_customer_user', '_order_key', '_order_currency', '_cart_discount', + '_cart_discount_tax', '_order_shipping', '_order_shipping_tax', + '_order_tax', '_order_total', '_order_version', '_prices_include_tax', + '_payment_tokens', + ); - /** @public string Order type. */ - public $order_type = false; + /** + * Internal meta type used to store order data. + * @var string + */ + protected $_meta_type = 'post'; - /** @public string Order Date. */ - public $order_date = ''; - - /** @public string Order Modified Date. */ - public $modified_date = ''; - - /** @public string Customer Message (excerpt). */ - public $customer_message = ''; - - /** @public string Customer Note */ - public $customer_note = ''; - - /** @public string Order Status. */ - public $post_status = ''; - - /** @public bool Do prices include tax? */ - public $prices_include_tax = false; - - /** @public string Display mode for taxes in cart. */ - public $tax_display_cart = ''; - - /** @public bool Do totals display ex tax? */ - public $display_totals_ex_tax = false; - - /** @public bool Do cart prices display ex tax? */ - public $display_cart_ex_tax = false; - - /** @protected string Formatted address. Accessed via get_formatted_billing_address(). */ - protected $formatted_billing_address = ''; - - /** @protected string Formatted address. Accessed via get_formatted_shipping_address(). */ - protected $formatted_shipping_address = ''; + /** + * Stores meta in cache for future reads. + * A group must be set to to enable caching. + * @var string + */ + protected $_cache_group = 'order'; /** * Get the order if ID is passed, otherwise the order is new and empty. @@ -99,51 +85,1002 @@ abstract class WC_Abstract_Order { * @param int|object|WC_Order $order Order to init. */ public function __construct( $order = 0 ) { - $this->prices_include_tax = get_option('woocommerce_prices_include_tax') == 'yes' ? true : false; - $this->tax_display_cart = get_option( 'woocommerce_tax_display_cart' ); - $this->display_totals_ex_tax = $this->tax_display_cart == 'excl' ? true : false; - $this->display_cart_ex_tax = $this->tax_display_cart == 'excl' ? true : false; - $this->init( $order ); + if ( is_numeric( $order ) && $order > 0 ) { + $this->read( $order ); + } elseif ( $order instanceof self ) { + $this->read( absint( $order->get_id() ) ); + } elseif ( ! empty( $order->ID ) ) { + $this->read( absint( $order->ID ) ); + } + } + + /* + |-------------------------------------------------------------------------- + | CRUD methods + |-------------------------------------------------------------------------- + | + | Methods which create, read, update and delete orders from the database. + | Written in abstract fashion so that the way orders are stored can be + | changed more easily in the future. + | + | A save method is included for convenience (chooses update or create based + | on if the order exists yet). + | + */ + + /** + * Get internal type (post type.) + * @return string + */ + public function get_type() { + return 'shop_order'; } /** - * Init/load the order object. Called from the constructor. - * - * @param int|object|WC_Order $order Order to init. + * Get a title for the new post type. */ - protected function init( $order ) { - if ( is_numeric( $order ) ) { - $this->id = absint( $order ); - $this->post = get_post( $order ); - $this->get_order( $this->id ); - } elseif ( $order instanceof WC_Order ) { - $this->id = absint( $order->id ); - $this->post = $order->post; - $this->get_order( $this->id ); - } elseif ( isset( $order->ID ) ) { - $this->id = absint( $order->ID ); - $this->post = $order; - $this->get_order( $this->id ); + protected function get_post_title() { + return sprintf( __( 'Order – %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) ); + } + + /** + * Insert data into the database. + * @since 2.7.0 + */ + public function create() { + $this->set_order_key( 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) ); + $this->set_date_created( current_time( 'timestamp' ) ); + + $order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', array( + 'post_date' => date( 'Y-m-d H:i:s', $this->get_date_created() ), + 'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ), + 'post_type' => $this->get_type(), + 'post_status' => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ), + 'ping_status' => 'closed', + 'post_author' => 1, + 'post_title' => $this->get_post_title(), + 'post_password' => uniqid( 'order_' ), + 'post_parent' => $this->get_parent_id(), + ) ), true ); + + if ( $order_id ) { + $this->set_id( $order_id ); + + // Set meta data + $this->update_post_meta( '_customer_user', $this->get_customer_id() ); + $this->update_post_meta( '_order_currency', $this->get_currency() ); + $this->update_post_meta( '_order_key', $this->get_order_key() ); + $this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) ); + $this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) ); + $this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) ); + $this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) ); + $this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) ); + $this->update_post_meta( '_order_total', $this->get_total( true ) ); + $this->update_post_meta( '_order_version', $this->get_version() ); + $this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() ); + $this->save_meta_data(); } } /** - * Remove all line items (products, coupons, shipping, taxes) from the order. + * Read from the database. + * @since 2.7.0 + * @param int $id ID of object to read. + */ + public function read( $id ) { + if ( empty( $id ) || ! ( $post_object = get_post( $id ) ) ) { + return; + } + + // Map standard post data + $this->set_id( $post_object->ID ); + $this->set_parent_id( $post_object->post_parent ); + $this->set_date_created( $post_object->post_date ); + $this->set_date_modified( $post_object->post_modified ); + $this->set_status( $post_object->post_status ); + $this->set_order_type( $post_object->post_type ); + $this->set_customer_id( get_post_meta( $this->get_id(), '_customer_user', true ) ); + $this->set_order_key( get_post_meta( $this->get_id(), '_order_key', true ) ); + $this->set_currency( get_post_meta( $this->get_id(), '_order_currency', true ) ); + $this->set_discount_total( get_post_meta( $this->get_id(), '_cart_discount', true ) ); + $this->set_discount_tax( get_post_meta( $this->get_id(), '_cart_discount_tax', true ) ); + $this->set_shipping_total( get_post_meta( $this->get_id(), '_order_shipping', true ) ); + $this->set_shipping_tax( get_post_meta( $this->get_id(), '_order_shipping_tax', true ) ); + $this->set_cart_tax( get_post_meta( $this->get_id(), '_order_tax', true ) ); + $this->set_total( get_post_meta( $this->get_id(), '_order_total', true ) ); + + // Orders store the state of prices including tax when created. + $this->set_prices_include_tax( metadata_exists( 'post', $this->get_id(), '_prices_include_tax' ) ? 'yes' === get_post_meta( $this->get_id(), '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ) ); + + // Load meta data + $this->read_meta_data(); + } + + /** + * Post meta update wrapper. Sets or deletes based on value. + * @since 2.7.0 + * @return bool Was it changed? + */ + protected function update_post_meta( $key, $value ) { + if ( '' !== $value ) { + return update_post_meta( $this->get_id(), $key, $value ); + } else { + return delete_post_meta( $this->get_id(), $key ); + } + } + + /** + * Update data in the database. + * @since 2.7.0 + */ + public function update() { + global $wpdb; + + $order_id = $this->get_id(); + + $wpdb->update( + $wpdb->posts, + array( + 'post_date' => date( 'Y-m-d H:i:s', $this->get_date_created() ), + 'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ), + 'post_status' => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ), + 'post_parent' => $this->get_parent_id(), + ), + array( + 'ID' => $order_id + ) + ); + + // Update meta data + $this->update_post_meta( '_customer_user', $this->get_customer_id() ); + $this->update_post_meta( '_order_currency', $this->get_currency() ); + $this->update_post_meta( '_order_key', $this->get_order_key() ); + $this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) ); + $this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) ); + $this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) ); + $this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) ); + $this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) ); + $this->update_post_meta( '_order_total', $this->get_total( true ) ); + $this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() ); + $this->save_meta_data(); + } + + /** + * Delete data from the database. + * @since 2.7.0 + */ + public function delete() { + wp_delete_post( $this->get_id() ); + } + + /** + * Save data to the database. + * @since 2.7.0 + * @return int order ID + */ + public function save() { + $this->set_version( WC_VERSION ); + + if ( ! $this->get_id() ) { + $this->create(); + } else { + $this->update(); + } + + clean_post_cache( $this->get_id() ); + wc_delete_shop_order_transients( $this->get_id() ); + + return $this->get_id(); + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + | + | Methods for getting data from the order object. + | + */ + + /** + * Get all class data in array format. + * @since 2.7.0 + * @return array + */ + public function get_data() { + return array_merge( + $this->_data, + array( + 'meta_data' => $this->get_meta_data(), + 'line_items' => $this->get_items( 'line_item' ), + 'tax_lines' => $this->get_items( 'tax' ), + 'shipping_lines' => $this->get_items( 'shipping' ), + 'fee_lines' => $this->get_items( 'fee' ), + 'coupon_lines' => $this->get_items( 'coupon' ), + ) + ); + } + + /** + * Get order ID. + * @since 2.7.0 + * @return integer + */ + public function get_id() { + return $this->_data['id']; + } + + /** + * Get parent order ID. + * @since 2.7.0 + * @return integer + */ + public function get_parent_id() { + return $this->_data['parent_id']; + } + + /** + * get_order_number function. * + * Gets the order number for display (by default, order ID). + * + * @return string + */ + public function get_order_number() { + return apply_filters( 'woocommerce_order_number', $this->get_id(), $this ); + } + + /** + * Get order key. + * @since 2.7.0 + * @return string + */ + public function get_order_key() { + return $this->_data['order_key']; + } + + /** + * Gets order currency. + * @return string + */ + public function get_currency() { + return apply_filters( 'woocommerce_get_currency', $this->_data['currency'], $this ); + } + + /** + * Get order_version + * @return string + */ + public function get_version() { + return $this->_data['version']; + } + + /** + * Get prices_include_tax + * @return bool + */ + public function get_prices_include_tax() { + return $this->_data['prices_include_tax']; + } + + /** + * Get Order Type + * @return string + */ + public function get_order_type() { + return $this->_data['type']; + } + + /** + * Get date_created + * @return int + */ + public function get_date_created() { + return $this->_data['date_created']; + } + + /** + * Get date_modified + * @return int + */ + public function get_date_modified() { + return $this->_data['date_modified']; + } + + /** + * Get customer_id + * @return int + */ + public function get_customer_id() { + return $this->_data['customer_id']; + } + + /** + * Return the order statuses without wc- internal prefix. + * @return string + */ + public function get_status() { + return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->_data['status'], 0, 3 ) ? substr( $this->_data['status'], 3 ) : $this->_data['status'], $this ); + } + + /** + * Alias for get_customer_id(). + * @since 2.2 + * @return int + */ + public function get_user_id() { + return $this->get_customer_id(); + } + + /** + * Get the user associated with the order. False for guests. + * + * @since 2.2 + * @return WP_User|false + */ + public function get_user() { + return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false; + } + + /** + * Get discount_total + * @param bool $raw Gets raw unfiltered value. + * @return string + */ + public function get_discount_total( $raw = false ) { + $value = wc_format_decimal( $this->_data['discount_total'] ); + return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_total', $value, $this ); + } + + /** + * Get discount_tax + * @param bool $raw Gets raw unfiltered value. + * @return string + */ + public function get_discount_tax( $raw = false ) { + $value = wc_format_decimal( $this->_data['discount_tax'] ); + return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_tax', $value, $this ); + } + + /** + * Get shipping_total + * @param bool $raw Gets raw unfiltered value. + * @return string + */ + public function get_shipping_total( $raw = false ) { + $value = wc_format_decimal( $this->_data['shipping_total'] ); + return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_total', $value, $this ); + } + + /** + * Get shipping_tax. + * @param bool $raw Gets raw unfiltered value. + * @return string + */ + public function get_shipping_tax( $raw = false ) { + $value = wc_format_decimal( $this->_data['shipping_tax'] ); + return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_tax', $value, $this ); + } + + /** + * Gets cart tax amount. + * @param bool $raw Gets raw unfiltered value. + * @return float + */ + public function get_cart_tax( $raw = false ) { + $value = wc_format_decimal( $this->_data['cart_tax'] ); + return $raw ? $value : apply_filters( 'woocommerce_order_amount_cart_tax', $value, $this ); + } + + /** + * Gets order grand total. incl. taxes. Used in gateways. Filtered. + * @param bool $raw Gets raw unfiltered value. + * @return float + */ + public function get_total( $raw = false ) { + $value = wc_format_decimal( $this->_data['total'], wc_get_price_decimals() ); + return $raw ? $value : apply_filters( 'woocommerce_order_amount_total', $value, $this ); + } + + /** + * Get total tax amount. Alias for get_order_tax(). + * + * @since 2.7.0 woocommerce_order_amount_total_tax filter has been removed to avoid + * these values being modified and then saved back to the DB. There are + * other, later hooks available to change totals on display. e.g. + * woocommerce_get_order_item_totals. + * @param bool $raw Gets raw unfiltered value. + * @return float + */ + public function get_total_tax( $raw = false ) { + $value = wc_format_decimal( $this->_data['total_tax'] ); + return $raw ? $value : apply_filters( 'woocommerce_order_amount_total_tax', $value, $this ); + } + + /** + * Gets the total discount amount. + * @param bool $ex_tax Show discount excl any tax. + * @return float + */ + public function get_total_discount( $ex_tax = true ) { + if ( $ex_tax ) { + $total_discount = $this->get_discount_total(); + } else { + $total_discount = $this->get_discount_total() + $this->get_discount_tax(); + } + return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this ); + } + + /** + * Gets order subtotal. + * @return float + */ + public function get_subtotal() { + $subtotal = 0; + + foreach ( $this->get_items() as $item ) { + $subtotal += $item->get_subtotal(); + } + + return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this ); + } + + /** + * Get taxes, merged by code, formatted ready for output. + * + * @return array + */ + public function get_tax_totals() { + $tax_totals = array(); + + foreach ( $this->get_items( 'tax' ) as $key => $tax ) { + $code = $tax->get_rate_code(); + + if ( ! isset( $tax_totals[ $code ] ) ) { + $tax_totals[ $code ] = new stdClass(); + $tax_totals[ $code ]->amount = 0; + } + + $tax_totals[ $code ]->id = $key; + $tax_totals[ $code ]->rate_id = $tax->get_rate_id(); + $tax_totals[ $code ]->is_compound = $tax->is_compound(); + $tax_totals[ $code ]->label = $tax->get_label(); + $tax_totals[ $code ]->amount += $tax->get_tax_total() + $tax->get_shipping_tax_total(); + $tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) ); + } + + if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) { + $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) ); + $tax_totals = array_intersect_key( $tax_totals, $amounts ); + } + + return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this ); + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + | + | Functions for setting order data. These should not update anything in the + | database itself and should only change what is stored in the class + | object. However, for backwards compatibility pre 2.7.0 some of these + | setters may handle both. + | + */ + + /** + * Set order ID. + * @since 2.7.0 + * @param int $value + */ + public function set_id( $value ) { + $this->_data['id'] = absint( $value ); + } + + /** + * Set parent order ID. + * @since 2.7.0 + * @param int $value + */ + public function set_parent_id( $value ) { + $this->_data['parent_id'] = absint( $value ); + } + + /** +<<<<<<< HEAD + * Set order status. + * @since 2.7.0 + * @param string $new_status Status to change the order to. No internal wc- prefix is required. + * @param array details of change + */ + public function set_status( $new_status ) { + $old_status = $this->get_status(); + $new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status; + + // If the old status is unknown (e.g. draft) assume its pending for action usage. + if ( ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) { + $old_status = 'pending'; + } + + if ( in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) && $new_status !== $old_status ) { + $this->_data['status'] = 'wc-' . $new_status; + } else { + $new_status = $old_status; + } + + return array( + 'from' => $old_status, + 'to' => $new_status + ); + } + + /** + * Set Order Type + * @param string $value + */ + public function set_order_type( $value ) { + $this->_data['type'] = $value; + } + + /** + * Set order_key. + * @param string $value Max length 20 chars. + */ + public function set_order_key( $value ) { + $this->_data['order_key'] = substr( $value, 0, 20 ); + } + + /** + * Set order_version + * @param string $value + */ + public function set_version( $value ) { + $this->_data['version'] = $value; + } + + /** + * Set order_currency + * @param string $value + */ + public function set_currency( $value ) { + $this->_data['currency'] = $value; + } + + /** + * Set prices_include_tax + * @param bool $value + */ + public function set_prices_include_tax( $value ) { + $this->_data['prices_include_tax'] = (bool) $value; + } + + /** + * Set date_created + * @param string $timestamp Timestamp + */ + public function set_date_created( $timestamp ) { + $this->_data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp ); + } + + /** + * Set date_modified + * @param string $timestamp + */ + public function set_date_modified( $timestamp ) { + $this->_data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp ); + } + + /** + * Set customer_id + * @param int $value + */ + public function set_customer_id( $value ) { + $this->_data['customer_id'] = absint( $value ); + } + + /** + * Set discount_total + * @param string $value + */ + public function set_discount_total( $value ) { + $this->_data['discount_total'] = wc_format_decimal( $value ); + } + + /** + * Set discount_tax + * @param string $value + */ + public function set_discount_tax( $value ) { + $this->_data['discount_tax'] = wc_format_decimal( $value ); + } + + /** + * Set shipping_total + * @param string $value + */ + public function set_shipping_total( $value ) { + $this->_data['shipping_total'] = wc_format_decimal( $value ); + } + + /** + * Set shipping_tax + * @param string $value + */ + public function set_shipping_tax( $value ) { + $this->_data['shipping_tax'] = wc_format_decimal( $value ); + $this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() ); + } + + /** + * Set cart tax + * @param string $value + */ + public function set_cart_tax( $value ) { + $this->_data['cart_tax'] = wc_format_decimal( $value ); + $this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() ); + } + + /** + * Sets order tax (sum of cart and shipping tax). Used internaly only. + * @param string $value + */ + protected function set_total_tax( $value ) { + $this->_data['total_tax'] = wc_format_decimal( $value ); + } + + /** + * Set total + * @param string $value + * @param string $deprecated Function used to set different totals based on this. + */ + public function set_total( $value, $deprecated = '' ) { + if ( $deprecated ) { + _deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' ); + return $this->legacy_set_total( $value, $deprecated ); + } + $this->_data['total'] = wc_format_decimal( $value, wc_get_price_decimals() ); + } + + /* + |-------------------------------------------------------------------------- + | Order Item Handling + |-------------------------------------------------------------------------- + | + | Order items are used for products, taxes, shipping, and fees within + | each order. + | + */ + + /** + * Remove all line items (products, coupons, shipping, taxes) from the order. * @param string $type Order item type. Default null. */ public function remove_order_items( $type = null ) { global $wpdb; - if ( ! empty( $type ) ) { - $wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id AND items.order_id = %d AND items.order_item_type = %s", $this->id, $type ) ); - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->id, $type ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id AND items.order_id = %d AND items.order_item_type = %s", $this->get_id(), $type ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) ); } else { - $wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id and items.order_id = %d", $this->id ) ); - $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->id ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id and items.order_id = %d", $this->get_id() ) ); + $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) ); } } + /** + * Return an array of items/products within this order. + * @param string|array $type Types of line items to get (array or string). + * @return Array of WC_Order_item + */ + public function get_items( $type = 'line_item' ) { + global $wpdb; + $get_items_sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d ", $this->get_id() ) . "AND order_item_type IN ( '" . implode( "','", array_map( 'esc_sql', (array) $type ) ) . "' ) ORDER BY order_item_id;"; + $items = $wpdb->get_results( $get_items_sql ); + + if ( ! empty( $items ) ) { + $items = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) ); + } else { + $items = array(); + } + + return apply_filters( 'woocommerce_order_get_items', $items, $this ); + } + + /** + * Return an array of fees within this order. + * @return array + */ + public function get_fees() { + return $this->get_items( 'fee' ); + } + + /** + * Return an array of taxes within this order. + * @return array + */ + public function get_taxes() { + return $this->get_items( 'tax' ); + } + + /** + * Return an array of shipping costs within this order. + * @return array + */ + public function get_shipping_methods() { + return $this->get_items( 'shipping' ); + } + + /** + * Gets formatted shipping method title. + * @return string + */ + public function get_shipping_method() { + $names = array(); + foreach ( $this->get_shipping_methods() as $shipping_method ) { + $names[] = $shipping_method->get_name(); + } + return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this ); + } + + /** + * Get coupon codes only. + * @return array + */ + public function get_used_coupons() { + $coupon_codes = array(); + if ( $coupons = $this->get_items( 'coupon' ) ) { + foreach ( $coupons as $coupon ) { + $coupon_codes[] = $coupon->get_code(); + } + } + return $coupon_codes; + } + + /** + * Gets the count of order items of a certain type. + * + * @param string $item_type + * @return string + */ + public function get_item_count( $item_type = '' ) { + $items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type ); + $count = 0; + + foreach ( $items as $item ) { + $count += $item->get_qty(); + } + + return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this ); + } + + /** + * Get an order item object, based on it's type. + * @since 2.7.0 + * @param int $item_id + * @return WC_Order_Item + */ + public function get_item( $item_id ) { + return WC_Order_Factory::get_order_item( $item_id ); + } + + /** + * Add a product line item to the order. + * Order must be saved prior to adding items. + * @param \WC_Product $product + * @param array $args + * @param array $deprecated qty was passed as arg 2 prior to 2.7.0 + * @return int order item ID + */ + public function add_product( $product, $args = array(), $deprecated = array() ) { + if ( ! is_array( $args ) ) { + _deprecated_argument( 'qty', '2.7', 'Pass only product and args' ); + $qty = $args; + $args = $deprecated; + $args['qty'] = $qty; + } + + $args = wp_parse_args( $args, array( + 'qty' => 1, + 'name' => $product ? $product->get_title() : '', + 'tax_class' => $product ? $product->get_tax_class() : '', + 'product_id' => $product ? $product->get_id() : '', + 'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0, + 'subtotal' => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '', + 'total' => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '', + 'subtotal_tax' => 0, + 'total_tax' => 0, + 'variation' => array(), + 'taxes' => array( + 'subtotal' => array(), + 'total' => array(), + ), + ) ); + + // BW compatibility with old args + if ( isset( $args['totals'] ) ) { + foreach ( $args['totals'] as $key => $value ) { + if ( 'tax' === $key ) { + $args['total_tax'] = $value; + } elseif ( 'tax_data' === $key ) { + $args['taxes'] = $value; + } else { + $args[ $key ] = $value; + } + } + } + + $item = new WC_Order_Item_Product( $args ); + + // Handle backorders + if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) { + $item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ), true ); + } + + $item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() ); + $item->save(); + + if ( has_action( 'woocommerce_order_add_product' ) ) { + _deprecated_function( 'Action: woocommerce_order_add_product', '2.7', 'Use woocommerce_new_order_item action instead.' ); + do_action( 'woocommerce_order_add_product', $this->get_id(), $item->get_id(), $product, $qty, $args ); + } + + return $item->get_id(); + } + + /** + * Add coupon code to the order. + * Order must be saved prior to adding items. + * @param array $args + * @param int $deprecated1 2.7.0 code, discount, tax were passed. + * @param int $deprecated2 2.7.0 code, discount, tax were passed. + * @return int order item ID + */ + public function add_coupon( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) { + if ( ! is_array( $args ) ) { + _deprecated_argument( 'code', '2.7', 'Pass only an array of args' ); + $args = array( + 'code' => $args, + 'discount' => $deprecated1, + 'discount_tax' => $deprecated2, + ); + } + + $args = wp_parse_args( $args, array( + 'code' => '', + 'discount' => 0, + 'discount_tax' => 0, + ) ); + + $item = new WC_Order_Item_Coupon( $args ); + $item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() ); + $item->save(); + + if ( has_action( 'woocommerce_order_add_coupon' ) ) { + _deprecated_function( 'Action: woocommerce_order_add_coupon', '2.7', 'Use woocommerce_new_order_item action instead.' ); + do_action( 'woocommerce_order_add_coupon', $this->get_id(), $item->get_id(), $args['code'], $args['discount'], $args['discount_tax'] ); + } + + return $item->get_id(); + } + + /** + * Add a tax row to the order. + * Order must be saved prior to adding items. + * @since 2.2 + * @param array $args + * @param int $deprecated1 2.7.0 tax_rate_id, amount, shipping amount. + * @param int $deprecated2 2.7.0 tax_rate_id, amount, shipping amount. + * @return int order item ID + */ + public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) { + if ( ! is_array( $args ) ) { + _deprecated_argument( 'tax_rate_id', '2.7', 'Pass only an array of args' ); + $args = array( + 'rate_id' => $args, + 'tax_total' => $deprecated1, + 'shipping_tax_total' => $deprecated2, + ); + } + + $args = wp_parse_args( $args, array( + 'rate_id' => '', + 'tax_total' => 0, + 'shipping_tax_total' => 0, + 'rate_code' => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '', + 'label' => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '', + 'compound' => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '', + ) ); + + $item = new WC_Order_Item_Tax( $args ); + $item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() ); + $item->save(); + + if ( has_action( 'woocommerce_order_add_tax' ) ) { + _deprecated_function( 'Action: woocommerce_order_add_tax', '2.7', 'Use woocommerce_new_order_item action instead.' ); + do_action( 'woocommerce_order_add_tax', $this->get_id(), $item->get_id(), $args['rate_id'], $args['tax_total'], $args['shipping_tax_total'] ); + } + + return $item->get_id(); + } + + /** + * Add a shipping row to the order. + * Order must be saved prior to adding items. + * @param WC_Shipping_Rate shipping_rate + * @return int order item ID + */ + public function add_shipping( $shipping_rate ) { + $item = new WC_Order_Item_Shipping( array( + 'method_title' => $shipping_rate->label, + 'method_id' => $shipping_rate->id, + 'total' => wc_format_decimal( $shipping_rate->cost ), + 'taxes' => $shipping_rate->taxes, + 'meta_data' => $shipping_rate->get_meta_data(), + ) ); + $item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() ); + $item->save(); + + if ( has_action( 'woocommerce_order_add_shipping' ) ) { + _deprecated_function( 'Action: woocommerce_order_add_shipping', '2.7', 'Use woocommerce_new_order_item action instead.' ); + do_action( 'woocommerce_order_add_shipping', $this->get_id(), $item->get_id(), $shipping_rate ); + } + + return $item->get_id(); + } + + /** + * Add a fee to the order. + * Order must be saved prior to adding items. + * @param object $fee + * @return int updated order item ID + */ + public function add_fee( $fee ) { + $item = new WC_Order_Item_Fee( array( + 'name' => $fee->name, + 'tax_class' => $fee->taxable ? $fee->tax_class : 0, + 'total' => $fee->amount, + 'total_tax' => $fee->tax, + 'taxes' => array( + 'total' => $fee->tax_data, + ), + ) ); + + $item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() ); + $item->save(); + + if ( has_action( 'woocommerce_order_add_fee' ) ) { + _deprecated_function( 'Action: woocommerce_order_add_fee', '2.7', 'Use woocommerce_new_order_item action instead.' ); + do_action( 'woocommerce_order_add_fee', $this->get_id(), $item->get_id(), $fee ); + } + + return $item->get_id(); + } + + /** + * Add a payment token to an order + * + * @since 2.6 + * @param WC_Payment_Token $token Payment token object + * @return boolean|int The new token ID or false if it failed. + */ + public function add_payment_token( $token ) { + if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) { + return false; + } + + $token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true ); + + if ( empty ( $token_ids ) ) { + $token_ids = array(); + } + + $token_ids[] = $token->get_id(); + + update_post_meta( $this->get_id(), '_payment_tokens', $token_ids ); + do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids ); + return $token->get_id(); + } + /** * Returns a list of all payment tokens associated with the current order * @@ -151,510 +1088,35 @@ abstract class WC_Abstract_Order { * @return array An array of payment token objects */ public function get_payment_tokens() { - return WC_Payment_Tokens::get_order_tokens( $this->id ); + return WC_Payment_Tokens::get_order_tokens( $this->get_id() ); } - /** - * Add a payment token to an order - * - * @since 2.6 - * @param WC_Payment_Token $token Payment token object - * @return boolean True if the token was added, false if not - */ - public function add_payment_token( $token ) { - if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) { - return false; - } - - $token_ids = get_post_meta( $this->id, '_payment_tokens', true ); - if ( empty ( $token_ids ) ) { - $token_ids = array(); - } - $token_ids[] = $token->get_id(); - - update_post_meta( $this->id, '_payment_tokens', $token_ids ); - do_action( 'woocommerce_payment_token_added_to_order', $this->id, $token->get_id(), $token, $token_ids ); - return true; - } + /* + |-------------------------------------------------------------------------- + | Calculations. + |-------------------------------------------------------------------------- + | + | These methods calculate order totals and taxes based on the current data. + | + */ /** - * Set the payment method for the order. - * - * @param WC_Payment_Gateway|string $payment_method - */ - public function set_payment_method( $payment_method = '' ) { - if ( is_object( $payment_method ) ) { - update_post_meta( $this->id, '_payment_method', $payment_method->id ); - update_post_meta( $this->id, '_payment_method_title', $payment_method->get_title() ); - } else { - update_post_meta( $this->id, '_payment_method', '' ); - update_post_meta( $this->id, '_payment_method_title', '' ); - } - } - - /** - * Set the customer address. - * - * @param array $address Address data. - * @param string $type billing or shipping. - */ - public function set_address( $address, $type = 'billing' ) { - - foreach ( $address as $key => $value ) { - update_post_meta( $this->id, "_{$type}_" . $key, $value ); - } - } - - /** - * Returns the requested address in raw, non-formatted way. - * @since 2.4.0 - * @param string $type Billing or shipping. Anything else besides 'billing' will return shipping address. - * @return array The stored address after filter. - */ - public function get_address( $type = 'billing' ) { - - if ( 'billing' === $type ) { - $address = array( - 'first_name' => $this->billing_first_name, - 'last_name' => $this->billing_last_name, - 'company' => $this->billing_company, - 'address_1' => $this->billing_address_1, - 'address_2' => $this->billing_address_2, - 'city' => $this->billing_city, - 'state' => $this->billing_state, - 'postcode' => $this->billing_postcode, - 'country' => $this->billing_country, - 'email' => $this->billing_email, - 'phone' => $this->billing_phone, - ); - } else { - $address = array( - 'first_name' => $this->shipping_first_name, - 'last_name' => $this->shipping_last_name, - 'company' => $this->shipping_company, - 'address_1' => $this->shipping_address_1, - 'address_2' => $this->shipping_address_2, - 'city' => $this->shipping_city, - 'state' => $this->shipping_state, - 'postcode' => $this->shipping_postcode, - 'country' => $this->shipping_country, - ); - } - - return apply_filters( 'woocommerce_get_order_address', $address, $type, $this ); - } - - /** - * Add a product line item to the order. + * Calculate shipping total. * * @since 2.2 - * @param \WC_Product $product - * @param int $qty Line item quantity. - * @param array $args - * @return int|bool Item ID or false. + * @return float */ - public function add_product( $product, $qty = 1, $args = array() ) { - $args = wp_parse_args( $args, array( - 'variation' => array(), - 'totals' => array() - ) ); + public function calculate_shipping() { + $shipping_total = 0; - if ( ! $product ) { - return false; + foreach ( $this->get_shipping_methods() as $shipping ) { + $shipping_total += $shipping->get_total(); } - $item_id = wc_add_order_item( $this->id, array( - 'order_item_name' => $product->get_title(), - 'order_item_type' => 'line_item' - ) ); + $this->set_shipping_total( $shipping_total ); + $this->save(); - if ( ! $item_id ) { - return false; - } - - wc_add_order_item_meta( $item_id, '_qty', wc_stock_amount( $qty ) ); - wc_add_order_item_meta( $item_id, '_tax_class', $product->get_tax_class() ); - wc_add_order_item_meta( $item_id, '_product_id', $product->id ); - wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 ); - - // Set line item totals, either passed in or from the product - wc_add_order_item_meta( $item_id, '_line_subtotal', wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $qty ) ) ); - wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( isset( $args['totals']['total'] ) ? $args['totals']['total'] : $product->get_price_excluding_tax( $qty ) ) ); - wc_add_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) ); - wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) ); - - // Save tax data - Since 2.2 - if ( isset( $args['totals']['tax_data'] ) ) { - - $tax_data = array(); - $tax_data['total'] = array_map( 'wc_format_decimal', $args['totals']['tax_data']['total'] ); - $tax_data['subtotal'] = array_map( 'wc_format_decimal', $args['totals']['tax_data']['subtotal'] ); - - wc_add_order_item_meta( $item_id, '_line_tax_data', $tax_data ); - } else { - wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) ); - } - - // Add variation meta - if ( ! empty( $args['variation'] ) ) { - foreach ( $args['variation'] as $key => $value ) { - wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value ); - } - } - - // Backorders - if ( $product->backorders_require_notification() && $product->is_on_backorder( $qty ) ) { - wc_add_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $qty - max( 0, $product->get_total_stock() ) ); - } - - do_action( 'woocommerce_order_add_product', $this->id, $item_id, $product, $qty, $args ); - - return $item_id; - } - - - /** - * Update a line item for the order. - * - * Note this does not update order totals. - * - * @since 2.2 - * @param int $item_id order item ID. - * @param array $args data to update. - * @param WC_Product $product - * @return bool - */ - public function update_product( $item_id, $product, $args ) { - - if ( ! $item_id || ! is_object( $product ) ) { - return false; - } - - // quantity - if ( isset( $args['qty'] ) ) { - wc_update_order_item_meta( $item_id, '_qty', wc_stock_amount( $args['qty'] ) ); - } - - // tax class - if ( isset( $args['tax_class'] ) ) { - wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] ); - } - - // set item totals, either provided or from product - if ( isset( $args['qty'] ) ) { - wc_update_order_item_meta( $item_id, '_line_subtotal', wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $args['qty'] ) ) ); - wc_update_order_item_meta( $item_id, '_line_total', wc_format_decimal( isset( $args['totals']['total'] ) ? $args['totals']['total'] : $product->get_price_excluding_tax( $args['qty'] ) ) ); - } - - // set item tax totals - wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) ); - wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) ); - - // variation meta - if ( isset( $args['variation'] ) && is_array( $args['variation'] ) ) { - - foreach ( $args['variation'] as $key => $value ) { - wc_update_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value ); - } - } - - // backorders - if ( isset( $args['qty'] ) && $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) { - wc_update_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ) ); - } - - do_action( 'woocommerce_order_edit_product', $this->id, $item_id, $args, $product ); - - return true; - } - - - /** - * Add coupon code to the order. - * - * @param string $code - * @param int $discount_amount - * @param int $discount_amount_tax "Discounted" tax - used for tax inclusive prices. - * @return int|bool Item ID or false. - */ - public function add_coupon( $code, $discount_amount = 0, $discount_amount_tax = 0 ) { - $item_id = wc_add_order_item( $this->id, array( - 'order_item_name' => $code, - 'order_item_type' => 'coupon' - ) ); - - if ( ! $item_id ) { - return false; - } - - wc_add_order_item_meta( $item_id, 'discount_amount', $discount_amount ); - wc_add_order_item_meta( $item_id, 'discount_amount_tax', $discount_amount_tax ); - - do_action( 'woocommerce_order_add_coupon', $this->id, $item_id, $code, $discount_amount, $discount_amount_tax ); - - return $item_id; - } - - /** - * Update coupon for order. - * - * Note this does not update order totals. - * - * @since 2.2 - * @param int $item_id - * @param array $args - * @return bool - */ - public function update_coupon( $item_id, $args ) { - if ( ! $item_id ) { - return false; - } - - // code - if ( isset( $args['code'] ) ) { - wc_update_order_item( $item_id, array( 'order_item_name' => $args['code'] ) ); - } - - // amount - if ( isset( $args['discount_amount'] ) ) { - wc_update_order_item_meta( $item_id, 'discount_amount', wc_format_decimal( $args['discount_amount'] ) ); - } - if ( isset( $args['discount_amount_tax'] ) ) { - wc_add_order_item_meta( $item_id, 'discount_amount_tax', wc_format_decimal( $args['discount_amount_tax'] ) ); - } - - do_action( 'woocommerce_order_update_coupon', $this->id, $item_id, $args ); - - return true; - } - - /** - * Add a tax row to the order. - * - * @since 2.2 - * @param int tax_rate_id - * @return int|bool Item ID or false. - */ - public function add_tax( $tax_rate_id, $tax_amount = 0, $shipping_tax_amount = 0 ) { - - $code = WC_Tax::get_rate_code( $tax_rate_id ); - - if ( ! $code ) { - return false; - } - - $item_id = wc_add_order_item( $this->id, array( - 'order_item_name' => $code, - 'order_item_type' => 'tax' - ) ); - - if ( ! $item_id ) { - return false; - } - - wc_add_order_item_meta( $item_id, 'rate_id', $tax_rate_id ); - wc_add_order_item_meta( $item_id, 'label', WC_Tax::get_rate_label( $tax_rate_id ) ); - wc_add_order_item_meta( $item_id, 'compound', WC_Tax::is_compound( $tax_rate_id ) ? 1 : 0 ); - wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( $tax_amount ) ); - wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( $shipping_tax_amount ) ); - - do_action( 'woocommerce_order_add_tax', $this->id, $item_id, $tax_rate_id, $tax_amount, $shipping_tax_amount ); - - return $item_id; - } - - /** - * Add a shipping row to the order. - * - * @param WC_Shipping_Rate shipping_rate - * @return int|bool Item ID or false. - */ - public function add_shipping( $shipping_rate ) { - - $item_id = wc_add_order_item( $this->id, array( - 'order_item_name' => $shipping_rate->label, - 'order_item_type' => 'shipping' - ) ); - - if ( ! $item_id ) { - return false; - } - - wc_add_order_item_meta( $item_id, 'method_id', $shipping_rate->id ); - wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_rate->cost ) ); - - // Save shipping taxes - Since 2.2 - $taxes = array_map( 'wc_format_decimal', $shipping_rate->taxes ); - wc_add_order_item_meta( $item_id, 'taxes', $taxes ); - - // Store meta - $shipping_meta = $shipping_rate->get_meta_data(); - if ( ! empty( $shipping_meta ) ) { - foreach ( $shipping_rate->get_meta_data() as $key => $value ) { - wc_add_order_item_meta( $item_id, $key, $value ); - } - } - - do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $shipping_rate ); - - // Update total - $this->set_total( $this->order_shipping + wc_format_decimal( $shipping_rate->cost ), 'shipping' ); - - return $item_id; - } - - /** - * Update shipping method for order. - * - * Note this does not update the order total. - * - * @since 2.2 - * @param int $item_id - * @param array $args - * @return bool - */ - public function update_shipping( $item_id, $args ) { - - if ( ! $item_id ) { - return false; - } - - // method title - if ( isset( $args['method_title'] ) ) { - wc_update_order_item( $item_id, array( 'order_item_name' => $args['method_title'] ) ); - } - - // method ID - if ( isset( $args['method_id'] ) ) { - wc_update_order_item_meta( $item_id, 'method_id', $args['method_id'] ); - } - - // method cost - if ( isset( $args['cost'] ) ) { - // Get old cost before updating - $old_cost = wc_get_order_item_meta( $item_id, 'cost' ); - - // Update - wc_update_order_item_meta( $item_id, 'cost', wc_format_decimal( $args['cost'] ) ); - - // Update total - $this->set_total( $this->order_shipping - wc_format_decimal( $old_cost ) + wc_format_decimal( $args['cost'] ), 'shipping' ); - } - - do_action( 'woocommerce_order_update_shipping', $this->id, $item_id, $args ); - - return true; - } - - /** - * Add a fee to the order. - * - * @param object $fee - * @return int|bool Item ID or false. - */ - public function add_fee( $fee ) { - - $item_id = wc_add_order_item( $this->id, array( - 'order_item_name' => $fee->name, - 'order_item_type' => 'fee' - ) ); - - if ( ! $item_id ) { - return false; - } - - if ( $fee->taxable ) { - wc_add_order_item_meta( $item_id, '_tax_class', $fee->tax_class ); - } else { - wc_add_order_item_meta( $item_id, '_tax_class', '0' ); - } - - wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $fee->amount ) ); - wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $fee->tax ) ); - - // Save tax data - Since 2.2 - $tax_data = array_map( 'wc_format_decimal', $fee->tax_data ); - wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $tax_data ) ); - - do_action( 'woocommerce_order_add_fee', $this->id, $item_id, $fee ); - - return $item_id; - } - - /** - * Update fee for order. - * - * Note this does not update order totals. - * - * @since 2.2 - * @param int $item_id - * @param array $args - * @return bool - */ - public function update_fee( $item_id, $args ) { - - if ( ! $item_id ) { - return false; - } - - // name - if ( isset( $args['name'] ) ) { - wc_update_order_item( $item_id, array( 'order_item_name' => $args['name'] ) ); - } - - // tax class - if ( isset( $args['tax_class'] ) ) { - wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] ); - } - - // total - if ( isset( $args['line_total'] ) ) { - wc_update_order_item_meta( $item_id, '_line_total', wc_format_decimal( $args['line_total'] ) ); - } - - // total tax - if ( isset( $args['line_tax'] ) ) { - wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $args['line_tax'] ) ); - } - - do_action( 'woocommerce_order_update_fee', $this->id, $item_id, $args ); - - return true; - } - - /** - * Set an order total. - * - * @param float $amount - * @param string $total_type - * - * @return bool - */ - public function set_total( $amount, $total_type = 'total' ) { - - if ( ! in_array( $total_type, array( 'shipping', 'tax', 'shipping_tax', 'total', 'cart_discount', 'cart_discount_tax' ) ) ) { - return false; - } - - switch ( $total_type ) { - case 'total' : - $key = '_order_total'; - $amount = wc_format_decimal( $amount, wc_get_price_decimals() ); - break; - case 'cart_discount' : - case 'cart_discount_tax' : - $key = '_' . $total_type; - $amount = wc_format_decimal( $amount ); - break; - default : - $key = '_order_' . $total_type; - $amount = wc_format_decimal( $amount ); - break; - } - - update_post_meta( $this->id, $key, $amount ); - - return true; + return $this->get_shipping_total(); } /** @@ -679,75 +1141,53 @@ abstract class WC_Abstract_Order { * Calculate taxes for all line items and shipping, and store the totals and tax rows. * * Will use the base country unless customer addresses are set. - * - * @return bool success or fail. + * @param $args array Added in 2.7.0 to pass things like location. */ - public function calculate_taxes() { - $tax_total = 0; - $shipping_tax_total = 0; - $taxes = array(); - $shipping_taxes = array(); - $tax_based_on = get_option( 'woocommerce_tax_based_on' ); - - // If is_vat_exempt is 'yes', or wc_tax_enabled is false, return and do nothing. - if ( 'yes' === $this->is_vat_exempt || ! wc_tax_enabled() ) { - return false; - } - - if ( 'billing' === $tax_based_on ) { - $country = $this->billing_country; - $state = $this->billing_state; - $postcode = $this->billing_postcode; - $city = $this->billing_city; - } elseif ( 'shipping' === $tax_based_on ) { - $country = $this->shipping_country; - $state = $this->shipping_state; - $postcode = $this->shipping_postcode; - $city = $this->shipping_city; - } + public function calculate_taxes( $args = array() ) { + $tax_based_on = get_option( 'woocommerce_tax_based_on' ); + $args = wp_parse_args( $args, array( + 'country' => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(), + 'state' => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(), + 'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(), + 'city' => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city(), + ) ); // Default to base - if ( 'base' === $tax_based_on || empty( $country ) ) { - $default = wc_get_base_location(); - $country = $default['country']; - $state = $default['state']; - $postcode = ''; - $city = ''; + if ( 'base' === $tax_based_on || empty( $args['country'] ) ) { + $default = wc_get_base_location(); + $args['country'] = $default['country']; + $args['state'] = $default['state']; + $args['postcode'] = ''; + $args['city'] = ''; } - // Get items + // Calc taxes for line items foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) { + $tax_class = $item->get_tax_class(); + $tax_status = $item->get_tax_status(); - $product = $this->get_product_from_item( $item ); - $line_total = isset( $item['line_total'] ) ? $item['line_total'] : 0; - $line_subtotal = isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0; - $tax_class = $item['tax_class']; - $item_tax_status = $product ? $product->get_tax_status() : 'taxable'; - - if ( '0' !== $tax_class && 'taxable' === $item_tax_status ) { - + if ( '0' !== $tax_class && 'taxable' === $tax_status ) { $tax_rates = WC_Tax::find_rates( array( - 'country' => $country, - 'state' => $state, - 'postcode' => $postcode, - 'city' => $city, - 'tax_class' => $tax_class + 'country' => $args['country'], + 'state' => $args['state'], + 'postcode' => $args['postcode'], + 'city' => $args['city'], + 'tax_class' => $tax_class, ) ); - $line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal, $tax_rates, false ); - $line_taxes = WC_Tax::calc_tax( $line_total, $tax_rates, false ); - $line_subtotal_tax = max( 0, array_sum( $line_subtotal_taxes ) ); - $line_tax = max( 0, array_sum( $line_taxes ) ); - $tax_total += $line_tax; + $total = $item->get_total(); + $taxes = WC_Tax::calc_tax( $total, $tax_rates, false ); - wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( $line_subtotal_tax ) ); - wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) ); - wc_update_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $line_taxes, 'subtotal' => $line_subtotal_taxes ) ); - - // Sum the item taxes - foreach ( array_keys( $taxes + $line_taxes ) as $key ) { - $taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 ); + if ( $item->is_type( 'line_item' ) ) { + $subtotal = $item->get_subtotal(); + $subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false ); + $subtotal_tax = max( 0, array_sum( $subtotal_taxes ) ); + $item->set_subtotal_tax( $subtotal_tax ); + $item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) ); + } else { + $item->set_taxes( array( 'total' => $taxes ) ); } + $item->save(); } } @@ -764,10 +1204,10 @@ abstract class WC_Abstract_Order { $tax_class = sanitize_title( $tax_class ); if ( in_array( $tax_class, $found_tax_classes ) ) { $tax_rates = WC_Tax::find_shipping_rates( array( - 'country' => $country, - 'state' => $state, - 'postcode' => $postcode, - 'city' => $city, + 'country' => $args['country'], + 'state' => $args['state'], + 'postcode' => $args['postcode'], + 'city' => $args['city'], 'tax_class' => $tax_class, ) ); break; @@ -775,100 +1215,37 @@ abstract class WC_Abstract_Order { } } else { $tax_rates = WC_Tax::find_shipping_rates( array( - 'country' => $country, - 'state' => $state, - 'postcode' => $postcode, - 'city' => $city, + 'country' => $args['country'], + 'state' => $args['state'], + 'postcode' => $args['postcode'], + 'city' => $args['city'], 'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class, ) ); } - - $line_taxes = WC_Tax::calc_tax( $item['cost'], $tax_rates, false ); - $line_tax = max( 0, array_sum( $line_taxes ) ); - $shipping_tax_total += $line_tax; - - wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) ); - wc_update_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $line_taxes ) ); - - // Sum the item taxes - foreach ( array_keys( $shipping_taxes + $line_taxes ) as $key ) { - $shipping_taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $shipping_taxes[ $key ] ) ? $shipping_taxes[ $key ] : 0 ); - } + $item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) ); + $item->save(); } - - // Save tax totals - $this->set_total( $shipping_tax_total, 'shipping_tax' ); - $this->set_total( $tax_total, 'tax' ); - - // Tax rows - $this->remove_order_items( 'tax' ); - - // Now merge to keep tax rows - foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) { - $this->add_tax( $tax_rate_id, isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0, isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 ); - } - - return true; - } - - - /** - * Calculate shipping total. - * - * @since 2.2 - * @return float - */ - public function calculate_shipping() { - - $shipping_total = 0; - - foreach ( $this->get_shipping_methods() as $shipping ) { - $shipping_total += $shipping['cost']; - } - - $this->set_total( $shipping_total, 'shipping' ); - - return $this->get_total_shipping(); + $this->update_taxes(); } /** - * Update tax lines at order level by looking at the line item taxes themselves. - * - * @return bool success or fail. + * Update tax lines for the order based on the line item taxes themselves. */ public function update_taxes() { - $order_taxes = array(); - $order_shipping_taxes = array(); + $cart_taxes = array(); + $shipping_taxes = array(); foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) { - - $line_tax_data = maybe_unserialize( $item['line_tax_data'] ); - - if ( isset( $line_tax_data['total'] ) ) { - - foreach ( $line_tax_data['total'] as $tax_rate_id => $tax ) { - - if ( ! isset( $order_taxes[ $tax_rate_id ] ) ) { - $order_taxes[ $tax_rate_id ] = 0; - } - - $order_taxes[ $tax_rate_id ] += $tax; - } + $taxes = $item->get_taxes(); + foreach ( $taxes['total'] as $tax_rate_id => $tax ) { + $cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax; } } - foreach ( $this->get_items( array( 'shipping' ) ) as $item_id => $item ) { - - $line_tax_data = maybe_unserialize( $item['taxes'] ); - - if ( isset( $line_tax_data ) ) { - foreach ( $line_tax_data as $tax_rate_id => $tax ) { - if ( ! isset( $order_shipping_taxes[ $tax_rate_id ] ) ) { - $order_shipping_taxes[ $tax_rate_id ] = 0; - } - - $order_shipping_taxes[ $tax_rate_id ] += $tax; - } + foreach ( $this->get_shipping_methods() as $item_id => $item ) { + $taxes = $item->get_taxes(); + foreach ( $taxes['total'] as $tax_rate_id => $tax ) { + $shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax; } } @@ -876,15 +1253,18 @@ abstract class WC_Abstract_Order { $this->remove_order_items( 'tax' ); // Now merge to keep tax rows. - foreach ( array_keys( $order_taxes + $order_shipping_taxes ) as $tax_rate_id ) { - $this->add_tax( $tax_rate_id, isset( $order_taxes[ $tax_rate_id ] ) ? $order_taxes[ $tax_rate_id ] : 0, isset( $order_shipping_taxes[ $tax_rate_id ] ) ? $order_shipping_taxes[ $tax_rate_id ] : 0 ); + foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) { + $this->add_tax( array( + 'rate_id' => $tax_rate_id, + 'tax_total' => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0, + 'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0, + ) ); } // Save tax totals - $this->set_total( WC_Tax::round( array_sum( $order_shipping_taxes ) ), 'shipping_tax' ); - $this->set_total( WC_Tax::round( array_sum( $order_taxes ) ), 'tax' ); - - return true; + $this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) ); + $this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) ); + $this->save(); } /** @@ -907,809 +1287,148 @@ abstract class WC_Abstract_Order { // line items foreach ( $this->get_items() as $item ) { - $cart_subtotal += wc_format_decimal( isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0 ); - $cart_total += wc_format_decimal( isset( $item['line_total'] ) ? $item['line_total'] : 0 ); - $cart_subtotal_tax += wc_format_decimal( isset( $item['line_subtotal_tax'] ) ? $item['line_subtotal_tax'] : 0 ); - $cart_total_tax += wc_format_decimal( isset( $item['line_tax'] ) ? $item['line_tax'] : 0 ); + $cart_subtotal += wc_format_decimal( $item->get_subtotal() ); + $cart_total += wc_format_decimal( $item->get_total() ); + $cart_subtotal_tax += wc_format_decimal( $item->get_subtotal_tax() ); + $cart_total_tax += wc_format_decimal( $item->get_total_tax() ); } $this->calculate_shipping(); foreach ( $this->get_fees() as $item ) { - $fee_total += $item['line_total']; + $fee_total += $item->get_total(); } - $this->set_total( $cart_subtotal - $cart_total, 'cart_discount' ); - $this->set_total( $cart_subtotal_tax - $cart_total_tax, 'cart_discount_tax' ); + $grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ); - $grand_total = round( $cart_total + $fee_total + $this->get_total_shipping() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ); - - $this->set_total( $grand_total, 'total' ); + $this->set_discount_total( $cart_subtotal - $cart_total ); + $this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax ); + $this->set_total( $grand_total ); + $this->save(); return $grand_total; } - /** - * Gets an order from the database. - * - * @param int $id (default: 0). - * @return bool - */ - public function get_order( $id = 0 ) { - - if ( ! $id ) { - return false; - } - - if ( $result = get_post( $id ) ) { - $this->populate( $result ); - return true; - } - - return false; - } - - /** - * Populates an order from the loaded post data. - * - * @param mixed $result - */ - public function populate( $result ) { - - // Standard post data - $this->id = $result->ID; - $this->order_date = $result->post_date; - $this->modified_date = $result->post_modified; - $this->customer_message = $result->post_excerpt; - $this->customer_note = $result->post_excerpt; - $this->post_status = $result->post_status; - - // Billing email can default to user if set. - if ( empty( $this->billing_email ) && ! empty( $this->customer_user ) && ( $user = get_user_by( 'id', $this->customer_user ) ) ) { - $this->billing_email = $user->user_email; - } - - // Orders store the state of prices including tax when created. - $this->prices_include_tax = metadata_exists( 'post', $this->id, '_prices_include_tax' ) ? get_post_meta( $this->id, '_prices_include_tax', true ) === 'yes' : $this->prices_include_tax; - } - - /** - * __isset function. - * - * @param mixed $key - * @return bool - */ - public function __isset( $key ) { - - if ( ! $this->id ) { - return false; - } - - return metadata_exists( 'post', $this->id, '_' . $key ); - } - - /** - * __get function. - * - * @param mixed $key - * @return mixed - */ - public function __get( $key ) { - // Get values or default if not set. - if ( 'completed_date' === $key ) { - $value = ( $value = get_post_meta( $this->id, '_completed_date', true ) ) ? $value : $this->modified_date; - } elseif ( 'user_id' === $key ) { - $value = ( $value = get_post_meta( $this->id, '_customer_user', true ) ) ? absint( $value ) : ''; - } elseif ( 'status' === $key ) { - $value = $this->get_status(); - } else { - $value = get_post_meta( $this->id, '_' . $key, true ); - } - - return $value; - } - - /** - * Return the order statuses without wc- internal prefix. - * - * Queries get_post_status() directly to avoid having out of date statuses, if updated elsewhere. - * - * @return string - */ - public function get_status() { - $this->post_status = get_post_status( $this->id ); - return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->post_status, 0, 3 ) ? substr( $this->post_status, 3 ) : $this->post_status, $this ); - } - - /** - * Checks the order status against a passed in status. - * - * @return bool - */ - public function has_status( $status ) { - return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status ); - } - - /** - * Gets the user ID associated with the order. Guests are 0. - * - * @since 2.2 - * @return int - */ - public function get_user_id() { - return $this->customer_user ? intval( $this->customer_user ) : 0; - } - - /** - * Get the user associated with the order. False for guests. - * - * @since 2.2 - * @return WP_User|false - */ - public function get_user() { - return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false; - } - - /** - * Get transaction id for the order. - * - * @return string - */ - public function get_transaction_id() { - return get_post_meta( $this->id, '_transaction_id', true ); - } - - /** - * Check if an order key is valid. - * - * @param mixed $key - * @return bool - */ - public function key_is_valid( $key ) { - - if ( $key == $this->order_key ) { - return true; - } - - return false; - } - - /** - * get_order_number function. - * - * Gets the order number for display (by default, order ID). - * - * @return string - */ - public function get_order_number() { - return apply_filters( 'woocommerce_order_number', $this->id, $this ); - } - - /** - * Get a formatted billing address for the order. - * - * @return string - */ - public function get_formatted_billing_address() { - if ( ! $this->formatted_billing_address ) { - - // Formatted Addresses. - $address = apply_filters( 'woocommerce_order_formatted_billing_address', array( - 'first_name' => $this->billing_first_name, - 'last_name' => $this->billing_last_name, - 'company' => $this->billing_company, - 'address_1' => $this->billing_address_1, - 'address_2' => $this->billing_address_2, - 'city' => $this->billing_city, - 'state' => $this->billing_state, - 'postcode' => $this->billing_postcode, - 'country' => $this->billing_country - ), $this ); - - $this->formatted_billing_address = WC()->countries->get_formatted_address( $address ); - } - - return $this->formatted_billing_address; - } - - /** - * Get a formatted shipping address for the order. - * - * @return string - */ - public function get_formatted_shipping_address() { - if ( ! $this->formatted_shipping_address ) { - - if ( $this->shipping_address_1 || $this->shipping_address_2 ) { - - // Formatted Addresses - $address = apply_filters( 'woocommerce_order_formatted_shipping_address', array( - 'first_name' => $this->shipping_first_name, - 'last_name' => $this->shipping_last_name, - 'company' => $this->shipping_company, - 'address_1' => $this->shipping_address_1, - 'address_2' => $this->shipping_address_2, - 'city' => $this->shipping_city, - 'state' => $this->shipping_state, - 'postcode' => $this->shipping_postcode, - 'country' => $this->shipping_country - ), $this ); - - $this->formatted_shipping_address = WC()->countries->get_formatted_address( $address ); - } - } - - return $this->formatted_shipping_address; - } - - /** - * Get a formatted shipping address for the order. - * - * @return string - */ - public function get_shipping_address_map_url() { - $address = apply_filters( 'woocommerce_shipping_address_map_url_parts', array( - 'address_1' => $this->shipping_address_1, - 'address_2' => $this->shipping_address_2, - 'city' => $this->shipping_city, - 'state' => $this->shipping_state, - 'postcode' => $this->shipping_postcode, - 'country' => $this->shipping_country - ), $this ); - - return apply_filters( 'woocommerce_shipping_address_map_url', 'https://maps.google.com/maps?&q=' . urlencode( implode( ', ', $address ) ) . '&z=16', $this ); - } - - /** - * Get the billing address in an array. - * @deprecated 2.3 - * @return string - */ - public function get_billing_address() { - _deprecated_function( 'get_billing_address', '2.3', 'get_formatted_billing_address' ); - return $this->get_formatted_billing_address(); - } - - /** - * Get the shipping address in an array. - * @deprecated 2.3 - * @return string - */ - public function get_shipping_address() { - _deprecated_function( 'get_shipping_address', '2.3', 'get_formatted_shipping_address' ); - return $this->get_formatted_shipping_address(); - } - - /** - * Get a formatted billing full name. - * - * @since 2.4.0 - * - * @return string - */ - public function get_formatted_billing_full_name() { - return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->billing_first_name, $this->billing_last_name ); - } - - /** - * Get a formatted shipping full name. - * - * @since 2.4.0 - * - * @return string - */ - public function get_formatted_shipping_full_name() { - return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->shipping_first_name, $this->shipping_last_name ); - } - - /** - * Return an array of items/products within this order. - * - * @param string|array $type Types of line items to get (array or string). - * @return array - */ - public function get_items( $type = '' ) { - global $wpdb; - - if ( empty( $type ) ) { - $type = array( 'line_item' ); - } - - if ( ! is_array( $type ) ) { - $type = array( $type ); - } - - $items = array(); - $get_items_sql = $wpdb->prepare( "SELECT order_item_id, order_item_name, order_item_type FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d ", $this->id ); - $get_items_sql .= "AND order_item_type IN ( '" . implode( "','", array_map( 'esc_sql', $type ) ) . "' ) ORDER BY order_item_id;"; - $line_items = $wpdb->get_results( $get_items_sql ); - - // Loop items - foreach ( $line_items as $item ) { - $items[ $item->order_item_id ]['name'] = $item->order_item_name; - $items[ $item->order_item_id ]['type'] = $item->order_item_type; - $items[ $item->order_item_id ]['item_meta'] = $this->get_item_meta( $item->order_item_id ); - $items[ $item->order_item_id ]['item_meta_array'] = $this->get_item_meta_array( $item->order_item_id ); - $items[ $item->order_item_id ] = $this->expand_item_meta( $items[ $item->order_item_id ] ); - } - - return apply_filters( 'woocommerce_order_get_items', $items, $this ); - } - - /** - * Expand item meta into the $item array. - * @since 2.4.0 - * @param array $item before expansion. - * @return array - */ - public function expand_item_meta( $item ) { - // Reserved meta keys - $reserved_item_meta_keys = array( - 'name', - 'type', - 'item_meta', - 'item_meta_array', - 'qty', - 'tax_class', - 'product_id', - 'variation_id', - 'line_subtotal', - 'line_total', - 'line_tax', - 'line_subtotal_tax' - ); - - // Expand item meta if set. - if ( ! empty( $item['item_meta'] ) ) { - foreach ( $item['item_meta'] as $name => $value ) { - if ( in_array( $name, $reserved_item_meta_keys ) ) { - continue; - } - if ( '_' === substr( $name, 0, 1 ) ) { - $item[ substr( $name, 1 ) ] = $value[0]; - } elseif ( ! in_array( $name, $reserved_item_meta_keys ) ) { - $item[ $name ] = make_clickable( $value[0] ); - } - } - } - return $item; - } - - /** - * Gets the count of order items of a certain type. - * - * @param string $item_type - * @return string - */ - public function get_item_count( $item_type = '' ) { - if ( empty( $item_type ) ) { - $item_type = array( 'line_item' ); - } - if ( ! is_array( $item_type ) ) { - $item_type = array( $item_type ); - } - - $items = $this->get_items( $item_type ); - $count = 0; - - foreach ( $items as $item ) { - $count += empty( $item['qty'] ) ? 1 : $item['qty']; - } - - return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this ); - } - - /** - * Get refunds - * @return array - */ - public function get_refunds() { return array(); } - - /** - * Return an array of fees within this order. - * - * @return array - */ - public function get_fees() { - return $this->get_items( 'fee' ); - } - - /** - * Return an array of taxes within this order. - * - * @return array - */ - public function get_taxes() { - return $this->get_items( 'tax' ); - } - - /** - * Return an array of shipping costs within this order. - * - * @return array - */ - public function get_shipping_methods() { - return $this->get_items( 'shipping' ); - } - - /** - * Check whether this order has a specific shipping method or not. - * - * @param string $method_id - * - * @return bool - */ - public function has_shipping_method( $method_id ) { - - $shipping_methods = $this->get_shipping_methods(); - $has_method = false; - - if ( empty( $shipping_methods ) ) { - return false; - } - - foreach ( $shipping_methods as $shipping_method ) { - if ( $shipping_method['method_id'] == $method_id ) { - $has_method = true; - } - } - - return $has_method; - } - - /** - * Get taxes, merged by code, formatted ready for output. - * - * @return array - */ - public function get_tax_totals() { - - $taxes = $this->get_items( 'tax' ); - $tax_totals = array(); - - foreach ( $taxes as $key => $tax ) { - - $code = $tax[ 'name' ]; - - if ( ! isset( $tax_totals[ $code ] ) ) { - $tax_totals[ $code ] = new stdClass(); - $tax_totals[ $code ]->amount = 0; - } - - $tax_totals[ $code ]->id = $key; - $tax_totals[ $code ]->rate_id = $tax['rate_id']; - $tax_totals[ $code ]->is_compound = $tax[ 'compound' ]; - $tax_totals[ $code ]->label = isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ]; - $tax_totals[ $code ]->amount += $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ]; - $tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array('currency' => $this->get_order_currency()) ); - } - - if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) { - $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) ); - $tax_totals = array_intersect_key( $tax_totals, $amounts ); - } - - return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this ); - } - - /** - * has_meta function for order items. - * - * @param string $order_item_id - * @return array of meta data. - */ - public function has_meta( $order_item_id ) { - global $wpdb; - - return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, order_item_id - FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d - ORDER BY meta_id", absint( $order_item_id ) ), ARRAY_A ); - } - - /** - * Get all item meta data in array format in the order it was saved. Does not group meta by key like get_item_meta(). - * - * @param mixed $order_item_id - * @return array of objects - */ - public function get_item_meta_array( $order_item_id ) { - global $wpdb; - - // Get cache key - uses get_cache_prefix to invalidate when needed - $cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $order_item_id; - $item_meta_array = wp_cache_get( $cache_key, 'orders' ); - - if ( false === $item_meta_array ) { - $item_meta_array = array(); - $metadata = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d ORDER BY meta_id", absint( $order_item_id ) ) ); - foreach ( $metadata as $metadata_row ) { - $item_meta_array[ $metadata_row->meta_id ] = (object) array( 'key' => $metadata_row->meta_key, 'value' => $metadata_row->meta_value ); - } - wp_cache_set( $cache_key, $item_meta_array, 'orders' ); - } - - return $item_meta_array ; - } - - /** - * Display meta data belonging to an item. - * @param array $item - */ - public function display_item_meta( $item ) { - $product = $this->get_product_from_item( $item ); - $item_meta = new WC_Order_Item_Meta( $item, $product ); - $item_meta->display(); - } - - /** - * Get order item meta. - * - * @param mixed $order_item_id - * @param string $key (default: '') - * @param bool $single (default: false) - * @return array|string - */ - public function get_item_meta( $order_item_id, $key = '', $single = false ) { - return get_metadata( 'order_item', $order_item_id, $key, $single ); - } - - /** Total Getters *******************************************************/ - - /** - * Gets the total discount amount. - * @param bool $ex_tax Show discount excl any tax. - * @return float - */ - public function get_total_discount( $ex_tax = true ) { - if ( ! $this->order_version || version_compare( $this->order_version, '2.3.7', '<' ) ) { - // Backwards compatible total calculation - totals were not stored consistently in old versions. - if ( $ex_tax ) { - if ( $this->prices_include_tax ) { - $total_discount = (double) $this->cart_discount - (double) $this->cart_discount_tax; - } else { - $total_discount = (double) $this->cart_discount; - } - } else { - if ( $this->prices_include_tax ) { - $total_discount = (double) $this->cart_discount; - } else { - $total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax; - } - } - // New logic - totals are always stored exclusive of tax, tax total is stored in cart_discount_tax - } else { - if ( $ex_tax ) { - $total_discount = (double) $this->cart_discount; - } else { - $total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax; - } - } - return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, wc_get_rounding_precision() ), $this ); - } - - /** - * Gets the discount amount. - * @deprecated in favour of get_total_discount() since we now only have one discount type. - * @return float - */ - public function get_cart_discount() { - _deprecated_function( 'get_cart_discount', '2.3', 'get_total_discount' ); - return apply_filters( 'woocommerce_order_amount_cart_discount', $this->get_total_discount(), $this ); - } - - /** - * Get cart discount (formatted). - * - * @deprecated order (after tax) discounts removed in 2.3.0. - * @return string - */ - public function get_order_discount_to_display() { - _deprecated_function( 'get_order_discount_to_display', '2.3' ); - } - - /** - * Gets the total (order) discount amount - these are applied after tax. - * - * @deprecated order (after tax) discounts removed in 2.3.0. - * @return float - */ - public function get_order_discount() { - _deprecated_function( 'get_order_discount', '2.3' ); - return apply_filters( 'woocommerce_order_amount_order_discount', (double) $this->order_discount, $this ); - } - - /** - * Gets cart tax amount. - * - * @return float - */ - public function get_cart_tax() { - return apply_filters( 'woocommerce_order_amount_cart_tax', (double) $this->order_tax, $this ); - } - - /** - * Gets shipping tax amount. - * - * @return float - */ - public function get_shipping_tax() { - return apply_filters( 'woocommerce_order_amount_shipping_tax', (double) $this->order_shipping_tax, $this ); - } - - /** - * Gets shipping and product tax. - * - * @return float - */ - public function get_total_tax() { - return apply_filters( 'woocommerce_order_amount_total_tax', wc_round_tax_total( $this->get_cart_tax() + $this->get_shipping_tax() ), $this ); - } - - /** - * Gets shipping total. - * - * @return float - */ - public function get_total_shipping() { - return apply_filters( 'woocommerce_order_amount_total_shipping', (double) $this->order_shipping, $this ); - } - - /** - * Gets order total. - * - * @return float - */ - public function get_total() { - return apply_filters( 'woocommerce_order_amount_total', (double) $this->order_total, $this ); - } - - /** - * Gets order subtotal. - * - * @return mixed|void - */ - public function get_subtotal() { - $subtotal = 0; - - foreach ( $this->get_items() as $item ) { - $subtotal += ( isset( $item['line_subtotal'] ) ) ? $item['line_subtotal'] : 0; - } - - return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this ); - } - /** * Get item subtotal - this is the cost before discount. * - * @param mixed $item + * @param object $item * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_item_subtotal( $item, $inc_tax = false, $round = true ) { - if ( $inc_tax ) { - $price = ( $item['line_subtotal'] + $item['line_subtotal_tax'] ) / max( 1, $item['qty'] ); - } else { - $price = ( $item['line_subtotal'] / max( 1, $item['qty'] ) ); + $subtotal = 0; + + if ( is_callable( array( $item, 'get_subtotal' ) ) ) { + if ( $inc_tax ) { + $subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_qty() ); + } else { + $subtotal = ( $item->get_subtotal() / max( 1, $item->get_qty() ) ); + } + + $subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal; } - $price = $round ? number_format( (float) $price, wc_get_price_decimals(), '.', '' ) : $price; - - return apply_filters( 'woocommerce_order_amount_item_subtotal', $price, $this, $item, $inc_tax, $round ); + return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round ); } /** * Get line subtotal - this is the cost before discount. * - * @param mixed $item + * @param object $item * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_line_subtotal( $item, $inc_tax = false, $round = true ) { - if ( $inc_tax ) { - $price = $item['line_subtotal'] + $item['line_subtotal_tax']; - } else { - $price = $item['line_subtotal']; + $subtotal = 0; + + if ( is_callable( array( $item, 'get_subtotal' ) ) ) { + if ( $inc_tax ) { + $subtotal = $item->get_subtotal() + $item->get_subtotal_tax(); + } else { + $subtotal = $item->get_subtotal(); + } + + $subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal; } - $price = $round ? round( $price, wc_get_price_decimals() ) : $price; - - return apply_filters( 'woocommerce_order_amount_line_subtotal', $price, $this, $item, $inc_tax, $round ); + return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round ); } /** * Calculate item cost - useful for gateways. * - * @param mixed $item + * @param object $item * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_item_total( $item, $inc_tax = false, $round = true ) { + $total = 0; - $qty = ( ! empty( $item['qty'] ) ) ? $item['qty'] : 1; + if ( is_callable( array( $item, 'get_total' ) ) ) { + if ( $inc_tax ) { + $total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_qty() ); + } else { + $total = $item->get_total() / max( 1, $item->get_qty() ); + } - if ( $inc_tax ) { - $price = ( $item['line_total'] + $item['line_tax'] ) / max( 1, $qty ); - } else { - $price = $item['line_total'] / max( 1, $qty ); + $total = $round ? round( $total, wc_get_price_decimals() ) : $total; } - $price = $round ? round( $price, wc_get_price_decimals() ) : $price; - - return apply_filters( 'woocommerce_order_amount_item_total', $price, $this, $item, $inc_tax, $round ); + return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round ); } /** * Calculate line total - useful for gateways. * - * @param mixed $item + * @param object $item * @param bool $inc_tax (default: false). * @param bool $round (default: true). * @return float */ public function get_line_total( $item, $inc_tax = false, $round = true ) { + $total = 0; - // Check if we need to add line tax to the line total. - $line_total = $inc_tax ? $item['line_total'] + $item['line_tax'] : $item['line_total']; + if ( is_callable( array( $item, 'get_total' ) ) ) { + // Check if we need to add line tax to the line total. + $total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total(); - // Check if we need to round. - $line_total = $round ? round( $line_total, wc_get_price_decimals() ) : $line_total; + // Check if we need to round. + $total = $round ? round( $total, wc_get_price_decimals() ) : $total; + } - return apply_filters( 'woocommerce_order_amount_line_total', $line_total, $this, $item, $inc_tax, $round ); + return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round ); } /** - * Calculate item tax - useful for gateways. + * Get item tax - useful for gateways. * * @param mixed $item * @param bool $round (default: true). * @return float */ public function get_item_tax( $item, $round = true ) { + $tax = 0; - $price = $item['line_tax'] / max( 1, $item['qty'] ); - $price = $round ? wc_round_tax_total( $price ) : $price; + if ( is_callable( array( $item, 'get_total_tax' ) ) ) { + $tax = $item->get_total_tax() / max( 1, $item->get_qty() ); + $tax = $round ? wc_round_tax_total( $tax ) : $tax; + } - return apply_filters( 'woocommerce_order_amount_item_tax', $price, $item, $round, $this ); + return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this ); } /** - * Calculate line tax - useful for gateways. + * Get line tax - useful for gateways. * * @param mixed $item * @return float */ public function get_line_tax( $item ) { - return apply_filters( 'woocommerce_order_amount_line_tax', wc_round_tax_total( $item['line_tax'] ), $item, $this ); - } - - /** End Total Getters *******************************************************/ - - /** - * Gets formatted shipping method title. - * - * @return string - */ - public function get_shipping_method() { - - $labels = array(); - - // Backwards compat < 2.1 - get shipping title stored in meta. - if ( $this->shipping_method_title ) { - $labels[] = $this->shipping_method_title; - } else { - - // 2.1+ get line items for shipping. - $shipping_methods = $this->get_shipping_methods(); - - foreach ( $shipping_methods as $shipping ) { - $labels[] = $shipping['name'] ? $shipping['name'] : __( 'Shipping', 'woocommerce' ); - } - } - - return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $labels ), $this ); + return apply_filters( 'woocommerce_order_amount_line_tax', is_callable( array( $item, 'get_total_tax' ) ) ? wc_round_tax_total( $item->get_total_tax() ) : 0, $item, $this ); } /** @@ -1720,42 +1439,25 @@ abstract class WC_Abstract_Order { * @return string */ public function get_formatted_line_subtotal( $item, $tax_display = '' ) { - - if ( ! $tax_display ) { - $tax_display = $this->tax_display_cart; - } - - if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) { - return ''; - } + $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); if ( 'excl' == $tax_display ) { - $ex_tax_label = $this->prices_include_tax ? 1 : 0; + $ex_tax_label = $this->get_prices_include_tax() ? 1 : 0; - $subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_order_currency() ) ); + $subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_currency() ) ); } else { - $subtotal = wc_price( $this->get_line_subtotal( $item, true ), array('currency' => $this->get_order_currency()) ); + $subtotal = wc_price( $this->get_line_subtotal( $item, true ), array('currency' => $this->get_currency()) ); } return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this ); } - /** - * Gets order currency. - * - * @return string - */ - public function get_order_currency() { - return apply_filters( 'woocommerce_get_order_currency', $this->order_currency, $this ); - } - /** * Gets order total - formatted for display. - * * @return string */ public function get_formatted_order_total() { - $formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_order_currency() ) ); + $formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) ); return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this ); } @@ -1767,96 +1469,77 @@ abstract class WC_Abstract_Order { * @return string */ public function get_subtotal_to_display( $compound = false, $tax_display = '' ) { - - if ( ! $tax_display ) { - $tax_display = $this->tax_display_cart; - } - - $subtotal = 0; + $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); + $subtotal = 0; if ( ! $compound ) { foreach ( $this->get_items() as $item ) { + $subtotal += $item->get_subtotal(); - if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) { - return ''; - } - - $subtotal += $item['line_subtotal']; - - if ( 'incl' == $tax_display ) { - $subtotal += $item['line_subtotal_tax']; + if ( 'incl' === $tax_display ) { + $subtotal += $item->get_subtotal_tax(); } } - $subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) ); + $subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) ); - if ( $tax_display == 'excl' && $this->prices_include_tax ) { + if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) { $subtotal .= ' ' . WC()->countries->ex_tax_or_vat() . ''; } } else { - - if ( 'incl' == $tax_display ) { + if ( 'incl' === $tax_display ) { return ''; } foreach ( $this->get_items() as $item ) { - - $subtotal += $item['line_subtotal']; - + $subtotal += $item->get_subtotal(); } // Add Shipping Costs. - $subtotal += $this->get_total_shipping(); + $subtotal += $this->get_shipping_total(); // Remove non-compound taxes. foreach ( $this->get_taxes() as $tax ) { - - if ( ! empty( $tax['compound'] ) ) { + if ( $this->is_compound() ) { continue; } - - $subtotal = $subtotal + $tax['tax_amount'] + $tax['shipping_tax_amount']; - + $subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total(); } // Remove discounts. $subtotal = $subtotal - $this->get_total_discount(); - - $subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) ); + $subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) ); } return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this ); } - /** * Gets shipping (formatted). * * @return string */ public function get_shipping_to_display( $tax_display = '' ) { - if ( ! $tax_display ) { - $tax_display = $this->tax_display_cart; - } + $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); - if ( $this->order_shipping != 0 ) { + if ( $this->get_shipping_total() != 0 ) { if ( $tax_display == 'excl' ) { // Show shipping excluding tax. - $shipping = wc_price( $this->order_shipping, array('currency' => $this->get_order_currency()) ); + $shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) ); - if ( $this->order_shipping_tax != 0 && $this->prices_include_tax ) { + if ( $this->get_shipping_tax() != 0 && $this->get_prices_include_tax() ) { $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', ' ' . WC()->countries->ex_tax_or_vat() . '', $this, $tax_display ); } } else { // Show shipping including tax. - $shipping = wc_price( $this->order_shipping + $this->order_shipping_tax, array('currency' => $this->get_order_currency()) ); + $shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) ); - if ( $this->order_shipping_tax != 0 && ! $this->prices_include_tax ) { + if ( $this->get_shipping_tax() != 0 && ! $this->get_prices_include_tax() ) { $shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', ' ' . WC()->countries->inc_tax_or_vat() . '', $this, $tax_display ); } @@ -1879,42 +1562,10 @@ abstract class WC_Abstract_Order { * @return string */ public function get_discount_to_display( $tax_display = '' ) { - if ( ! $tax_display ) { - $tax_display = $this->tax_display_cart; - } - return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( $tax_display === 'excl' && $this->display_totals_ex_tax ), array( 'currency' => $this->get_order_currency() ) ), $this ); + $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); + return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( 'excl' === $tax_display && 'excl' === get_option( 'woocommerce_tax_display_cart' ) ), array( 'currency' => $this->get_currency() ) ), $this ); } - /** - * Get cart discount (formatted). - * @deprecated - * @return string - */ - public function get_cart_discount_to_display( $tax_display = '' ) { - _deprecated_function( 'get_cart_discount_to_display', '2.3', 'get_discount_to_display' ); - return apply_filters( 'woocommerce_order_cart_discount_to_display', $this->get_discount_to_display( $tax_display ), $this ); - } - - /** - * Get a product (either product or variation). - * - * @param mixed $item - * @return WC_Product - */ - public function get_product_from_item( $item ) { - - if ( ! empty( $item['variation_id'] ) && 'product_variation' === get_post_type( $item['variation_id'] ) ) { - $_product = wc_get_product( $item['variation_id'] ); - } elseif ( ! empty( $item['product_id'] ) ) { - $_product = wc_get_product( $item['product_id'] ); - } else { - $_product = false; - } - - return apply_filters( 'woocommerce_get_product_from_item', $_product, $item, $this ); - } - - /** * Get totals for display on pages and in emails. * @@ -1922,55 +1573,39 @@ abstract class WC_Abstract_Order { * @return array */ public function get_order_item_totals( $tax_display = '' ) { - - if ( ! $tax_display ) { - $tax_display = $this->tax_display_cart; - } - - $total_rows = array(); + $tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' ); + $total_rows = array(); if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) { $total_rows['cart_subtotal'] = array( 'label' => __( 'Subtotal:', 'woocommerce' ), - 'value' => $subtotal + 'value' => $subtotal, ); } if ( $this->get_total_discount() > 0 ) { $total_rows['discount'] = array( 'label' => __( 'Discount:', 'woocommerce' ), - 'value' => '-' . $this->get_discount_to_display( $tax_display ) + 'value' => '-' . $this->get_discount_to_display( $tax_display ), ); } if ( $this->get_shipping_method() ) { $total_rows['shipping'] = array( 'label' => __( 'Shipping:', 'woocommerce' ), - 'value' => $this->get_shipping_to_display( $tax_display ) + 'value' => $this->get_shipping_to_display( $tax_display ), ); } if ( $fees = $this->get_fees() ) { foreach ( $fees as $id => $fee ) { - - if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', $fee['line_total'] + $fee['line_tax'] == 0, $id ) ) { + if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) { continue; } - - if ( 'excl' == $tax_display ) { - - $total_rows[ 'fee_' . $id ] = array( - 'label' => ( $fee['name'] ? $fee['name'] : __( 'Fee', 'woocommerce' ) ) . ':', - 'value' => wc_price( $fee['line_total'], array('currency' => $this->get_order_currency()) ) - ); - - } else { - - $total_rows[ 'fee_' . $id ] = array( - 'label' => $fee['name'] . ':', - 'value' => wc_price( $fee['line_total'] + $fee['line_tax'], array('currency' => $this->get_order_currency()) ) - ); - } + $total_rows[ 'fee_' . $fee->get_id() ] = array( + 'label' => $fee->get_name() . ':', + 'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) ) + ); } } @@ -1983,7 +1618,7 @@ abstract class WC_Abstract_Order { $total_rows[ sanitize_title( $code ) ] = array( 'label' => $tax->label . ':', - 'value' => $tax->formatted_amount + 'value' => $tax->formatted_amount, ); } @@ -1991,15 +1626,15 @@ abstract class WC_Abstract_Order { $total_rows['tax'] = array( 'label' => WC()->countries->tax_or_vat() . ':', - 'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_order_currency() ) ) + 'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ), ); } } - if ( $this->get_total() > 0 && $this->payment_method_title ) { + if ( $this->get_total() > 0 && $this->get_payment_method_title() ) { $total_rows['payment_method'] = array( 'label' => __( 'Payment Method:', 'woocommerce' ), - 'value' => $this->payment_method_title + 'value' => $this->get_payment_method_title(), ); } @@ -2007,97 +1642,62 @@ abstract class WC_Abstract_Order { foreach ( $refunds as $id => $refund ) { $total_rows[ 'refund_' . $id ] = array( 'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':', - 'value' => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_order_currency() ) ) + 'value' => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ), ); } } $total_rows['order_total'] = array( 'label' => __( 'Total:', 'woocommerce' ), - 'value' => $this->get_formatted_order_total( $tax_display ) + 'value' => $this->get_formatted_order_total( $tax_display ), ); return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this ); } + /* + |-------------------------------------------------------------------------- + | Conditionals + |-------------------------------------------------------------------------- + | + | Checks if a condition is true or false. + | + */ /** - * Output items for display in html emails. - * - * @param array $args Items args. - * @param null $deprecated1 Deprecated arg. - * @param null $deprecated2 Deprecated arg. - * @param null $deprecated3 Deprecated arg. - * @param null $deprecated4 Deprecated arg. - * @param null $deprecated5 Deprecated arg. - * @return string - */ - public function email_order_items_table( $args = array(), $deprecated1 = null, $deprecated2 = null, $deprecated3 = null, $deprecated4 = null, $deprecated5 = null ) { - ob_start(); - - if ( ! is_null( $deprecated1 ) || ! is_null( $deprecated2 ) || ! is_null( $deprecated3 ) || ! is_null( $deprecated4 ) || ! is_null( $deprecated5 ) ) { - _deprecated_argument( __FUNCTION__, '2.5.0' ); - } - - $defaults = array( - 'show_sku' => false, - 'show_image' => false, - 'image_size' => array( 32, 32 ), - 'plain_text' => false, - 'sent_to_admin' => false - ); - - $args = wp_parse_args( $args, $defaults ); - $template = $args['plain_text'] ? 'emails/plain/email-order-items.php' : 'emails/email-order-items.php'; - - wc_get_template( $template, apply_filters( 'woocommerce_email_order_items_args', array( - 'order' => $this, - 'items' => $this->get_items(), - 'show_download_links' => $this->is_download_permitted() && ! $args['sent_to_admin'], - 'show_sku' => $args['show_sku'], - 'show_purchase_note' => $this->is_paid() && ! $args['sent_to_admin'], - 'show_image' => $args['show_image'], - 'image_size' => $args['image_size'], - 'plain_text' => $args['plain_text'], - 'sent_to_admin' => $args['sent_to_admin'] - ) ) ); - - return apply_filters( 'woocommerce_email_order_items_table', ob_get_clean(), $this ); - } - - /** - * Returns if an order has been paid for based on the order status. - * @since 2.5.0 - * @return bool - */ - public function is_paid() { - return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this ); - } - - /** - * Checks if product download is permitted. + * Checks the order status against a passed in status. * * @return bool */ - public function is_download_permitted() { - return apply_filters( 'woocommerce_order_is_download_permitted', $this->has_status( 'completed' ) || ( get_option( 'woocommerce_downloads_grant_access_after_payment' ) == 'yes' && $this->has_status( 'processing' ) ), $this ); + public function has_status( $status ) { + return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status ); } /** - * Returns true if the order contains a downloadable product. + * Check whether this order has a specific shipping method or not. + * + * @param string $method_id * @return bool */ - public function has_downloadable_item() { - foreach ( $this->get_items() as $item ) { - $_product = $this->get_product_from_item( $item ); - - if ( $_product && $_product->exists() && $_product->is_downloadable() && $_product->has_file() ) { + public function has_shipping_method( $method_id ) { + foreach ( $this->get_shipping_methods() as $shipping_method ) { + if ( $shipping_method->get_method_id() === $method_id ) { return true; } } return false; } + /** + * Check if an order key is valid. + * + * @param mixed $key + * @return bool + */ + public function key_is_valid( $key ) { + return $key === $this->get_order_key(); + } + /** * Returns true if the order contains a free product. * @since 2.5.0 @@ -2105,664 +1705,10 @@ abstract class WC_Abstract_Order { */ public function has_free_item() { foreach ( $this->get_items() as $item ) { - if ( ! $item['line_total'] ) { + if ( ! $item->get_total() ) { return true; } } return false; } - - /** - * Generates a URL so that a customer can pay for their (unpaid - pending) order. Pass 'true' for the checkout version which doesn't offer gateway choices. - * - * @param bool $on_checkout - * @return string - */ - public function get_checkout_payment_url( $on_checkout = false ) { - - $pay_url = wc_get_endpoint_url( 'order-pay', $this->id, wc_get_page_permalink( 'checkout' ) ); - - if ( 'yes' == get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) { - $pay_url = str_replace( 'http:', 'https:', $pay_url ); - } - - if ( $on_checkout ) { - $pay_url = add_query_arg( 'key', $this->order_key, $pay_url ); - } else { - $pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->order_key ), $pay_url ); - } - - return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this ); - } - - /** - * Generates a URL for the thanks page (order received). - * - * @return string - */ - public function get_checkout_order_received_url() { - - $order_received_url = wc_get_endpoint_url( 'order-received', $this->id, wc_get_page_permalink( 'checkout' ) ); - - if ( 'yes' == get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) { - $order_received_url = str_replace( 'http:', 'https:', $order_received_url ); - } - - $order_received_url = add_query_arg( 'key', $this->order_key, $order_received_url ); - - return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this ); - } - - /** - * Generates a URL so that a customer can cancel their (unpaid - pending) order. - * - * @param string $redirect - * - * @return string - */ - public function get_cancel_order_url( $redirect = '' ) { - - // Get cancel endpoint - $cancel_endpoint = $this->get_cancel_endpoint(); - - return apply_filters( 'woocommerce_get_cancel_order_url', esc_url( add_query_arg( array( - 'cancel_order' => 'true', - 'order' => $this->order_key, - 'order_id' => $this->id, - 'redirect' => $redirect, - ), $cancel_endpoint ) ) ); - } - - /** - * Generates a raw (unescaped) cancel-order URL for use by payment gateways. - * - * @param string $redirect - * @return string The unescaped cancel-order URL. - */ - public function get_cancel_order_url_raw( $redirect = '' ) { - - // Get cancel endpoint - $cancel_endpoint = $this->get_cancel_endpoint(); - - return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array( - 'cancel_order' => 'true', - 'order' => $this->order_key, - 'order_id' => $this->id, - 'redirect' => $redirect, - ), $cancel_endpoint ) ); - } - - - /** - * Helper method to return the cancel endpoint. - * - * @return string the cancel endpoint; either the cart page or the home page. - */ - public function get_cancel_endpoint() { - - $cancel_endpoint = wc_get_page_permalink( 'cart' ); - if ( ! $cancel_endpoint ) { - $cancel_endpoint = home_url(); - } - - if ( false === strpos( $cancel_endpoint, '?' ) ) { - $cancel_endpoint = trailingslashit( $cancel_endpoint ); - } - - return $cancel_endpoint; - } - - - /** - * Generates a URL to view an order from the my account page. - * - * @return string - */ - public function get_view_order_url() { - - $view_order_url = wc_get_endpoint_url( 'view-order', $this->id, wc_get_page_permalink( 'myaccount' ) ); - - return apply_filters( 'woocommerce_get_view_order_url', $view_order_url, $this ); - } - - /** - * Get the downloadable files for an item in this order. - * - * @param array $item - * @return array - */ - public function get_item_downloads( $item ) { - global $wpdb; - - $product_id = $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id']; - $product = wc_get_product( $product_id ); - if ( ! $product ) { - /** - * $product can be `false`. Example: checking an old order, when a product or variation has been deleted. - * @see \WC_Product_Factory::get_product - */ - return array(); - } - $download_ids = $wpdb->get_col( $wpdb->prepare(" - SELECT download_id - FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions - WHERE user_email = %s - AND order_key = %s - AND product_id = %s - ORDER BY permission_id - ", $this->billing_email, $this->order_key, $product_id ) ); - - $files = array(); - - foreach ( $download_ids as $download_id ) { - - if ( $product->has_file( $download_id ) ) { - $files[ $download_id ] = $product->get_file( $download_id ); - $files[ $download_id ]['download_url'] = $this->get_download_url( $product_id, $download_id ); - } - } - - return apply_filters( 'woocommerce_get_item_downloads', $files, $item, $this ); - } - - /** - * Display download links for an order item. - * @param array $item - */ - public function display_item_downloads( $item ) { - $product = $this->get_product_from_item( $item ); - - if ( $product && $product->exists() && $product->is_downloadable() && $this->is_download_permitted() ) { - $download_files = $this->get_item_downloads( $item ); - $i = 0; - $links = array(); - - foreach ( $download_files as $download_id => $file ) { - $i++; - $prefix = count( $download_files ) > 1 ? sprintf( __( 'Download %d', 'woocommerce' ), $i ) : __( 'Download', 'woocommerce' ); - $links[] = '' . $prefix . ': ' . esc_html( $file['name'] ) . '' . "\n"; - } - - echo '
' . implode( '
', $links ); - } - } - - /** - * Get the Download URL. - * - * @param int $product_id - * @param int $download_id - * @return string - */ - public function get_download_url( $product_id, $download_id ) { - return add_query_arg( array( - 'download_file' => $product_id, - 'order' => $this->order_key, - 'email' => urlencode( $this->billing_email ), - 'key' => $download_id - ), trailingslashit( home_url() ) ); - } - - /** - * Adds a note (comment) to the order. - * - * @param string $note Note to add. - * @param int $is_customer_note (default: 0) Is this a note for the customer? - * @param bool added_by_user Was the note added by a user? - * @return int Comment ID. - */ - public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) { - if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->id ) && $added_by_user ) { - $user = get_user_by( 'id', get_current_user_id() ); - $comment_author = $user->display_name; - $comment_author_email = $user->user_email; - } else { - $comment_author = __( 'WooCommerce', 'woocommerce' ); - $comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@'; - $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com'; - $comment_author_email = sanitize_email( $comment_author_email ); - } - - $comment_post_ID = $this->id; - $comment_author_url = ''; - $comment_content = $note; - $comment_agent = 'WooCommerce'; - $comment_type = 'order_note'; - $comment_parent = 0; - $comment_approved = 1; - $commentdata = apply_filters( 'woocommerce_new_order_note_data', compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ), array( 'order_id' => $this->id, 'is_customer_note' => $is_customer_note ) ); - - $comment_id = wp_insert_comment( $commentdata ); - - if ( $is_customer_note ) { - add_comment_meta( $comment_id, 'is_customer_note', 1 ); - - do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->id, 'customer_note' => $commentdata['comment_content'] ) ); - } - - return $comment_id; - } - - /** - * Updates status of order. - * - * @param string $new_status Status to change the order to. No internal wc- prefix is required. - * @param string $note (default: '') Optional note to add. - * @param bool $manual is this a manual order status change? - * @return bool Successful change or not - */ - public function update_status( $new_status, $note = '', $manual = false ) { - if ( ! $this->id ) { - return false; - } - - // Standardise status names. - $new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status; - $old_status = $this->get_status(); - - // If the old status is unknown (e.g. draft) assume its pending for action usage. - if ( ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) { - $old_status = 'pending'; - } - - // If the statuses are the same there is no need to update, unless the post status is not a valid 'wc' status. - if ( $new_status === $old_status && in_array( $this->post_status, array_keys( wc_get_order_statuses() ) ) ) { - return false; - } - - $this->post_status = 'wc-' . $new_status; - $update_post_data = array( - 'ID' => $this->id, - 'post_status' => $this->post_status, - ); - - if ( 'pending' === $old_status && ! $manual ) { - $update_post_data[ 'post_date' ] = current_time( 'mysql', 0 ); - $update_post_data[ 'post_date_gmt' ] = current_time( 'mysql', 1 ); - } - - if ( ! wp_update_post( $update_post_data ) ) { - $this->add_order_note( sprintf( __( 'Unable to update order from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $old_status ), wc_get_order_status_name( $new_status ) ), 0, $manual ); - return false; - } - - // Status was set. - do_action( 'woocommerce_order_status_' . $new_status, $this->id ); - - // Status was changed. - if ( $new_status !== $old_status ) { - $this->add_order_note( trim( $note . ' ' . sprintf( __( 'Order status changed from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $old_status ), wc_get_order_status_name( $new_status ) ) ), 0, $manual ); - do_action( 'woocommerce_order_status_' . $old_status . '_to_' . $new_status, $this->id ); - do_action( 'woocommerce_order_status_changed', $this->id, $old_status, $new_status ); - } else { - $this->add_order_note( trim( $note . ' ' . sprintf( __( 'Order status changed to %s.', 'woocommerce' ), wc_get_order_status_name( $new_status ) ) ), 0, $manual ); - } - - switch ( $new_status ) { - - case 'completed' : - // Record the sales. - $this->record_product_sales(); - - // Increase coupon usage counts. - $this->increase_coupon_usage_counts(); - - // Record the completed date of the order. - update_post_meta( $this->id, '_completed_date', current_time('mysql') ); - - // Update reports. - wc_delete_shop_order_transients( $this->id ); - break; - - case 'processing' : - case 'on-hold' : - // Record the sales. - $this->record_product_sales(); - - // Increase coupon usage counts. - $this->increase_coupon_usage_counts(); - - // Update reports. - wc_delete_shop_order_transients( $this->id ); - break; - - case 'cancelled' : - // If the order is cancelled, restore used coupons. - $this->decrease_coupon_usage_counts(); - - // Update reports. - wc_delete_shop_order_transients( $this->id ); - break; - } - - return true; - } - - - /** - * Cancel the order and restore the cart (before payment). - * - * @param string $note (default: '') Optional note to add. - */ - public function cancel_order( $note = '' ) { - WC()->session->set( 'order_awaiting_payment', false ); - $this->update_status( 'cancelled', $note ); - } - - /** - * When a payment is complete this function is called. - * - * Most of the time this should mark an order as 'processing' so that admin can process/post the items. - * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action. - * Stock levels are reduced at this point. - * Sales are also recorded for products. - * Finally, record the date of payment. - * - * @param string $transaction_id Optional transaction id to store in post meta. - */ - public function payment_complete( $transaction_id = '' ) { - do_action( 'woocommerce_pre_payment_complete', $this->id ); - - if ( null !== WC()->session ) { - WC()->session->set( 'order_awaiting_payment', false ); - } - - $valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this ); - - if ( $this->id && $this->has_status( $valid_order_statuses ) ) { - $order_needs_processing = false; - - if ( sizeof( $this->get_items() ) > 0 ) { - foreach ( $this->get_items() as $item ) { - if ( $_product = $this->get_product_from_item( $item ) ) { - $virtual_downloadable_item = $_product->is_downloadable() && $_product->is_virtual(); - - if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $_product, $this->id ) ) { - $order_needs_processing = true; - break; - } - } else { - $order_needs_processing = true; - break; - } - } - } - - $this->update_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->id ) ); - - add_post_meta( $this->id, '_paid_date', current_time( 'mysql' ), true ); - - if ( ! empty( $transaction_id ) ) { - update_post_meta( $this->id, '_transaction_id', $transaction_id ); - } - - // Payment is complete so reduce stock levels - if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $this->id, '_order_stock_reduced', true ), $this->id ) ) { - $this->reduce_order_stock(); - } - - do_action( 'woocommerce_payment_complete', $this->id ); - } else { - do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->id ); - } - } - - - /** - * Record sales. - */ - public function record_product_sales() { - if ( 'yes' === get_post_meta( $this->id, '_recorded_sales', true ) ) { - return; - } - - if ( sizeof( $this->get_items() ) > 0 ) { - - foreach ( $this->get_items() as $item ) { - - if ( $item['product_id'] > 0 ) { - $sales = (int) get_post_meta( $item['product_id'], 'total_sales', true ); - $sales += (int) $item['qty']; - - if ( $sales ) { - update_post_meta( $item['product_id'], 'total_sales', $sales ); - } - } - } - } - - update_post_meta( $this->id, '_recorded_sales', 'yes' ); - - /** - * Called when sales for an order are recorded - * - * @param int $order_id order id - */ - do_action( 'woocommerce_recorded_sales', $this->id ); - } - - - /** - * Get coupon codes only. - * - * @return array - */ - public function get_used_coupons() { - - $codes = array(); - $coupons = $this->get_items( 'coupon' ); - - foreach ( $coupons as $item_id => $item ) { - $codes[] = trim( $item['name'] ); - } - - return $codes; - } - - - /** - * Increase applied coupon counts. - */ - public function increase_coupon_usage_counts() { - if ( 'yes' == get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) { - return; - } - - if ( sizeof( $this->get_used_coupons() ) > 0 ) { - - foreach ( $this->get_used_coupons() as $code ) { - if ( ! $code ) { - continue; - } - - $coupon = new WC_Coupon( $code ); - - $used_by = $this->get_user_id(); - - if ( ! $used_by ) { - $used_by = $this->billing_email; - } - - $coupon->inc_usage_count( $used_by ); - } - - update_post_meta( $this->id, '_recorded_coupon_usage_counts', 'yes' ); - } - } - - - /** - * Decrease applied coupon counts. - */ - public function decrease_coupon_usage_counts() { - - if ( 'yes' != get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) { - return; - } - - if ( sizeof( $this->get_used_coupons() ) > 0 ) { - - foreach ( $this->get_used_coupons() as $code ) { - - if ( ! $code ) { - continue; - } - - $coupon = new WC_Coupon( $code ); - - $used_by = $this->get_user_id(); - if ( ! $used_by ) { - $used_by = $this->billing_email; - } - - $coupon->dcr_usage_count( $used_by ); - } - - delete_post_meta( $this->id, '_recorded_coupon_usage_counts' ); - } - } - - /** - * Reduce stock levels for all line items in the order. - * Runs if stock management is enabled, but can be disabled on per-order basis by extensions @since 2.4.0 via woocommerce_can_reduce_order_stock hook. - */ - public function reduce_order_stock() { - if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && apply_filters( 'woocommerce_can_reduce_order_stock', true, $this ) && sizeof( $this->get_items() ) > 0 ) { - foreach ( $this->get_items() as $item ) { - if ( $item['product_id'] > 0 ) { - $_product = $this->get_product_from_item( $item ); - - if ( $_product && $_product->exists() && $_product->managing_stock() ) { - $qty = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $this, $item ); - $new_stock = $_product->reduce_stock( $qty ); - $item_name = $_product->get_sku() ? $_product->get_sku(): $item['product_id']; - - if ( isset( $item['variation_id'] ) && $item['variation_id'] ) { - $this->add_order_note( sprintf( __( 'Item %1$s variation #%2$s stock reduced from %3$s to %4$s.', 'woocommerce' ), $item_name, $item['variation_id'], $new_stock + $qty, $new_stock) ); - } else { - $this->add_order_note( sprintf( __( 'Item %1$s stock reduced from %2$s to %3$s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock) ); - } - $this->send_stock_notifications( $_product, $new_stock, $item['qty'] ); - } - } - } - - add_post_meta( $this->id, '_order_stock_reduced', '1', true ); - - do_action( 'woocommerce_reduce_order_stock', $this ); - } - } - - /** - * Send the stock notifications. - * - * @param WC_Product $product - * @param int $new_stock - * @param int $qty_ordered - */ - public function send_stock_notifications( $product, $new_stock, $qty_ordered ) { - - // Backorders - if ( $new_stock < 0 ) { - do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $this->id, 'quantity' => $qty_ordered ) ); - } - - // stock status notifications - $notification_sent = false; - - if ( 'yes' == get_option( 'woocommerce_notify_no_stock' ) && get_option( 'woocommerce_notify_no_stock_amount' ) >= $new_stock ) { - do_action( 'woocommerce_no_stock', $product ); - $notification_sent = true; - } - - if ( ! $notification_sent && 'yes' == get_option( 'woocommerce_notify_low_stock' ) && get_option( 'woocommerce_notify_low_stock_amount' ) >= $new_stock ) { - do_action( 'woocommerce_low_stock', $product ); - } - - do_action( 'woocommerce_after_send_stock_notifications', $product, $new_stock, $qty_ordered ); - } - - - /** - * List order notes (public) for the customer. - * - * @return array - */ - public function get_customer_order_notes() { - $notes = array(); - $args = array( - 'post_id' => $this->id, - 'approve' => 'approve', - 'type' => '' - ); - - remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) ); - - $comments = get_comments( $args ); - - foreach ( $comments as $comment ) { - if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) { - continue; - } - $comment->comment_content = make_clickable( $comment->comment_content ); - $notes[] = $comment; - } - - add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) ); - - return $notes; - } - - /** - * Checks if an order needs payment, based on status and order total. - * - * @return bool - */ - public function needs_payment() { - - $valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this ); - - if ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ) { - $needs_payment = true; - } else { - $needs_payment = false; - } - - return apply_filters( 'woocommerce_order_needs_payment', $needs_payment, $this, $valid_order_statuses ); - } - - /** - * Checks if an order needs display the shipping address, based on shipping method. - * - * @return boolean - */ - public function needs_shipping_address() { - if ( ! wc_shipping_enabled() ) { - return false; - } - - $hide = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this ); - $needs_address = false; - - foreach ( $this->get_shipping_methods() as $shipping_method ) { - // Remove any instance IDs after : - $shipping_method_id = current( explode( ':', $shipping_method['method_id'] ) ); - - if ( ! in_array( $shipping_method_id, $hide ) ) { - $needs_address = true; - break; - } - } - - return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this ); - } - - /** - * Checks if an order can be edited, specifically for use on the Edit Order screen. - * - * @return bool - */ - public function is_editable() { - return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft', 'failed' ) ), $this ); - } } diff --git a/includes/class-wc-order-factory.php b/includes/class-wc-order-factory.php index 530104be857..c5da64f000b 100644 --- a/includes/class-wc-order-factory.php +++ b/includes/class-wc-order-factory.php @@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) { * The WooCommerce order factory creating the right order objects. * * @class WC_Order_Factory - * @version 2.2.0 + * @version 2.7.0 * @package WooCommerce/Classes * @category Class * @author WooThemes @@ -23,7 +23,7 @@ class WC_Order_Factory { * @param bool $the_order (default: false) * @return WC_Order|bool */ - public function get_order( $the_order = false ) { + public static function get_order( $the_order = false ) { global $post; if ( false === $the_order ) { @@ -31,7 +31,7 @@ class WC_Order_Factory { } elseif ( is_numeric( $the_order ) ) { $the_order = get_post( $the_order ); } elseif ( $the_order instanceof WC_Order ) { - $the_order = get_post( $the_order->id ); + $the_order = get_post( $the_order->get_id() ); } if ( ! $the_order || ! is_object( $the_order ) ) { @@ -56,4 +56,49 @@ class WC_Order_Factory { return new $classname( $the_order ); } + + /** + * Get order item. + * @param int + * @return WC_Order_Item + */ + public static function get_order_item( $item_id = 0 ) { + global $wpdb; + + if ( is_numeric( $item_id ) ) { + $item_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d LIMIT 1;", $item_id ) ); + $item_type = $item_data->order_item_type; + } elseif ( $item_id instanceof WC_Order_Item ) { + $item_data = $item_id->get_data(); + $item_type = $item_data->get_type(); + } elseif( is_object( $item_id ) && ! empty( $item_id->order_item_type ) ) { + $item_data = $item_id; + $item_type = $item_id->order_item_type; + } else { + $item_data = false; + $item_type = false; + } + + if ( $item_data && $item_type ) { + switch ( $item_type ) { + case 'line_item' : + case 'product' : + return new WC_Order_Item_Product( $item_data ); + break; + case 'coupon' : + return new WC_Order_Item_Coupon( $item_data ); + break; + case 'fee' : + return new WC_Order_Item_Fee( $item_data ); + break; + case 'shipping' : + return new WC_Order_Item_Shipping( $item_data ); + break; + case 'tax' : + return new WC_Order_Item_Tax( $item_data ); + break; + } + } + return new WC_Order_Item(); + } } diff --git a/includes/class-wc-order-item-coupon.php b/includes/class-wc-order-item-coupon.php new file mode 100644 index 00000000000..8229ed18dde --- /dev/null +++ b/includes/class-wc-order-item-coupon.php @@ -0,0 +1,187 @@ + 0, + 'order_item_id' => 0, + 'code' => '', + 'discount' => 0, + 'discount_tax' => 0, + ); + + /** + * offsetGet for ArrayAccess/Backwards compatibility. + * @deprecated Add deprecation notices in future release. + * @param string $offset + * @return mixed + */ + public function offsetGet( $offset ) { + if ( 'discount_amount' === $offset ) { + $offset = 'discount'; + } elseif ( 'discount_amount_tax' === $offset ) { + $offset = 'discount_tax'; + } + return parent::offsetGet( $offset ); + } + + /** + * offsetSet for ArrayAccess/Backwards compatibility. + * @deprecated Add deprecation notices in future release. + * @param string $offset + * @param mixed $value + */ + public function offsetSet( $offset, $value ) { + if ( 'discount_amount' === $offset ) { + $offset = 'discount'; + } elseif ( 'discount_amount_tax' === $offset ) { + $offset = 'discount_tax'; + } + parent::offsetSet( $offset, $value ); + } + + /** + * offsetExists for ArrayAccess + * @param string $offset + * @return bool + */ + public function offsetExists( $offset ) { + if ( in_array( $offset, array( 'discount_amount', 'discount_amount_tax' ) ) ) { + return true; + } + return parent::offsetExists( $offset ); + } + + /** + * Read/populate data properties specific to this order item. + */ + public function read( $id ) { + parent::read( $id ); + if ( $this->get_id() ) { + $this->set_discount( get_metadata( 'order_item', $this->get_id(), 'discount_amount', true ) ); + $this->set_discount_tax( get_metadata( 'order_item', $this->get_id(), 'discount_amount_tax', true ) ); + } + } + + /** + * Save properties specific to this order item. + * @return int Item ID + */ + public function save() { + parent::save(); + if ( $this->get_id() ) { + wc_update_order_item_meta( $this->get_id(), 'discount_amount', $this->get_discount() ); + wc_update_order_item_meta( $this->get_id(), 'discount_amount_tax', $this->get_discount_tax() ); + } + + return $this->get_id(); + } + + /** + * Internal meta keys we don't want exposed as part of meta_data. + * @return array() + */ + protected function get_internal_meta_keys() { + return array( 'discount_amount', 'discount_amount_tax' ); + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set order item name. + * @param string $value + */ + public function set_name( $value ) { + $this->set_code( $value ); + } + + /** + * Set code. + * @param string $value + */ + public function set_code( $value ) { + $this->_data['code'] = wc_clean( $value ); + } + + /** + * Set discount amount. + * @param string $value + */ + public function set_discount( $value ) { + $this->_data['discount'] = wc_format_decimal( $value ); + } + + /** + * Set discounted tax amount. + * @param string $value + */ + public function set_discount_tax( $value ) { + $this->_data['discount_tax'] = wc_format_decimal( $value ); + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + */ + + /** + * Get order item type. + * @return string + */ + public function get_type() { + return 'coupon'; + } + + /** + * Get order item name. + * @return string + */ + public function get_name() { + return $this->get_code(); + } + + /** + * Get coupon code. + * @return string + */ + public function get_code() { + return $this->_data['code']; + } + + /** + * Get discount amount. + * @return string + */ + public function get_discount() { + return wc_format_decimal( $this->_data['discount'] ); + } + + /** + * Get discounted tax amount. + * @return string + */ + public function get_discount_tax() { + return wc_format_decimal( $this->_data['discount_tax'] ); + } +} diff --git a/includes/class-wc-order-item-fee.php b/includes/class-wc-order-item-fee.php new file mode 100644 index 00000000000..47332d6b64f --- /dev/null +++ b/includes/class-wc-order-item-fee.php @@ -0,0 +1,231 @@ + 0, + 'order_item_id' => 0, + 'name' => '', + 'tax_class' => '', + 'tax_status' => 'taxable', + 'total' => '', + 'total_tax' => '', + 'taxes' => array( + 'total' => array() + ) + ); + + /** + * offsetGet for ArrayAccess/Backwards compatibility. + * @deprecated Add deprecation notices in future release. + * @param string $offset + * @return mixed + */ + public function offsetGet( $offset ) { + if ( 'line_total' === $offset ) { + $offset = 'total'; + } elseif ( 'line_tax' === $offset ) { + $offset = 'total_tax'; + } elseif ( 'line_tax_data' === $offset ) { + $offset = 'taxes'; + } + return parent::offsetGet( $offset ); + } + + /** + * offsetSet for ArrayAccess/Backwards compatibility. + * @deprecated Add deprecation notices in future release. + * @param string $offset + * @param mixed $value + */ + public function offsetSet( $offset, $value ) { + if ( 'line_total' === $offset ) { + $offset = 'total'; + } elseif ( 'line_tax' === $offset ) { + $offset = 'total_tax'; + } elseif ( 'line_tax_data' === $offset ) { + $offset = 'taxes'; + } + parent::offsetSet( $offset, $value ); + } + + /** + * offsetExists for ArrayAccess + * @param string $offset + * @return bool + */ + public function offsetExists( $offset ) { + if ( in_array( $offset, array( 'line_total', 'line_tax', 'line_tax_data' ) ) ) { + return true; + } + return parent::offsetExists( $offset ); + } + + /** + * Read/populate data properties specific to this order item. + */ + public function read( $id ) { + parent::read( $id ); + if ( $this->get_id() ) { + $this->set_tax_class( get_metadata( 'order_item', $this->get_id(), '_tax_class', true ) ); + $this->set_tax_status( get_metadata( 'order_item', $this->get_id(), '_tax_status', true ) ); + $this->set_total( get_metadata( 'order_item', $this->get_id(), '_line_total', true ) ); + $this->set_total_tax( get_metadata( 'order_item', $this->get_id(), '_line_tax', true ) ); + $this->set_taxes( get_metadata( 'order_item', $this->get_id(), '_line_tax_data', true ) ); + } + } + + /** + * Save properties specific to this order item. + * @return int Item ID + */ + public function save() { + parent::save(); + if ( $this->get_id() ) { + wc_update_order_item_meta( $this->get_id(), '_tax_class', $this->get_tax_class() ); + wc_update_order_item_meta( $this->get_id(), '_tax_status', $this->get_tax_status() ); + wc_update_order_item_meta( $this->get_id(), '_line_total', $this->get_total() ); + wc_update_order_item_meta( $this->get_id(), '_line_tax', $this->get_total_tax() ); + wc_update_order_item_meta( $this->get_id(), '_line_tax_data', $this->get_taxes() ); + } + + return $this->get_id(); + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set tax class. + * @param string $value + */ + public function set_tax_class( $value ) { + $this->_data['tax_class'] = $value; + } + + /** + * Set tax_status. + * @param string $value + */ + public function set_tax_status( $value ) { + if ( in_array( $value, array( 'taxable', 'none' ) ) ) { + $this->_data['tax_status'] = $value; + } else { + $this->_data['tax_status'] = 'taxable'; + } + } + + /** + * Set total. + * @param string $value + */ + public function set_total( $value ) { + $this->_data['total'] = wc_format_decimal( $value ); + } + + /** + * Set total tax. + * @param string $value + */ + public function set_total_tax( $value ) { + $this->_data['total_tax'] = wc_format_decimal( $value ); + } + + /** + * Set taxes. + * + * This is an array of tax ID keys with total amount values. + * @param array $raw_tax_data + */ + public function set_taxes( $raw_tax_data ) { + $raw_tax_data = maybe_unserialize( $raw_tax_data ); + $tax_data = array( + 'total' => array(), + ); + if ( ! empty( $raw_tax_data['total'] ) ) { + $tax_data['total'] = array_map( 'wc_format_decimal', $raw_tax_data['total'] ); + } + $this->_data['taxes'] = $tax_data; + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + */ + + /** + * Get order item name. + * @return string + */ + public function get_name() { + return $this->_data['name'] ? $this->_data['name'] : __( 'Fee', 'woocommerce' ); + } + + /** + * Get order item type. + * @return string + */ + public function get_type() { + return 'fee'; + } + + /** + * Get tax class. + * @return string + */ + public function get_tax_class() { + return $this->_data['tax_class']; + } + + /** + * Get tax status. + * @return string + */ + public function get_tax_status() { + return $this->_data['tax_status']; + } + + /** + * Get total fee. + * @return string + */ + public function get_total() { + return wc_format_decimal( $this->_data['total'] ); + } + + /** + * Get total tax. + * @return string + */ + public function get_total_tax() { + return wc_format_decimal( $this->_data['total_tax'] ); + } + + /** + * Get fee taxes. + * @return array + */ + public function get_taxes() { + return $this->_data['taxes']; + } +} diff --git a/includes/class-wc-order-item-meta.php b/includes/class-wc-order-item-meta.php index aef65beaddf..c20cc28f30b 100644 --- a/includes/class-wc-order-item-meta.php +++ b/includes/class-wc-order-item-meta.php @@ -1,7 +1,6 @@ meta = array_filter( (array) $item ); return; } - $this->item = $item; $this->meta = array_filter( (array) $item['item_meta'] ); $this->product = $product; diff --git a/includes/class-wc-order-item-product.php b/includes/class-wc-order-item-product.php new file mode 100644 index 00000000000..88772eedbbe --- /dev/null +++ b/includes/class-wc-order-item-product.php @@ -0,0 +1,412 @@ + 0, + 'order_item_id' => 0, + 'name' => '', + 'product_id' => 0, + 'variation_id' => 0, + 'qty' => 0, + 'tax_class' => '', + 'subtotal' => 0, + 'subtotal_tax' => 0, + 'total' => 0, + 'total_tax' => 0, + 'taxes' => array( + 'subtotal' => array(), + 'total' => array() + ), + ); + + /** + * offsetGet for ArrayAccess/Backwards compatibility. + * @deprecated Add deprecation notices in future release. + * @param string $offset + * @return mixed + */ + public function offsetGet( $offset ) { + if ( 'line_subtotal' === $offset ) { + $offset = 'subtotal'; + } elseif ( 'line_subtotal_tax' === $offset ) { + $offset = 'subtotal_tax'; + } elseif ( 'line_total' === $offset ) { + $offset = 'total'; + } elseif ( 'line_tax' === $offset ) { + $offset = 'total_tax'; + } elseif ( 'line_tax_data' === $offset ) { + $offset = 'taxes'; + } + return parent::offsetGet( $offset ); + } + + /** + * offsetSet for ArrayAccess/Backwards compatibility. + * @deprecated Add deprecation notices in future release. + * @param string $offset + * @param mixed $value + */ + public function offsetSet( $offset, $value ) { + if ( 'line_subtotal' === $offset ) { + $offset = 'subtotal'; + } elseif ( 'line_subtotal_tax' === $offset ) { + $offset = 'subtotal_tax'; + } elseif ( 'line_total' === $offset ) { + $offset = 'total'; + } elseif ( 'line_tax' === $offset ) { + $offset = 'total_tax'; + } elseif ( 'line_tax_data' === $offset ) { + $offset = 'taxes'; + } + parent::offsetSet( $offset, $value ); + } + + /** + * offsetExists for ArrayAccess + * @param string $offset + * @return bool + */ + public function offsetExists( $offset ) { + if ( in_array( $offset, array( 'line_subtotal', 'line_subtotal_tax', 'line_total', 'line_tax', 'line_tax_data', 'item_meta_array', 'item_meta' ) ) ) { + return true; + } + return parent::offsetExists( $offset ); + } + + /** + * Read/populate data properties specific to this order item. + */ + public function read( $id ) { + parent::read( $id ); + if ( $this->get_id() ) { + $this->set_product_id( get_metadata( 'order_item', $this->get_id(), '_product_id', true ) ); + $this->set_variation_id( get_metadata( 'order_item', $this->get_id(), '_variation_id', true ) ); + $this->set_qty( get_metadata( 'order_item', $this->get_id(), '_qty', true ) ); + $this->set_tax_class( get_metadata( 'order_item', $this->get_id(), '_tax_class', true ) ); + $this->set_subtotal( get_metadata( 'order_item', $this->get_id(), '_line_subtotal', true ) ); + $this->set_subtotal_tax( get_metadata( 'order_item', $this->get_id(), '_line_subtotal_tax', true ) ); + $this->set_total( get_metadata( 'order_item', $this->get_id(), '_line_total', true ) ); + $this->set_total_tax( get_metadata( 'order_item', $this->get_id(), '_line_tax', true ) ); + $this->set_taxes( get_metadata( 'order_item', $this->get_id(), '_line_tax_data', true ) ); + } + } + + /** + * Save properties specific to this order item. + * @return int Item ID + */ + public function save() { + parent::save(); + if ( $this->get_id() ) { + wc_update_order_item_meta( $this->get_id(), '_product_id', $this->get_product_id() ); + wc_update_order_item_meta( $this->get_id(), '_variation_id', $this->get_variation_id() ); + wc_update_order_item_meta( $this->get_id(), '_qty', $this->get_qty() ); + wc_update_order_item_meta( $this->get_id(), '_tax_class', $this->get_tax_class() ); + wc_update_order_item_meta( $this->get_id(), '_line_subtotal', $this->get_subtotal() ); + wc_update_order_item_meta( $this->get_id(), '_line_subtotal_tax', $this->get_subtotal_tax() ); + wc_update_order_item_meta( $this->get_id(), '_line_total', $this->get_total() ); + wc_update_order_item_meta( $this->get_id(), '_line_tax', $this->get_total_tax() ); + wc_update_order_item_meta( $this->get_id(), '_line_tax_data', $this->get_taxes() ); + } + + return $this->get_id(); + } + + /** + * Internal meta keys we don't want exposed as part of meta_data. + * @return array() + */ + protected function get_internal_meta_keys() { + return array( '_product_id', '_variation_id', '_qty', '_tax_class', '_line_subtotal', '_line_subtotal_tax', '_line_total', '_line_tax', '_line_tax_data' ); + } + + /** + * Get the associated product. + * @return WC_Product|bool + */ + public function get_product() { + if ( $this->get_variation_id() ) { + $product = wc_get_product( $this->get_variation_id() ); + } else { + $product = wc_get_product( $this->get_product_id() ); + } + + // Backwards compatible filter from WC_Order::get_product_from_item() + if ( has_filter( 'woocommerce_get_product_from_item' ) ) { + $product = apply_filters( 'woocommerce_get_product_from_item', $product, $this, wc_get_order( $this->get_order_id() ) ); + } + + return apply_filters( 'woocommerce_order_item_product', $product, $this ); + } + + /** + * Get the Download URL. + * @param int $download_id + * @return string + */ + public function get_item_download_url( $download_id ) { + $order = $this->get_order(); + + return $order ? add_query_arg( array( + 'download_file' => $this->get_variation_id() ? $this->get_variation_id() : $this->get_product_id(), + 'order' => $order->get_order_key(), + 'email' => urlencode( $order->get_billing_email() ), + 'key' => $download_id + ), trailingslashit( home_url() ) ) : ''; + } + + /** + * Get any associated downloadable files. + * @return array + */ + public function get_item_downloads() { + global $wpdb; + + $files = array(); + $product = $this->get_product(); + $order = $this->get_order(); + + if ( $product && $order && $product->is_downloadable() && $order->is_download_permitted() ) { + $download_ids = $wpdb->get_col( + $wpdb->prepare( + "SELECT download_id FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions WHERE user_email = %s AND order_key = %s AND product_id = %d ORDER BY permission_id", + $order->get_billing_email(), + $order->get_order_key(), + $this->get_variation_id() ? $this->get_variation_id() : $this->get_product_id() + ) + ); + + foreach ( $download_ids as $download_id ) { + if ( $product->has_file( $download_id ) ) { + $files[ $download_id ] = $product->get_file( $download_id ); + $files[ $download_id ]['download_url'] = $this->get_item_download_url( $download_id ); + } + } + } + + return apply_filters( 'woocommerce_get_item_downloads', $files, $this, $order ); + } + + /** + * Get tax status. + * @return string + */ + public function get_tax_status() { + $product = $this->get_product(); + return $product ? $product->get_tax_status() : 'taxable'; + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set qty. + * @param int $value + */ + public function set_qty( $value ) { + $this->_data['qty'] = wc_stock_amount( $value ); + } + + /** + * Set tax class. + * @param string $value + */ + public function set_tax_class( $value ) { + $this->_data['tax_class'] = $value; + } + + /** + * Set Product ID + * @param int $value + */ + public function set_product_id( $value ) { + $this->_data['product_id'] = absint( $value ); + } + + /** + * Set variation ID. + * @param int $value + */ + public function set_variation_id( $value ) { + $this->_data['variation_id'] = absint( $value ); + } + + /** + * Line subtotal (before discounts). + * @param string $value + */ + public function set_subtotal( $value ) { + $this->_data['subtotal'] = wc_format_decimal( $value ); + } + + /** + * Line total (after discounts). + * @param string $value + */ + public function set_total( $value ) { + $this->_data['total'] = wc_format_decimal( $value ); + } + + /** + * Line subtotal tax (before discounts). + * @param string $value + */ + public function set_subtotal_tax( $value ) { + $this->_data['subtotal_tax'] = wc_format_decimal( $value ); + } + + /** + * Line total tax (after discounts). + * @param string $value + */ + public function set_total_tax( $value ) { + $this->_data['total_tax'] = wc_format_decimal( $value ); + } + + /** + * Set line taxes. + * @param array $raw_tax_data + */ + public function set_taxes( $raw_tax_data ) { + $raw_tax_data = maybe_unserialize( $raw_tax_data ); + $tax_data = array( + 'total' => array(), + 'subtotal' => array() + ); + if ( ! empty( $raw_tax_data['total'] ) && ! empty( $raw_tax_data['subtotal'] ) ) { + $tax_data['total'] = array_map( 'wc_format_decimal', $raw_tax_data['total'] ); + $tax_data['subtotal'] = array_map( 'wc_format_decimal', $raw_tax_data['subtotal'] ); + } + $this->_data['taxes'] = $tax_data; + } + + /** + * Set variation data (stored as meta data - write only). + * @param array $data Key/Value pairs + */ + public function set_variation( $data ) { + foreach ( $data as $key => $value ) { + $this->_meta_data[ str_replace( 'attribute_', '', $key ) ] = $value; + } + } + + /** + * Set properties based on passed in product object. + * @param WC_Product $product + */ + public function set_product( $product ) { + if ( $product ) { + $this->set_product_id( $product->get_id() ); + $this->set_name( $product->get_title() ); + $this->set_tax_class( $product->get_tax_class() ); + $this->set_variation_id( is_callable( array( $product, 'get_variation_id' ) ) ? $product->get_variation_id() : 0 ); + $this->set_variation( is_callable( array( $product, 'get_variation_attributes' ) ) ? $product->get_variation_attributes() : array() ); + } + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + */ + + /** + * Get order item type. + * @return string + */ + public function get_type() { + return 'line_item'; + } + + /** + * Get product ID. + * @return int + */ + public function get_product_id() { + return absint( $this->_data['product_id'] ); + } + + /** + * Get variation ID. + * @return int + */ + public function get_variation_id() { + return absint( $this->_data['variation_id'] ); + } + + /** + * Get qty. + * @return int + */ + public function get_qty() { + return wc_stock_amount( $this->_data['qty'] ); + } + + /** + * Get tax class. + * @return string + */ + public function get_tax_class() { + return $this->_data['tax_class']; + } + + /** + * Get subtotal. + * @return string + */ + public function get_subtotal() { + return wc_format_decimal( $this->_data['subtotal'] ); + } + + /** + * Get subtotal tax. + * @return string + */ + public function get_subtotal_tax() { + return wc_format_decimal( $this->_data['subtotal_tax'] ); + } + + /** + * Get total. + * @return string + */ + public function get_total() { + return wc_format_decimal( $this->_data['total'] ); + } + + /** + * Get total tax. + * @return string + */ + public function get_total_tax() { + return wc_format_decimal( $this->_data['total_tax'] ); + } + + /** + * Get fee taxes. + * @return array + */ + public function get_taxes() { + return $this->_data['taxes']; + } +} diff --git a/includes/class-wc-order-item-shipping.php b/includes/class-wc-order-item-shipping.php new file mode 100644 index 00000000000..ba51826e5cf --- /dev/null +++ b/includes/class-wc-order-item-shipping.php @@ -0,0 +1,233 @@ + 0, + 'order_item_id' => 0, + 'method_title' => '', + 'method_id' => '', + 'total' => 0, + 'total_tax' => 0, + 'taxes' => array( + 'total' => array() + ), + ); + + /** + * offsetGet for ArrayAccess/Backwards compatibility. + * @deprecated Add deprecation notices in future release. + * @param string $offset + * @return mixed + */ + public function offsetGet( $offset ) { + if ( 'cost' === $offset ) { + $offset = 'total'; + } + return parent::offsetGet( $offset ); + } + + /** + * offsetSet for ArrayAccess/Backwards compatibility. + * @deprecated Add deprecation notices in future release. + * @param string $offset + * @param mixed $value + */ + public function offsetSet( $offset, $value ) { + if ( 'cost' === $offset ) { + $offset = 'total'; + } + parent::offsetSet( $offset, $value ); + } + + /** + * offsetExists for ArrayAccess + * @param string $offset + * @return bool + */ + public function offsetExists( $offset ) { + if ( in_array( $offset, array( 'cost' ) ) ) { + return true; + } + return parent::offsetExists( $offset ); + } + + /** + * Read/populate data properties specific to this order item. + */ + public function read( $id ) { + parent::read( $id ); + if ( $this->get_id() ) { + $this->set_method_id( get_metadata( 'order_item', $this->get_id(), 'method_id', true ) ); + $this->set_total( get_metadata( 'order_item', $this->get_id(), 'cost', true ) ); + $this->set_total_tax( get_metadata( 'order_item', $this->get_id(), 'total_tax', true ) ); + $this->set_taxes( get_metadata( 'order_item', $this->get_id(), 'taxes', true ) ); + } + } + + /** + * Save properties specific to this order item. + * @return int Item ID + */ + public function save() { + parent::save(); + if ( $this->get_id() ) { + wc_update_order_item_meta( $this->get_id(), 'method_id', $this->get_method_id() ); + wc_update_order_item_meta( $this->get_id(), 'cost', $this->get_total() ); + wc_update_order_item_meta( $this->get_id(), 'total_tax', $this->get_total_tax() ); + wc_update_order_item_meta( $this->get_id(), 'taxes', $this->get_taxes() ); + } + + return $this->get_id(); + } + + /** + * Internal meta keys we don't want exposed as part of meta_data. + * @return array() + */ + protected function get_internal_meta_keys() { + return array( 'method_id', 'cost', 'total_tax', 'taxes' ); + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set order item name. + * @param string $value + */ + public function set_name( $value ) { + $this->set_method_title( $value ); + } + + /** + * Set code. + * @param string $value + */ + public function set_method_title( $value ) { + $this->_data['method_title'] = wc_clean( $value ); + } + + /** + * Set shipping method id. + * @param string $value + */ + public function set_method_id( $value ) { + $this->_data['method_id'] = wc_clean( $value ); + } + + /** + * Set total. + * @param string $value + */ + public function set_total( $value ) { + $this->_data['total'] = wc_format_decimal( $value ); + } + + /** + * Set total tax. + * @param string $value + */ + public function set_total_tax( $value ) { + $this->_data['total_tax'] = wc_format_decimal( $value ); + } + + /** + * Set taxes. + * + * This is an array of tax ID keys with total amount values. + * @param array $raw_tax_data + */ + public function set_taxes( $raw_tax_data ) { + $raw_tax_data = maybe_unserialize( $raw_tax_data ); + $tax_data = array( + 'total' => array() + ); + if ( ! empty( $raw_tax_data['total'] ) ) { + $tax_data['total'] = array_map( 'wc_format_decimal', $raw_tax_data['total'] ); + } + $this->_data['taxes'] = $tax_data; + $this->set_total_tax( array_sum( $tax_data['total'] ) ); + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + */ + + /** + * Get order item type. + * @return string + */ + public function get_type() { + return 'shipping'; + } + + /** + * Get order item name. + * @return string + */ + public function get_name() { + return $this->get_method_title(); + } + + /** + * Get title. + * @return string + */ + public function get_method_title() { + return $this->_data['method_title'] ? $this->_data['method_title'] : __( 'Shipping', 'woocommerce' ); + } + + /** + * Get method ID. + * @return string + */ + public function get_method_id() { + return $this->_data['method_id']; + } + + /** + * Get total cost. + * @return string + */ + public function get_total() { + return wc_format_decimal( $this->_data['total'] ); + } + + /** + * Get total tax. + * @return string + */ + public function get_total_tax() { + return wc_format_decimal( $this->_data['total_tax'] ); + } + + /** + * Get taxes. + * @return array + */ + public function get_taxes() { + return $this->_data['taxes']; + } +} diff --git a/includes/class-wc-order-item-tax.php b/includes/class-wc-order-item-tax.php new file mode 100644 index 00000000000..5a3cc70a442 --- /dev/null +++ b/includes/class-wc-order-item-tax.php @@ -0,0 +1,210 @@ + 0, + 'order_item_id' => 0, + 'rate_code' => '', + 'rate_id' => 0, + 'label' => '', + 'compound' => false, + 'tax_total' => 0, + 'shipping_tax_total' => 0 + ); + + /** + * Read/populate data properties specific to this order item. + */ + public function read( $id ) { + parent::read( $id ); + if ( $this->get_id() ) { + $this->set_rate_id( get_metadata( 'order_item', $this->get_id(), 'rate_id', true ) ); + $this->set_label( get_metadata( 'order_item', $this->get_id(), 'label', true ) ); + $this->set_compound( get_metadata( 'order_item', $this->get_id(), 'compound', true ) ); + $this->set_tax_total( get_metadata( 'order_item', $this->get_id(), 'tax_amount', true ) ); + $this->set_shipping_tax_total( get_metadata( 'order_item', $this->get_id(), 'shipping_tax_amount', true ) ); + } + } + + /** + * Save properties specific to this order item. + * @return int Item ID + */ + public function save() { + parent::save(); + if ( $this->get_id() ) { + wc_update_order_item_meta( $this->get_id(), 'rate_id', $this->get_rate_id() ); + wc_update_order_item_meta( $this->get_id(), 'label', $this->get_label() ); + wc_update_order_item_meta( $this->get_id(), 'compound', $this->get_compound() ); + wc_update_order_item_meta( $this->get_id(), 'tax_amount', $this->get_tax_total() ); + wc_update_order_item_meta( $this->get_id(), 'shipping_tax_amount', $this->get_shipping_tax_total() ); + } + + return $this->get_id(); + } + + /** + * Internal meta keys we don't want exposed as part of meta_data. + * @return array() + */ + protected function get_internal_meta_keys() { + return array( 'rate_id', 'label', 'compound', 'tax_amount', 'shipping_tax_amount' ); + } + + /** + * Is this a compound tax rate? + * @return boolean + */ + public function is_compound() { + return $this->get_compound(); + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set order item name. + * @param string $value + */ + public function set_name( $value ) { + $this->set_rate_code( $value ); + } + + /** + * Set item name. + * @param string $value + */ + public function set_rate_code( $value ) { + $this->_data['rate_code'] = wc_clean( $value ); + } + + /** + * Set item name. + * @param string $value + */ + public function set_label( $value ) { + $this->_data['label'] = wc_clean( $value ); + } + + /** + * Set tax rate id. + * @param int $value + */ + public function set_rate_id( $value ) { + $this->_data['rate_id'] = absint( $value ); + } + + /** + * Set tax total. + * @param string $value + */ + public function set_tax_total( $value ) { + $this->_data['tax_total'] = wc_format_decimal( $value ); + } + + /** + * Set shipping_tax_total + * @param string $value + */ + public function set_shipping_tax_total( $value ) { + $this->_data['shipping_tax_total'] = wc_format_decimal( $value ); + } + + /** + * Set compound + * @param bool $value + */ + public function set_compound( $value ) { + $this->_data['compound'] = (bool) $value; + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + */ + + /** + * Get order item type. + * @return string + */ + public function get_type() { + return 'tax'; + } + + /** + * Get rate code/name. + * @return string + */ + public function get_name() { + return $this->get_rate_code(); + } + + /** + * Get rate code/name. + * @return string + */ + public function get_rate_code() { + return $this->_data['rate_code']; + } + + /** + * Get label. + * @return string + */ + public function get_label() { + return $this->_data['label'] ? $this->_data['label'] : __( 'Tax', 'woocommerce' ); + } + + /** + * Get tax rate ID. + * @return int + */ + public function get_rate_id() { + return absint( $this->_data['rate_id'] ); + } + + /** + * Get tax_total + * @return string + */ + public function get_tax_total() { + return wc_format_decimal( $this->_data['tax_total'] ); + } + + /** + * Get shipping_tax_total + * @return string + */ + public function get_shipping_tax_total() { + return wc_format_decimal( $this->_data['shipping_tax_total'] ); + } + + /** + * Get compound. + * @return bool + */ + public function get_compound() { + return (bool) $this->_data['compound']; + } +} diff --git a/includes/class-wc-order-item.php b/includes/class-wc-order-item.php new file mode 100644 index 00000000000..c728ba84942 --- /dev/null +++ b/includes/class-wc-order-item.php @@ -0,0 +1,428 @@ + 0, + 'order_item_id' => 0, + 'name' => '', + 'type' => '', + ); + + /** + * May store an order to prevent retriving it multiple times. + * @var object + */ + protected $_order; + + /** + * Stores meta in cache for future reads. + * A group must be set to to enable caching. + * @var string + */ + protected $_cache_group = 'order_itemmeta'; + + /** + * Meta type. This should match up with + * the types avaiable at https://codex.wordpress.org/Function_Reference/add_metadata. + * WP defines 'post', 'user', 'comment', and 'term'. + */ + protected $_meta_type = 'order_item'; + + /** + * Constructor. + * @param int|object|array $order_item ID to load from the DB (optional) or already queried data. + */ + public function __construct( $item = 0 ) { + if ( $item instanceof WC_Order_Item ) { + if ( $this->is_type( $item->get_type() ) ) { + $this->set_all( $item->get_data() ); + } + } elseif ( is_array( $item ) ) { + $this->set_all( $item ); + } else { + $this->read( $item ); + } + } + + /** + * Set all data based on input array. + * @param array $data + * @access private + */ + public function set_all( $data ) { + foreach ( $data as $key => $value ) { + if ( is_callable( array( $this, "set_$key" ) ) ) { + $this->{"set_$key"}( $value ); + } else { + $this->_data[ $key ] = $value; + } + } + } + + /** + * Type checking + * @param string|array $Type + * @return boolean + */ + public function is_type( $type ) { + return is_array( $type ) ? in_array( $this->get_type(), $type ) : $type === $this->get_type(); + } + + /** + * Get qty. + * @return int + */ + public function get_qty() { + return 1; + } + + /** + * Get parent order object. + * @return int + */ + public function get_order() { + if ( ! $this->_order ) { + $this->_order = wc_get_order( $this->get_order_id() ); + } + return $this->_order; + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + */ + + /** + * Get order item ID. + * @return int + */ + public function get_id() { + return $this->get_order_item_id(); + } + + /** + * Get order ID this meta belongs to. + * @return int + */ + public function get_order_id() { + return absint( $this->_data['order_id'] ); + } + + /** + * Get order item ID this meta belongs to. + * @return int + */ + protected function get_order_item_id() { + return absint( $this->_data['order_item_id'] ); + } + + /** + * Get order item name. + * @return string + */ + public function get_name() { + return $this->_data['name']; + } + + /** + * Get order item type. + * @return string + */ + public function get_type() { + return $this->_data['type']; + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + + /** + * Set ID + * @param int $value + */ + public function set_id( $value ) { + $this->set_order_item_id( $value ); + } + + /** + * Set order ID. + * @param int $value + */ + public function set_order_id( $value ) { + $this->_data['order_id'] = absint( $value ); + } + + /** + * Set order item ID. + * @param int $value + */ + protected function set_order_item_id( $value ) { + $this->_data['order_item_id'] = absint( $value ); + } + + /** + * Set order item name. + * @param string $value + */ + public function set_name( $value ) { + $this->_data['name'] = wc_clean( $value ); + } + + /** + * Set order item type. + * @param string $value + */ + protected function set_type( $value ) { + $this->_data['type'] = wc_clean( $value ); + } + + /* + |-------------------------------------------------------------------------- + | CRUD methods + |-------------------------------------------------------------------------- + | + | Methods which create, read, update and delete data from the database. + | + */ + + /** + * Insert data into the database. + * @since 2.7.0 + */ + public function create() { + global $wpdb; + + $wpdb->insert( $wpdb->prefix . 'woocommerce_order_items', array( + 'order_item_name' => $this->get_name(), + 'order_item_type' => $this->get_type(), + 'order_id' => $this->get_order_id() + ) ); + $this->set_id( $wpdb->insert_id ); + + do_action( 'woocommerce_new_order_item', $this->get_id(), $this, $this->get_order_id() ); + } + + /** + * Update data in the database. + * @since 2.7.0 + */ + public function update() { + global $wpdb; + + $wpdb->update( $wpdb->prefix . 'woocommerce_order_items', array( + 'order_item_name' => $this->get_name(), + 'order_item_type' => $this->get_type(), + 'order_id' => $this->get_order_id() + ), array( 'order_item_id' => $this->get_id() ) ); + + do_action( 'woocommerce_update_order_item', $this->get_id(), $this, $this->get_order_id() ); + } + + /** + * Read from the database. + * @since 2.7.0 + * @param int|object $item ID of object to read, or already queried object. + */ + public function read( $item ) { + global $wpdb; + + if ( is_numeric( $item ) && ! empty( $item ) ) { + $data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d LIMIT 1;", $item ) ); + } elseif ( ! empty( $item->order_item_id ) ) { + $data = $item; + } else { + $data = false; + } + + if ( $data ) { + $this->set_order_id( $data->order_id ); + $this->set_id( $data->order_item_id ); + $this->set_name( $data->order_item_name ); + $this->set_type( $data->order_item_type ); + $this->read_meta_data(); + } + } + + /** + * Save data to the database. + * @since 2.7.0 + * @return int Item ID + */ + public function save() { + if ( ! $this->get_id() ) { + $this->create(); + } else { + $this->update(); + } + $this->save_meta_data(); + + return $this->get_id(); + } + + /** + * Delete data from the database. + * @since 2.7.0 + */ + public function delete() { + if ( $this->get_id() ) { + global $wpdb; + do_action( 'woocommerce_before_delete_order_item', $this->get_id() ); + $wpdb->delete( $wpdb->prefix . 'woocommerce_order_items', array( 'order_item_id' => $this->get_id() ) ); + $wpdb->delete( $wpdb->prefix . 'woocommerce_order_itemmeta', array( 'order_item_id' => $this->get_id() ) ); + do_action( 'woocommerce_delete_order_item', $this->get_id() ); + } + } + + /* + |-------------------------------------------------------------------------- + | Meta Data Handling + |-------------------------------------------------------------------------- + */ + + /** + * Expands things like term slugs before return. + * @param string $hideprefix (default: _) + * @return array + */ + public function get_formatted_meta_data( $hideprefix = '_' ) { + $formatted_meta = array(); + $meta_data = $this->get_meta_data(); + + foreach ( $meta_data as $meta ) { + if ( "" === $meta->value || is_serialized( $meta->value ) || ( ! empty( $hideprefix ) && substr( $meta->key, 0, 1 ) === $hideprefix ) ) { + continue; + } + + $attribute_key = urldecode( str_replace( 'attribute_', '', $meta->key ) ); + $display_key = wc_attribute_label( $attribute_key, is_callable( array( $this, 'get_product' ) ) ? $this->get_product() : false ); + $display_value = $meta->value; + + if ( taxonomy_exists( $attribute_key ) ) { + $term = get_term_by( 'slug', $meta->value, $attribute_key ); + if ( ! is_wp_error( $term ) && is_object( $term ) && $term->name ) { + $display_value = $term->name; + } + } + + $formatted_meta[ $meta->meta_id ] = (object) array( + 'key' => $meta->key, + 'value' => $meta->key, + 'display_key' => apply_filters( 'woocommerce_order_item_display_meta_key', $display_key ), + 'display_value' => apply_filters( 'woocommerce_order_item_display_meta_value', $display_value ), + ); + } + + return $formatted_meta; + } + + /* + |-------------------------------------------------------------------------- + | Array Access Methods + |-------------------------------------------------------------------------- + | + | For backwards compat with legacy arrays. + | + */ + + /** + * offsetSet for ArrayAccess + * @param string $offset + * @param mixed $value + */ + public function offsetSet( $offset, $value ) { + if ( 'item_meta_array' === $offset ) { + foreach ( $value as $meta_id => $meta ) { + $this->update_meta_data( $meta->key, $meta->value, $meta_id ); + } + return; + } + + if ( array_key_exists( $offset, $this->_data ) ) { + $this->_data[ $offset ] = $value; + } + + $this->update_meta_data( '_' . $offset, $value ); + } + + /** + * offsetUnset for ArrayAccess + * @param string $offset + */ + public function offsetUnset( $offset ) { + if ( 'item_meta_array' === $offset || 'item_meta' === $offset ) { + $this->_meta_data = array(); + return; + } + + if ( array_key_exists( $offset, $this->_data ) ) { + unset( $this->_data[ $offset ] ); + } + + $this->delete_meta_data( '_' . $offset ); + } + + /** + * offsetExists for ArrayAccess + * @param string $offset + * @return bool + */ + public function offsetExists( $offset ) { + if ( 'item_meta_array' === $offset || 'item_meta' === $offset || array_key_exists( $offset, $this->_data ) ) { + return true; + } + return array_key_exists( '_' . $offset, wp_list_pluck( $this->_meta_data, 'value', 'key' ) ); + } + + /** + * offsetGet for ArrayAccess + * @param string $offset + * @return mixed + */ + public function offsetGet( $offset ) { + if ( 'item_meta_array' === $offset ) { + $return = array(); + + foreach ( $this->_meta_data as $meta ) { + $return[ $meta->meta_id ] = $meta; + } + + return $return; + } + + $meta_values = wp_list_pluck( $this->_meta_data, 'value', 'key' ); + + if ( 'item_meta' === $offset ) { + return $meta_values; + } elseif ( array_key_exists( $offset, $this->_data ) ) { + return $this->_data[ $offset ]; + } elseif ( array_key_exists( '_' . $offset, $meta_values ) ) { + // Item meta was expanded in previous versions, with prefixes removed. This maintains support. + return $meta_values[ '_' . $offset ]; + } + + return null; + } +} diff --git a/includes/class-wc-order-refund.php b/includes/class-wc-order-refund.php index b3ab4ce106e..46f0f5efc62 100644 --- a/includes/class-wc-order-refund.php +++ b/includes/class-wc-order-refund.php @@ -1,113 +1,191 @@ _data = array_merge( $this->_data, array( + 'refund_amount' => '', + 'refund_reason' => '', + 'refunded_by' => 0, + ) ); + parent::__construct( $order ); + } /** - * Init/load the refund object. Called from the constructor. - * - * @param string|int|object|WC_Order_Refund $refund Refund to init - * @uses WP_POST + * Insert data into the database. + * @since 2.7.0 */ - protected function init( $refund ) { - if ( is_numeric( $refund ) ) { - $this->id = absint( $refund ); - $this->post = get_post( $refund ); - $this->get_refund( $this->id ); - } elseif ( $refund instanceof WC_Order_Refund ) { - $this->id = absint( $refund->id ); - $this->post = $refund->post; - $this->get_refund( $this->id ); - } elseif ( isset( $refund->ID ) ) { - $this->id = absint( $refund->ID ); - $this->post = $refund; - $this->get_refund( $this->id ); + public function create() { + parent::create(); + + // Store additonal order data + if ( $this->get_id() ) { + $this->update_post_meta( '_refund_amount', $this->get_refund_amount() ); + $this->update_post_meta( '_refunded_by', $this->get_refunded_by() ); + $this->update_post_meta( '_refund_reason', $this->get_refund_reason() ); } } /** - * Gets an refund from the database. - * - * @since 2.2 - * @param int $id - * @return bool + * Read from the database. + * @since 2.7.0 + * @param int $id ID of object to read. */ - public function get_refund( $id = 0 ) { - if ( ! $id ) { - return false; + public function read( $id ) { + parent::read( $id ); + + // Read additonal order data + if ( $this->get_id() ) { + $post_object = get_post( $id ); + $this->set_refund_amount( get_post_meta( $this->get_id(), '_refund_amount', true ) ); + + // post_author was used before refunded_by meta. + $this->set_refunded_by( metadata_exists( 'post', $this->get_id(), '_refunded_by' ) ? get_post_meta( $this->get_id(), '_refunded_by', true ) : absint( $post_object->post_author ) ); + + // post_excerpt was used before refund_reason meta. + $this->set_refund_reason( metadata_exists( 'post', $this->get_id(), '_refund_reason' ) ? get_post_meta( $this->get_id(), '_refund_reason', true ) : absint( $post_object->post_excerpt ) ); } - - if ( $result = get_post( $id ) ) { - $this->populate( $result ); - - return true; - } - - return false; } /** - * Populates an refund from the loaded post data. - * - * @param mixed $result + * Update data in the database. + * @since 2.7.0 */ - public function populate( $result ) { - // Standard post data - $this->id = $result->ID; - $this->date = $result->post_date; - $this->modified_date = $result->post_modified; - $this->reason = $result->post_excerpt; + public function update() { + parent::update(); + + // Store additonal order data + $this->update_post_meta( '_refund_amount', $this->get_refund_amount() ); + $this->update_post_meta( '_refunded_by', $this->get_refunded_by() ); + $this->update_post_meta( '_refund_reason', $this->get_refund_reason() ); + } + + /** + * Get internal type (post type.) + * @return string + */ + public function get_type() { + return 'shop_order_refund'; + } + + /** + * Get a title for the new post type. + */ + protected function get_post_title() { + return sprintf( __( 'Refund – %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) ); + } + + /** + * Set refunded amount. + * @param string $value + */ + public function set_refund_amount( $value ) { + $this->_data['refund_amount'] = wc_format_decimal( $value ); } /** * Get refunded amount. - * * @since 2.2 * @return int|float */ public function get_refund_amount() { - return apply_filters( 'woocommerce_refund_amount', (double) $this->refund_amount, $this ); + return apply_filters( 'woocommerce_refund_amount', (double) $this->_data['refund_amount'], $this ); } /** * Get formatted refunded amount. - * * @since 2.4 * @return string */ public function get_formatted_refund_amount() { - return apply_filters( 'woocommerce_formatted_refund_amount', wc_price( $this->refund_amount, array('currency' => $this->get_order_currency()) ), $this ); + return apply_filters( 'woocommerce_formatted_refund_amount', wc_price( $this->get_refund_amount(), array( 'currency' => $this->get_currency() ) ), $this ); } + /** + * Set refund reason. + * @param string $value + */ + public function set_refund_reason( $value ) { + $this->_data['refund_reason'] = $value; + } /** - * Get refunded amount. - * + * Get refund reason. * @since 2.2 * @return int|float */ public function get_refund_reason() { - return apply_filters( 'woocommerce_refund_reason', $this->reason, $this ); + return apply_filters( 'woocommerce_refund_reason', $this->_data['refund_reason'], $this ); + } + + /** + * Set refunded by. + * @param int $value + */ + public function set_refunded_by( $value ) { + $this->_data['refunded_by'] = absint( $value ); + } + + /** + * Get ID of user who did the refund. + * @since 2.7 + * @return int + */ + public function get_refunded_by() { + return absint( $this->_data['refunded_by'] ); + } + + /** + * Magic __get method for backwards compatibility. + * @param string $key + * @return mixed + */ + public function __get( $key ) { + _doing_it_wrong( $key, 'Refund properties should not be accessed directly.', '2.7' ); + + /** + * Maps legacy vars to new getters. + */ + if ( 'reason' === $key ) { + return $this->get_refund_reason(); + } elseif ( 'refund_amount' === $key ) { + return $this->get_refund_amount(); + } + return parent::__get( $key ); + } + + /** + * Gets an refund from the database. + * @deprecated 2.7 + * @param int $id (default: 0). + * @return bool + */ + public function get_refund( $id = 0 ) { + _deprecated_function( 'get_refund', '2.7', 'read' ); + if ( ! $id ) { + return false; + } + if ( $result = get_post( $id ) ) { + $this->populate( $result ); + return true; + } + return false; } } diff --git a/includes/class-wc-order.php b/includes/class-wc-order.php index d8ff76290ce..df383a3dba9 100644 --- a/includes/class-wc-order.php +++ b/includes/class-wc-order.php @@ -1,11 +1,13 @@ _data = array_merge( $this->_data, array( + 'billing' => array( + 'first_name' => '', + 'last_name' => '', + 'company' => '', + 'address_1' => '', + 'address_2' => '', + 'city' => '', + 'state' => '', + 'postcode' => '', + 'country' => '', + 'email' => '', + 'phone' => '', + ), + 'shipping' => array( + 'first_name' => '', + 'last_name' => '', + 'company' => '', + 'address_1' => '', + 'address_2' => '', + 'city' => '', + 'state' => '', + 'postcode' => '', + 'country' => '', + ), + 'payment_method' => '', + 'payment_method_title' => '', + 'transaction_id' => '', + 'customer_ip_address' => '', + 'customer_user_agent' => '', + 'created_via' => '', + 'customer_note' => '', + 'date_completed' => '', + 'date_paid' => '', + 'cart_hash' => '', + ) ); + parent::__construct( $order ); + } + + /** + * When a payment is complete this function is called. + * + * Most of the time this should mark an order as 'processing' so that admin can process/post the items. + * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action. + * Stock levels are reduced at this point. + * Sales are also recorded for products. + * Finally, record the date of payment. + * + * Order must exist. + * + * @param string $transaction_id Optional transaction id to store in post meta. + * @return bool success + */ + public function payment_complete( $transaction_id = '' ) { + if ( ! $this->get_id() ) { + return false; + } + + do_action( 'woocommerce_pre_payment_complete', $this->get_id() ); + + if ( ! empty( WC()->session ) ) { + WC()->session->set( 'order_awaiting_payment', false ); + } + + if ( $this->has_status( apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this ) ) ) { + $order_needs_processing = false; + + if ( sizeof( $this->get_items() ) > 0 ) { + foreach ( $this->get_items() as $item ) { + if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) ) { + $virtual_downloadable_item = $product->is_downloadable() && $product->is_virtual(); + + if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $product, $this->get_id() ) ) { + $order_needs_processing = true; + break; + } + } + } + } + + if ( ! empty( $transaction_id ) ) { + $this->set_transaction_id( $transaction_id ); + } + + $this->set_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->get_id() ) ); + $this->set_date_paid( current_time( 'timestamp' ) ); + $this->save(); + + do_action( 'woocommerce_payment_complete', $this->get_id() ); + } else { + do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->get_id() ); + } + + return true; + } /** * Gets order total - formatted for display. - * * @return string */ public function get_formatted_order_total( $tax_display = '', $display_refunded = true ) { - $formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_order_currency() ) ); + $formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) ); $order_total = $this->get_total(); $total_refunded = $this->get_total_refunded(); $tax_string = ''; @@ -35,12 +160,12 @@ class WC_Order extends WC_Abstract_Order { if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) { foreach ( $this->get_tax_totals() as $code => $tax ) { - $tax_amount = ( $total_refunded && $display_refunded ) ? wc_price( WC_Tax::round( $tax->amount - $this->get_total_tax_refunded_by_rate_id( $tax->rate_id ) ), array( 'currency' => $this->get_order_currency() ) ) : $tax->formatted_amount; + $tax_amount = ( $total_refunded && $display_refunded ) ? wc_price( WC_Tax::round( $tax->amount - $this->get_total_tax_refunded_by_rate_id( $tax->rate_id ) ), array( 'currency' => $this->get_currency() ) ) : $tax->formatted_amount; $tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label ); } } else { $tax_amount = ( $total_refunded && $display_refunded ) ? $this->get_total_tax() - $this->get_total_tax_refunded() : $this->get_total_tax(); - $tax_string_array[] = sprintf( '%s %s', wc_price( $tax_amount, array( 'currency' => $this->get_order_currency() ) ), WC()->countries->tax_or_vat() ); + $tax_string_array[] = sprintf( '%s %s', wc_price( $tax_amount, array( 'currency' => $this->get_currency() ) ), WC()->countries->tax_or_vat() ); } if ( ! empty( $tax_string_array ) ) { $tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) ); @@ -48,7 +173,7 @@ class WC_Order extends WC_Abstract_Order { } if ( $total_refunded && $display_refunded ) { - $formatted_total = '' . strip_tags( $formatted_total ) . ' ' . wc_price( $order_total - $total_refunded, array( 'currency' => $this->get_order_currency() ) ) . $tax_string . ''; + $formatted_total = '' . strip_tags( $formatted_total ) . ' ' . wc_price( $order_total - $total_refunded, array( 'currency' => $this->get_currency() ) ) . $tax_string . ''; } else { $formatted_total .= $tax_string; } @@ -56,19 +181,1108 @@ class WC_Order extends WC_Abstract_Order { return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this ); } + /* + |-------------------------------------------------------------------------- + | CRUD methods + |-------------------------------------------------------------------------- + | + | Methods which create, read, update and delete orders from the database. + | Written in abstract fashion so that the way orders are stored can be + | changed more easily in the future. + | + | A save method is included for convenience (chooses update or create based + | on if the order exists yet). + | + */ + + /** + * Insert data into the database. + * @since 2.7.0 + */ + public function create() { + parent::create(); + + // Store additonal order data + if ( $this->get_id() ) { + $this->update_post_meta( '_billing_first_name', $this->get_billing_first_name() ); + $this->update_post_meta( '_billing_last_name', $this->get_billing_last_name() ); + $this->update_post_meta( '_billing_company', $this->get_billing_company() ); + $this->update_post_meta( '_billing_address_1', $this->get_billing_address_1() ); + $this->update_post_meta( '_billing_address_2', $this->get_billing_address_2() ); + $this->update_post_meta( '_billing_city', $this->get_billing_city() ); + $this->update_post_meta( '_billing_state', $this->get_billing_state() ); + $this->update_post_meta( '_billing_postcode', $this->get_billing_postcode() ); + $this->update_post_meta( '_billing_country', $this->get_billing_country() ); + $this->update_post_meta( '_billing_email', $this->get_billing_email() ); + $this->update_post_meta( '_billing_phone', $this->get_billing_phone() ); + $this->update_post_meta( '_shipping_first_name', $this->get_shipping_first_name() ); + $this->update_post_meta( '_shipping_last_name', $this->get_shipping_last_name() ); + $this->update_post_meta( '_shipping_company', $this->get_shipping_company() ); + $this->update_post_meta( '_shipping_address_1', $this->get_shipping_address_1() ); + $this->update_post_meta( '_shipping_address_2', $this->get_shipping_address_2() ); + $this->update_post_meta( '_shipping_city', $this->get_shipping_city() ); + $this->update_post_meta( '_shipping_state', $this->get_shipping_state() ); + $this->update_post_meta( '_shipping_postcode', $this->get_shipping_postcode() ); + $this->update_post_meta( '_shipping_country', $this->get_shipping_country() ); + $this->update_post_meta( '_payment_method', $this->get_payment_method() ); + $this->update_post_meta( '_payment_method_title', $this->get_payment_method_title() ); + $this->update_post_meta( '_transaction_id', $this->get_transaction_id() ); + $this->update_post_meta( '_customer_ip_address', $this->get_customer_ip_address() ); + $this->update_post_meta( '_customer_user_agent', $this->get_customer_user_agent() ); + $this->update_post_meta( '_created_via', $this->get_created_via() ); + $this->update_post_meta( '_customer_note', $this->get_customer_note() ); + $this->update_post_meta( '_date_completed', $this->get_date_completed() ); + $this->update_post_meta( '_date_paid', $this->get_date_paid() ); + $this->update_post_meta( '_cart_hash', $this->get_cart_hash() ); + do_action( 'woocommerce_new_order', $this->get_id() ); + } + } + + /** + * Read from the database. + * @since 2.7.0 + * @param int $id ID of object to read. + */ + public function read( $id ) { + parent::read( $id ); + + // Read additonal order data + if ( $order_id = $this->get_id() ) { + $post_object = get_post( $this->get_id() ); + $this->set_billing_first_name( get_post_meta( $order_id, '_billing_first_name', true ) ); + $this->set_billing_last_name( get_post_meta( $order_id, '_billing_last_name', true ) ); + $this->set_billing_company( get_post_meta( $order_id, '_billing_company', true ) ); + $this->set_billing_address_1( get_post_meta( $order_id, '_billing_address_1', true ) ); + $this->set_billing_address_2( get_post_meta( $order_id, '_billing_address_2', true ) ); + $this->set_billing_city( get_post_meta( $order_id, '_billing_city', true ) ); + $this->set_billing_state( get_post_meta( $order_id, '_billing_state', true ) ); + $this->set_billing_postcode( get_post_meta( $order_id, '_billing_postcode', true ) ); + $this->set_billing_country( get_post_meta( $order_id, '_billing_country', true ) ); + $this->set_billing_email( get_post_meta( $order_id, '_billing_email', true ) ); + $this->set_billing_phone( get_post_meta( $order_id, '_billing_phone', true ) ); + $this->set_shipping_first_name( get_post_meta( $order_id, '_shipping_first_name', true ) ); + $this->set_shipping_last_name( get_post_meta( $order_id, '_shipping_last_name', true ) ); + $this->set_shipping_company( get_post_meta( $order_id, '_shipping_company', true ) ); + $this->set_shipping_address_1( get_post_meta( $order_id, '_shipping_address_1', true ) ); + $this->set_shipping_address_2( get_post_meta( $order_id, '_shipping_address_2', true ) ); + $this->set_shipping_city( get_post_meta( $order_id, '_shipping_city', true ) ); + $this->set_shipping_state( get_post_meta( $order_id, '_shipping_state', true ) ); + $this->set_shipping_postcode( get_post_meta( $order_id, '_shipping_postcode', true ) ); + $this->set_shipping_country( get_post_meta( $order_id, '_shipping_country', true ) ); + $this->set_payment_method( get_post_meta( $order_id, '_payment_method', true ) ); + $this->set_payment_method_title( get_post_meta( $order_id, '_payment_method_title', true ) ); + $this->set_transaction_id( get_post_meta( $order_id, '_transaction_id', true ) ); + $this->set_customer_ip_address( get_post_meta( $order_id, '_customer_ip_address', true ) ); + $this->set_customer_user_agent( get_post_meta( $order_id, '_customer_user_agent', true ) ); + $this->set_created_via( get_post_meta( $order_id, '_created_via', true ) ); + $this->set_customer_note( get_post_meta( $order_id, '_customer_note', true ) ); + $this->set_date_completed( get_post_meta( $order_id, '_completed_date', true ) ); + $this->set_date_paid( get_post_meta( $order_id, '_paid_date', true ) ); + $this->set_cart_hash( get_post_meta( $order_id, '_cart_hash', true ) ); + $this->set_customer_note( $post_object->post_excerpt ); + + // Map user data + if ( ! $this->get_billing_email() && ( $user = $this->get_user() ) ) { + $this->set_billing_email( $user->user_email ); + } + } + } + + /** + * Update data in the database. + * @since 2.7.0 + */ + public function update() { + // Store additonal order data + $this->update_post_meta( '_billing_first_name', $this->get_billing_first_name() ); + $this->update_post_meta( '_billing_last_name', $this->get_billing_last_name() ); + $this->update_post_meta( '_billing_company', $this->get_billing_company() ); + $this->update_post_meta( '_billing_address_1', $this->get_billing_address_1() ); + $this->update_post_meta( '_billing_address_2', $this->get_billing_address_2() ); + $this->update_post_meta( '_billing_city', $this->get_billing_city() ); + $this->update_post_meta( '_billing_state', $this->get_billing_state() ); + $this->update_post_meta( '_billing_postcode', $this->get_billing_postcode() ); + $this->update_post_meta( '_billing_country', $this->get_billing_country() ); + $this->update_post_meta( '_billing_email', $this->get_billing_email() ); + $this->update_post_meta( '_billing_phone', $this->get_billing_phone() ); + $this->update_post_meta( '_shipping_first_name', $this->get_shipping_first_name() ); + $this->update_post_meta( '_shipping_last_name', $this->get_shipping_last_name() ); + $this->update_post_meta( '_shipping_company', $this->get_shipping_company() ); + $this->update_post_meta( '_shipping_address_1', $this->get_shipping_address_1() ); + $this->update_post_meta( '_shipping_address_2', $this->get_shipping_address_2() ); + $this->update_post_meta( '_shipping_city', $this->get_shipping_city() ); + $this->update_post_meta( '_shipping_state', $this->get_shipping_state() ); + $this->update_post_meta( '_shipping_postcode', $this->get_shipping_postcode() ); + $this->update_post_meta( '_shipping_country', $this->get_shipping_country() ); + $this->update_post_meta( '_payment_method', $this->get_payment_method() ); + $this->update_post_meta( '_payment_method_title', $this->get_payment_method_title() ); + $this->update_post_meta( '_transaction_id', $this->get_transaction_id() ); + $this->update_post_meta( '_customer_ip_address', $this->get_customer_ip_address() ); + $this->update_post_meta( '_customer_user_agent', $this->get_customer_user_agent() ); + $this->update_post_meta( '_created_via', $this->get_created_via() ); + $this->update_post_meta( '_customer_note', $this->get_customer_note() ); + $this->update_post_meta( '_date_completed', $this->get_date_completed() ); + $this->update_post_meta( '_date_paid', $this->get_date_paid() ); + $this->update_post_meta( '_cart_hash', $this->get_cart_hash() ); + + $customer_changed = $this->update_post_meta( '_customer_user', $this->get_customer_id() ); + + // Update parent + parent::update(); + + // If customer changed, update any downloadable permissions + if ( $customer_changed ) { + $wpdb->update( $wpdb->prefix . "woocommerce_downloadable_product_permissions", + array( + 'user_id' => $this->get_customer_id(), + 'user_email' => $this->get_billing_email(), + ), + array( + 'order_id' => $this->get_id(), + ), + array( + '%d', + '%s', + ), + array( + '%d', + ) + ); + } + + // Handle status change + $this->status_transition(); + } + + /** + * Set order status. + * @since 2.7.0 + * @param string $new_status Status to change the order to. No internal wc- prefix is required. + * @param string $note (default: '') Optional note to add. + * @param bool $manual_update is this a manual order status change? + * @param array details of change + */ + public function set_status( $new_status, $note = '', $manual_update = false ) { + $result = parent::set_status( $new_status ); + + if ( ! empty( $result['from'] ) && $result['from'] !== $result['to'] ) { + $this->_status_transition = array( + 'from' => ! empty( $this->_status_transition['from'] ) ? $this->_status_transition['from'] : $result['from'], + 'to' => $result['to'], + 'note' => $note, + 'manual' => (bool) $manual_update, + ); + + if ( 'pending' === $result['from'] && ! $manual_update ) { + $this->set_date_paid( current_time( 'timestamp' ) ); + } + + if ( 'completed' === $result['to'] ) { + $this->set_date_completed( current_time( 'timestamp' ) ); + } + } + + return $result; + } + + /** + * Updates status of order immediately. Order must exist. + * @uses WC_Order::set_status() + */ + public function update_status( $new_status, $note = '', $manual = false ) { + if ( ! $this->get_id() ) { + return false; + } + $this->set_status( $new_status, $note, $manual ); + $this->save(); + return true; + } + + /** + * Handle the status transition. + */ + protected function status_transition() { + if ( $this->_status_transition ) { + if ( ! empty( $this->_status_transition['from'] ) ) { + $transition_note = sprintf( __( 'Order status changed from %s to %s.', 'woocommerce' ), wc_get_order_status_name( $this->_status_transition['from'] ), wc_get_order_status_name( $this->_status_transition['to'] ) ); + + do_action( 'woocommerce_order_status_' . $this->_status_transition['from'] . '_to_' . $this->_status_transition['to'], $this->get_id() ); + do_action( 'woocommerce_order_status_changed', $this->get_id(), $this->_status_transition['from'], $this->_status_transition['to'] ); + } else { + $transition_note = sprintf( __( 'Order status set to %s.', 'woocommerce' ), wc_get_order_status_name( $this->_status_transition['to'] ) ); + } + + do_action( 'woocommerce_order_status_' . $this->_status_transition['to'], $this->get_id() ); + + // Note the transition occured + $this->add_order_note( trim( $this->_status_transition['note'] . ' ' . $transition_note ), 0, $this->_status_transition['manual'] ); + + // This has ran, so reset status transition variable + $this->_status_transition = false; + } + } + + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- + | + | Methods for getting data from the order object. + | + */ + + /** + * Get billing_first_name + * @return string + */ + public function get_billing_first_name() { + return $this->_data['billing']['first_name']; + } + + /** + * Get billing_last_name + * @return string + */ + public function get_billing_last_name() { + return $this->_data['billing']['last_name']; + } + + /** + * Get billing_company + * @return string + */ + public function get_billing_company() { + return $this->_data['billing']['company']; + } + + /** + * Get billing_address_1 + * @return string + */ + public function get_billing_address_1() { + return $this->_data['billing']['address_1']; + } + + /** + * Get billing_address_2 + * @return string $value + */ + public function get_billing_address_2() { + return $this->_data['billing']['address_2']; + } + + /** + * Get billing_city + * @return string $value + */ + public function get_billing_city() { + return $this->_data['billing']['city']; + } + + /** + * Get billing_state + * @return string + */ + public function get_billing_state() { + return $this->_data['billing']['state']; + } + + /** + * Get billing_postcode + * @return string + */ + public function get_billing_postcode() { + return $this->_data['billing']['postcode']; + } + + /** + * Get billing_country + * @return string + */ + public function get_billing_country() { + return $this->_data['billing']['country']; + } + + /** + * Get billing_email + * @return string + */ + public function get_billing_email() { + return $this->_data['billing']['email']; + } + + /** + * Get billing_phone + * @return string + */ + public function get_billing_phone() { + return $this->_data['billing']['phone']; + } + + /** + * Get shipping_first_name + * @return string + */ + public function get_shipping_first_name() { + return $this->_data['shipping']['first_name']; + } + + /** + * Get shipping_last_name + * @return string + */ + public function get_shipping_last_name() { + return $this->_data['shipping']['last_name']; + } + + /** + * Get shipping_company + * @return string + */ + public function get_shipping_company() { + return $this->_data['shipping']['company']; + } + + /** + * Get shipping_address_1 + * @return string + */ + public function get_shipping_address_1() { + return $this->_data['shipping']['address_1']; + } + + /** + * Get shipping_address_2 + * @return string + */ + public function get_shipping_address_2() { + return $this->_data['shipping']['address_2']; + } + + /** + * Get shipping_city + * @return string + */ + public function get_shipping_city() { + return $this->_data['shipping']['city']; + } + + /** + * Get shipping_state + * @return string + */ + public function get_shipping_state() { + return $this->_data['shipping']['state']; + } + + /** + * Get shipping_postcode + * @return string + */ + public function get_shipping_postcode() { + return $this->_data['shipping']['postcode']; + } + + /** + * Get shipping_country + * @return string + */ + public function get_shipping_country() { + return $this->_data['shipping']['country']; + } + + /** + * Get the payment method. + * @return string + */ + public function get_payment_method() { + return $this->_data['payment_method']; + } + + /** + * Get payment_method_title + * @return string + */ + public function get_payment_method_title() { + return $this->_data['payment_method_title']; + } + + /** + * Get transaction_id + * @return string + */ + public function get_transaction_id() { + return $this->_data['transaction_id']; + } + + /** + * Get customer_ip_address + * @return string + */ + public function get_customer_ip_address() { + return $this->_data['customer_ip_address']; + } + + /** + * Get customer_user_agent + * @return string + */ + public function get_customer_user_agent() { + return $this->_data['customer_user_agent']; + } + + /** + * Get created_via + * @return string + */ + public function get_created_via() { + return $this->_data['created_via']; + } + + /** + * Get customer_note + * @return string + */ + public function get_customer_note() { + return $this->_data['customer_note']; + } + + /** + * Get date_completed + * @return int + */ + public function get_date_completed() { + return absint( $this->_data['date_completed'] ); + } + + /** + * Get date_paid + * @return int + */ + public function get_date_paid() { + return absint( $this->_data['date_paid'] ); + } + + /** + * Returns the requested address in raw, non-formatted way. + * @since 2.4.0 + * @param string $type Billing or shipping. Anything else besides 'billing' will return shipping address. + * @return array The stored address after filter. + */ + public function get_address( $type = 'billing' ) { + return apply_filters( 'woocommerce_get_order_address', isset( $this->_data[ $type ] ) ? $this->_data[ $type ] : array(), $type, $this ); + } + + /** + * Get a formatted shipping address for the order. + * + * @return string + */ + public function get_shipping_address_map_url() { + $address = apply_filters( 'woocommerce_shipping_address_map_url_parts', $this->get_address( 'shipping' ), $this ); + return apply_filters( 'woocommerce_shipping_address_map_url', 'http://maps.google.com/maps?&q=' . urlencode( implode( ', ', $address ) ) . '&z=16', $this ); + } + + /** + * Get a formatted billing full name. + * @return string + */ + public function get_formatted_billing_full_name() { + return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->get_billing_first_name(), $this->get_billing_last_name() ); + } + + /** + * Get a formatted shipping full name. + * @return string + */ + public function get_formatted_shipping_full_name() { + return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->get_shipping_first_name(), $this->get_shipping_last_name() ); + } + + /** + * Get a formatted billing address for the order. + * @return string + */ + public function get_formatted_billing_address() { + return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_order_formatted_billing_address', $this->get_address( 'billing' ), $this ) ); + } + + /** + * Get a formatted shipping address for the order. + * @return string + */ + public function get_formatted_shipping_address() { + if ( $this->get_shipping_address_1() || $this->get_shipping_address_2() ) { + return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_order_formatted_shipping_address', $this->get_address( 'shipping' ), $this ) ); + } else { + return ''; + } + } + + /** + * Get cart hash + * @return string + */ + public function get_cart_hash() { + return $this->_data['cart_hash']; + } + + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + | + | Functions for setting order data. These should not update anything in the + | database itself and should only change what is stored in the class + | object. However, for backwards compatibility pre 2.7.0 some of these + | setters may handle both. + | + */ + + /** + * Set billing_first_name + * @param string $value + */ + public function set_billing_first_name( $value ) { + $this->_data['billing']['first_name'] = $value; + } + + /** + * Set billing_last_name + * @param string $value + */ + public function set_billing_last_name( $value ) { + $this->_data['billing']['last_name'] = $value; + } + + /** + * Set billing_company + * @param string $value + */ + public function set_billing_company( $value ) { + $this->_data['billing']['company'] = $value; + } + + /** + * Set billing_address_1 + * @param string $value + */ + public function set_billing_address_1( $value ) { + $this->_data['billing']['address_1'] = $value; + } + + /** + * Set billing_address_2 + * @param string $value + */ + public function set_billing_address_2( $value ) { + $this->_data['billing']['address_2'] = $value; + } + + /** + * Set billing_city + * @param string $value + */ + public function set_billing_city( $value ) { + $this->_data['billing']['city'] = $value; + } + + /** + * Set billing_state + * @param string $value + */ + public function set_billing_state( $value ) { + $this->_data['billing']['state'] = $value; + } + + /** + * Set billing_postcode + * @param string $value + */ + public function set_billing_postcode( $value ) { + $this->_data['billing']['postcode'] = $value; + } + + /** + * Set billing_country + * @param string $value + */ + public function set_billing_country( $value ) { + $this->_data['billing']['country'] = $value; + } + + /** + * Set billing_email + * @param string $value + */ + public function set_billing_email( $value ) { + $value = sanitize_email( $value ); + $this->_data['billing']['email'] = is_email( $value ) ? $value : ''; + } + + /** + * Set billing_phone + * @param string $value + */ + public function set_billing_phone( $value ) { + $this->_data['billing']['phone'] = $value; + } + + /** + * Set shipping_first_name + * @param string $value + */ + public function set_shipping_first_name( $value ) { + $this->_data['shipping']['first_name'] = $value; + } + + /** + * Set shipping_last_name + * @param string $value + */ + public function set_shipping_last_name( $value ) { + $this->_data['shipping']['last_name'] = $value; + } + + /** + * Set shipping_company + * @param string $value + */ + public function set_shipping_company( $value ) { + $this->_data['shipping']['company'] = $value; + } + + /** + * Set shipping_address_1 + * @param string $value + */ + public function set_shipping_address_1( $value ) { + $this->_data['shipping']['address_1'] = $value; + } + + /** + * Set shipping_address_2 + * @param string $value + */ + public function set_shipping_address_2( $value ) { + $this->_data['shipping']['address_2'] = $value; + } + + /** + * Set shipping_city + * @param string $value + */ + public function set_shipping_city( $value ) { + $this->_data['shipping']['city'] = $value; + } + + /** + * Set shipping_state + * @param string $value + */ + public function set_shipping_state( $value ) { + $this->_data['shipping']['state'] = $value; + } + + /** + * Set shipping_postcode + * @param string $value + */ + public function set_shipping_postcode( $value ) { + $this->_data['shipping']['postcode'] = $value; + } + + /** + * Set shipping_country + * @param string $value + */ + public function set_shipping_country( $value ) { + $this->_data['shipping']['country'] = $value; + } + + /** + * Set the payment method. + * @param string $payment_method Supports WC_Payment_Gateway for bw compatibility with < 2.7 + */ + public function set_payment_method( $payment_method = '' ) { + if ( is_object( $payment_method ) ) { + $this->set_payment_method( $payment_method->id ); + $this->set_payment_method_title( $payment_method->get_title() ); + } elseif ( '' === $payment_method ) { + $this->_data['payment_method'] = ''; + $this->_data['payment_method_title'] = ''; + } else { + $this->_data['payment_method'] = $payment_method; + } + } + + /** + * Set payment_method_title + * @param string $value + */ + public function set_payment_method_title( $value ) { + $this->_data['payment_method_title'] = $value; + } + + /** + * Set transaction_id + * @param string $value + */ + public function set_transaction_id( $value ) { + $this->_data['transaction_id'] = $value; + } + + /** + * Set customer_ip_address + * @param string $value + */ + public function set_customer_ip_address( $value ) { + $this->_data['customer_ip_address'] = $value; + } + + /** + * Set customer_user_agent + * @param string $value + */ + public function set_customer_user_agent( $value ) { + $this->_data['customer_user_agent'] = $value; + } + + /** + * Set created_via + * @param string $value + */ + public function set_created_via( $value ) { + $this->_data['created_via'] = $value; + } + + /** + * Set customer_note + * @param string $value + */ + public function set_customer_note( $value ) { + $this->_data['customer_note'] = $value; + } + + /** + * Set date_completed + * @param string $timestamp + */ + public function set_date_completed( $timestamp ) { + $this->_data['date_completed'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp ); + } + + /** + * Set date_paid + * @param string $timestamp + */ + public function set_date_paid( $timestamp ) { + $this->_data['date_paid'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp ); + } + + /** + * Set cart hash + * @param string $value + */ + public function set_cart_hash( $value ) { + $this->_data['cart_hash'] = $value; + } + + /* + |-------------------------------------------------------------------------- + | Conditionals + |-------------------------------------------------------------------------- + | + | Checks if a condition is true or false. + | + */ + + /** + * See if order matches cart_hash. + * @return bool + */ + public function has_cart_hash( $cart_hash = '' ) { + return hash_equals( $this->get_cart_hash(), $cart_hash ); + } + + /** + * Checks if an order can be edited, specifically for use on the Edit Order screen. + * @return bool + */ + public function is_editable() { + return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft' ) ), $this ); + } + + /** + * Returns if an order has been paid for based on the order status. + * @since 2.5.0 + * @return bool + */ + public function is_paid() { + return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this ); + } + + /** + * Checks if product download is permitted. + * + * @return bool + */ + public function is_download_permitted() { + return apply_filters( 'woocommerce_order_is_download_permitted', $this->has_status( 'completed' ) || ( 'yes' === get_option( 'woocommerce_downloads_grant_access_after_payment' ) && $this->has_status( 'processing' ) ), $this ); + } + + /** + * Checks if an order needs display the shipping address, based on shipping method. + * @return bool + */ + public function needs_shipping_address() { + if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) { + return false; + } + + $hide = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this ); + $needs_address = false; + + foreach ( $this->get_shipping_methods() as $shipping_method ) { + // Remove any instance IDs after : + $shipping_method_id = current( explode( ':', $shipping_method['method_id'] ) ); + + if ( ! in_array( $shipping_method_id, $hide ) ) { + $needs_address = true; + break; + } + } + + return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this ); + } + + /** + * Returns true if the order contains a downloadable product. + * @return bool + */ + public function has_downloadable_item() { + foreach ( $this->get_items() as $item ) { + if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->is_downloadable() && $product->has_file() ) { + return true; + } + } + return false; + } + + /** + * Checks if an order needs payment, based on status and order total. + * + * @return bool + */ + public function needs_payment() { + $valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this ); + return apply_filters( 'woocommerce_order_needs_payment', ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ), $this, $valid_order_statuses ); + } + + /* + |-------------------------------------------------------------------------- + | URLs and Endpoints + |-------------------------------------------------------------------------- + */ + + /** + * Generates a URL so that a customer can pay for their (unpaid - pending) order. Pass 'true' for the checkout version which doesn't offer gateway choices. + * + * @param bool $on_checkout + * @return string + */ + public function get_checkout_payment_url( $on_checkout = false ) { + $pay_url = wc_get_endpoint_url( 'order-pay', $this->get_id(), wc_get_page_permalink( 'checkout' ) ); + + if ( 'yes' == get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) { + $pay_url = str_replace( 'http:', 'https:', $pay_url ); + } + + if ( $on_checkout ) { + $pay_url = add_query_arg( 'key', $this->get_order_key(), $pay_url ); + } else { + $pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->get_order_key() ), $pay_url ); + } + + return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this ); + } + + /** + * Generates a URL for the thanks page (order received). + * + * @return string + */ + public function get_checkout_order_received_url() { + $order_received_url = wc_get_endpoint_url( 'order-received', $this->get_id(), wc_get_page_permalink( 'checkout' ) ); + + if ( 'yes' === get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) { + $order_received_url = str_replace( 'http:', 'https:', $order_received_url ); + } + + $order_received_url = add_query_arg( 'key', $this->get_order_key(), $order_received_url ); + + return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this ); + } + + /** + * Generates a URL so that a customer can cancel their (unpaid - pending) order. + * + * @param string $redirect + * + * @return string + */ + public function get_cancel_order_url( $redirect = '' ) { + return apply_filters( 'woocommerce_get_cancel_order_url', wp_nonce_url( add_query_arg( array( + 'cancel_order' => 'true', + 'order' => $this->get_order_key(), + 'order_id' => $this->get_id(), + 'redirect' => $redirect + ), $this->get_cancel_endpoint() ), 'woocommerce-cancel_order' ) ); + } + + /** + * Generates a raw (unescaped) cancel-order URL for use by payment gateways. + * + * @param string $redirect + * + * @return string The unescaped cancel-order URL. + */ + public function get_cancel_order_url_raw( $redirect = '' ) { + return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array( + 'cancel_order' => 'true', + 'order' => $this->get_order_key(), + 'order_id' => $this->get_id(), + 'redirect' => $redirect, + '_wpnonce' => wp_create_nonce( 'woocommerce-cancel_order' ) + ), $this->get_cancel_endpoint() ) ); + } + + /** + * Helper method to return the cancel endpoint. + * + * @return string the cancel endpoint; either the cart page or the home page. + */ + public function get_cancel_endpoint() { + $cancel_endpoint = wc_get_page_permalink( 'cart' ); + if ( ! $cancel_endpoint ) { + $cancel_endpoint = home_url(); + } + + if ( false === strpos( $cancel_endpoint, '?' ) ) { + $cancel_endpoint = trailingslashit( $cancel_endpoint ); + } + + return $cancel_endpoint; + } + + /** + * Generates a URL to view an order from the my account page. + * + * @return string + */ + public function get_view_order_url() { + return apply_filters( 'woocommerce_get_view_order_url', wc_get_endpoint_url( 'view-order', $this->get_id(), wc_get_page_permalink( 'myaccount' ) ), $this ); + } + + /* + |-------------------------------------------------------------------------- + | Order notes. + |-------------------------------------------------------------------------- + */ + + /** + * Adds a note (comment) to the order. Order must exist. + * + * @param string $note Note to add. + * @param int $is_customer_note (default: 0) Is this a note for the customer? + * @param bool added_by_user Was the note added by a user? + * @return int Comment ID. + */ + public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) { + if ( ! $this->get_id() ) { + return 0; + } + + if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->get_id() ) && $added_by_user ) { + $user = get_user_by( 'id', get_current_user_id() ); + $comment_author = $user->display_name; + $comment_author_email = $user->user_email; + } else { + $comment_author = __( 'WooCommerce', 'woocommerce' ); + $comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@'; + $comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com'; + $comment_author_email = sanitize_email( $comment_author_email ); + } + $commentdata = apply_filters( 'woocommerce_new_order_note_data', array( + 'comment_post_ID' => $this->get_id(), + 'comment_author' => $comment_author, + 'comment_author_email' => $comment_author_email, + 'comment_author_url' => '', + 'comment_content' => $note, + 'comment_agent' => 'WooCommerce', + 'comment_type' => 'order_note', + 'comment_parent' => 0, + 'comment_approved' => 1, + ), array( 'order_id' => $this->get_id(), 'is_customer_note' => $is_customer_note ) ); + + $comment_id = wp_insert_comment( $commentdata ); + + if ( $is_customer_note ) { + add_comment_meta( $comment_id, 'is_customer_note', 1 ); + + do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->get_id(), 'customer_note' => $commentdata['comment_content'] ) ); + } + + return $comment_id; + } + + /** + * List order notes (public) for the customer. + * + * @return array + */ + public function get_customer_order_notes() { + $notes = array(); + $args = array( + 'post_id' => $this->get_id(), + 'approve' => 'approve', + 'type' => '' + ); + + remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) ); + + $comments = get_comments( $args ); + + foreach ( $comments as $comment ) { + if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) { + continue; + } + $comment->comment_content = make_clickable( $comment->comment_content ); + $notes[] = $comment; + } + + add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) ); + + return $notes; + } + + /* + |-------------------------------------------------------------------------- + | Refunds + |-------------------------------------------------------------------------- + */ + /** * Get order refunds. * @since 2.2 * @return array of WC_Order_Refund objects */ public function get_refunds() { - if ( empty( $this->refunds ) && ! is_array( $this->refunds ) ) { - $this->refunds = wc_get_orders( array( - 'type' => 'shop_order_refund', - 'parent' => $this->id, - 'limit' => -1, - ) ); - } + $this->refunds = wc_get_orders( array( + 'type' => 'shop_order_refund', + 'parent' => $this->get_id(), + 'limit' => -1, + ) ); return $this->refunds; } @@ -87,7 +1301,7 @@ class WC_Order extends WC_Abstract_Order { INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d ) WHERE postmeta.meta_key = '_refund_amount' AND postmeta.post_id = posts.ID - ", $this->id ) ); + ", $this->get_id() ) ); return $total; } @@ -108,7 +1322,7 @@ class WC_Order extends WC_Abstract_Order { INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON ( order_items.order_id = posts.ID AND order_items.order_item_type = 'tax' ) WHERE order_itemmeta.order_item_id = order_items.order_item_id AND order_itemmeta.meta_key IN ('tax_amount', 'shipping_tax_amount') - ", $this->id ) ); + ", $this->get_id() ) ); return abs( $total ); } @@ -129,7 +1343,7 @@ class WC_Order extends WC_Abstract_Order { INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON ( order_items.order_id = posts.ID AND order_items.order_item_type = 'shipping' ) WHERE order_itemmeta.order_item_id = order_items.order_item_id AND order_itemmeta.meta_key IN ('cost') - ", $this->id ) ); + ", $this->get_id() ) ); return abs( $total ); } @@ -151,7 +1365,7 @@ class WC_Order extends WC_Abstract_Order { foreach ( $this->get_refunds() as $refund ) { foreach ( $refund->get_items( $item_type ) as $refunded_item ) { - $count += empty( $refunded_item['qty'] ) ? 0 : $refunded_item['qty']; + $count += $refunded_item->get_qty(); } } @@ -169,7 +1383,7 @@ class WC_Order extends WC_Abstract_Order { $qty = 0; foreach ( $this->get_refunds() as $refund ) { foreach ( $refund->get_items( $item_type ) as $refunded_item ) { - $qty += $refunded_item['qty']; + $qty += $refunded_item->get_qty(); } } return $qty; @@ -186,8 +1400,8 @@ class WC_Order extends WC_Abstract_Order { $qty = 0; foreach ( $this->get_refunds() as $refund ) { foreach ( $refund->get_items( $item_type ) as $refunded_item ) { - if ( isset( $refunded_item['refunded_item_id'] ) && $refunded_item['refunded_item_id'] == $item_id ) { - $qty += $refunded_item['qty']; + if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) { + $qty += $refunded_item->get_qty(); } } } @@ -205,15 +1419,8 @@ class WC_Order extends WC_Abstract_Order { $total = 0; foreach ( $this->get_refunds() as $refund ) { foreach ( $refund->get_items( $item_type ) as $refunded_item ) { - if ( isset( $refunded_item['refunded_item_id'] ) && $refunded_item['refunded_item_id'] == $item_id ) { - switch ( $item_type ) { - case 'shipping' : - $total += $refunded_item['cost']; - break; - default : - $total += $refunded_item['line_total']; - break; - } + if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) { + $total += $refunded_item->get_total(); } } } @@ -232,21 +1439,8 @@ class WC_Order extends WC_Abstract_Order { $total = 0; foreach ( $this->get_refunds() as $refund ) { foreach ( $refund->get_items( $item_type ) as $refunded_item ) { - if ( isset( $refunded_item['refunded_item_id'] ) && $refunded_item['refunded_item_id'] == $item_id ) { - switch ( $item_type ) { - case 'shipping' : - $tax_data = maybe_unserialize( $refunded_item['taxes'] ); - if ( isset( $tax_data[ $tax_id ] ) ) { - $total += $tax_data[ $tax_id ]; - } - break; - default : - $tax_data = maybe_unserialize( $refunded_item['line_tax_data'] ); - if ( isset( $tax_data['total'][ $tax_id ] ) ) { - $total += $tax_data['total'][ $tax_id ]; - } - break; - } + if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) { + $total += $refunded_item->get_total_tax(); } } } @@ -264,8 +1458,8 @@ class WC_Order extends WC_Abstract_Order { $total = 0; foreach ( $this->get_refunds() as $refund ) { foreach ( $refund->get_items( 'tax' ) as $refunded_item ) { - if ( isset( $refunded_item['rate_id'] ) && $refunded_item['rate_id'] == $rate_id ) { - $total += abs( $refunded_item['tax_amount'] ) + abs( $refunded_item['shipping_tax_amount'] ); + if ( absint( $refunded_item->get_rate_id() ) === $rate_id ) { + $total += abs( $refunded_item->tax_total() ) + abs( $refunded_item->shipping_tax_total() ); } } } diff --git a/includes/wc-order-functions.php b/includes/wc-order-functions.php index c5c0dbb6f66..ab9d020c587 100644 --- a/includes/wc-order-functions.php +++ b/includes/wc-order-functions.php @@ -860,7 +860,7 @@ function wc_create_refund( $args = array() ) { $order = wc_get_order( $args['order_id'] ); // Refund currency is the same used for the parent order - update_post_meta( $refund_id, '_order_currency', $order->get_order_currency() ); + update_post_meta( $refund_id, '_order_currency', $order->get_currency() ); // Negative line items if ( sizeof( $args['line_items'] ) > 0 ) { @@ -917,7 +917,7 @@ function wc_create_refund( $args = array() ) { $refund->calculate_totals( false ); // Set total to total refunded which may vary from order items - $refund->set_total( wc_format_decimal( $args['amount'] ) * -1, 'total' ); + $refund->set_total( wc_format_decimal( $args['amount'] ) * -1 ); do_action( 'woocommerce_refund_created', $refund_id, $args ); } @@ -1065,3 +1065,134 @@ function wc_order_search( $term ) { return $post_ids; } + + +/** + * Update total sales amount for each product within a paid order. + * + * @since 2.7.0 + * @param int $order_id + */ +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 ) ) { + 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'] ) ); + } + } + } + + update_post_meta( $order_id, '_recorded_sales', 'yes' ); + + /** + * Called when sales for an order are recorded + * + * @param int $order_id order id + */ + do_action( 'woocommerce_recorded_sales', $order_id ); +} +add_action( 'woocommerce_order_status_completed', 'wc_update_total_sales_counts' ); +add_action( 'woocommerce_order_status_processing', 'wc_update_total_sales_counts' ); +add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' ); + +/** + * Update used coupon amount for each coupon within an order. + * + * @since 2.7.0 + * @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 ) { + return; + } + + if ( $order->has_status( 'cancelled' ) && 'yes' === $has_recorded ) { + $action = 'reduce'; + delete_post_meta( $order_id, '_recorded_coupon_usage_counts' ); + } elseif ( ! $order->has_status( 'cancelled' ) && 'yes' !== $has_recorded ) { + $action = 'increase'; + update_post_meta( $order_id, '_recorded_coupon_usage_counts', 'yes' ); + } else { + return; + } + + if ( sizeof( $order->get_used_coupons() ) > 0 ) { + foreach ( $order->get_used_coupons() as $code ) { + if ( ! $code ) { + continue; + } + + $coupon = new WC_Coupon( $code ); + + if ( ! $used_by = $order->get_user_id() ) { + $used_by = $order->get_billing_email(); + } + + switch ( $action ) { + case 'reduce' : + $coupon->dcr_usage_count( $used_by ); + break; + case 'increase' : + $coupon->inc_usage_count( $used_by ); + break; + } + } + } +} +add_action( 'woocommerce_order_status_completed', 'wc_update_total_sales_counts' ); +add_action( 'woocommerce_order_status_processing', 'wc_update_total_sales_counts' ); +add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' ); +add_action( 'woocommerce_order_status_cancelled', 'wc_update_total_sales_counts' ); + +/** + * When a payment is complete, we can reduce stock levels for items within an order. + * @since 2.7.0 + * @param int $order_id + */ +function wc_maybe_reduce_stock_levels( $order_id ) { + if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $order_id, '_order_stock_reduced', true ), $order_id ) ) { + wc_reduce_stock_levels( $order_id ); + add_post_meta( $order_id, '_order_stock_reduced', '1', true ); + } +} +add_action( 'woocommerce_payment_complete', 'wc_maybe_reduce_stock_levels' ); + +/** + * Reduce stock levels for items within an order. + * @since 2.7.0 + * @param int $order_id + */ +function wc_reduce_stock_levels( $order_id ) { + $order = wc_get_order( $order_id ); + + if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && $order && apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) && sizeof( $order->get_items() ) > 0 ) { + foreach ( $order->get_items() as $item ) { + if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->managing_stock() ) { + $qty = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $order, $item ); + $new_stock = $product->reduce_stock( $qty ); + $item_name = $product->get_sku() ? $product->get_sku(): $item['product_id']; + + if ( ! empty( $item['variation_id'] ) ) { + $order->add_order_note( sprintf( __( 'Item %s variation #%s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $item['variation_id'], $new_stock + $qty, $new_stock ) ); + } else { + $order->add_order_note( sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock ) ); + } + + if ( $new_stock < 0 ) { + do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $order_id, 'quantity' => $qty_ordered ) ); + } + } + } + + do_action( 'woocommerce_reduce_order_stock', $order ); + } +} diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index dc62f534dab..80e9ebfede6 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -2304,3 +2304,126 @@ if ( ! function_exists( 'woocommerce_account_edit_account' ) ) { WC_Shortcode_My_Account::edit_account(); } } + + +if ( ! function_exists( 'wc_get_email_order_items' ) ) { + /** + * Get HTML for the order items to be shown in emails. + * @param WC_Order $order + * @param array $args + * @since 2.7.0 + */ + function wc_get_email_order_items( $order, $args = array() ) { + ob_start(); + + $defaults = array( + 'show_sku' => false, + 'show_image' => false, + 'image_size' => array( 32, 32 ), + 'plain_text' => false + ); + + $args = wp_parse_args( $args, $defaults ); + $template = $args['plain_text'] ? 'emails/plain/email-order-items.php' : 'emails/email-order-items.php'; + + wc_get_template( $template, array( + 'order' => $order, + 'items' => $order->get_items(), + 'show_download_links' => $order->is_download_permitted(), + 'show_sku' => $args['show_sku'], + 'show_purchase_note' => $order->is_paid(), + 'show_image' => $args['show_image'], + 'image_size' => $args['image_size'], + ) ); + + return apply_filters( 'woocommerce_email_order_items_table', ob_get_clean(), $order ); + } +} + +if ( ! function_exists( 'wc_display_item_meta' ) ) { + /** + * Display item meta data. + * @since 2.7.0 + * @param WC_Item $item + * @param array $args + * @return string|void + */ + function wc_display_item_meta( $item, $args = array() ) { + $strings = array(); + $html = ''; + $args = wp_parse_args( $args, array( + 'before' => '', + 'separator' => '
  • ', + 'echo' => true, + 'autop' => false, + ) ); + + foreach ( $item->get_formatted_meta_data() as $meta_id => $meta ) { + if ( '_' === substr( $meta->key, 0, 1 ) ) { + continue; + } + $value = $args['autop'] ? wp_kses_post( wpautop( make_clickable( $meta->display_value ) ) ) : wp_kses_post( make_clickable( $meta->display_value ) ); + $strings[] = '' . wp_kses_post( $meta->display_key ) . ': ' . $value; + } + + if ( $strings ) { + $html = $args['before'] . implode( $args['separator'], $strings ) . $args['after']; + } + + $html = apply_filters( 'woocommerce_display_item_meta', $html, $item, $args ); + + if ( $args['echo'] ) { + echo $html; + } else { + return $html; + } + } +} + +if ( ! function_exists( 'wc_display_item_downloads' ) ) { + /** + * Display item download links. + * @since 2.7.0 + * @param WC_Item $item + * @param array $args + * @return string|void + */ + function wc_display_item_downloads( $item, $args = array() ) { + $strings = array(); + $html = ''; + $args = wp_parse_args( $args, array( + 'before' => '', + 'separator' => '
  • ', + 'echo' => true, + 'show_url' => false, + ) ); + + if ( is_object( $item ) && $item->is_type( 'line_item' ) && ( $downloads = $item->get_item_downloads() ) ) { + $i = 0; + foreach ( $downloads as $file ) { + $i ++; + + if ( $args['show_url'] ) { + $strings[] = '' . esc_html( $file['name'] ) . ': ' . esc_html( $file['download_url'] ); + } else { + $prefix = sizeof( $downloads ) > 1 ? sprintf( __( 'Download %d', 'woocommerce' ), $i ) : __( 'Download', 'woocommerce' ); + $strings[] = '' . $prefix . ': ' . esc_html( $file['name'] ) . ''; + } + } + } + + if ( $strings ) { + $html = $args['before'] . implode( $args['separator'], $strings ) . $args['after']; + } + + $html = apply_filters( 'woocommerce_display_item_downloads', $html, $item, $args ); + + if ( $args['echo'] ) { + echo $html; + } else { + return $html; + } + } +} diff --git a/includes/wc-user-functions.php b/includes/wc-user-functions.php index 40b611ba200..53e3fb079f5 100644 --- a/includes/wc-user-functions.php +++ b/includes/wc-user-functions.php @@ -176,17 +176,18 @@ function wc_update_new_customer_past_orders( $customer_id ) { * @param int $order_id */ function wc_paying_customer( $order_id ) { - $order = wc_get_order( $order_id ); + $order = wc_get_order( $order_id ); + $customer_id = $order->get_customer_id(); - if ( $order->user_id > 0 && 'refund' !== $order->order_type ) { - update_user_meta( $order->user_id, 'paying_customer', 1 ); + if ( $customer_id > 0 && 'refund' !== $order->get_type() ) { + update_user_meta( $customer_id, 'paying_customer', 1 ); - $old_spent = absint( get_user_meta( $order->user_id, '_money_spent', true ) ); - update_user_meta( $order->user_id, '_money_spent', $old_spent + $order->order_total ); + $old_spent = absint( get_user_meta( $customer_id, '_money_spent', true ) ); + update_user_meta( $customer_id, '_money_spent', $old_spent + $order->order_total ); } - if ( $order->user_id > 0 && 'simple' === $order->order_type ) { - $old_count = absint( get_user_meta( $order->user_id, '_order_count', true ) ); - update_user_meta( $order->user_id, '_order_count', $old_count + 1 ); + if ( $customer_id > 0 && 'shop_order' === $order->get_type() ) { + $old_count = absint( get_user_meta( $customer_id, '_order_count', true ) ); + update_user_meta( $customer_id, '_order_count', $old_count + 1 ); } } add_action( 'woocommerce_order_status_completed', 'wc_paying_customer' ); diff --git a/tests/framework/helpers/class-wc-helper-order.php b/tests/framework/helpers/class-wc-helper-order.php index 480f03e03a3..5b4ea31cd82 100644 --- a/tests/framework/helpers/class-wc-helper-order.php +++ b/tests/framework/helpers/class-wc-helper-order.php @@ -51,7 +51,7 @@ class WC_Helper_Order { $order = wc_create_order( $order_data ); // Add order products - $item_id = $order->add_product( $product, 4 ); + $item_id = $order->add_product( $product, array( 'qty' => 4 ) ); // Set billing address $billing_address = array( @@ -78,13 +78,13 @@ class WC_Helper_Order { $order->set_payment_method( $payment_gateways['bacs'] ); // Set totals - $order->set_total( 10, 'shipping' ); - $order->set_total( 0, 'cart_discount' ); - $order->set_total( 0, 'cart_discount_tax' ); - $order->set_total( 0, 'tax' ); - $order->set_total( 0, 'shipping_tax' ); - $order->set_total( 40, 'total' ); // 4 x $10 simple helper product + $order->set_shipping_total( 10 ); + $order->set_discount_total( 0 ); + $order->set_discount_tax( 0 ); + $order->set_cart_tax( 0 ); + $order->set_shipping_tax( 0 ); + $order->set_total( 40 ); // 4 x $10 simple helper product - return wc_get_order( $order->id ); + return wc_get_order( $order->get_id() ); } } diff --git a/tests/unit-tests/crud/orders.php b/tests/unit-tests/crud/orders.php new file mode 100644 index 00000000000..76b1e493d64 --- /dev/null +++ b/tests/unit-tests/crud/orders.php @@ -0,0 +1,1323 @@ +assertEquals( 'shop_order', $object->get_type() ); + } + + /** + * Test: get_type + */ + function test_get_order_type() { + $object = new WC_Order(); + $id = $object->save(); + $this->assertEquals( 'shop_order', $object->get_order_type() ); + } + + /** + * Test: get_data + */ + function test_get_data() { + $object = new WC_Order(); + $this->assertInternalType( 'array', $object->get_data() ); + } + + /** + * Test: get_id + */ + function test_get_id() { + $object = new WC_Order(); + $id = $object->save(); + $this->assertEquals( $id, $object->get_id() ); + } + + /** + * Test: get_parent_id + */ + function test_get_parent_id() { + $object = new WC_Order(); + $set_to = 100; + $object->set_parent_id( $set_to ); + $this->assertEquals( $set_to, $object->get_parent_id() ); + } + + /** + * Test: get_order_number + */ + function test_get_order_number() { + $object = new WC_Order(); + $id = $object->save(); + $this->assertEquals( $id, $object->get_order_number() ); + } + + /** + * Test: get_order_key + */ + function test_get_order_key() { + $object = new WC_Order(); + $set_to = 'some_key'; + $object->set_order_key( $set_to ); + $this->assertEquals( $set_to, $object->get_order_key() ); + } + + /** + * Test: get_currency + */ + function test_get_currency() { + $object = new WC_Order(); + $set_to = 'USD'; + $object->set_currency( $set_to ); + $this->assertEquals( $set_to, $object->get_currency() ); + } + + /** + * Test: get_version + */ + function test_get_version() { + $object = new WC_Order(); + $set_to = '2.7.0'; + $object->set_version( $set_to ); + $this->assertEquals( $set_to, $object->get_version() ); + } + + /** + * Test: get_prices_include_tax + */ + function test_get_prices_include_tax() { + $object = new WC_Order(); + $set_to = 'USD'; + $object->set_prices_include_tax( 1 ); + $this->assertEquals( true, $object->get_prices_include_tax() ); + } + + /** + * Test: get_date_created + */ + function test_get_date_created() { + $object = new WC_Order(); + $object->set_date_created( '2016-12-12' ); + $this->assertEquals( '1481500800', $object->get_date_created() ); + + $object->set_date_created( '1481500800' ); + $this->assertEquals( 1481500800, $object->get_date_created() ); + } + + /** + * Test: get_date_modified + */ + function test_get_date_modified() { + $object = new WC_Order(); + $object->set_date_modified( '2016-12-12' ); + $this->assertEquals( '1481500800', $object->get_date_modified() ); + + $object->set_date_modified( '1481500800' ); + $this->assertEquals( 1481500800, $object->get_date_modified() ); + } + + /** + * Test: get_customer_id + */ + function test_get_customer_id() { + $object = new WC_Order(); + $set_to = 10; + $object->set_customer_id( $set_to ); + $this->assertEquals( $set_to, $object->get_customer_id() ); + } + + /** + * Test: get_user + */ + function test_get_user() { + $object = new WC_Order(); + $this->assertEquals( false, $object->get_user() ); + $set_to = '1'; + $object->set_customer_id( $set_to ); + $this->assertInstanceOf( 'WP_User', $object->get_user() ); + } + + /** + * Test: get_discount_total + */ + function test_get_discount_total() { + $object = new WC_Order(); + $object->set_discount_total( 50 ); + $this->assertEquals( 50, $object->get_discount_total() ); + } + + /** + * Test: get_discount_tax + */ + function test_get_discount_tax() { + $object = new WC_Order(); + $object->set_discount_tax( 5 ); + $this->assertEquals( 5, $object->get_discount_tax() ); + } + + /** + * Test: get_shipping_total + */ + function test_get_shipping_total() { + $object = new WC_Order(); + $object->set_shipping_total( 5 ); + $this->assertEquals( 5, $object->get_shipping_total() ); + } + + /** + * Test: get_shipping_tax + */ + function test_get_shipping_tax() { + $object = new WC_Order(); + $object->set_shipping_tax( 5 ); + $this->assertEquals( 5, $object->get_shipping_tax() ); + } + + /** + * Test: get_cart_tax + */ + function test_get_cart_tax() { + $object = new WC_Order(); + $object->set_cart_tax( 5 ); + $this->assertEquals( 5, $object->get_cart_tax() ); + } + + /** + * Test: get_total + */ + function test_get_total() { + $object = new WC_Order(); + $object->set_total( 5 ); + $this->assertEquals( 5, $object->get_total() ); + } + + /** + * Test: get_total_tax + */ + function test_get_total_tax() { + $object = new WC_Order(); + $object->set_cart_tax( 5 ); + $object->set_shipping_tax( 5 ); + $this->assertEquals( 10, $object->get_total_tax() ); + } + + /** + * Test: get_total_discount + */ + function test_get_total_discount() { + $object = new WC_Order(); + $object->set_discount_total( 50 ); + $object->set_discount_tax( 5 ); + $this->assertEquals( 50, $object->get_total_discount() ); + $this->assertEquals( 55, $object->get_total_discount( false ) ); + } + + /** + * Test: get_subtotal + */ + function test_get_subtotal() { + $object = WC_Helper_Order::create_order(); + $this->assertEquals( 40, $object->get_subtotal() ); + } + + /** + * Test: get_tax_totals + */ + function test_get_tax_totals() { + $object = WC_Helper_Order::create_order(); + $this->assertEquals( array(), $object->get_tax_totals() ); + } + + /** + * Test: remove_order_items + */ + function test_remove_order_items() { + $product = WC_Helper_Product::create_simple_product(); + $object = new WC_Order(); + $object->save(); + $item_id = $object->add_product( $product, array( 'qty' => 4 ) ); + $item_id = $object->add_product( $product, array( 'qty' => 2 ) ); + $this->assertCount( 2, $object->get_items() ); + $object->remove_order_items(); + $this->assertCount( 0, $object->get_items() ); + } + + /** + * Test: get_items + */ + function test_get_items() { + $object = new WC_Order(); + $object->save(); + $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 4 ) ); + $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 2 ) ); + $this->assertCount( 2, $object->get_items() ); + } + + /** + * Test: get_fees + */ + function test_get_fees() { + $object = new WC_Order(); + $object->save(); + $object->add_fee( (object) array( + 'name' => 'Some Fee', + 'taxable' => true, + 'amount' => '100', + 'tax_class' => '', + 'tax' => '', + 'tax_data' => '', + ) ); + $this->assertCount( 1, $object->get_fees() ); + } + + /** + * Test: get_taxes + */ + function test_get_taxes() { + global $wpdb; + + update_option( 'woocommerce_calc_taxes', 'yes' ); + $tax_rate = array( + 'tax_rate_country' => '', + 'tax_rate_state' => '', + 'tax_rate' => '10.0000', + 'tax_rate_name' => 'TAX', + 'tax_rate_priority' => '1', + 'tax_rate_compound' => '0', + 'tax_rate_shipping' => '1', + 'tax_rate_order' => '1', + 'tax_rate_class' => '' + ); + WC_Tax::_insert_tax_rate( $tax_rate ); + + $object = new WC_Order(); + $object->save(); + $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 4 ) ); + $object->calculate_totals(); + $this->assertCount( 1, $object->get_taxes() ); + + $object->add_tax( + array( + 'rate_id' => '100', + 'tax_total' => '100', + 'shipping_tax_total' => '0', + ) + ); + + $this->assertCount( 2, $object->get_taxes() ); + + // Cleanup + $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations" ); + update_option( 'woocommerce_calc_taxes', 'no' ); + } + + /** + * Test: get_shipping_methods + */ + function test_get_shipping_methods() { + $object = new WC_Order(); + $object->save(); + $object->add_shipping( new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping', '10', array(), 'flat_rate' ) ); + $this->assertCount( 1, $object->get_shipping_methods() ); + } + + /** + * Test: get_shipping_method + */ + function test_get_shipping_method() { + $object = new WC_Order(); + $object->save(); + $object->add_shipping( new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping', '10', array(), 'flat_rate' ) ); + $this->assertEquals( 'Flat rate shipping', $object->get_shipping_method() ); + $object->add_shipping( new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping 2', '10', array(), 'flat_rate' ) ); + $this->assertEquals( 'Flat rate shipping, Flat rate shipping 2', $object->get_shipping_method() ); + } + + /** + * Test: get_used_coupons + */ + function test_get_used_coupons() { + $object = new WC_Order(); + $object->save(); + $object->add_coupon( + array( + 'code' => '12345', + 'discount' => '10', + 'discount_tax' => '5', + ) + ); + $this->assertCount( 1, $object->get_used_coupons() ); + } + + /** + * Test: get_item_count + */ + function test_get_item_count() { + $object = new WC_Order(); + $object->save(); + $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 4 ) ); + $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 2 ) ); + $this->assertEquals( 6, $object->get_item_count() ); + } + + /** + * Test: get_item + */ + function test_get_item() { + $object = new WC_Order(); + $object->save(); + + $item_id = $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 4 ) ); + $this->assertTrue( $object->get_item( $item_id ) instanceOf WC_Order_Item_Product ); + + $item_id = $object->add_coupon( + array( + 'code' => '12345', + 'discount' => '10', + 'discount_tax' => '5', + ) + ); + $this->assertTrue( $object->get_item( $item_id ) instanceOf WC_Order_Item_Coupon ); + } + + /** + * Test: add_payment_token + */ + function test_add_payment_token() { + $object = new WC_Order(); + $object->save(); + $this->assertFalse( $object->add_payment_token( 'fish' ) ); + $token = new WC_Payment_Token_Stub(); + $token->set_extra( __FUNCTION__ ); + $token->set_token( time() ); + $token->create(); + $this->assertTrue( 0 < $object->add_payment_token( $token ) ); + } + + /** + * Test: get_payment_tokens + */ + function test_get_payment_tokens() { + $object = new WC_Order(); + $object->save(); + $token = new WC_Payment_Token_Stub(); + $token->set_extra( __FUNCTION__ ); + $token->set_token( time() ); + $token->create(); + $object->add_payment_token( $token ); + $this->assertCount( 1, $object->get_payment_tokens() ); + } + + /** + * Test: calculate_shipping + */ + function test_calculate_shipping() { + $object = new WC_Order(); + $object->save(); + $object->add_shipping( new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping', '10', array(), 'flat_rate' ) ); + $object->add_shipping( new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping', '10', array(), 'flat_rate' ) ); + $object->calculate_shipping(); + $this->assertEquals( 20, $object->get_shipping_total() ); + } + + /** + * Test: calculate_taxes + */ + function test_calculate_taxes() { + global $wpdb; + update_option( 'woocommerce_calc_taxes', 'yes' ); + $tax_rate = array( + 'tax_rate_country' => '', + 'tax_rate_state' => '', + 'tax_rate' => '10.0000', + 'tax_rate_name' => 'TAX', + 'tax_rate_priority' => '1', + 'tax_rate_compound' => '0', + 'tax_rate_shipping' => '1', + 'tax_rate_order' => '1', + 'tax_rate_class' => '' + ); + WC_Tax::_insert_tax_rate( $tax_rate ); + + $object = new WC_Order(); + $object->save(); + $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 4 ) ); + $object->add_shipping( new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping', '10', array(), 'flat_rate' ) ); + $object->calculate_taxes(); + $this->assertEquals( 5, $object->get_total_tax() ); + + // Cleanup + $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations" ); + update_option( 'woocommerce_calc_taxes', 'no' ); + } + + /** + * Test: calculate_totals + */ + function test_calculate_totals() { + global $wpdb; + update_option( 'woocommerce_calc_taxes', 'yes' ); + $tax_rate = array( + 'tax_rate_country' => '', + 'tax_rate_state' => '', + 'tax_rate' => '10.0000', + 'tax_rate_name' => 'TAX', + 'tax_rate_priority' => '1', + 'tax_rate_compound' => '0', + 'tax_rate_shipping' => '1', + 'tax_rate_order' => '1', + 'tax_rate_class' => '' + ); + WC_Tax::_insert_tax_rate( $tax_rate ); + + $object = new WC_Order(); + $object->save(); + $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 4 ) ); + $object->add_shipping( new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping', '10', array(), 'flat_rate' ) ); + $object->calculate_totals(); + $this->assertEquals( 55, $object->get_total() ); + + // Cleanup + $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rates" ); + $wpdb->query( "DELETE FROM {$wpdb->prefix}woocommerce_tax_rate_locations" ); + update_option( 'woocommerce_calc_taxes', 'no' ); + } + + /** + * Test: has_status + */ + function test_has_status() { + $object = new WC_Order(); + $this->assertFalse( $object->has_status( 'completed' ) ); + $this->assertFalse( $object->has_status( array( 'processing', 'completed' ) ) ); + $this->assertTrue( $object->has_status( 'pending' ) ); + $this->assertTrue( $object->has_status( array( 'processing', 'pending' ) ) ); + } + + /** + * Test: has_shipping_method + */ + function test_has_shipping_method() { + $object = new WC_Order(); + $object->save(); + $this->assertFalse( $object->has_shipping_method( 'flat_rate_shipping' ) ); + $object->add_shipping( new WC_Shipping_Rate( 'flat_rate_shipping', 'Flat rate shipping', '10', array(), 'flat_rate' ) ); + $this->assertTrue( $object->has_shipping_method( 'flat_rate_shipping' ) ); + } + + /** + * Test: key_is_valid + */ + function test_key_is_valid() { + $object = new WC_Order(); + $object->save(); + $this->assertFalse( $object->key_is_valid( '1234' ) ); + $object->set_order_key( '1234' ); + $this->assertTrue( $object->key_is_valid( '1234' ) ); + } + + /** + * Test: has_free_item + */ + function test_has_free_item() { + $object = new WC_Order(); + $object->save(); + $object->add_product( WC_Helper_Product::create_simple_product(), array( 'qty' => 4 ) ); + $this->assertFalse( $object->has_free_item() ); + + $free_product = WC_Helper_Product::create_simple_product(); + $free_product->set_price( 0 ); + $object->add_product( $free_product, array( 'qty' => 4 ) ); + $this->assertTrue( $object->has_free_item() ); + } + + /** + * Test: CRUD + */ + function test_CRUD() { + $object = new WC_Order(); + + // Save + create + $save_id = $object->save(); + $post = get_post( $save_id ); + $this->assertEquals( 'shop_order', $post->post_type ); + $this->assertEquals( 'shop_order', $post->post_type ); + + // Update + $update_id = $object->save(); + $this->assertEquals( $update_id, $save_id ); + + // Delete + $object->delete(); + $post = get_post( $save_id ); + $this->assertNull( $post ); + } + + /** + * Test: payment_complete + */ + function test_payment_complete() { + $object = new WC_Order(); + + $this->assertFalse( $object->payment_complete() ); + $object->save(); + $this->assertTrue( $object->payment_complete( '12345' ) ); + $this->assertEquals( 'completed', $object->get_status() ); + $this->assertEquals( '12345', $object->get_transaction_id() ); + } + + /** + * Test: get_formatted_order_total + */ + function test_get_formatted_order_total() { + $object = new WC_Order(); + $object->set_total( 100 ); + $object->set_currency( 'USD' ); + $this->assertEquals( '$100.00', $object->get_formatted_order_total() ); + } + + /** + * Test: set_status + */ + function test_set_status() { + $object = new WC_Order(); + $object->set_status( 'on-hold' ); + $this->assertEquals( 'on-hold', $object->get_status() ); + } + + /** + * Test: update_status + */ + function test_update_status() { + $object = new WC_Order(); + $this->assertFalse( $object->update_status( 'on-hold' ) ); + $object->save(); + $this->assertTrue( $object->update_status( 'on-hold' ) ); + $this->assertEquals( 'on-hold', $object->get_status() ); + } + + /** + * Test: get_billing_first_name + */ + function test_get_billing_first_name() { + $object = new WC_Order(); + $set_to = 'Fred'; + $object->set_billing_first_name( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_first_name() ); + } + + /** + * Test: get_billing_last_name + */ + function test_get_billing_last_name() { + $object = new WC_Order(); + $set_to = 'Flintstone'; + $object->set_billing_last_name( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_last_name() ); + } + + /** + * Test: get_billing_company + */ + function test_get_billing_company() { + $object = new WC_Order(); + $set_to = 'Bedrock Ltd.'; + $object->set_billing_company( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_company() ); + } + + /** + * Test: get_billing_address_1 + */ + function test_get_billing_address_1() { + $object = new WC_Order(); + $set_to = '34 Stonepants avenue'; + $object->set_billing_address_1( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_address_1() ); + } + + /** + * Test: get_billing_address_2 + */ + function test_get_billing_address_2() { + $object = new WC_Order(); + $set_to = 'Rockville'; + $object->set_billing_address_2( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_address_2() ); + } + + /** + * Test: get_billing_city + */ + function test_get_billing_city() { + $object = new WC_Order(); + $set_to = 'Bedrock'; + $object->set_billing_city( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_city() ); + } + + /** + * Test: get_billing_state + */ + function test_get_billing_state() { + $object = new WC_Order(); + $set_to = 'Boulder'; + $object->set_billing_state( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_state() ); + } + + /** + * Test: get_billing_postcode + */ + function test_get_billing_postcode() { + $object = new WC_Order(); + $set_to = '00001'; + $object->set_billing_postcode( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_postcode() ); + } + + /** + * Test: get_billing_country + */ + function test_get_billing_country() { + $object = new WC_Order(); + $set_to = 'US'; + $object->set_billing_country( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_country() ); + } + + /** + * Test: get_billing_email + */ + function test_get_billing_email() { + $object = new WC_Order(); + $set_to = 'test@test.com'; + $object->set_billing_email( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_email() ); + + $set_to = 'not an email'; + $object->set_billing_email( $set_to ); + $this->assertEquals( '', $object->get_billing_email() ); + } + + /** + * Test: get_billing_phone + */ + function test_get_billing_phone() { + $object = new WC_Order(); + $set_to = '123456678'; + $object->set_billing_phone( $set_to ); + $this->assertEquals( $set_to, $object->get_billing_phone() ); + } + + /** + * Test: get_shipping_first_name + */ + function test_get_shipping_first_name() { + $object = new WC_Order(); + $set_to = 'Fred'; + $object->set_shipping_first_name( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_first_name() ); + } + + /** + * Test: get_shipping_last_name + */ + function test_get_shipping_last_name() { + $object = new WC_Order(); + $set_to = 'Flintstone'; + $object->set_shipping_last_name( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_last_name() ); + } + + /** + * Test: get_shipping_company + */ + function test_get_shipping_company() { + $object = new WC_Order(); + $set_to = 'Bedrock Ltd.'; + $object->set_shipping_company( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_company() ); + } + + /** + * Test: get_shipping_address_1 + */ + function test_get_shipping_address_1() { + $object = new WC_Order(); + $set_to = '34 Stonepants avenue'; + $object->set_shipping_address_1( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_address_1() ); + } + + /** + * Test: get_shipping_address_2 + */ + function test_get_shipping_address_2() { + $object = new WC_Order(); + $set_to = 'Rockville'; + $object->set_shipping_address_2( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_address_2() ); + } + + /** + * Test: get_shipping_city + */ + function test_get_shipping_city() { + $object = new WC_Order(); + $set_to = 'Bedrock'; + $object->set_shipping_city( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_city() ); + } + + /** + * Test: get_shipping_state + */ + function test_get_shipping_state() { + $object = new WC_Order(); + $set_to = 'Boulder'; + $object->set_shipping_state( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_state() ); + } + + /** + * Test: get_shipping_postcode + */ + function test_get_shipping_postcode() { + $object = new WC_Order(); + $set_to = '00001'; + $object->set_shipping_postcode( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_postcode() ); + } + + /** + * Test: get_shipping_country + */ + function test_get_shipping_country() { + $object = new WC_Order(); + $set_to = 'US'; + $object->set_shipping_country( $set_to ); + $this->assertEquals( $set_to, $object->get_shipping_country() ); + } + + /** + * Test: get_payment_method + */ + function test_get_payment_method() { + $object = new WC_Order(); + $set_to = 'paypal'; + $object->set_payment_method( $set_to ); + $this->assertEquals( $set_to, $object->get_payment_method() ); + } + + /** + * Test: get_payment_method_title + */ + function test_get_payment_method_title() { + $object = new WC_Order(); + $set_to = 'PayPal'; + $object->set_payment_method_title( $set_to ); + $this->assertEquals( $set_to, $object->get_payment_method_title() ); + } + + /** + * Test: get_transaction_id + */ + function test_get_transaction_id() { + $object = new WC_Order(); + $set_to = '12345'; + $object->set_transaction_id( $set_to ); + $this->assertEquals( $set_to, $object->get_transaction_id() ); + } + + /** + * Test: get_customer_ip_address + */ + function test_get_customer_ip_address() { + $object = new WC_Order(); + $set_to = '192.168.1.1'; + $object->set_customer_ip_address( $set_to ); + $this->assertEquals( $set_to, $object->get_customer_ip_address() ); + } + + /** + * Test: get_customer_user_agent + */ + function test_get_customer_user_agent() { + $object = new WC_Order(); + $set_to = 'UAstring'; + $object->set_customer_user_agent( $set_to ); + $this->assertEquals( $set_to, $object->get_customer_user_agent() ); + } + + /** + * Test: get_created_via + */ + function test_get_created_via() { + $object = new WC_Order(); + $set_to = 'WooCommerce'; + $object->set_created_via( $set_to ); + $this->assertEquals( $set_to, $object->get_created_via() ); + } + + /** + * Test: get_customer_note + */ + function test_get_customer_note() { + $object = new WC_Order(); + $set_to = 'Leave on porch.'; + $object->set_customer_note( $set_to ); + $this->assertEquals( $set_to, $object->get_customer_note() ); + } + + /** + * Test: get_date_completed + */ + function test_get_date_completed() { + $object = new WC_Order(); + $object->set_date_completed( '2016-12-12' ); + $this->assertEquals( '1481500800', $object->get_date_completed() ); + + $object->set_date_completed( '1481500800' ); + $this->assertEquals( 1481500800, $object->get_date_completed() ); + } + + /** + * Test: get_date_paid + */ + function test_get_date_paid() { + $object = new WC_Order(); + $set_to = 'PayPal'; + $object->set_date_paid( '2016-12-12' ); + $this->assertEquals( 1481500800, $object->get_date_paid() ); + + $object->set_date_paid( '1481500800' ); + $this->assertEquals( 1481500800, $object->get_date_paid() ); + } + + /** + * Test: get_cart_hash + */ + function test_get_cart_hash() { + $object = new WC_Order(); + $set_to = '12345'; + $object->set_cart_hash( $set_to ); + $this->assertEquals( $set_to, $object->get_cart_hash() ); + } + + /** + * Test: get_address + */ + function test_get_address() { + $object = new WC_Order(); + + $billing = array( + 'first_name' => 'Fred', + 'last_name' => 'Flintstone', + 'company' => 'Bedrock Ltd.', + 'address_1' => '34 Stonepants avenue', + 'address_2' => 'Rockville', + 'city' => 'Bedrock', + 'state' => 'Boulder', + 'postcode' => '00001', + 'country' => 'US', + 'email' => '', + 'phone' => '', + ); + + $shipping = array( + 'first_name' => 'Barney', + 'last_name' => 'Rubble', + 'company' => 'Bedrock Ltd.', + 'address_1' => '34 Stonepants avenue', + 'address_2' => 'Rockville', + 'city' => 'Bedrock', + 'state' => 'Boulder', + 'postcode' => '00001', + 'country' => 'US', + ); + + $object->set_billing_first_name( 'Fred' ); + $object->set_billing_last_name( 'Flintstone' ); + $object->set_billing_company( 'Bedrock Ltd.' ); + $object->set_billing_address_1( '34 Stonepants avenue' ); + $object->set_billing_address_2( 'Rockville' ); + $object->set_billing_city( 'Bedrock' ); + $object->set_billing_state( 'Boulder' ); + $object->set_billing_postcode( '00001' ); + $object->set_billing_country( 'US' ); + + $object->set_shipping_first_name( 'Barney' ); + $object->set_shipping_last_name( 'Rubble' ); + $object->set_shipping_company( 'Bedrock Ltd.' ); + $object->set_shipping_address_1( '34 Stonepants avenue' ); + $object->set_shipping_address_2( 'Rockville' ); + $object->set_shipping_city( 'Bedrock' ); + $object->set_shipping_state( 'Boulder' ); + $object->set_shipping_postcode( '00001' ); + $object->set_shipping_country( 'US' ); + + $this->assertEquals( $billing, $object->get_address() ); + $this->assertEquals( $shipping, $object->get_address( 'shipping' ) ); + } + + /** + * Test: get_shipping_address_map_url + */ + function test_get_shipping_address_map_url() { + $object = new WC_Order(); + $object->set_shipping_first_name( 'Barney' ); + $object->set_shipping_last_name( 'Rubble' ); + $object->set_shipping_company( 'Bedrock Ltd.' ); + $object->set_shipping_address_1( '34 Stonepants avenue' ); + $object->set_shipping_address_2( 'Rockville' ); + $object->set_shipping_city( 'Bedrock' ); + $object->set_shipping_state( 'Boulder' ); + $object->set_shipping_postcode( '00001' ); + $object->set_shipping_country( 'US' ); + $this->assertEquals( 'http://maps.google.com/maps?&q=Barney%2C+Rubble%2C+Bedrock+Ltd.%2C+34+Stonepants+avenue%2C+Rockville%2C+Bedrock%2C+Boulder%2C+00001%2C+US&z=16', $object->get_shipping_address_map_url() ); + } + + /** + * Test: get_formatted_billing_full_name + */ + function test_get_formatted_billing_full_name() { + $object = new WC_Order(); + $object->set_billing_first_name( 'Fred' ); + $object->set_billing_last_name( 'Flintstone' ); + $this->assertEquals( 'Fred Flintstone', $object->get_formatted_billing_full_name() ); + } + + /** + * Test: get_formatted_shipping_full_name + */ + function test_get_formatted_shipping_full_name() { + $object = new WC_Order(); + $object->set_shipping_first_name( 'Barney' ); + $object->set_shipping_last_name( 'Rubble' ); + $this->assertEquals( 'Barney Rubble', $object->get_formatted_shipping_full_name() ); + } + + /** + * Test: get_formatted_billing_address + */ + function test_get_formatted_billing_address() { + $object = new WC_Order(); + $object->set_billing_first_name( 'Fred' ); + $object->set_billing_last_name( 'Flintstone' ); + $object->set_billing_company( 'Bedrock Ltd.' ); + $object->set_billing_address_1( '34 Stonepants avenue' ); + $object->set_billing_address_2( 'Rockville' ); + $object->set_billing_city( 'Bedrock' ); + $object->set_billing_state( 'Boulder' ); + $object->set_billing_postcode( '00001' ); + $object->set_billing_country( 'US' ); + $this->assertEquals( 'Fred Flintstone
    Bedrock Ltd.
    34 Stonepants avenue
    Rockville
    Bedrock, BOULDER 00001
    United States (US)', $object->get_formatted_billing_address() ); + } + + /** + * Test: get_formatted_shipping_address + */ + function test_get_formatted_shipping_address() { + $object = new WC_Order(); + $object->set_shipping_first_name( 'Barney' ); + $object->set_shipping_last_name( 'Rubble' ); + $object->set_shipping_company( 'Bedrock Ltd.' ); + $object->set_shipping_address_1( '34 Stonepants avenue' ); + $object->set_shipping_address_2( 'Rockville' ); + $object->set_shipping_city( 'Bedrock' ); + $object->set_shipping_state( 'Boulder' ); + $object->set_shipping_postcode( '00001' ); + $object->set_shipping_country( 'US' ); + $this->assertEquals( 'Barney Rubble
    Bedrock Ltd.
    34 Stonepants avenue
    Rockville
    Bedrock, BOULDER 00001
    United States (US)', $object->get_formatted_shipping_address() ); + } + + /** + * Test: has_cart_hash + */ + function test_has_cart_hash() { + $object = new WC_Order(); + $this->assertFalse( $object->has_cart_hash( '12345' ) ); + $set_to = '12345'; + $object->set_cart_hash( $set_to ); + $this->assertTrue( $object->has_cart_hash( '12345' ) ); + } + + /** + * Test: is_editable + */ + function test_is_editable() { + $object = new WC_Order(); + $object->set_status( 'pending' ); + $this->assertTrue( $object->is_editable() ); + $object->set_status( 'processing' ); + $this->assertFalse( $object->is_editable() ); + } + + /** + * Test: is_paid + */ + function test_is_paid() { + $object = new WC_Order(); + $object->set_status( 'pending' ); + $this->assertFalse( $object->is_paid() ); + $object->set_status( 'processing' ); + $this->assertTrue( $object->is_paid() ); + } + + /** + * Test: is_download_permitted + */ + function test_is_download_permitted() { + $object = new WC_Order(); + $object->set_status( 'pending' ); + $this->assertFalse( $object->is_download_permitted() ); + $object->set_status( 'completed' ); + $this->assertTrue( $object->is_download_permitted() ); + } + + /** + * Test: needs_shipping_address + */ + function test_needs_shipping_address() { + $object = new WC_Order(); + $this->assertFalse( $object->needs_shipping_address() ); + + $object = WC_Helper_Order::create_order(); + $this->assertTrue( $object->needs_shipping_address() ); + } + + /** + * Test: has_downloadable_item + */ + function test_has_downloadable_item() { + $object = new WC_Order(); + $this->assertFalse( $object->has_downloadable_item() ); + + $object = WC_Helper_Order::create_order(); + $this->assertFalse( $object->has_downloadable_item() ); + } + + /** + * Test: needs_payment + */ + function test_needs_payment() { + $object = new WC_Order(); + + $object->set_status( 'pending' ); + $this->assertFalse( $object->needs_payment() ); + + $object->set_total( 100 ); + $this->assertTrue( $object->needs_payment() ); + + $object->set_status( 'processing' ); + $this->assertFalse( $object->needs_payment() ); + } + + /** + * Test: get_checkout_payment_url + */ + function test_get_checkout_payment_url() { + $object = new WC_Order(); + $id = $object->save(); + $this->assertEquals( 'http://example.org?order-pay=' . $id . '&pay_for_order=true&key=' . $object->get_order_key(), $object->get_checkout_payment_url() ); + } + + /** + * Test: get_checkout_order_received_url + */ + function test_get_checkout_order_received_url() { + $object = new WC_Order(); + $object->set_order_key( 'xxx' ); + $id = $object->save(); + $this->assertEquals( 'http://example.org?order-received=' . $id . '&key=' . $object->get_order_key(), $object->get_checkout_order_received_url() ); + } + + /** + * Test: get_cancel_order_url + */ + function test_get_cancel_order_url() { + $object = new WC_Order(); + $this->assertInternalType( 'string', $object->get_cancel_order_url() ); + } + + /** + * Test: get_cancel_order_url_raw + */ + function test_get_cancel_order_url_raw() { + $object = new WC_Order(); + $this->assertInternalType( 'string', $object->get_cancel_order_url_raw() ); + } + + /** + * Test: get_cancel_endpoint + */ + function test_get_cancel_endpoint() { + $object = new WC_Order(); + $this->assertEquals( 'http://example.org/', $object->get_cancel_endpoint() ); + } + + /** + * Test: get_view_order_url + */ + function test_get_view_order_url() { + $object = new WC_Order(); + $id = $object->save(); + $this->assertEquals( 'http://example.org?view-order=' . $id, $object->get_view_order_url() ); + } + + /** + * Test: add_order_note + */ + function test_add_order_note() { + $object = new WC_Order(); + $id = $object->save(); + $comment_id = $object->add_order_note( "Hello, I am a fish" ); + $this->assertTrue( $comment_id > 0 ); + + $comment = get_comment( $comment_id ); + $this->assertEquals( "Hello, I am a fish", $comment->comment_content ); + } + + /** + * Test: get_customer_order_notes + */ + function test_get_customer_order_notes() { + $object = new WC_Order(); + $id = $object->save(); + + $this->assertCount( 0, $object->get_customer_order_notes() ); + + $object->add_order_note( "Hello, I am a fish", true ); + $object->add_order_note( "Hello, I am a fish", false ); + $object->add_order_note( "Hello, I am a fish", true ); + + $this->assertCount( 2, $object->get_customer_order_notes() ); + } + + /** + * Test: get_refunds + */ + function test_get_refunds() { + $object = new WC_Order(); + $id = $object->save(); + + $this->assertCount( 0, $object->get_refunds() ); + + wc_create_refund( array( + 'order_id' => $id, + 'amount' => '100', + 'line_items' => array(), + ) ); + + $this->assertCount( 1, $object->get_refunds() ); + } + + /** + * Test: get_total_refunded + */ + function test_get_total_refunded() { + $object = new WC_Order(); + $object->set_total( 400 ); + $id = $object->save(); + wc_create_refund( array( + 'order_id' => $id, + 'amount' => '100', + 'line_items' => array(), + ) ); + wc_create_refund( array( + 'order_id' => $id, + 'amount' => '100', + 'line_items' => array(), + ) ); + $this->assertEquals( 200, $object->get_total_refunded() ); + } + + /** + * Test: get_total_tax_refunded + */ + function test_get_total_tax_refunded() { + $object = new WC_Order(); + $this->assertEquals( 0, $object->get_total_tax_refunded() ); + } + + /** + * Test: get_total_shipping_refunded + */ + function test_get_total_shipping_refunded() { + $object = new WC_Order(); + $this->assertEquals( 0, $object->get_total_shipping_refunded() ); + } + + /** + * Test: get_total_shipping_refunded + */ + function test_get_total_qty_refunded() { + $object = new WC_Order(); + $this->assertEquals( 0, $object->get_total_shipping_refunded() ); + } + + /** + * Test: get_qty_refunded_for_item + */ + function test_get_qty_refunded_for_item() { + $object = new WC_Order(); + $this->assertEquals( 0, $object->get_qty_refunded_for_item( 2 ) ); + } + + /** + * Test: test_get_total_refunded_for_item + */ + function test_get_total_refunded_for_item() { + $object = new WC_Order(); + $this->assertEquals( 0, $object->get_total_refunded_for_item( 2 ) ); + } + + /** + * Test: get_tax_refunded_for_item + */ + function test_get_tax_refunded_for_item() { + $object = new WC_Order(); + $this->assertEquals( 0, $object->get_tax_refunded_for_item( 1, 1 ) ); + } + + /** + * Test: get_total_tax_refunded_by_rate_id + */ + function test_get_total_tax_refunded_by_rate_id() { + $object = new WC_Order(); + $this->assertEquals( 0, $object->get_total_tax_refunded_by_rate_id( 2 ) ); + } + + /** + * Test: get_remaining_refund_amount + */ + function test_get_remaining_refund_amount() { + $object = new WC_Order(); + $object->set_total( 400 ); + $id = $object->save(); + wc_create_refund( array( + 'order_id' => $id, + 'amount' => '100', + 'line_items' => array(), + ) ); + $this->assertEquals( 300, $object->get_remaining_refund_amount() ); + } + + /** + * Test: get_total_tax_refunded_by_rate_id + */ + function test_get_remaining_refund_items() { + $object = WC_Helper_Order::create_order(); + $this->assertEquals( 4, $object->get_remaining_refund_items() ); + } +} diff --git a/tests/unit-tests/crud/refunds.php b/tests/unit-tests/crud/refunds.php new file mode 100644 index 00000000000..38f077ef62d --- /dev/null +++ b/tests/unit-tests/crud/refunds.php @@ -0,0 +1,43 @@ +assertEquals( 'shop_order_refund', $object->get_type() ); + } + + /** + * Test: get_refund_amount + */ + function test_get_refund_amount() { + $object = new WC_Order_Refund(); + $object->set_refund_amount( 20 ); + $this->assertEquals( '20.00', $object->get_refund_amount() ); + } + + /** + * Test: get_refund_reason + */ + function test_get_refund_reason() { + $object = new WC_Order_Refund(); + $object->set_refund_reason( 'Customer is an idiot' ); + $this->assertEquals( 'Customer is an idiot', $object->get_refund_reason() ); + } + + /** + * Test: get_refunded_by + */ + function test_get_refunded_by() { + $object = new WC_Order_Refund(); + $object->set_refunded_by( 1 ); + $this->assertEquals( 1, $object->get_refunded_by() ); + } +} diff --git a/tests/unit-tests/order/functions.php b/tests/unit-tests/order/functions.php index b17717aa31c..ad516a63739 100644 --- a/tests/unit-tests/order/functions.php +++ b/tests/unit-tests/order/functions.php @@ -109,7 +109,7 @@ class WC_Tests_Order_Functions extends WC_Unit_Test_Case { $this->assertInstanceOf( 'WC_Order', wc_get_order( $order ) ); // Assert that wc_get_order() accepts a order post id. - $this->assertInstanceOf( 'WC_Order', wc_get_order( $order->id ) ); + $this->assertInstanceOf( 'WC_Order', wc_get_order( $order->get_id() ) ); // Assert that a non-shop_order post returns false $post = $this->factory->post->create_and_get( array( 'post_type' => 'post' ) ); @@ -132,7 +132,7 @@ class WC_Tests_Order_Functions extends WC_Unit_Test_Case { $this->assertEmpty( $order->get_payment_tokens() ); $token = WC_Helper_Payment_Token::create_cc_token(); - update_post_meta( $order->id, '_payment_tokens', array( $token->get_id() ) ); + update_post_meta( $order->get_id(), '_payment_tokens', array( $token->get_id() ) ); $this->assertCount( 1, $order->get_payment_tokens() ); } diff --git a/tests/unit-tests/payment-tokens/payment-tokens.php b/tests/unit-tests/payment-tokens/payment-tokens.php index 27843506792..558bb37ae5d 100644 --- a/tests/unit-tests/payment-tokens/payment-tokens.php +++ b/tests/unit-tests/payment-tokens/payment-tokens.php @@ -18,12 +18,12 @@ class WC_Tests_Payment_Tokens extends WC_Unit_Test_Case { */ function test_wc_payment_tokens_get_order_tokens() { $order = WC_Helper_Order::create_order(); - $this->assertEmpty( WC_Payment_Tokens::get_order_tokens( $order->id ) ); + $this->assertEmpty( WC_Payment_Tokens::get_order_tokens( $order->get_id() ) ); $token = WC_Helper_Payment_Token::create_cc_token(); - update_post_meta( $order->id, '_payment_tokens', array( $token->get_id() ) ); + update_post_meta( $order->get_id(), '_payment_tokens', array( $token->get_id() ) ); - $this->assertCount( 1, WC_Payment_Tokens::get_order_tokens( $order->id ) ); + $this->assertCount( 1, WC_Payment_Tokens::get_order_tokens( $order->get_id() ) ); }