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' => '