<?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', ' <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', ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display);
}
}
$shipping .= apply_filters('woocommerce_order_shipping_to_display_shipped_via', ' <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;
}
}