<?php use Automattic\WooCommerce\Utilities\NumberUtil; defined('ABSPATH') || exit; require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php'; abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { use WC_Item_Totals; protected $data = array('parent_id' => 0, 'status' => '', 'currency' => '', 'version' => '', 'prices_include_tax' => false, 'date_created' => null, 'date_modified' => null, 'discount_total' => 0, 'discount_tax' => 0, 'shipping_total' => 0, 'shipping_tax' => 0, 'cart_tax' => 0, 'total' => 0, 'total_tax' => 0); protected $items = array(); protected $items_to_delete = array(); protected $cache_group = 'orders'; protected $data_store_name = 'order'; protected $object_type = 'order'; public function __construct($order = 0) { parent::__construct($order); if (is_numeric($order) && $order > 0) { $this->set_id($order); } elseif ($order instanceof self) { $this->set_id($order->get_id()); } elseif (!empty($order->ID)) { $this->set_id($order->ID); } else { $this->set_object_read(true); } $this->data_store = WC_Data_Store::load($this->data_store_name); if ($this->get_id() > 0) { $this->data_store->read($this); } } public function get_type() { return 'shop_order'; } public function get_data() { return array_merge(array('id' => $this->get_id()), $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'))); } public function save() { if (!$this->data_store) { return $this->get_id(); } try { do_action('woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store); if ($this->get_id()) { $this->data_store->update($this); } else { $this->data_store->create($this); } $this->save_items(); do_action('woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store); } catch (Exception $e) { $this->handle_exception($e, __('Error saving order.', 'woocommerce')); } return $this->get_id(); } protected function handle_exception($e, $message = 'Error') { wc_get_logger()->error($message, array('order' => $this, 'error' => $e)); } protected function save_items() { $items_changed = false; foreach ($this->items_to_delete as $item) { $item->delete(); $items_changed = true; } $this->items_to_delete = array(); foreach ($this->items as $item_group => $items) { if (is_array($items)) { $items = array_filter($items); foreach ($items as $item_key => $item) { $item->set_order_id($this->get_id()); $item_id = $item->save(); if ($item_id !== $item_key) { $this->items[$item_group][$item_id] = $item; unset($this->items[$item_group][$item_key]); $items_changed = true; } } } } if ($items_changed) { delete_transient('wc_order_' . $this->get_id() . '_needs_processing'); } } public function get_parent_id($context = 'view') { return $this->get_prop('parent_id', $context); } public function get_currency($context = 'view') { return $this->get_prop('currency', $context); } public function get_version($context = 'view') { return $this->get_prop('version', $context); } public function get_prices_include_tax($context = 'view') { return $this->get_prop('prices_include_tax', $context); } public function get_date_created($context = 'view') { return $this->get_prop('date_created', $context); } public function get_date_modified($context = 'view') { return $this->get_prop('date_modified', $context); } public function get_status($context = 'view') { $status = $this->get_prop('status', $context); if (empty($status) && 'view' === $context) { $status = apply_filters('woocommerce_default_order_status', 'pending'); } return $status; } public function get_discount_total($context = 'view') { return $this->get_prop('discount_total', $context); } public function get_discount_tax($context = 'view') { return $this->get_prop('discount_tax', $context); } public function get_shipping_total($context = 'view') { return $this->get_prop('shipping_total', $context); } public function get_shipping_tax($context = 'view') { return $this->get_prop('shipping_tax', $context); } public function get_cart_tax($context = 'view') { return $this->get_prop('cart_tax', $context); } public function get_total($context = 'view') { return $this->get_prop('total', $context); } public function get_total_tax($context = 'view') { return $this->get_prop('total_tax', $context); } 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_get_total_discount', NumberUtil::round($total_discount, WC_ROUNDING_PRECISION), $this); } public function get_subtotal() { $subtotal = NumberUtil::round($this->get_cart_subtotal_for_order(), wc_get_price_decimals()); return apply_filters('woocommerce_order_get_subtotal', (float) $subtotal, $this); } 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 += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total(); $tax_totals[$code]->formatted_amount = wc_price($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_get_tax_totals', $tax_totals, $this); } protected function get_valid_statuses() { return array_keys(wc_get_order_statuses()); } public function get_user_id($context = 'view') { return 0; } public function get_user() { return false; } public function set_parent_id($value) { if ($value && ($value === $this->get_id() || !wc_get_order($value))) { $this->error('order_invalid_parent_id', __('Invalid parent ID', 'woocommerce')); } $this->set_prop('parent_id', absint($value)); } 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 (true === $this->object_read) { if (!in_array('wc-' . $new_status, $this->get_valid_statuses(), true) && 'trash' !== $new_status) { $new_status = 'pending'; } if ($old_status && !in_array('wc-' . $old_status, $this->get_valid_statuses(), true) && 'trash' !== $old_status) { $old_status = 'pending'; } } $this->set_prop('status', $new_status); return array('from' => $old_status, 'to' => $new_status); } public function set_version($value) { $this->set_prop('version', $value); } public function set_currency($value) { if ($value && !in_array($value, array_keys(get_woocommerce_currencies()), true)) { $this->error('order_invalid_currency', __('Invalid currency code', 'woocommerce')); } $this->set_prop('currency', $value ? $value : get_woocommerce_currency()); } public function set_prices_include_tax($value) { $this->set_prop('prices_include_tax', (bool) $value); } public function set_date_created($date = null) { $this->set_date_prop('date_created', $date); } public function set_date_modified($date = null) { $this->set_date_prop('date_modified', $date); } public function set_discount_total($value) { $this->set_prop('discount_total', wc_format_decimal($value)); } public function set_discount_tax($value) { $this->set_prop('discount_tax', wc_format_decimal($value)); } public function set_shipping_total($value) { $this->set_prop('shipping_total', wc_format_decimal($value)); } public function set_shipping_tax($value) { $this->set_prop('shipping_tax', wc_format_decimal($value)); $this->set_total_tax((float) $this->get_cart_tax() + (float) $this->get_shipping_tax()); } public function set_cart_tax($value) { $this->set_prop('cart_tax', wc_format_decimal($value)); $this->set_total_tax((float) $this->get_cart_tax() + (float) $this->get_shipping_tax()); } protected function set_total_tax($value) { $this->set_prop('total_tax', wc_format_decimal(NumberUtil::round($value, wc_get_price_decimals()))); } public function set_total($value, $deprecated = '') { if ($deprecated) { wc_deprecated_argument('total_type', '3.0', 'Use dedicated total setter methods instead.'); return $this->legacy_set_total($value, $deprecated); } $this->set_prop('total', wc_format_decimal($value, wc_get_price_decimals())); } public function remove_order_items($type = null) { if (!empty($type)) { $this->data_store->delete_items($this, $type); $group = $this->type_to_group($type); if ($group) { unset($this->items[$group]); } } else { $this->data_store->delete_items($this); $this->items = array(); } } protected function type_to_group($type) { $type_to_group = apply_filters('woocommerce_order_type_to_group', array('line_item' => 'line_items', 'tax' => 'tax_lines', 'shipping' => 'shipping_lines', 'fee' => 'fee_lines', 'coupon' => 'coupon_lines')); return isset($type_to_group[$type]) ? $type_to_group[$type] : ''; } public function get_items($types = 'line_item') { $items = array(); $types = array_filter((array) $types); foreach ($types as $type) { $group = $this->type_to_group($type); if ($group) { if (!isset($this->items[$group])) { $this->items[$group] = array_filter($this->data_store->read_items($this, $type)); } $items = $items + $this->items[$group]; } } return apply_filters('woocommerce_order_get_items', $items, $this, $types); } protected function get_values_for_total($field) { $items = array_map(function ($item) use($field) { return wc_add_number_precision($item[$field], false); }, array_values($this->get_items())); return $items; } public function get_coupons() { return $this->get_items('coupon'); } public function get_fees() { return $this->get_items('fee'); } public function get_taxes() { return $this->get_items('tax'); } public function get_shipping_methods() { return $this->get_items('shipping'); } 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); } public function get_coupon_codes() { $coupon_codes = array(); $coupons = $this->get_items('coupon'); if ($coupons) { foreach ($coupons as $coupon) { $coupon_codes[] = $coupon->get_code(); } } return $coupon_codes; } 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_quantity(); } return apply_filters('woocommerce_get_item_count', $count, $item_type, $this); } public function get_item($item_id, $load_from_db = true) { if ($load_from_db) { return WC_Order_Factory::get_order_item($item_id); } if ($this->items) { foreach ($this->items as $group => $items) { if (isset($items[$item_id])) { return $items[$item_id]; } } } $type = $this->data_store->get_order_item_type($this, $item_id); if (!$type) { return false; } $items = $this->get_items($type); return !empty($items[$item_id]) ? $items[$item_id] : false; } protected function get_items_key($item) { if (is_a($item, 'WC_Order_Item_Product')) { return 'line_items'; } elseif (is_a($item, 'WC_Order_Item_Fee')) { return 'fee_lines'; } elseif (is_a($item, 'WC_Order_Item_Shipping')) { return 'shipping_lines'; } elseif (is_a($item, 'WC_Order_Item_Tax')) { return 'tax_lines'; } elseif (is_a($item, 'WC_Order_Item_Coupon')) { return 'coupon_lines'; } return apply_filters('woocommerce_get_items_key', '', $item); } public function remove_item($item_id) { $item = $this->get_item($item_id, false); $items_key = $item ? $this->get_items_key($item) : false; if (!$items_key) { return false; } $this->items_to_delete[] = $item; unset($this->items[$items_key][$item->get_id()]); } public function add_item($item) { $items_key = $this->get_items_key($item); if (!$items_key) { return false; } if (!isset($this->items[$items_key])) { $this->items[$items_key] = $this->get_items($item->get_type()); } $item->set_order_id($this->get_id()); $item_id = $item->get_id(); if ($item_id) { $this->items[$items_key][$item_id] = $item; } else { $this->items[$items_key]['new:' . $items_key . count($this->items[$items_key])] = $item; } } public function hold_applied_coupons($billing_email) { $held_keys = array(); $held_keys_for_user = array(); $error = null; try { foreach (WC()->cart->get_applied_coupons() as $code) { $coupon = new WC_Coupon($code); if (!$coupon->get_data_store()) { continue; } if (0 < $coupon->get_usage_limit()) { $held_key = $this->hold_coupon($coupon); if ($held_key) { $held_keys[$coupon->get_id()] = $held_key; } } if (0 < $coupon->get_usage_limit_per_user()) { if (!isset($user_ids_and_emails)) { $user_alias = get_current_user_id() ? wp_get_current_user()->ID : sanitize_email($billing_email); $user_ids_and_emails = $this->get_billing_and_current_user_aliases($billing_email); } $held_key_for_user = $this->hold_coupon_for_users($coupon, $user_ids_and_emails, $user_alias); if ($held_key_for_user) { $held_keys_for_user[$coupon->get_id()] = $held_key_for_user; } } } } catch (Exception $e) { $error = $e; } finally { if (0 < count($held_keys_for_user) || 0 < count($held_keys)) { $this->get_data_store()->set_coupon_held_keys($this, $held_keys, $held_keys_for_user); } if ($error instanceof Exception) { throw $error; } } } private function hold_coupon($coupon) { $result = $coupon->get_data_store()->check_and_hold_coupon($coupon); if (false === $result) { throw new Exception(sprintf(__('An unexpected error happened while applying the Coupon %s.', 'woocommerce'), esc_html($coupon->get_code()))); } elseif (0 === $result) { throw new Exception(sprintf(__('Coupon %s was used in another transaction during this checkout, and coupon usage limit is reached. Please remove the coupon and try again.', 'woocommerce'), esc_html($coupon->get_code()))); } return $result; } private function hold_coupon_for_users($coupon, $user_ids_and_emails, $user_alias) { $result = $coupon->get_data_store()->check_and_hold_coupon_for_user($coupon, $user_ids_and_emails, $user_alias); if (false === $result) { throw new Exception(sprintf(__('An unexpected error happened while applying the Coupon %s.', 'woocommerce'), esc_html($coupon->get_code()))); } elseif (0 === $result) { throw new Exception(sprintf(__('You have used this coupon %s in another transaction during this checkout, and coupon usage limit is reached. Please remove the coupon and try again.', 'woocommerce'), esc_html($coupon->get_code()))); } return $result; } private function get_billing_and_current_user_aliases($billing_email) { $emails = array($billing_email); if (get_current_user_id()) { $emails[] = wp_get_current_user()->user_email; } $emails = array_unique(array_map('strtolower', array_map('sanitize_email', $emails))); $customer_data_store = WC_Data_Store::load('customer'); $user_ids = $customer_data_store->get_user_ids_for_billing_email($emails); return array_merge($user_ids, $emails); } public function apply_coupon($raw_coupon) { if (is_a($raw_coupon, 'WC_Coupon')) { $coupon = $raw_coupon; } elseif (is_string($raw_coupon)) { $code = wc_format_coupon_code($raw_coupon); $coupon = new WC_Coupon($code); if ($coupon->get_code() !== $code) { return new WP_Error('invalid_coupon', __('Invalid coupon code', 'woocommerce')); } } else { return new WP_Error('invalid_coupon', __('Invalid coupon', 'woocommerce')); } $applied_coupons = $this->get_items('coupon'); foreach ($applied_coupons as $applied_coupon) { if ($applied_coupon->get_code() === $coupon->get_code()) { return new WP_Error('invalid_coupon', __('Coupon code already applied!', 'woocommerce')); } } $discounts = new WC_Discounts($this); $applied = $discounts->apply_coupon($coupon); if (is_wp_error($applied)) { return $applied; } $data_store = $coupon->get_data_store(); if ($data_store && 0 === $this->get_customer_id()) { $usage_count = $data_store->get_usage_by_email($coupon, $this->get_billing_email()); if (0 < $coupon->get_usage_limit_per_user() && $usage_count >= $coupon->get_usage_limit_per_user()) { return new WP_Error('invalid_coupon', $coupon->get_coupon_error(106), array('status' => 400)); } } $this->set_coupon_discount_amounts($discounts); $this->save(); $this->recalculate_coupons(); $used_by = $this->get_user_id(); if (!$used_by) { $used_by = $this->get_billing_email(); } $coupon->increase_usage_count($used_by); return true; } public function remove_coupon($code) { $coupons = $this->get_items('coupon'); foreach ($coupons as $item_id => $coupon) { if ($coupon->get_code() === $code) { $this->remove_item($item_id); $coupon_object = new WC_Coupon($code); $coupon_object->decrease_usage_count($this->get_user_id()); $this->recalculate_coupons(); break; } } } public function recalculate_coupons() { foreach ($this->get_items() as $item) { $item->set_total($item->get_subtotal()); $item->set_total_tax($item->get_subtotal_tax()); } $discounts = new WC_Discounts($this); foreach ($this->get_items('coupon') as $coupon_item) { $coupon_code = $coupon_item->get_code(); $coupon_id = wc_get_coupon_id_by_code($coupon_code); if ($coupon_id) { $coupon_object = new WC_Coupon($coupon_id); } else { $coupon_object = new WC_Coupon(); $coupon_object->set_props((array) $coupon_item->get_meta('coupon_data', true)); $coupon_object->set_code($coupon_code); $coupon_object->set_virtual(true); if (!$coupon_object->get_amount()) { if ($this->get_prices_include_tax()) { $coupon_object->set_amount($coupon_item->get_discount() + $coupon_item->get_discount_tax()); } else { $coupon_object->set_amount($coupon_item->get_discount()); } $coupon_object->set_discount_type('fixed_cart'); } } $coupon_object = apply_filters('woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this); if ($coupon_object) { $discounts->apply_coupon($coupon_object, false); } } $this->set_coupon_discount_amounts($discounts); $this->set_item_discount_amounts($discounts); $this->calculate_totals(true); } protected function set_item_discount_amounts($discounts) { $item_discounts = $discounts->get_discounts_by_item(); $tax_location = $this->get_tax_location(); $tax_location = array($tax_location['country'], $tax_location['state'], $tax_location['postcode'], $tax_location['city']); if ($item_discounts) { foreach ($item_discounts as $item_id => $amount) { $item = $this->get_item($item_id, false); if ($this->get_prices_include_tax() && wc_tax_enabled() && 'taxable' === $item->get_tax_status()) { $taxes = WC_Tax::calc_tax($amount, $this->get_tax_rates($item->get_tax_class(), $tax_location), true); $amount = $amount - array_sum($taxes); } $item->set_total(max(0, $item->get_total() - $amount)); } } } protected function set_coupon_discount_amounts($discounts) { $coupons = $this->get_items('coupon'); $coupon_code_to_id = wc_list_pluck($coupons, 'get_id', 'get_code'); $all_discounts = $discounts->get_discounts(); $coupon_discounts = $discounts->get_discounts_by_coupon(); $tax_location = $this->get_tax_location(); $tax_location = array($tax_location['country'], $tax_location['state'], $tax_location['postcode'], $tax_location['city']); if ($coupon_discounts) { foreach ($coupon_discounts as $coupon_code => $amount) { $item_id = isset($coupon_code_to_id[$coupon_code]) ? $coupon_code_to_id[$coupon_code] : 0; if (!$item_id) { $coupon_item = new WC_Order_Item_Coupon(); $coupon_item->set_code($coupon_code); } else { $coupon_item = $this->get_item($item_id, false); } $discount_tax = 0; foreach ($all_discounts[$coupon_code] as $item_id => $item_discount_amount) { $item = $this->get_item($item_id, false); if ('taxable' !== $item->get_tax_status() || !wc_tax_enabled()) { continue; } $taxes = array_sum(WC_Tax::calc_tax($item_discount_amount, $this->get_tax_rates($item->get_tax_class(), $tax_location), $this->get_prices_include_tax())); if ('yes' !== get_option('woocommerce_tax_round_at_subtotal')) { $taxes = wc_round_tax_total($taxes); } $discount_tax += $taxes; if ($this->get_prices_include_tax()) { $amount = $amount - $taxes; } } $coupon_item->set_discount($amount); $coupon_item->set_discount_tax($discount_tax); $this->add_item($coupon_item); } } } public function add_product($product, $qty = 1, $args = array()) { if ($product) { $default_args = array('name' => $product->get_name(), 'tax_class' => $product->get_tax_class(), 'product_id' => $product->is_type('variation') ? $product->get_parent_id() : $product->get_id(), 'variation_id' => $product->is_type('variation') ? $product->get_id() : 0, 'variation' => $product->is_type('variation') ? $product->get_attributes() : array(), 'subtotal' => wc_get_price_excluding_tax($product, array('qty' => $qty)), 'total' => wc_get_price_excluding_tax($product, array('qty' => $qty)), 'quantity' => $qty); } else { $default_args = array('quantity' => $qty); } $args = wp_parse_args($args, $default_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(); $item->set_props($args); $item->set_backorder_meta(); $item->set_order_id($this->get_id()); $item->save(); $this->add_item($item); wc_do_deprecated_action('woocommerce_order_add_product', array($this->get_id(), $item->get_id(), $product, $qty, $args), '3.0', 'woocommerce_new_order_item action instead'); delete_transient('wc_order_' . $this->get_id() . '_needs_processing'); return $item->get_id(); } public function add_payment_token($token) { if (empty($token) || !$token instanceof WC_Payment_Token) { return false; } $token_ids = $this->data_store->get_payment_token_ids($this); $token_ids[] = $token->get_id(); $this->data_store->update_payment_token_ids($this, $token_ids); do_action('woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids); return $token->get_id(); } public function get_payment_tokens() { return $this->data_store->get_payment_token_ids($this); } public function calculate_shipping() { $shipping_total = 0; foreach ($this->get_shipping_methods() as $shipping) { $shipping_total += $shipping->get_total(); } $this->set_shipping_total($shipping_total); $this->save(); return $this->get_shipping_total(); } public function get_items_tax_classes() { $found_tax_classes = array(); foreach ($this->get_items() as $item) { if (is_callable(array($item, 'get_tax_status')) && in_array($item->get_tax_status(), array('taxable', 'shipping'), true)) { $found_tax_classes[] = $item->get_tax_class(); } } return array_unique($found_tax_classes); } protected function get_tax_location($args = array()) { $tax_based_on = get_option('woocommerce_tax_based_on'); if ('shipping' === $tax_based_on && !$this->get_shipping_country()) { $tax_based_on = 'billing'; } $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())); if ('base' === $tax_based_on || empty($args['country'])) { $args['country'] = WC()->countries->get_base_country(); $args['state'] = WC()->countries->get_base_state(); $args['postcode'] = WC()->countries->get_base_postcode(); $args['city'] = WC()->countries->get_base_city(); } return apply_filters('woocommerce_order_get_tax_location', $args, $this); } protected function get_tax_rates($tax_class, $location_args = array(), $customer = null) { $tax_location = $this->get_tax_location($location_args); $tax_location = array($tax_location['country'], $tax_location['state'], $tax_location['postcode'], $tax_location['city']); return WC_Tax::get_rates_from_location($tax_class, $tax_location, $customer); } public function calculate_taxes($args = array()) { do_action('woocommerce_order_before_calculate_taxes', $args, $this); $calculate_tax_for = $this->get_tax_location($args); $shipping_tax_class = get_option('woocommerce_shipping_tax_class'); if ('inherit' === $shipping_tax_class) { $found_classes = array_intersect(array_merge(array(''), WC_Tax::get_tax_class_slugs()), $this->get_items_tax_classes()); $shipping_tax_class = count($found_classes) ? current($found_classes) : false; } $is_vat_exempt = apply_filters('woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta('is_vat_exempt'), $this); foreach ($this->get_items(array('line_item', 'fee')) as $item_id => $item) { if (!$is_vat_exempt) { $item->calculate_taxes($calculate_tax_for); } else { $item->set_taxes(false); } } foreach ($this->get_shipping_methods() as $item_id => $item) { if (false !== $shipping_tax_class && !$is_vat_exempt) { $item->calculate_taxes(array_merge($calculate_tax_for, array('tax_class' => $shipping_tax_class))); } else { $item->set_taxes(false); } } $this->update_taxes(); } public function get_total_fees() { return array_reduce($this->get_fees(), function ($carry, $item) { return $carry + $item->get_total(); }); } public function update_taxes() { $cart_taxes = array(); $shipping_taxes = array(); $existing_taxes = $this->get_taxes(); $saved_rate_ids = array(); foreach ($this->get_items(array('line_item', 'fee')) as $item_id => $item) { $taxes = $item->get_taxes(); foreach ($taxes['total'] as $tax_rate_id => $tax) { $tax_amount = (float) $this->round_line_tax($tax, false); $cart_taxes[$tax_rate_id] = isset($cart_taxes[$tax_rate_id]) ? (float) $cart_taxes[$tax_rate_id] + $tax_amount : $tax_amount; } } foreach ($this->get_shipping_methods() as $item_id => $item) { $taxes = $item->get_taxes(); foreach ($taxes['total'] as $tax_rate_id => $tax) { $tax_amount = (float) $tax; if ('yes' !== get_option('woocommerce_tax_round_at_subtotal')) { $tax_amount = wc_round_tax_total($tax_amount); } $shipping_taxes[$tax_rate_id] = isset($shipping_taxes[$tax_rate_id]) ? $shipping_taxes[$tax_rate_id] + $tax_amount : $tax_amount; } } foreach ($existing_taxes as $tax) { if (!array_key_exists($tax->get_rate_id(), $cart_taxes) && !array_key_exists($tax->get_rate_id(), $shipping_taxes) || in_array($tax->get_rate_id(), $saved_rate_ids, true)) { $this->remove_item($tax->get_id()); continue; } $saved_rate_ids[] = $tax->get_rate_id(); $tax->set_rate($tax->get_rate_id()); $tax->set_tax_total(isset($cart_taxes[$tax->get_rate_id()]) ? $cart_taxes[$tax->get_rate_id()] : 0); $tax->set_label(WC_Tax::get_rate_label($tax->get_rate_id())); $tax->set_shipping_tax_total(!empty($shipping_taxes[$tax->get_rate_id()]) ? $shipping_taxes[$tax->get_rate_id()] : 0); $tax->save(); } $new_rate_ids = wp_parse_id_list(array_diff(array_keys($cart_taxes + $shipping_taxes), $saved_rate_ids)); foreach ($new_rate_ids as $tax_rate_id) { $item = new WC_Order_Item_Tax(); $item->set_rate($tax_rate_id); $item->set_tax_total(isset($cart_taxes[$tax_rate_id]) ? $cart_taxes[$tax_rate_id] : 0); $item->set_shipping_tax_total(!empty($shipping_taxes[$tax_rate_id]) ? $shipping_taxes[$tax_rate_id] : 0); $this->add_item($item); } $this->set_shipping_tax(array_sum($shipping_taxes)); $this->set_cart_tax(array_sum($cart_taxes)); $this->save(); } protected function get_cart_subtotal_for_order() { return wc_remove_number_precision($this->get_rounded_items_total($this->get_values_for_total('subtotal'))); } protected function get_cart_total_for_order() { return wc_remove_number_precision($this->get_rounded_items_total($this->get_values_for_total('total'))); } public function calculate_totals($and_taxes = true) { do_action('woocommerce_order_before_calculate_totals', $and_taxes, $this); $fees_total = 0; $shipping_total = 0; $cart_subtotal_tax = 0; $cart_total_tax = 0; $cart_subtotal = $this->get_cart_subtotal_for_order(); $cart_total = $this->get_cart_total_for_order(); foreach ($this->get_shipping_methods() as $shipping) { $shipping_total += NumberUtil::round($shipping->get_total(), wc_get_price_decimals()); } $this->set_shipping_total($shipping_total); foreach ($this->get_fees() as $item) { $fee_total = $item->get_total(); if (0 > $fee_total) { $max_discount = NumberUtil::round($cart_total + $fees_total + $shipping_total, wc_get_price_decimals()) * -1; if ($fee_total < $max_discount && 0 > $max_discount) { $item->set_total($max_discount); } } $fees_total += $item->get_total(); } if ($and_taxes) { $this->calculate_taxes(); } foreach ($this->get_items() as $item) { $taxes = $item->get_taxes(); foreach ($taxes['total'] as $tax_rate_id => $tax) { $cart_total_tax += (float) $tax; } foreach ($taxes['subtotal'] as $tax_rate_id => $tax) { $cart_subtotal_tax += (float) $tax; } } $this->set_discount_total(NumberUtil::round($cart_subtotal - $cart_total, wc_get_price_decimals())); $this->set_discount_tax(wc_round_tax_total($cart_subtotal_tax - $cart_total_tax)); $this->set_total(NumberUtil::round($cart_total + $fees_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals())); do_action('woocommerce_order_after_calculate_totals', $and_taxes, $this); $this->save(); return $this->get_total(); } public function get_item_subtotal($item, $inc_tax = false, $round = true) { $subtotal = 0; if (is_callable(array($item, 'get_subtotal')) && $item->get_quantity()) { if ($inc_tax) { $subtotal = ($item->get_subtotal() + $item->get_subtotal_tax()) / $item->get_quantity(); } else { $subtotal = floatval($item->get_subtotal()) / $item->get_quantity(); } $subtotal = $round ? number_format((float) $subtotal, wc_get_price_decimals(), '.', '') : $subtotal; } return apply_filters('woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round); } public function get_line_subtotal($item, $inc_tax = false, $round = true) { $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 ? NumberUtil::round($subtotal, wc_get_price_decimals()) : $subtotal; } return apply_filters('woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round); } public function get_item_total($item, $inc_tax = false, $round = true) { $total = 0; if (is_callable(array($item, 'get_total')) && $item->get_quantity()) { if ($inc_tax) { $total = ($item->get_total() + $item->get_total_tax()) / $item->get_quantity(); } else { $total = floatval($item->get_total()) / $item->get_quantity(); } $total = $round ? NumberUtil::round($total, wc_get_price_decimals()) : $total; } return apply_filters('woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round); } public function get_line_total($item, $inc_tax = false, $round = true) { $total = 0; if (is_callable(array($item, 'get_total'))) { $total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total(); $total = $round ? NumberUtil::round($total, wc_get_price_decimals()) : $total; } return apply_filters('woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round); } public function get_item_tax($item, $round = true) { $tax = 0; if (is_callable(array($item, 'get_total_tax')) && $item->get_quantity()) { $tax = $item->get_total_tax() / $item->get_quantity(); $tax = $round ? wc_round_tax_total($tax) : $tax; } return apply_filters('woocommerce_order_amount_item_tax', $tax, $item, $round, $this); } public function get_line_tax($item) { 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); } public function get_formatted_line_subtotal($item, $tax_display = '') { $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart'); if ('excl' === $tax_display) { $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_currency())); } else { $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); } public function get_formatted_order_total() { $formatted_total = wc_price($this->get_total(), array('currency' => $this->get_currency())); return apply_filters('woocommerce_get_formatted_order_total', $formatted_total, $this); } public function get_subtotal_to_display($compound = false, $tax_display = '') { $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart'); $subtotal = $this->get_cart_subtotal_for_order(); if (!$compound) { if ('incl' === $tax_display) { $subtotal_taxes = 0; foreach ($this->get_items() as $item) { $subtotal_taxes += self::round_line_tax($item->get_subtotal_tax(), false); } $subtotal += wc_round_tax_total($subtotal_taxes); } $subtotal = wc_price($subtotal, array('currency' => $this->get_currency())); if ('excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled()) { $subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; } } else { if ('incl' === $tax_display) { return ''; } $subtotal += $this->get_shipping_total(); foreach ($this->get_taxes() as $tax) { if ($tax->is_compound()) { continue; } $subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total(); } $subtotal = $subtotal - $this->get_total_discount(); $subtotal = wc_price($subtotal, array('currency' => $this->get_currency())); } return apply_filters('woocommerce_order_subtotal_to_display', $subtotal, $compound, $this); } public function get_shipping_to_display($tax_display = '') { $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart'); if (0 < abs((float) $this->get_shipping_total())) { if ('excl' === $tax_display) { $shipping = wc_price($this->get_shipping_total(), array('currency' => $this->get_currency())); if ((float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax()) { $shipping .= apply_filters('woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display); } } else { $shipping = wc_price($this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency())); if ((float) $this->get_shipping_tax() > 0 && !$this->get_prices_include_tax()) { $shipping .= apply_filters('woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display); } } $shipping .= apply_filters('woocommerce_order_shipping_to_display_shipped_via', '&nbsp;<small class="shipped_via">' . sprintf(__('via %s', 'woocommerce'), $this->get_shipping_method()) . '</small>', $this); } elseif ($this->get_shipping_method()) { $shipping = $this->get_shipping_method(); } else { $shipping = __('Free!', 'woocommerce'); } return apply_filters('woocommerce_order_shipping_to_display', $shipping, $this, $tax_display); } public function get_discount_to_display($tax_display = '') { $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); } protected function add_order_item_totals_subtotal_row(&$total_rows, $tax_display) { $subtotal = $this->get_subtotal_to_display(false, $tax_display); if ($subtotal) { $total_rows['cart_subtotal'] = array('label' => __('Subtotal:', 'woocommerce'), 'value' => $subtotal); } } protected function add_order_item_totals_discount_row(&$total_rows, $tax_display) { if ($this->get_total_discount() > 0) { $total_rows['discount'] = array('label' => __('Discount:', 'woocommerce'), 'value' => '-' . $this->get_discount_to_display($tax_display)); } } protected function add_order_item_totals_shipping_row(&$total_rows, $tax_display) { if ($this->get_shipping_method()) { $total_rows['shipping'] = array('label' => __('Shipping:', 'woocommerce'), 'value' => $this->get_shipping_to_display($tax_display)); } } protected function add_order_item_totals_fee_rows(&$total_rows, $tax_display) { $fees = $this->get_fees(); if ($fees) { foreach ($fees as $id => $fee) { if (apply_filters('woocommerce_get_order_item_totals_excl_free_fees', empty($fee['line_total']) && empty($fee['line_tax']), $id)) { continue; } $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()))); } } } protected function add_order_item_totals_tax_rows(&$total_rows, $tax_display) { if ('excl' === $tax_display && wc_tax_enabled()) { if ('itemized' === get_option('woocommerce_tax_total_display')) { foreach ($this->get_tax_totals() as $code => $tax) { $total_rows[sanitize_title($code)] = array('label' => $tax->label . ':', 'value' => $tax->formatted_amount); } } else { $total_rows['tax'] = array('label' => WC()->countries->tax_or_vat() . ':', 'value' => wc_price($this->get_total_tax(), array('currency' => $this->get_currency()))); } } } protected function add_order_item_totals_total_row(&$total_rows, $tax_display) { $total_rows['order_total'] = array('label' => __('Total:', 'woocommerce'), 'value' => $this->get_formatted_order_total($tax_display)); } public function get_order_item_totals($tax_display = '') { $tax_display = $tax_display ? $tax_display : get_option('woocommerce_tax_display_cart'); $total_rows = array(); $this->add_order_item_totals_subtotal_row($total_rows, $tax_display); $this->add_order_item_totals_discount_row($total_rows, $tax_display); $this->add_order_item_totals_shipping_row($total_rows, $tax_display); $this->add_order_item_totals_fee_rows($total_rows, $tax_display); $this->add_order_item_totals_tax_rows($total_rows, $tax_display); $this->add_order_item_totals_total_row($total_rows, $tax_display); return apply_filters('woocommerce_get_order_item_totals', $total_rows, $this, $tax_display); } public function has_status($status) { return apply_filters('woocommerce_order_has_status', is_array($status) && in_array($this->get_status(), $status, true) || $this->get_status() === $status, $this, $status); } public function has_shipping_method($method_id) { foreach ($this->get_shipping_methods() as $shipping_method) { if (strpos($shipping_method->get_method_id(), $method_id) === 0) { return true; } } return false; } public function has_free_item() { foreach ($this->get_items() as $item) { if (!$item->get_total()) { return true; } } return false; } }