PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 10.9.4
WooCommerce v10.9.4
10.9.4 10.9.3 10.9.2 10.9.1 10.9.0 10.9.0-rc.1 10.9.0-beta.2 10.9.0-beta.1 10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / includes / class-wc-cart.php
woocommerce / includes Last commit date
abstracts 1 month ago admin 6 days ago blocks 11 months ago cli 1 month ago customizer 4 months ago data-stores 1 month ago emails 5 days ago export 1 year ago gateways 1 month ago import 3 months ago integrations 2 months ago interfaces 4 months ago legacy 4 months ago libraries 1 month ago log-handlers 1 year ago payment-tokens 5 years ago product-usage 1 year ago queue 4 months ago react-admin 1 month ago rest-api 1 month ago shipping 3 months ago shortcodes 1 month ago theme-support 2 years ago tracks 1 month ago traits 5 years ago walkers 5 years ago wccom-site 1 month ago widgets 2 months ago class-wc-ajax.php 1 month ago class-wc-auth.php 1 year ago class-wc-autoloader.php 9 months ago class-wc-background-emailer.php 2 months ago class-wc-background-updater.php 5 years ago class-wc-brands-brand-settings-manager.php 1 year ago class-wc-brands-coupons.php 1 year ago class-wc-brands.php 1 month ago class-wc-breadcrumb.php 4 months ago class-wc-cache-helper.php 1 month ago class-wc-cart-fees.php 2 years ago class-wc-cart-session.php 3 months ago class-wc-cart-totals.php 11 months ago class-wc-cart.php 1 month ago class-wc-checkout.php 1 month ago class-wc-cli.php 10 months ago class-wc-comments.php 4 months ago class-wc-countries.php 2 months ago class-wc-coupon.php 2 months ago class-wc-customer-download-log.php 5 years ago class-wc-customer-download.php 1 year ago class-wc-customer.php 1 day ago class-wc-data-exception.php 8 years ago class-wc-data-store.php 3 years ago class-wc-datetime.php 4 years ago class-wc-deprecated-action-hooks.php 2 years ago class-wc-deprecated-filter-hooks.php 3 months ago class-wc-discounts.php 11 months ago class-wc-download-handler.php 1 month ago class-wc-emails.php 1 month ago class-wc-embed.php 1 year ago class-wc-form-handler.php 3 months ago class-wc-frontend-scripts.php 2 months ago class-wc-geo-ip.php 9 months ago class-wc-geolite-integration.php 6 years ago class-wc-geolocation.php 2 months ago class-wc-https.php 3 years ago class-wc-install.php 6 days ago class-wc-integrations.php 5 years ago class-wc-log-levels.php 2 years ago class-wc-logger.php 4 months ago class-wc-meta-data.php 1 month ago class-wc-order-factory.php 2 months ago class-wc-order-item-coupon.php 4 years ago class-wc-order-item-fee.php 5 months ago class-wc-order-item-meta.php 4 years ago class-wc-order-item-product.php 1 month ago class-wc-order-item-shipping.php 5 months ago class-wc-order-item-tax.php 1 month ago class-wc-order-item.php 1 month ago class-wc-order-query.php 1 month ago class-wc-order-refund.php 1 year ago class-wc-order.php 1 month ago class-wc-payment-gateways.php 1 month ago class-wc-payment-tokens.php 3 years ago class-wc-post-data.php 2 months ago class-wc-post-types.php 1 month ago class-wc-privacy-background-process.php 1 year ago class-wc-privacy-erasers.php 10 months ago class-wc-privacy-exporters.php 4 years ago class-wc-privacy.php 1 year ago class-wc-product-attribute.php 4 months ago class-wc-product-download.php 4 months ago class-wc-product-external.php 1 year ago class-wc-product-factory.php 3 months ago class-wc-product-grouped.php 3 months ago class-wc-product-query.php 4 months ago class-wc-product-simple.php 11 months ago class-wc-product-variable.php 1 month ago class-wc-product-variation.php 1 month ago class-wc-query.php 1 month ago class-wc-rate-limiter.php 4 years ago class-wc-regenerate-images-request.php 3 years ago class-wc-regenerate-images.php 2 years ago class-wc-register-wp-admin-settings.php 5 years ago class-wc-rest-authentication.php 2 years ago class-wc-rest-exception.php 5 years ago class-wc-session-handler.php 3 months ago class-wc-shipping-rate.php 1 year ago class-wc-shipping-zone.php 5 years ago class-wc-shipping-zones.php 7 months ago class-wc-shipping.php 2 months ago class-wc-shortcodes.php 1 year ago class-wc-structured-data.php 1 month ago class-wc-tax.php 2 months ago class-wc-template-loader.php 7 months ago class-wc-tracker.php 1 month ago class-wc-validation.php 2 years ago class-wc-webhook.php 1 month ago class-woocommerce.php 1 day ago wc-account-functions.php 1 month ago wc-attribute-functions.php 1 month ago wc-brands-functions.php 1 year ago wc-cart-functions.php 1 month ago wc-conditional-functions.php 11 months ago wc-core-functions.php 2 months ago wc-coupon-functions.php 5 months ago wc-deprecated-functions.php 1 month ago wc-formatting-functions.php 1 month ago wc-interactivity-api-functions.php 2 months ago wc-notice-functions.php 5 months ago wc-order-functions.php 1 month ago wc-order-item-functions.php 3 years ago wc-order-step-logger-functions.php 4 months ago wc-page-functions.php 1 month ago wc-product-functions.php 2 months ago wc-rest-functions.php 7 months ago wc-stock-functions.php 7 months ago wc-template-functions.php 1 month ago wc-template-hooks.php 10 months ago wc-term-functions.php 2 months ago wc-update-functions.php 6 days ago wc-user-functions.php 1 month ago wc-webhook-functions.php 1 month ago wc-widget-functions.php 5 years ago
class-wc-cart.php
2463 lines
1 <?php
2 /**
3 * WooCommerce cart
4 *
5 * The WooCommerce cart class stores cart data and active coupons as well as handling customer sessions and some cart related urls.
6 * The cart class also has a price calculation function which calls upon other classes to calculate totals.
7 *
8 * @package WooCommerce\Classes
9 * @version 2.1.0
10 */
11
12 use Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils;
13 use Automattic\WooCommerce\Enums\ProductStatus;
14 use Automattic\WooCommerce\Enums\ProductType;
15 use Automattic\WooCommerce\Utilities\DiscountsUtil;
16 use Automattic\WooCommerce\Utilities\NumberUtil;
17 use Automattic\WooCommerce\Utilities\ShippingUtil;
18 use Automattic\WooCommerce\StoreApi\Utilities\LocalPickupUtils;
19
20 defined( 'ABSPATH' ) || exit;
21
22 require_once WC_ABSPATH . 'includes/legacy/class-wc-legacy-cart.php';
23 require_once WC_ABSPATH . 'includes/class-wc-cart-fees.php';
24 require_once WC_ABSPATH . 'includes/class-wc-cart-session.php';
25
26 /**
27 * WC_Cart class.
28 */
29 class WC_Cart extends WC_Legacy_Cart {
30
31 /**
32 * Cart context, used to determine if the cart is being used in a StoreAPI or shortcode context. This should only
33 * be used internally.
34 *
35 * @var string shortcode|store-api
36 */
37 public $cart_context = 'shortcode';
38
39 /**
40 * Contains an array of cart items.
41 *
42 * @var array
43 */
44 public $cart_contents = array();
45
46 /**
47 * Contains an array of removed cart items so we can restore them if needed.
48 *
49 * @var array
50 */
51 public $removed_cart_contents = array();
52
53 /**
54 * Contains an array of coupon codes applied to the cart.
55 *
56 * @var array
57 */
58 public $applied_coupons = array();
59
60 /**
61 * This stores the chosen shipping methods for the cart item packages.
62 *
63 * @var array
64 */
65 protected $shipping_methods;
66
67 /**
68 * Whether the shipping totals have been calculated. This will only return true if shipping was calculated, not if
69 * shipping is disabled or if there are no cart contents.
70 *
71 * @var bool
72 */
73 protected $has_calculated_shipping = false;
74
75 /**
76 * Total defaults used to reset.
77 *
78 * @var array
79 */
80 protected $default_totals = array(
81 'subtotal' => 0,
82 'subtotal_tax' => 0,
83 'shipping_total' => 0,
84 'shipping_tax' => 0,
85 'shipping_taxes' => array(),
86 'discount_total' => 0,
87 'discount_tax' => 0,
88 'cart_contents_total' => 0,
89 'cart_contents_tax' => 0,
90 'cart_contents_taxes' => array(),
91 'fee_total' => 0,
92 'fee_tax' => 0,
93 'fee_taxes' => array(),
94 'total' => 0,
95 'total_tax' => 0,
96 );
97 /**
98 * Store calculated totals.
99 *
100 * @var array
101 */
102 protected $totals = array();
103
104 /**
105 * Reference to the cart session handling class.
106 *
107 * @var WC_Cart_Session
108 */
109 protected $session;
110
111 /**
112 * Reference to the cart fees API class.
113 *
114 * @var WC_Cart_Fees
115 */
116 protected $fees_api;
117
118 /**
119 * Constructor for the cart class. Loads options and hooks in the init method.
120 *
121 * @return void
122 */
123 public function __construct() {
124 $this->session = new WC_Cart_Session( $this );
125 $this->fees_api = new WC_Cart_Fees();
126
127 // Register hooks for the objects.
128 $this->session->init();
129
130 add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 );
131 add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 );
132 add_action( 'woocommerce_removed_coupon', array( $this, 'calculate_totals' ), 20, 0 );
133 add_action( 'woocommerce_cart_item_removed', array( $this, 'calculate_totals' ), 20, 0 );
134 add_action( 'woocommerce_cart_item_restored', array( $this, 'calculate_totals' ), 20, 0 );
135 add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_items' ), 1 );
136 add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_coupons' ), 1 );
137 add_action( 'woocommerce_after_checkout_validation', array( $this, 'check_customer_coupons' ), 1, 2 );
138 }
139
140 /**
141 * When cloning, ensure object properties are handled.
142 *
143 * These properties store a reference to the cart, so we use new instead of clone.
144 *
145 * @return void
146 */
147 public function __clone() {
148 $this->session = clone $this->session;
149 $this->fees_api = clone $this->fees_api;
150
151 $this->session->set_cart( $this );
152 }
153
154 /*
155 |--------------------------------------------------------------------------
156 | Getters.
157 |--------------------------------------------------------------------------
158 |
159 | Methods to retrieve class properties and avoid direct access.
160 */
161
162 /**
163 * Gets cart contents.
164 *
165 * @since 3.2.0
166 * @return array of cart items
167 */
168 public function get_cart_contents() {
169 return apply_filters( 'woocommerce_get_cart_contents', (array) $this->cart_contents );
170 }
171
172 /**
173 * Return items removed from the cart.
174 *
175 * @since 3.2.0
176 * @return array
177 */
178 public function get_removed_cart_contents() {
179 return (array) $this->removed_cart_contents;
180 }
181
182 /**
183 * Gets the array of applied coupon codes.
184 *
185 * @return array of applied coupons
186 */
187 public function get_applied_coupons() {
188 return (array) $this->applied_coupons;
189 }
190
191 /**
192 * Return all calculated coupon totals.
193 *
194 * @since 3.2.0
195 * @return array
196 */
197 public function get_coupon_discount_totals() {
198 return (array) $this->coupon_discount_totals;
199 }
200 /**
201 * Return all calculated coupon tax totals.
202 *
203 * @since 3.2.0
204 * @return array
205 */
206 public function get_coupon_discount_tax_totals() {
207 return (array) $this->coupon_discount_tax_totals;
208 }
209
210 /**
211 * Return all calculated totals.
212 *
213 * @since 3.2.0
214 * @return array
215 */
216 public function get_totals() {
217 return empty( $this->totals ) ? $this->default_totals : $this->totals;
218 }
219
220 /**
221 * Get a total.
222 *
223 * @since 3.2.0
224 * @param string $key Key of element in $totals array.
225 * @return mixed
226 */
227 protected function get_totals_var( $key ) {
228 return isset( $this->totals[ $key ] ) ? $this->totals[ $key ] : $this->default_totals[ $key ];
229 }
230
231 /**
232 * Get subtotal.
233 *
234 * @since 3.2.0
235 * @return float
236 */
237 public function get_subtotal() {
238 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'subtotal' ) );
239 }
240
241 /**
242 * Get subtotal_tax.
243 *
244 * @since 3.2.0
245 * @return float
246 */
247 public function get_subtotal_tax() {
248 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'subtotal_tax' ) );
249 }
250
251 /**
252 * Get discount_total.
253 *
254 * @since 3.2.0
255 * @return float
256 */
257 public function get_discount_total() {
258 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'discount_total' ) );
259 }
260
261 /**
262 * Get discount_tax.
263 *
264 * @since 3.2.0
265 * @return float
266 */
267 public function get_discount_tax() {
268 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'discount_tax' ) );
269 }
270
271 /**
272 * Get shipping_total.
273 *
274 * @since 3.2.0
275 * @return float
276 */
277 public function get_shipping_total() {
278 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_total' ) );
279 }
280
281 /**
282 * Get shipping_tax.
283 *
284 * @since 3.2.0
285 * @return float
286 */
287 public function get_shipping_tax() {
288 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_tax' ) );
289 }
290
291 /**
292 * Gets cart total. This is the total of items in the cart, but after discounts. Subtotal is before discounts.
293 *
294 * @since 3.2.0
295 * @return float|string|int
296 */
297 public function get_cart_contents_total() {
298 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_total' ) );
299 }
300
301 /**
302 * Gets cart tax amount.
303 *
304 * @since 3.2.0
305 * @return float
306 */
307 public function get_cart_contents_tax() {
308 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_tax' ) );
309 }
310
311 /**
312 * Gets cart total after calculation.
313 *
314 * @since 3.2.0
315 * @param string $context If the context is view, the value will be formatted for display. This keeps it compatible with pre-3.2 versions.
316 * @return float|string
317 */
318 public function get_total( $context = 'view' ) {
319 $total = apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'total' ) );
320 return 'view' === $context ? apply_filters( 'woocommerce_cart_total', wc_price( $total ) ) : $total;
321 }
322
323 /**
324 * Get total tax amount.
325 *
326 * @since 3.2.0
327 * @return float
328 */
329 public function get_total_tax() {
330 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'total_tax' ) );
331 }
332
333 /**
334 * Get total fee amount.
335 *
336 * @since 3.2.0
337 * @return float
338 */
339 public function get_fee_total() {
340 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_total' ) );
341 }
342
343 /**
344 * Get total fee tax amount.
345 *
346 * @since 3.2.0
347 * @return float
348 */
349 public function get_fee_tax() {
350 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_tax' ) );
351 }
352
353 /**
354 * Get taxes.
355 *
356 * @since 3.2.0
357 * @return array
358 */
359 public function get_shipping_taxes() {
360 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_taxes' ) );
361 }
362
363 /**
364 * Get taxes.
365 *
366 * @since 3.2.0
367 * @return array
368 */
369 public function get_cart_contents_taxes() {
370 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_taxes' ) );
371 }
372
373 /**
374 * Get taxes.
375 *
376 * @since 3.2.0
377 * @return array
378 */
379 public function get_fee_taxes() {
380 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_taxes' ) );
381 }
382
383 /**
384 * Return whether or not the cart is displaying prices including tax, rather than excluding tax.
385 *
386 * @since 3.3.0
387 * @return bool
388 */
389 public function display_prices_including_tax() {
390 return apply_filters( 'woocommerce_cart_' . __FUNCTION__, 'incl' === $this->get_tax_price_display_mode() );
391 }
392
393 /*
394 |--------------------------------------------------------------------------
395 | Setters.
396 |--------------------------------------------------------------------------
397 |
398 | Methods to set class properties and avoid direct access.
399 */
400
401 /**
402 * Sets the contents of the cart.
403 *
404 * @param array $value Cart array.
405 * @return void
406 */
407 public function set_cart_contents( $value ) {
408 $this->cart_contents = (array) $value;
409 }
410
411 /**
412 * Set items removed from the cart.
413 *
414 * @since 3.2.0
415 * @param array $value Item array.
416 * @return void
417 */
418 public function set_removed_cart_contents( $value = array() ) {
419 $this->removed_cart_contents = (array) $value;
420 }
421
422 /**
423 * Sets the array of applied coupon codes.
424 *
425 * @param array $value List of applied coupon codes.
426 * @return void
427 */
428 public function set_applied_coupons( $value = array() ) {
429 $this->applied_coupons = (array) $value;
430 }
431
432 /**
433 * Sets the array of calculated coupon totals.
434 *
435 * @since 3.2.0
436 * @param array $value Value to set.
437 * @return void
438 */
439 public function set_coupon_discount_totals( $value = array() ) {
440 $this->coupon_discount_totals = (array) $value;
441 }
442 /**
443 * Sets the array of calculated coupon tax totals.
444 *
445 * @since 3.2.0
446 * @param array $value Value to set.
447 * @return void
448 */
449 public function set_coupon_discount_tax_totals( $value = array() ) {
450 $this->coupon_discount_tax_totals = (array) $value;
451 }
452
453 /**
454 * Set all calculated totals.
455 *
456 * @since 3.2.0
457 * @param array $value Value to set.
458 * @return void
459 */
460 public function set_totals( $value = array() ) {
461 $this->totals = wp_parse_args( $value, $this->default_totals );
462 }
463
464 /**
465 * Set subtotal.
466 *
467 * @since 3.2.0
468 * @param string $value Value to set.
469 * @return void
470 */
471 public function set_subtotal( $value ) {
472 $this->totals['subtotal'] = wc_format_decimal( $value );
473 }
474
475 /**
476 * Set subtotal.
477 *
478 * @since 3.2.0
479 * @param string $value Value to set.
480 * @return void
481 */
482 public function set_subtotal_tax( $value ) {
483 $this->totals['subtotal_tax'] = $value;
484 }
485
486 /**
487 * Set discount_total.
488 *
489 * @since 3.2.0
490 * @param string $value Value to set.
491 * @return void
492 */
493 public function set_discount_total( $value ) {
494 $this->totals['discount_total'] = $value;
495 }
496
497 /**
498 * Set discount_tax.
499 *
500 * @since 3.2.0
501 * @param string $value Value to set.
502 * @return void
503 */
504 public function set_discount_tax( $value ) {
505 $this->totals['discount_tax'] = $value;
506 }
507
508 /**
509 * Set shipping_total.
510 *
511 * @since 3.2.0
512 * @param string $value Value to set.
513 * @return void
514 */
515 public function set_shipping_total( $value ) {
516 $this->totals['shipping_total'] = wc_format_decimal( $value );
517 }
518
519 /**
520 * Set shipping_tax.
521 *
522 * @since 3.2.0
523 * @param string $value Value to set.
524 * @return void
525 */
526 public function set_shipping_tax( $value ) {
527 $this->totals['shipping_tax'] = $value;
528 }
529
530 /**
531 * Set cart_contents_total.
532 *
533 * @since 3.2.0
534 * @param string $value Value to set.
535 * @return void
536 */
537 public function set_cart_contents_total( $value ) {
538 $this->totals['cart_contents_total'] = wc_format_decimal( $value );
539 }
540
541 /**
542 * Set cart tax amount.
543 *
544 * @since 3.2.0
545 * @param string $value Value to set.
546 * @return void
547 */
548 public function set_cart_contents_tax( $value ) {
549 $this->totals['cart_contents_tax'] = $value;
550 }
551
552 /**
553 * Set cart total.
554 *
555 * @since 3.2.0
556 * @param string $value Value to set.
557 * @return void
558 */
559 public function set_total( $value ) {
560 $this->totals['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
561 }
562
563 /**
564 * Set total tax amount.
565 *
566 * @since 3.2.0
567 * @param string $value Value to set.
568 * @return void
569 */
570 public function set_total_tax( $value ) {
571 // We round here because this is a total entry, as opposed to line items in other setters.
572 $this->totals['total_tax'] = wc_round_tax_total( $value );
573 }
574
575 /**
576 * Set fee amount.
577 *
578 * @since 3.2.0
579 * @param string $value Value to set.
580 * @return void
581 */
582 public function set_fee_total( $value ) {
583 $this->totals['fee_total'] = wc_format_decimal( $value );
584 }
585
586 /**
587 * Set fee tax.
588 *
589 * @since 3.2.0
590 * @param string $value Value to set.
591 * @return void
592 */
593 public function set_fee_tax( $value ) {
594 $this->totals['fee_tax'] = $value;
595 }
596
597 /**
598 * Set taxes.
599 *
600 * @since 3.2.0
601 * @param array $value Tax values.
602 * @return void
603 */
604 public function set_shipping_taxes( $value ) {
605 $this->totals['shipping_taxes'] = (array) $value;
606 }
607
608 /**
609 * Set taxes.
610 *
611 * @since 3.2.0
612 * @param array $value Tax values.
613 * @return void
614 */
615 public function set_cart_contents_taxes( $value ) {
616 $this->totals['cart_contents_taxes'] = (array) $value;
617 }
618
619 /**
620 * Set taxes.
621 *
622 * @since 3.2.0
623 * @param array $value Tax values.
624 * @return void
625 */
626 public function set_fee_taxes( $value ) {
627 $this->totals['fee_taxes'] = (array) $value;
628 }
629
630 /*
631 |--------------------------------------------------------------------------
632 | Helper methods.
633 |--------------------------------------------------------------------------
634 */
635
636 /**
637 * Returns the cart and shipping taxes, merged.
638 *
639 * @return array merged taxes
640 */
641 public function get_taxes() {
642 return apply_filters( 'woocommerce_cart_get_taxes', wc_array_merge_recursive_numeric( $this->get_shipping_taxes(), $this->get_cart_contents_taxes(), $this->get_fee_taxes() ), $this );
643 }
644
645 /**
646 * Returns the contents of the cart in an array.
647 *
648 * @return array contents of the cart
649 */
650 public function get_cart() {
651 if ( ! did_action( 'wp_loaded' ) ) {
652 wc_doing_it_wrong( __FUNCTION__, __( 'Get cart should not be called before the wp_loaded action.', 'woocommerce' ), '2.3' );
653 }
654 if ( ! did_action( 'woocommerce_load_cart_from_session' ) ) {
655 $this->session->get_cart_from_session();
656 }
657 return array_filter( $this->get_cart_contents() );
658 }
659
660 /**
661 * Returns a specific item in the cart.
662 *
663 * @param string $item_key Cart item key.
664 * @return array Item data
665 */
666 public function get_cart_item( $item_key ) {
667 return isset( $this->cart_contents[ $item_key ] ) ? $this->cart_contents[ $item_key ] : array();
668 }
669
670 /**
671 * Checks if the cart is empty.
672 *
673 * @return bool
674 */
675 public function is_empty() {
676 return 0 === count( $this->get_cart() );
677 }
678
679 /**
680 * Empties the cart and optionally the persistent cart too.
681 *
682 * @since 9.7.0 Also clears shipping methods and packages since the items they are linked to are cleared.
683 *
684 * @param bool $clear_persistent_cart Should the persistent cart be cleared too. Defaults to true.
685 * @return void
686 */
687 public function empty_cart( $clear_persistent_cart = true ) {
688 /**
689 * Fires before the cart is emptied.
690 *
691 * @since 9.7.0
692 *
693 * @param bool $clear_persistent_cart Whether the persistent cart will be cleared too.
694 */
695 do_action( 'woocommerce_before_cart_emptied', $clear_persistent_cart );
696
697 $this->cart_contents = array();
698 $this->removed_cart_contents = array();
699 $this->shipping_methods = array();
700 $this->coupon_discount_totals = array();
701 $this->coupon_discount_tax_totals = array();
702 $this->applied_coupons = array();
703 $this->totals = $this->default_totals;
704
705 if ( $clear_persistent_cart ) {
706 $this->session->persistent_cart_destroy();
707 }
708
709 $this->fees_api->remove_all_fees();
710 WC()->shipping()->reset_shipping();
711
712 /**
713 * Fires after the cart is emptied.
714 *
715 * @since 9.7.0
716 *
717 * @param bool $clear_persistent_cart Whether the persistent cart was cleared too.
718 */
719 do_action( 'woocommerce_cart_emptied', $clear_persistent_cart );
720 }
721
722 /**
723 * Get number of items in the cart.
724 *
725 * @return int
726 */
727 public function get_cart_contents_count() {
728 return apply_filters( 'woocommerce_cart_contents_count', array_sum( wp_list_pluck( $this->get_cart(), 'quantity' ) ) );
729 }
730
731 /**
732 * Get weight of items in the cart.
733 *
734 * @since 2.5.0
735 * @return float
736 */
737 public function get_cart_contents_weight() {
738 $weight = 0.0;
739
740 foreach ( $this->get_cart() as $values ) {
741 if ( $values['data']->has_weight() ) {
742 $weight += (float) $values['data']->get_weight() * $values['quantity'];
743 }
744 }
745
746 return apply_filters( 'woocommerce_cart_contents_weight', $weight );
747 }
748
749 /**
750 * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines.
751 *
752 * @return array
753 */
754 public function get_cart_item_quantities() {
755 $quantities = array();
756
757 foreach ( $this->get_cart() as $values ) {
758 $managed_by_id = $values['data']->get_stock_managed_by_id();
759 $quantities[ $managed_by_id ] = $values['quantity'] + ( $quantities[ $managed_by_id ] ?? 0 );
760 }
761
762 return $quantities;
763 }
764
765 /**
766 * Check all cart items for errors.
767 */
768 public function check_cart_items() {
769 $return = true;
770 $result = $this->check_cart_item_validity();
771
772 if ( is_wp_error( $result ) ) {
773 wc_add_notice( $result->get_error_message(), 'error' );
774 $return = false;
775 }
776
777 $result = $this->check_cart_item_sold_individually();
778
779 if ( is_wp_error( $result ) ) {
780 foreach ( $result->get_error_messages() as $message ) {
781 wc_add_notice( $message, 'error' );
782 }
783 $return = false;
784 }
785
786 $result = $this->check_cart_item_stock();
787
788 if ( is_wp_error( $result ) ) {
789 wc_add_notice( $result->get_error_message(), 'error' );
790 $return = false;
791 }
792
793 return $return;
794 }
795
796 /**
797 * Check cart coupons for errors.
798 *
799 * @return void
800 */
801 public function check_cart_coupons() {
802 foreach ( $this->get_applied_coupons() as $code ) {
803 $coupon = new WC_Coupon( $code );
804
805 if ( ! $coupon->is_valid() ) {
806 $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED );
807 $this->remove_coupon( $code );
808 }
809 }
810 }
811
812 /**
813 * Looks through cart items and checks the posts are not trashed or deleted.
814 *
815 * @return bool|WP_Error
816 */
817 public function check_cart_item_validity() {
818 $return = true;
819
820 foreach ( $this->get_cart() as $cart_item_key => $values ) {
821 $product = $values['data'];
822
823 if ( ! $product || ! $product->exists() || ProductStatus::TRASH === $product->get_status() ) {
824 $this->set_quantity( $cart_item_key, 0 );
825 $return = new WP_Error( 'invalid', __( 'An item which is no longer available was removed from your cart.', 'woocommerce' ) );
826 }
827 }
828
829 return $return;
830 }
831
832 /**
833 * Looks through cart items and ensures sold individually products have quantity of 1.
834 *
835 * @since 10.7.0
836 * @return bool|WP_Error
837 */
838 public function check_cart_item_sold_individually() {
839 $errors = new WP_Error();
840
841 foreach ( $this->get_cart() as $cart_item_key => $values ) {
842 $product = $values['data'];
843
844 if ( ! $product || ! $product->exists() ) {
845 continue;
846 }
847
848 $product_id = $values['variation_id'] ? $values['variation_id'] : $values['product_id'];
849 $product_to_check = wc_get_product( $product_id );
850
851 if ( ! $product_to_check || ! $product_to_check->exists() ) {
852 continue;
853 }
854
855 if ( $product_to_check->is_sold_individually() && $values['quantity'] > 1 ) {
856 // Re-fetch and overwrite to reflect product changes made after item was added to cart.
857 $this->cart_contents[ $cart_item_key ]['data'] = $product_to_check;
858 $this->set_quantity( $cart_item_key, 1, false );
859 /* translators: %s: product name */
860 $errors->add( 'sold-individually', sprintf( __( 'You can only have 1 %s in your cart.', 'woocommerce' ), $product_to_check->get_name() ) );
861 }
862 }
863
864 return $errors->has_errors() ? $errors : true;
865 }
866
867 /**
868 * Looks through the cart to check each item is in stock. If not, add an error.
869 *
870 * @return bool|WP_Error
871 */
872 public function check_cart_item_stock() {
873 $error = new WP_Error();
874 $product_qty_in_cart = $this->get_cart_item_quantities();
875 $current_session_order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : absint( WC()->session->get( 'store_api_draft_order', 0 ) );
876
877 foreach ( $this->get_cart() as $values ) {
878 $product = $values['data'];
879
880 // Check stock based on stock-status.
881 if ( ! $product->is_in_stock() ) {
882 /* translators: %s: product name */
883 $error->add( 'out-of-stock', sprintf( __( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name() ) );
884 return $error;
885 }
886
887 // We only need to check products managing stock, with a limited stock qty.
888 if ( ! $product->managing_stock() || $product->backorders_allowed() ) {
889 continue;
890 }
891
892 // Check stock based on all items in the cart and consider any held stock within pending orders.
893 $held_stock = wc_get_held_stock_quantity( $product, $current_session_order_id );
894 $required_stock = $product_qty_in_cart[ $product->get_stock_managed_by_id() ];
895
896 /**
897 * Allows filter if product have enough stock to get added to the cart.
898 *
899 * @since 4.6.0
900 * @param bool $has_stock If have enough stock.
901 * @param WC_Product $product Product instance.
902 * @param array $values Cart item values.
903 */
904 if ( apply_filters( 'woocommerce_cart_item_required_stock_is_not_enough', $product->get_stock_quantity() < ( $held_stock + $required_stock ), $product, $values ) ) {
905 /* translators: 1: product name 2: quantity in stock */
906 $error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order (%2$s available). We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity() - $held_stock, $product ) ) );
907 return $error;
908 }
909 }
910
911 return true;
912 }
913
914 /**
915 * Gets and formats a list of cart item data + variations for display on the frontend.
916 *
917 * @param array $cart_item Cart item object.
918 * @param bool $flat Should the data be returned flat or in a list.
919 * @return string
920 */
921 public function get_item_data( $cart_item, $flat = false ) {
922 wc_deprecated_function( 'WC_Cart::get_item_data', '3.3', 'wc_get_formatted_cart_item_data' );
923
924 return wc_get_formatted_cart_item_data( $cart_item, $flat );
925 }
926
927 /**
928 * Gets cross sells based on the items in the cart.
929 *
930 * @return array cross_sells (item ids)
931 */
932 public function get_cross_sells() {
933 $cross_sells = array();
934 $in_cart = array();
935 if ( ! $this->is_empty() ) {
936 foreach ( $this->get_cart() as $values ) {
937 if ( $values['quantity'] > 0 ) {
938 $cross_sells = array_merge( $values['data']->get_cross_sell_ids(), $cross_sells );
939 $in_cart[] = $values['product_id'];
940
941 // Add variations to the in cart array.
942 if ( $values['data']->is_type( ProductType::VARIATION ) ) {
943 $in_cart[] = $values['variation_id'];
944 }
945 }
946 }
947 }
948 $cross_sells = array_diff( $cross_sells, $in_cart );
949 return apply_filters( 'woocommerce_cart_crosssell_ids', wp_parse_id_list( $cross_sells ), $this );
950 }
951
952 /**
953 * Gets the url to remove an item from the cart.
954 *
955 * @param string $cart_item_key contains the id of the cart item.
956 * @return string url to page
957 */
958 public function get_remove_url( $cart_item_key ) {
959 wc_deprecated_function( 'WC_Cart::get_remove_url', '3.3', 'wc_get_cart_remove_url' );
960
961 return wc_get_cart_remove_url( $cart_item_key );
962 }
963
964 /**
965 * Gets the url to re-add an item into the cart.
966 *
967 * @param string $cart_item_key Cart item key to undo.
968 * @return string url to page
969 */
970 public function get_undo_url( $cart_item_key ) {
971 wc_deprecated_function( 'WC_Cart::get_undo_url', '3.3', 'wc_get_cart_undo_url' );
972
973 return wc_get_cart_undo_url( $cart_item_key );
974 }
975
976 /**
977 * Get taxes, merged by code, formatted ready for output.
978 *
979 * @return array
980 */
981 public function get_tax_totals() {
982 $shipping_taxes = $this->get_shipping_taxes(); // Shipping taxes are rounded differently, so we will subtract from all taxes, then round and then add them back.
983 $taxes = $this->get_taxes();
984 $tax_totals = array();
985
986 foreach ( $taxes as $key => $tax ) {
987 $code = WC_Tax::get_rate_code( $key );
988
989 if ( $code || apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) === $key ) {
990 if ( ! isset( $tax_totals[ $code ] ) ) {
991 $tax_totals[ $code ] = new stdClass();
992 $tax_totals[ $code ]->amount = 0;
993 }
994
995 $tax_totals[ $code ]->tax_rate_id = $key;
996 $tax_totals[ $code ]->is_compound = WC_Tax::is_compound( $key );
997 $tax_totals[ $code ]->label = WC_Tax::get_rate_label( $key );
998
999 if ( isset( $shipping_taxes[ $key ] ) ) {
1000 $tax -= $shipping_taxes[ $key ];
1001 $tax = wc_round_tax_total( $tax );
1002 $tax += NumberUtil::round( $shipping_taxes[ $key ], wc_get_price_decimals() );
1003 unset( $shipping_taxes[ $key ] );
1004 }
1005 $tax_totals[ $code ]->amount += wc_round_tax_total( $tax );
1006 $tax_totals[ $code ]->formatted_amount = wc_price( $tax_totals[ $code ]->amount );
1007 }
1008 }
1009
1010 if ( apply_filters( 'woocommerce_cart_hide_zero_taxes', true ) ) {
1011 $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
1012 $tax_totals = array_intersect_key( $tax_totals, $amounts );
1013 }
1014
1015 return apply_filters( 'woocommerce_cart_tax_totals', $tax_totals, $this );
1016 }
1017
1018 /**
1019 * Get all tax classes for items in the cart.
1020 *
1021 * @return array
1022 */
1023 public function get_cart_item_tax_classes() {
1024 $found_tax_classes = array();
1025
1026 foreach ( $this->get_cart() as $item ) {
1027 if ( $item['data'] && ( $item['data']->is_taxable() || $item['data']->is_shipping_taxable() ) ) {
1028 $found_tax_classes[] = $item['data']->get_tax_class();
1029 }
1030 }
1031
1032 return array_unique( $found_tax_classes );
1033 }
1034
1035 /**
1036 * Get all tax classes for shipping based on the items in the cart.
1037 *
1038 * @return array
1039 */
1040 public function get_cart_item_tax_classes_for_shipping() {
1041 $found_tax_classes = array();
1042
1043 foreach ( $this->get_cart() as $item ) {
1044 if ( $item['data'] && ( $item['data']->is_shipping_taxable() ) ) {
1045 $found_tax_classes[] = $item['data']->get_tax_class();
1046 }
1047 }
1048
1049 return array_unique( $found_tax_classes );
1050 }
1051
1052 /**
1053 * Determines the value that the customer spent and the subtotal
1054 * displayed, used for things like coupon validation.
1055 *
1056 * Since the coupon lines are displayed based on the TAX DISPLAY value
1057 * of cart, this is used to determine the spend.
1058 *
1059 * If cart totals are shown including tax, use the subtotal.
1060 * If cart totals are shown excluding tax, use the subtotal ex tax
1061 * (tax is shown after coupons).
1062 *
1063 * @since 2.6.0
1064 * @return float
1065 */
1066 public function get_displayed_subtotal() {
1067 return $this->display_prices_including_tax() ? $this->get_subtotal() + $this->get_subtotal_tax() : $this->get_subtotal();
1068 }
1069
1070 /**
1071 * Check if product is in the cart and return cart item key.
1072 *
1073 * Cart item key will be unique based on the item and its properties, such as variations.
1074 *
1075 * @param mixed $cart_id id of product to find in the cart.
1076 * @return string cart item key
1077 */
1078 public function find_product_in_cart( $cart_id = false ) {
1079 if ( false !== $cart_id ) {
1080 if ( is_array( $this->cart_contents ) && isset( $this->cart_contents[ $cart_id ] ) ) {
1081 return $cart_id;
1082 }
1083 }
1084 return '';
1085 }
1086
1087 /**
1088 * Generate a unique ID for the cart item being added.
1089 *
1090 * @param int $product_id - id of the product the key is being generated for.
1091 * @param int $variation_id of the product the key is being generated for.
1092 * @param array $variation data for the cart item.
1093 * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart.
1094 * @return string cart item key
1095 */
1096 public function generate_cart_id( $product_id, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
1097 $id_parts = array( $product_id );
1098
1099 if ( $variation_id && 0 !== $variation_id ) {
1100 $id_parts[] = $variation_id;
1101 }
1102
1103 if ( is_array( $variation ) && ! empty( $variation ) ) {
1104 $variation_key = '';
1105 foreach ( $variation as $key => $value ) {
1106 $variation_key .= trim( $key ) . trim( $value );
1107 }
1108 $id_parts[] = $variation_key;
1109 }
1110
1111 if ( is_array( $cart_item_data ) && ! empty( $cart_item_data ) ) {
1112 $cart_item_data_key = '';
1113 foreach ( $cart_item_data as $key => $value ) {
1114 if ( is_array( $value ) || is_object( $value ) ) {
1115 $value = http_build_query( $value );
1116 }
1117 $cart_item_data_key .= trim( $key ) . trim( $value );
1118
1119 }
1120 $id_parts[] = $cart_item_data_key;
1121 }
1122
1123 return apply_filters( 'woocommerce_cart_id', md5( implode( '_', $id_parts ) ), $product_id, $variation_id, $variation, $cart_item_data );
1124 }
1125
1126 /**
1127 * Add a product to the cart.
1128 *
1129 * @throws Exception Plugins can throw an exception to prevent adding to cart.
1130 * @param int $product_id contains the id of the product to add to the cart.
1131 * @param int $quantity contains the quantity of the item to add.
1132 * @param int $variation_id ID of the variation being added to the cart.
1133 * @param array $variation attribute values.
1134 * @param array $cart_item_data extra cart item data we want to pass into the item.
1135 * @return string|bool $cart_item_key
1136 */
1137 public function add_to_cart( $product_id = 0, $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
1138 try {
1139 $product_id = absint( $product_id );
1140 $variation_id = absint( $variation_id );
1141
1142 // Ensure we don't add a variation to the cart directly by variation ID.
1143 if ( 'product_variation' === get_post_type( $product_id ) ) {
1144 $variation_id = $product_id;
1145 $product_id = wp_get_post_parent_id( $variation_id );
1146 }
1147
1148 $product_data = wc_get_product( $variation_id ? $variation_id : $product_id );
1149 $quantity = apply_filters( 'woocommerce_add_to_cart_quantity', $quantity, $product_id );
1150
1151 if ( $quantity <= 0 || ! $product_data || ProductStatus::TRASH === $product_data->get_status() ) {
1152 return false;
1153 }
1154
1155 // Variable product cannot be added to cart without a specified variation.
1156 if ( ! $variation_id && $product_data->is_type( ProductType::VARIABLE ) ) {
1157 /* translators: 1: product link, 2: product name */
1158 throw new Exception( sprintf( __( 'Please choose product options by visiting <a href="%1$s" title="%2$s">%2$s</a>.', 'woocommerce' ), esc_url( $product_data->get_permalink() ), esc_html( $product_data->get_name() ) ) );
1159 }
1160
1161 if ( $product_data->is_type( ProductType::VARIATION ) ) {
1162 $missing_attributes = array();
1163 $parent_data = wc_get_product( $product_data->get_parent_id() );
1164
1165 $variation_attributes = $product_data->get_variation_attributes();
1166 // Filter out 'any' variations, which are empty, as they need to be explicitly specified while adding to cart.
1167 $variation_attributes = array_filter( $variation_attributes );
1168
1169 // Gather posted attributes.
1170 $posted_attributes = array();
1171 foreach ( $parent_data->get_attributes() as $attribute ) {
1172 if ( ! $attribute['is_variation'] ) {
1173 continue;
1174 }
1175 $attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
1176
1177 if ( isset( $variation[ $attribute_key ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1178 if ( $attribute['is_taxonomy'] ) {
1179 // Don't use wc_clean as it destroys sanitized characters.
1180 $value = sanitize_title( wp_unslash( $variation[ $attribute_key ] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1181 } else {
1182 $value = html_entity_decode( wc_clean( wp_unslash( $variation[ $attribute_key ] ) ), ENT_QUOTES, get_bloginfo( 'charset' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1183 }
1184
1185 // Don't include if it's empty.
1186 if ( ! empty( $value ) || '0' === $value ) {
1187 $posted_attributes[ $attribute_key ] = $value;
1188 }
1189 }
1190 }
1191
1192 // Merge variation attributes and posted attributes.
1193 $posted_and_variation_attributes = array_merge( $variation_attributes, $posted_attributes );
1194
1195 // If no variation ID is set, attempt to get a variation ID from posted attributes.
1196 if ( empty( $variation_id ) ) {
1197 $data_store = WC_Data_Store::load( 'product' );
1198 $variation_id = $data_store->find_matching_product_variation( $parent_data, $posted_attributes );
1199 }
1200
1201 // Do we have a variation ID?
1202 if ( empty( $variation_id ) ) {
1203 throw new Exception( __( 'Please choose product options&hellip;', 'woocommerce' ) );
1204 }
1205
1206 // Check the data we have is valid.
1207 $variation_data = wc_get_product_variation_attributes( $variation_id );
1208 $attributes = array();
1209
1210 foreach ( $parent_data->get_attributes() as $attribute ) {
1211 if ( ! $attribute['is_variation'] ) {
1212 continue;
1213 }
1214
1215 // Get valid value from variation data.
1216 $attribute_key = 'attribute_' . sanitize_title( $attribute['name'] );
1217 $valid_value = isset( $variation_data[ $attribute_key ] ) ? $variation_data[ $attribute_key ] : '';
1218
1219 /**
1220 * If the attribute value was posted, check if it's valid.
1221 *
1222 * If no attribute was posted, only error if the variation has an 'any' attribute which requires a value.
1223 */
1224 if ( isset( $posted_and_variation_attributes[ $attribute_key ] ) ) {
1225 $value = $posted_and_variation_attributes[ $attribute_key ];
1226
1227 // Allow if valid or show error.
1228 if ( $valid_value === $value ) {
1229 $attributes[ $attribute_key ] = $value;
1230 } elseif ( '' === $valid_value && in_array( $value, $attribute->get_slugs(), true ) ) {
1231 // If valid values are empty, this is an 'any' variation so get all possible values.
1232 $attributes[ $attribute_key ] = $value;
1233 } else {
1234 /* translators: %s: Attribute name. */
1235 throw new Exception( sprintf( __( 'Invalid value posted for %s', 'woocommerce' ), wc_attribute_label( $attribute['name'] ) ) );
1236 }
1237 } elseif ( '' === $valid_value ) {
1238 $missing_attributes[] = wc_attribute_label( $attribute['name'] );
1239 }
1240
1241 $variation = $attributes;
1242 }
1243 if ( ! empty( $missing_attributes ) ) {
1244 /* translators: %s: Attribute name. */
1245 throw new Exception( sprintf( _n( '%s is a required field', '%s are required fields', count( $missing_attributes ), 'woocommerce' ), wc_format_list_of_items( $missing_attributes ) ) );
1246 }
1247 }
1248
1249 // Validate variation ID.
1250 if (
1251 0 < $variation_id && // Only check if there's any variation_id.
1252 (
1253 ! $product_data->is_type( ProductType::VARIATION ) || // Check if isn't a variation, it suppose to be a variation at this point.
1254 $product_data->get_parent_id() !== $product_id // Check if belongs to the selected variable product.
1255 )
1256 ) {
1257 $product = wc_get_product( $product_id );
1258 if ( ! ( $product instanceof WC_Product ) ) {
1259 throw new Exception( __( 'The selected product is invalid.', 'woocommerce' ) );
1260 }
1261
1262 /* translators: 1: product link, 2: product name */
1263 throw new Exception( sprintf( __( 'The selected product isn\'t a variation of %2$s, please choose product options by visiting <a href="%1$s" title="%2$s">%2$s</a>.', 'woocommerce' ), esc_url( $product->get_permalink() ), esc_html( $product->get_name() ) ) );
1264 }
1265
1266 // Load cart item data - may be added by other plugins.
1267 $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id, $quantity );
1268
1269 // Generate a ID based on product ID, variation ID, variation data, and other cart item data.
1270 $cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data );
1271
1272 // Find the cart item key in the existing cart.
1273 $cart_item_key = $this->find_product_in_cart( $cart_id );
1274
1275 // Force quantity to 1 if sold individually and check for existing item in cart.
1276 if ( $product_data->is_sold_individually() ) {
1277 $quantity = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data );
1278 $found_in_cart = apply_filters( 'woocommerce_add_to_cart_sold_individually_found_in_cart', $cart_item_key && $this->cart_contents[ $cart_item_key ]['quantity'] > 0, $product_id, $variation_id, $cart_item_data, $cart_id );
1279
1280 if ( $found_in_cart ) {
1281 /* translators: %s: product name */
1282 $message = sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_name() );
1283
1284 /**
1285 * Filters message about more than 1 product being added to cart.
1286 *
1287 * @since 4.5.0
1288 * @param string $message Message.
1289 * @param WC_Product $product_data Product data.
1290 */
1291 $message = apply_filters( 'woocommerce_cart_product_cannot_add_another_message', $message, $product_data );
1292 $wp_button_class = wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '';
1293
1294 if ( ! CartCheckoutUtils::has_cart_page() ) {
1295 $message = sprintf( '%s', esc_html( $message ) );
1296 } else {
1297 $message = sprintf( '%s <a href="%s" class="button wc-forward%s">%s</a>', $message, esc_url( wc_get_cart_url() ), esc_attr( $wp_button_class ), __( 'View cart', 'woocommerce' ) );
1298 }
1299
1300 throw new Exception( $message );
1301 }
1302 }
1303
1304 if ( ! $product_data->is_purchasable() ) {
1305 $message = __( 'Sorry, this product cannot be purchased.', 'woocommerce' );
1306 /**
1307 * Filters message about product unable to be purchased.
1308 *
1309 * @since 3.8.0
1310 * @param string $message Message.
1311 * @param WC_Product $product_data Product data.
1312 */
1313 $message = apply_filters( 'woocommerce_cart_product_cannot_be_purchased_message', $message, $product_data );
1314 throw new Exception( $message );
1315 }
1316
1317 // Stock check - only check if we're managing stock and backorders are not allowed.
1318 if ( ! $product_data->is_in_stock() ) {
1319 /* translators: %s: product name */
1320 $message = sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_name() );
1321
1322 /**
1323 * Filters message about product being out of stock.
1324 *
1325 * @since 4.5.0
1326 * @param string $message Message.
1327 * @param WC_Product $product_data Product data.
1328 */
1329 $message = apply_filters( 'woocommerce_cart_product_out_of_stock_message', $message, $product_data );
1330 throw new Exception( $message );
1331 }
1332
1333 if ( ! $product_data->has_enough_stock( $quantity ) ) {
1334 $stock_quantity = $product_data->get_stock_quantity();
1335
1336 /* translators: 1: product name 2: quantity in stock */
1337 $message = sprintf( __( 'You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_name(), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ) );
1338
1339 /**
1340 * Filters message about product not having enough stock.
1341 *
1342 * @since 4.5.0
1343 * @param string $message Message.
1344 * @param WC_Product $product_data Product data.
1345 * @param int $stock_quantity Quantity remaining.
1346 */
1347 $message = apply_filters( 'woocommerce_cart_product_not_enough_stock_message', $message, $product_data, $stock_quantity );
1348
1349 throw new Exception( $message );
1350 }
1351
1352 // Stock check - this time accounting for whats already in-cart.
1353 if ( $product_data->managing_stock() ) {
1354 $products_qty_in_cart = $this->get_cart_item_quantities();
1355
1356 if ( isset( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] ) && ! $product_data->has_enough_stock( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] + $quantity ) ) {
1357 $stock_quantity = $product_data->get_stock_quantity();
1358 $stock_quantity_in_cart = $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ];
1359 $wp_button_class = wc_wp_theme_get_element_class_name( 'button' ) ? ' ' . wc_wp_theme_get_element_class_name( 'button' ) : '';
1360
1361 $message = CartCheckoutUtils::has_cart_page() ? sprintf(
1362 '%s <a href="%s" class="button wc-forward%s">%s</a>',
1363 /* translators: 1: quantity in stock 2: current quantity */
1364 sprintf( __( 'You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ), wc_format_stock_quantity_for_display( $stock_quantity_in_cart, $product_data ) ),
1365 esc_url( wc_get_cart_url() ),
1366 esc_attr( $wp_button_class ),
1367 __( 'View cart', 'woocommerce' )
1368 ) : sprintf(
1369 '%s',
1370 /* translators: 1: quantity in stock 2: current quantity */
1371 sprintf( __( 'You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_quantity, $product_data ), wc_format_stock_quantity_for_display( $stock_quantity_in_cart, $product_data ) )
1372 );
1373
1374 /**
1375 * Filters message about product not having enough stock accounting for what's already in the cart.
1376 *
1377 * @param string $message Message.
1378 * @param WC_Product $product_data Product data.
1379 * @param int $stock_quantity Quantity remaining.
1380 * @param int $stock_quantity_in_cart
1381 *
1382 * @since 5.3.0
1383 */
1384 $message = apply_filters( 'woocommerce_cart_product_not_enough_stock_already_in_cart_message', $message, $product_data, $stock_quantity, $stock_quantity_in_cart );
1385
1386 throw new Exception( $message );
1387 }
1388 }
1389
1390 $item_was_already_in_cart = false;
1391 // If cart_item_key is set, the item is already in the cart.
1392 if ( $cart_item_key ) {
1393 $new_quantity = $quantity + $this->cart_contents[ $cart_item_key ]['quantity'];
1394 $this->set_quantity( $cart_item_key, $new_quantity, false );
1395 $item_was_already_in_cart = true;
1396 } else {
1397 $cart_item_key = $cart_id;
1398
1399 // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item.
1400 $this->cart_contents[ $cart_item_key ] = apply_filters(
1401 'woocommerce_add_cart_item',
1402 array_merge(
1403 $cart_item_data,
1404 array(
1405 'key' => $cart_item_key,
1406 'product_id' => $product_id,
1407 'variation_id' => $variation_id,
1408 'variation' => $variation,
1409 'quantity' => $quantity,
1410 'data' => $product_data,
1411 'data_hash' => wc_get_cart_item_data_hash( $product_data ),
1412 )
1413 ),
1414 $cart_item_key
1415 );
1416 }
1417
1418 $this->cart_contents = apply_filters( 'woocommerce_cart_contents_changed', $this->cart_contents );
1419
1420 do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data );
1421
1422 return $cart_item_key;
1423
1424 } catch ( Exception $e ) {
1425 if ( $e->getMessage() ) {
1426 wc_add_notice( $e->getMessage(), 'error' );
1427 }
1428 return false;
1429 }
1430 }
1431
1432 /**
1433 * Remove a cart item.
1434 *
1435 * @since 2.3.0
1436 * @param string $cart_item_key Cart item key to remove from the cart.
1437 * @return bool
1438 */
1439 public function remove_cart_item( $cart_item_key ) {
1440 if ( isset( $this->cart_contents[ $cart_item_key ] ) ) {
1441 $this->removed_cart_contents[ $cart_item_key ] = $this->cart_contents[ $cart_item_key ];
1442
1443 unset( $this->removed_cart_contents[ $cart_item_key ]['data'] );
1444
1445 do_action( 'woocommerce_remove_cart_item', $cart_item_key, $this );
1446
1447 unset( $this->cart_contents[ $cart_item_key ] );
1448
1449 do_action( 'woocommerce_cart_item_removed', $cart_item_key, $this );
1450
1451 return true;
1452 }
1453 return false;
1454 }
1455
1456 /**
1457 * Restore a cart item.
1458 *
1459 * @param string $cart_item_key Cart item key to restore to the cart.
1460 * @return bool
1461 */
1462 public function restore_cart_item( $cart_item_key ) {
1463 if ( isset( $this->removed_cart_contents[ $cart_item_key ] ) ) {
1464 $restore_item = $this->removed_cart_contents[ $cart_item_key ];
1465 $this->cart_contents[ $cart_item_key ] = $restore_item;
1466 $this->cart_contents[ $cart_item_key ]['data'] = wc_get_product( $restore_item['variation_id'] ? $restore_item['variation_id'] : $restore_item['product_id'] );
1467
1468 do_action( 'woocommerce_restore_cart_item', $cart_item_key, $this );
1469
1470 unset( $this->removed_cart_contents[ $cart_item_key ] );
1471
1472 do_action( 'woocommerce_cart_item_restored', $cart_item_key, $this );
1473
1474 return true;
1475 }
1476 return false;
1477 }
1478
1479 /**
1480 * Set the quantity for an item in the cart using it's key.
1481 *
1482 * @param string $cart_item_key contains the id of the cart item.
1483 * @param int $quantity contains the quantity of the item.
1484 * @param bool $refresh_totals whether or not to calculate totals after setting the new qty. Can be used to defer calculations if setting quantities in bulk.
1485 * @return bool
1486 */
1487 public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) {
1488 if ( 0 === $quantity || $quantity < 0 ) {
1489 wc_do_deprecated_action( 'woocommerce_before_cart_item_quantity_zero', array( $cart_item_key, $this ), '3.7.0', 'woocommerce_remove_cart_item' );
1490 // If we're setting qty to 0 we're removing the item from the cart.
1491 return $this->remove_cart_item( $cart_item_key );
1492 }
1493
1494 // Update qty.
1495 $old_quantity = $this->cart_contents[ $cart_item_key ]['quantity'];
1496 $this->cart_contents[ $cart_item_key ]['quantity'] = $quantity;
1497
1498 do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity, $this );
1499
1500 if ( $refresh_totals ) {
1501 $this->calculate_totals();
1502 }
1503
1504 /**
1505 * Fired after qty has been changed.
1506 *
1507 * @since 3.6.0
1508 * @param string $cart_item_key contains the id of the cart item. This may be empty if the cart item does not exist any more.
1509 * @param int $quantity contains the quantity of the item.
1510 * @param WC_Cart $this Cart class.
1511 */
1512 do_action( 'woocommerce_cart_item_set_quantity', $cart_item_key, $quantity, $this );
1513
1514 return true;
1515 }
1516
1517 /**
1518 * Get cart's owner.
1519 *
1520 * @since 3.2.0
1521 * @return \WC_Customer
1522 */
1523 public function get_customer() {
1524 return WC()->customer;
1525 }
1526
1527 /**
1528 * Calculate totals for the items in the cart.
1529 *
1530 * @uses WC_Cart_Totals
1531 * @return void
1532 */
1533 public function calculate_totals() {
1534 $this->reset_totals();
1535
1536 if ( $this->is_empty() ) {
1537 $this->session->set_session();
1538 return;
1539 }
1540
1541 do_action( 'woocommerce_before_calculate_totals', $this );
1542
1543 new WC_Cart_Totals( $this );
1544
1545 do_action( 'woocommerce_after_calculate_totals', $this );
1546 }
1547
1548 /**
1549 * Looks at the totals to see if payment is actually required.
1550 *
1551 * @return bool
1552 */
1553 public function needs_payment() {
1554 return apply_filters( 'woocommerce_cart_needs_payment', 0 < $this->get_total( 'edit' ), $this );
1555 }
1556
1557 /*
1558 * Shipping related functions.
1559 */
1560
1561 /**
1562 * Get selected shipping methods after calculation.
1563 *
1564 * @return array
1565 */
1566 public function get_shipping_methods() {
1567 return $this->shipping_methods;
1568 }
1569
1570 /**
1571 * Whether the shipping totals have been calculated.
1572 *
1573 * @return bool
1574 */
1575 public function has_calculated_shipping() {
1576 return $this->has_calculated_shipping;
1577 }
1578
1579 /**
1580 * Uses the shipping class to calculate shipping then gets the totals when its finished.
1581 *
1582 * @return array
1583 */
1584 public function calculate_shipping() {
1585 // Reset totals.
1586 $this->set_shipping_total( 0 );
1587 $this->set_shipping_tax( 0 );
1588 $this->set_shipping_taxes( array() );
1589 $this->shipping_methods = array();
1590 $this->has_calculated_shipping = false;
1591
1592 if ( ! $this->needs_shipping() || ! $this->show_shipping() ) {
1593 return $this->shipping_methods;
1594 }
1595
1596 $this->has_calculated_shipping = true;
1597 $this->shipping_methods = $this->get_chosen_shipping_methods( WC()->shipping()->calculate_shipping( $this->get_shipping_packages() ) );
1598
1599 $shipping_costs = wp_list_pluck( $this->shipping_methods, 'cost' );
1600 $shipping_taxes = wp_list_pluck( $this->shipping_methods, 'taxes' );
1601 $merged_taxes = array();
1602 foreach ( $shipping_taxes as $taxes ) {
1603 foreach ( $taxes as $tax_id => $tax_amount ) {
1604 $merged_taxes[ $tax_id ] = ( $merged_taxes[ $tax_id ] ?? 0 ) + $tax_amount;
1605 }
1606 }
1607
1608 $this->set_shipping_total( array_sum( array_filter( $shipping_costs ) ) );
1609 $this->set_shipping_tax( array_sum( $merged_taxes ) );
1610 $this->set_shipping_taxes( $merged_taxes );
1611
1612 return $this->shipping_methods;
1613 }
1614
1615 /**
1616 * Given a set of packages with rates, get the chosen ones only.
1617 *
1618 * @since 3.2.0
1619 * @param array $calculated_shipping_packages Array of packages.
1620 * @return array
1621 */
1622 protected function get_chosen_shipping_methods( $calculated_shipping_packages = array() ) {
1623 return ShippingUtil::get_selected_shipping_rates_from_packages( $calculated_shipping_packages );
1624 }
1625
1626 /**
1627 * Filter items needing shipping callback.
1628 *
1629 * @since 3.0.0
1630 * @param array $item Item to check for shipping.
1631 * @return bool
1632 */
1633 protected function filter_items_needing_shipping( $item ) {
1634 $product = $item['data'];
1635 return $product && $product->needs_shipping();
1636 }
1637
1638 /**
1639 * Get only items that need shipping.
1640 *
1641 * @since 3.0.0
1642 * @return array
1643 */
1644 protected function get_items_needing_shipping() {
1645 return array_filter( $this->get_cart(), array( $this, 'filter_items_needing_shipping' ) );
1646 }
1647
1648 /**
1649 * Get packages to calculate shipping for.
1650 *
1651 * This lets us calculate costs for carts that are shipped to multiple locations.
1652 *
1653 * Shipping methods are responsible for looping through these packages.
1654 *
1655 * By default we pass the cart itself as a package - plugins can change this.
1656 * through the filter and break it up.
1657 *
1658 * @since 1.5.4
1659 * @return array of cart items
1660 */
1661 public function get_shipping_packages() {
1662 /**
1663 * Filters the shipping packages for the cart.
1664 *
1665 * @since 1.5.4
1666 * @param array $packages The shipping packages.
1667 * @return array The shipping packages.
1668 */
1669 $shipping_packages = apply_filters(
1670 'woocommerce_cart_shipping_packages',
1671 array(
1672 array(
1673 'contents' => $this->get_items_needing_shipping(),
1674 'contents_cost' => array_sum( wp_list_pluck( $this->get_items_needing_shipping(), 'line_total' ) ),
1675 'applied_coupons' => $this->get_applied_coupons(),
1676 'user' => array(
1677 'ID' => get_current_user_id(),
1678 ),
1679 'destination' => array(
1680 'country' => $this->get_customer()->get_shipping_country(),
1681 'state' => $this->get_customer()->get_shipping_state(),
1682 'postcode' => $this->get_customer()->get_shipping_postcode(),
1683 'city' => $this->get_customer()->get_shipping_city(),
1684 'address' => $this->get_customer()->get_shipping_address(), // This is an alias of address_1, provided for backwards compatibility.
1685 'address_1' => $this->get_customer()->get_shipping_address_1(),
1686 'address_2' => $this->get_customer()->get_shipping_address_2(),
1687 ),
1688 'cart_subtotal' => $this->get_displayed_subtotal(),
1689 ),
1690 )
1691 );
1692
1693 // Return empty array if invalid object supplied by the filter or no packages.
1694 if ( ! is_array( $shipping_packages ) || empty( $shipping_packages ) ) {
1695 return array();
1696 }
1697
1698 // Remove any invalid packages before adding package IDs.
1699 $shipping_packages = array_filter(
1700 $shipping_packages,
1701 function ( $package ) {
1702 return ! empty( $package ) && is_array( $package );
1703 }
1704 );
1705
1706 // Add package ID and package name to each package after the filter is applied.
1707 $index = 1;
1708 foreach ( $shipping_packages as $key => $package ) {
1709 $shipping_packages[ $key ]['package_id'] = $package['package_id'] ?? $key;
1710 $shipping_packages[ $key ]['package_name'] = $this->get_shipping_package_name( $shipping_packages[ $key ], $index, count( $shipping_packages ) );
1711 ++$index;
1712 }
1713
1714 return $shipping_packages;
1715 }
1716
1717 /**
1718 * Get the package name.
1719 *
1720 * @param array $package Shipping package data.
1721 * @param int $index Package number.
1722 * @param int $total_packages Total number of packages.
1723 * @return string
1724 */
1725 private function get_shipping_package_name( $package, $index, $total_packages ) {
1726
1727 $shipping_package_name = _x( 'Shipment', 'shipping packages', 'woocommerce' );
1728
1729 /**
1730 * If there are multiple packages, use the index to show the package number.
1731 */
1732 if ( 1 !== $total_packages ) {
1733 $shipping_package_name = sprintf(
1734 /* translators: %d: shipping package number. */
1735 _x( 'Shipment %d', 'shipping packages', 'woocommerce' ),
1736 $index
1737 );
1738 }
1739
1740 /**
1741 * Filters the shipping package name.
1742 *
1743 * @since 4.3.0
1744 * @param string $shipping_package_name Shipping package name.
1745 * @param string $package_id Shipping package ID.
1746 * @param array $package Shipping package from WooCommerce.
1747 * @param int $total_packages Total number of shipping packages.
1748 * @return string Shipping package name.
1749 */
1750 return apply_filters(
1751 'woocommerce_shipping_package_name',
1752 $shipping_package_name,
1753 $package['package_id'],
1754 $package,
1755 $total_packages
1756 );
1757 }
1758
1759 /**
1760 * Looks through the cart to see if shipping is actually required.
1761 *
1762 * @return bool whether or not the cart needs shipping
1763 */
1764 public function needs_shipping() {
1765 if ( ! wc_shipping_enabled() || 0 === wc_get_shipping_method_count( true ) ) {
1766 return false;
1767 }
1768
1769 $needs_shipping = false;
1770
1771 foreach ( $this->get_cart_contents() as $values ) {
1772 if ( $values['data']->needs_shipping() ) {
1773 $needs_shipping = true;
1774 break;
1775 }
1776 }
1777
1778 return apply_filters( 'woocommerce_cart_needs_shipping', $needs_shipping );
1779 }
1780
1781 /**
1782 * Should the shipping address form be shown.
1783 *
1784 * @return bool
1785 */
1786 public function needs_shipping_address() {
1787 return apply_filters( 'woocommerce_cart_needs_shipping_address', true === $this->needs_shipping() && ! wc_ship_to_billing_address_only() );
1788 }
1789
1790 /**
1791 * Sees if the customer has entered enough data to calculate shipping.
1792 *
1793 * @return bool
1794 */
1795 public function show_shipping() {
1796 // If there are no shipping methods or no cart contents, no need to calculate shipping.
1797 if ( ! wc_shipping_enabled() || 0 === wc_get_shipping_method_count( true ) || ! $this->get_cart_contents() ) {
1798 return false;
1799 }
1800
1801 if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) {
1802 // If local pickup is enabled, shipping should be shown so that pickup locations are visible before address entry.
1803 if ( LocalPickupUtils::is_local_pickup_enabled() ) {
1804 /**
1805 * This filter is documented below.
1806 *
1807 * @since 2.7.0
1808 */
1809 return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', true );
1810 }
1811
1812 if ( 'shortcode' === $this->cart_context ) {
1813 $country = $this->get_customer()->get_shipping_country();
1814 if ( ! $country ) {
1815 return false;
1816 }
1817 $country_fields = WC()->countries->get_address_fields( $country, 'shipping_' );
1818 $checkout_fields = WC()->checkout()->get_checkout_fields();
1819
1820 /**
1821 * Filter to not require shipping state for shipping calculation, even if it is required at checkout.
1822 * This can be used to allow shipping calculations to be done without a state.
1823 *
1824 * @since 8.4.0
1825 *
1826 * @param bool $show_state Whether to use the state field. Default true.
1827 */
1828 $state_enabled = apply_filters( 'woocommerce_shipping_calculator_enable_state', true );
1829 $state_required = isset( $country_fields['shipping_state'] ) && $country_fields['shipping_state']['required'];
1830 // Takes care of late unsetting of checkout fields via hooks (woocommerce_checkout_fields, woocommerce_shipping_fields).
1831 $checkout_state_field_exists = isset( $checkout_fields['shipping']['shipping_state'] );
1832 if ( $state_enabled && $state_required && ! $this->get_customer()->get_shipping_state() && $checkout_state_field_exists ) {
1833 return false;
1834 }
1835 /**
1836 * Filter to not require shipping postcode for shipping calculation, even if it is required at checkout.
1837 * This can be used to allow shipping calculations to be done without a postcode.
1838 *
1839 * @since 8.4.0
1840 *
1841 * @param bool $show_postcode Whether to use the postcode field. Default true.
1842 */
1843 $postcode_enabled = apply_filters( 'woocommerce_shipping_calculator_enable_postcode', true );
1844 $postcode_required = isset( $country_fields['shipping_postcode'] ) && $country_fields['shipping_postcode']['required'];
1845 // Takes care of late unsetting of checkout fields via hooks (woocommerce_checkout_fields, woocommerce_shipping_fields).
1846 $checkout_postcode_field_exists = isset( $checkout_fields['shipping']['shipping_postcode'] );
1847 if ( $postcode_enabled && $postcode_required && '' === $this->get_customer()->get_shipping_postcode() && $checkout_postcode_field_exists ) {
1848 return false;
1849 }
1850 } else {
1851 $customer = $this->get_customer();
1852
1853 if ( ! $customer instanceof \WC_Customer || ! $customer->has_full_shipping_address() ) {
1854 return false;
1855 }
1856 }
1857 }
1858
1859 /**
1860 * Filter to allow plugins to prevent shipping calculations.
1861 *
1862 * @since 2.7.0
1863 *
1864 * @param bool $ready Whether the cart is ready to calculate shipping.
1865 */
1866 return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', true );
1867 }
1868
1869 /**
1870 * Gets the shipping total (after calculation).
1871 *
1872 * @return string price or string for the shipping total
1873 */
1874 public function get_cart_shipping_total() {
1875
1876 // Default total assumes Free shipping.
1877 $total = __( 'Free!', 'woocommerce' );
1878
1879 if ( 0 < $this->get_shipping_total() ) {
1880
1881 if ( $this->display_prices_including_tax() ) {
1882 $total = wc_price( $this->shipping_total + $this->shipping_tax_total );
1883
1884 if ( $this->shipping_tax_total > 0 && ! wc_prices_include_tax() ) {
1885 $total .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
1886 }
1887 } else {
1888 $total = wc_price( $this->shipping_total );
1889
1890 if ( $this->shipping_tax_total > 0 && wc_prices_include_tax() ) {
1891 $total .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1892 }
1893 }
1894 }
1895 return apply_filters( 'woocommerce_cart_shipping_total', $total, $this );
1896 }
1897
1898 /**
1899 * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error.
1900 *
1901 * Checks two types of coupons:
1902 * 1. Where a list of customer emails are set (limits coupon usage to those defined).
1903 * 2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email).
1904 *
1905 * @param array $posted Post data.
1906 * @return void
1907 */
1908 public function check_customer_coupons( $posted ) {
1909 foreach ( $this->get_applied_coupons() as $code ) {
1910 $coupon = new WC_Coupon( $code );
1911
1912 if ( $coupon->is_valid() ) {
1913
1914 // Get user and posted emails to compare.
1915 $current_user = wp_get_current_user();
1916 $billing_email = isset( $posted['billing_email'] ) ? $posted['billing_email'] : '';
1917 $check_emails = array_unique(
1918 array_filter(
1919 array_map(
1920 'strtolower',
1921 array_map(
1922 'sanitize_email',
1923 array(
1924 $billing_email,
1925 $current_user->user_email,
1926 )
1927 )
1928 )
1929 )
1930 );
1931
1932 // Limit to defined email addresses.
1933 $restrictions = $coupon->get_email_restrictions();
1934
1935 if ( is_array( $restrictions ) && 0 < count( $restrictions ) && ! DiscountsUtil::is_coupon_emails_allowed( $check_emails, $restrictions ) ) {
1936 $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
1937 $this->remove_coupon( $code );
1938 }
1939
1940 $coupon_usage_limit = $coupon->get_usage_limit_per_user();
1941 if ( 0 < $coupon_usage_limit && 0 === get_current_user_id() ) {
1942 // For guest, usage per user has not been enforced yet. Enforce it now.
1943 $coupon_data_store = $coupon->get_data_store();
1944 $billing_email = strtolower( sanitize_email( $billing_email ) );
1945 if ( $coupon_data_store && $coupon_data_store->get_usage_by_email( $coupon, $billing_email ) >= $coupon_usage_limit ) {
1946 if ( $coupon_data_store->get_tentative_usages_for_user( $coupon->get_id(), array( $billing_email ) ) ) {
1947 $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_COUPON_STUCK_GUEST );
1948 } else {
1949 $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
1950 }
1951 }
1952 }
1953 }
1954 }
1955 }
1956
1957 /**
1958 * Checks if the given email address(es) matches the ones specified on the coupon.
1959 *
1960 * @param array $check_emails Array of customer email addresses.
1961 * @param array $restrictions Array of allowed email addresses.
1962 *
1963 * @return bool
1964 * @deprecated 9.0.0 In favor of static method Automattic\WooCommerce\Utilities\DiscountsUtil::is_coupon_emails_allowed.
1965 */
1966 public function is_coupon_emails_allowed( $check_emails, $restrictions ) {
1967 wc_doing_it_wrong(
1968 'WC_Cart::is_coupon_emails_allowed',
1969 __( 'This method has been deprecated and will be removed soon. Use Automattic\WooCommerce\Utilities\DiscountsUtil::is_coupon_emails_allowed instead.', 'woocommerce' ),
1970 '9.0.0'
1971 );
1972
1973 return DiscountsUtil::is_coupon_emails_allowed( $check_emails, $restrictions );
1974 }
1975
1976
1977 /**
1978 * Returns whether or not a discount has been applied.
1979 *
1980 * @param string $coupon_code Coupon code to check.
1981 * @return bool
1982 */
1983 public function has_discount( $coupon_code = '' ) {
1984 $applied_coupons = $this->get_applied_coupons();
1985
1986 if ( ! $coupon_code ) {
1987 return count( $applied_coupons ) > 0;
1988 }
1989
1990 $coupon_code = wc_format_coupon_code( $coupon_code );
1991
1992 // Check if the coupon is in applied coupons using case-insensitive comparison.
1993 foreach ( $applied_coupons as $applied_coupon ) {
1994 if ( wc_is_same_coupon( $applied_coupon, $coupon_code ) ) {
1995 return true;
1996 }
1997 }
1998
1999 return false;
2000 }
2001
2002 /**
2003 * Applies a coupon code passed to the method.
2004 *
2005 * @param string $coupon_code - The code to apply.
2006 * @return bool True if the coupon is applied, false if it does not exist or cannot be applied.
2007 */
2008 public function apply_coupon( $coupon_code ) {
2009 // Coupons are globally disabled.
2010 if ( ! wc_coupons_enabled() ) {
2011 return false;
2012 }
2013
2014 // Sanitize coupon code.
2015 $coupon_code = wc_format_coupon_code( $coupon_code );
2016
2017 // Get the coupon.
2018 $the_coupon = new WC_Coupon( $coupon_code );
2019
2020 // Prevent adding coupons by post ID.
2021 if ( ! wc_is_same_coupon( $the_coupon->get_code(), $coupon_code ) ) {
2022 $the_coupon->set_code( $coupon_code );
2023 $the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_EXIST );
2024 return false;
2025 }
2026
2027 // Check it can be used with cart.
2028 if ( ! $the_coupon->is_valid() ) {
2029 wc_add_notice( $the_coupon->get_error_message(), 'error' );
2030 return false;
2031 }
2032
2033 // Check if applied.
2034 if ( $this->has_discount( $coupon_code ) ) {
2035 $the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED );
2036 return false;
2037 }
2038
2039 // If its individual use then remove other coupons.
2040 if ( $the_coupon->get_individual_use() ) {
2041 $coupons_to_keep = apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $the_coupon, $this->applied_coupons );
2042
2043 foreach ( $this->applied_coupons as $applied_coupon ) {
2044 $keep_key = array_search( $applied_coupon, $coupons_to_keep, true );
2045 if ( false === $keep_key ) {
2046 $this->remove_coupon( $applied_coupon );
2047 } else {
2048 unset( $coupons_to_keep[ $keep_key ] );
2049 }
2050 }
2051
2052 if ( ! empty( $coupons_to_keep ) ) {
2053 $this->applied_coupons += $coupons_to_keep;
2054 }
2055 }
2056
2057 // Check to see if an individual use coupon is set.
2058 if ( $this->applied_coupons ) {
2059 foreach ( $this->applied_coupons as $code ) {
2060 $coupon = new WC_Coupon( $code );
2061
2062 if ( $coupon->get_individual_use() && false === apply_filters( 'woocommerce_apply_with_individual_use_coupon', false, $the_coupon, $coupon, $this->applied_coupons ) ) {
2063
2064 // Reject new coupon.
2065 $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED_INDIV_USE_ONLY );
2066
2067 return false;
2068 }
2069 }
2070 }
2071
2072 $this->applied_coupons[] = $coupon_code;
2073
2074 // Choose free shipping.
2075 if ( $the_coupon->get_free_shipping() ) {
2076 $packages = WC()->shipping()->get_packages();
2077 $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
2078
2079 foreach ( $packages as $i => $package ) {
2080 $chosen_shipping_methods[ $i ] = 'free_shipping';
2081 }
2082
2083 WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
2084 }
2085
2086 $the_coupon->add_coupon_message( WC_Coupon::WC_COUPON_SUCCESS );
2087
2088 /**
2089 * Action ran after a coupon is applied.
2090 *
2091 * @since 2.0.0
2092 * @param string $coupon_code The coupon code that was applied.
2093 */
2094 do_action( 'woocommerce_applied_coupon', $coupon_code );
2095
2096 return true;
2097 }
2098
2099 /**
2100 * Get array of applied coupon objects and codes.
2101 *
2102 * @param null $deprecated No longer used.
2103 * @return array of applied coupons
2104 */
2105 public function get_coupons( $deprecated = null ) {
2106 $coupons = array();
2107
2108 if ( 'order' === $deprecated ) {
2109 return $coupons;
2110 }
2111
2112 foreach ( $this->get_applied_coupons() as $code ) {
2113 $coupon = new WC_Coupon( $code );
2114 $coupons[ $code ] = $coupon;
2115 }
2116
2117 return $coupons;
2118 }
2119
2120 /**
2121 * Get the discount amount for a used coupon.
2122 *
2123 * @param string $code coupon code.
2124 * @param bool $ex_tax inc or ex tax.
2125 * @return float discount amount
2126 */
2127 public function get_coupon_discount_amount( $code, $ex_tax = true ) {
2128 $totals = $this->get_coupon_discount_totals();
2129 $discount_amount = 0;
2130 foreach ( $totals as $key => $value ) {
2131 if ( wc_is_same_coupon( $key, $code ) ) {
2132 $discount_amount = $value;
2133 break;
2134 }
2135 }
2136
2137 if ( ! $ex_tax ) {
2138 $discount_amount += $this->get_coupon_discount_tax_amount( $code );
2139 }
2140
2141 return wc_cart_round_discount( $discount_amount, wc_get_price_decimals() );
2142 }
2143
2144 /**
2145 * Get the discount tax amount for a used coupon (for tax inclusive prices).
2146 *
2147 * @param string $code coupon code.
2148 * @return float discount amount
2149 */
2150 public function get_coupon_discount_tax_amount( $code ) {
2151 $totals = $this->get_coupon_discount_tax_totals();
2152 $tax_amount = 0;
2153 foreach ( $totals as $key => $value ) {
2154 if ( wc_is_same_coupon( $key, $code ) ) {
2155 $tax_amount = $value;
2156 break;
2157 }
2158 }
2159 return wc_cart_round_discount( $tax_amount, wc_get_price_decimals() );
2160 }
2161
2162 /**
2163 * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax.
2164 *
2165 * @param null $deprecated No longer used.
2166 * @return void
2167 */
2168 public function remove_coupons( $deprecated = null ) {
2169 $this->set_coupon_discount_totals( array() );
2170 $this->set_coupon_discount_tax_totals( array() );
2171 $this->set_applied_coupons( array() );
2172 $this->session->set_session();
2173 }
2174
2175 /**
2176 * Remove a single coupon by code.
2177 *
2178 * @param string $coupon_code Code of the coupon to remove.
2179 * @return bool
2180 */
2181 public function remove_coupon( $coupon_code ) {
2182 $coupon_code = wc_format_coupon_code( $coupon_code );
2183
2184 // Find the coupon in applied coupons using case-insensitive comparison.
2185 foreach ( $this->get_applied_coupons() as $key => $applied_coupon ) {
2186 if ( wc_is_same_coupon( $applied_coupon, $coupon_code ) ) {
2187 unset( $this->applied_coupons[ $key ] );
2188 break;
2189 }
2190 }
2191
2192 WC()->session->set( 'refresh_totals', true );
2193
2194 do_action( 'woocommerce_removed_coupon', $coupon_code );
2195
2196 return true;
2197 }
2198
2199 /**
2200 * Trigger an action so 3rd parties can add custom fees.
2201 *
2202 * @since 2.0.0
2203 * @return void
2204 */
2205 public function calculate_fees() {
2206 do_action( 'woocommerce_cart_calculate_fees', $this );
2207 }
2208
2209 /**
2210 * Return reference to fees API.
2211 *
2212 * @since 3.2.0
2213 * @return WC_Cart_Fees
2214 */
2215 public function fees_api() {
2216 return $this->fees_api;
2217 }
2218
2219 /**
2220 * Add additional fee to the cart.
2221 *
2222 * This method should be called on a callback attached to the
2223 * woocommerce_cart_calculate_fees action during cart/checkout. Fees do not
2224 * persist.
2225 *
2226 * @uses WC_Cart_Fees::add_fee
2227 * @param string $name Unique name for the fee. Multiple fees of the same name cannot be added.
2228 * @param float $amount Fee amount (do not enter negative amounts).
2229 * @param bool $taxable Is the fee taxable? (default: false).
2230 * @param string $tax_class The tax class for the fee if taxable. A blank string is standard tax class. (default: '').
2231 * @return void
2232 */
2233 public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) {
2234 $this->fees_api()->add_fee(
2235 array(
2236 'name' => $name,
2237 'amount' => (float) $amount,
2238 'taxable' => $taxable,
2239 'tax_class' => $tax_class,
2240 )
2241 );
2242 }
2243
2244 /**
2245 * Return all added fees from the Fees API.
2246 *
2247 * @uses WC_Cart_Fees::get_fees
2248 * @return array
2249 */
2250 public function get_fees() {
2251 $fees = $this->fees_api()->get_fees();
2252
2253 if ( property_exists( $this, 'fees' ) ) {
2254 $fees = $fees + (array) $this->fees;
2255 }
2256 return $fees;
2257 }
2258
2259 /**
2260 * Gets the total excluding taxes.
2261 *
2262 * @return string formatted price
2263 */
2264 public function get_total_ex_tax() {
2265 return apply_filters( 'woocommerce_cart_total_ex_tax', wc_price( max( 0, $this->get_total( 'edit' ) - $this->get_total_tax() ) ) );
2266 }
2267
2268 /**
2269 * Gets the cart contents total (after calculation).
2270 *
2271 * @return string formatted price
2272 */
2273 public function get_cart_total() {
2274 return apply_filters( 'woocommerce_cart_contents_total', wc_price( wc_prices_include_tax() ? $this->get_cart_contents_total() + $this->get_cart_contents_tax() : $this->get_cart_contents_total() ) );
2275 }
2276
2277 /**
2278 * Gets the sub total (after calculation).
2279 *
2280 * @param bool $compound whether to include compound taxes.
2281 * @return string formatted price
2282 */
2283 public function get_cart_subtotal( $compound = false ) {
2284 /**
2285 * If the cart has compound tax, we want to show the subtotal as cart + shipping + non-compound taxes (after discount).
2286 */
2287 if ( $compound ) {
2288 $cart_subtotal = wc_price( $this->get_cart_contents_total() + $this->get_shipping_total() + $this->get_taxes_total( false, false ) );
2289
2290 } elseif ( $this->display_prices_including_tax() ) {
2291 $cart_subtotal = wc_price( $this->get_subtotal() + $this->get_subtotal_tax() );
2292
2293 if ( $this->get_subtotal_tax() > 0 && ! wc_prices_include_tax() ) {
2294 $cart_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
2295 }
2296 } else {
2297 $cart_subtotal = wc_price( $this->get_subtotal() );
2298
2299 if ( $this->get_subtotal_tax() > 0 && wc_prices_include_tax() ) {
2300 $cart_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
2301 }
2302 }
2303
2304 return apply_filters( 'woocommerce_cart_subtotal', $cart_subtotal, $compound, $this );
2305 }
2306
2307 /**
2308 * Get the product row price per item.
2309 *
2310 * @param WC_Product $product Product object.
2311 * @return string formatted price
2312 */
2313 public function get_product_price( $product ) {
2314 if ( $this->display_prices_including_tax() ) {
2315 $product_price = wc_get_price_including_tax( $product );
2316 } else {
2317 $product_price = wc_get_price_excluding_tax( $product );
2318 }
2319 return apply_filters( 'woocommerce_cart_product_price', wc_price( $product_price ), $product );
2320 }
2321
2322 /**
2323 * Get the product row subtotal.
2324 *
2325 * Gets the tax etc to avoid rounding issues.
2326 *
2327 * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate.
2328 *
2329 * @param WC_Product $product Product object.
2330 * @param int $quantity Quantity being purchased.
2331 * @return string formatted price
2332 */
2333 public function get_product_subtotal( $product, $quantity ) {
2334 $price = $product->get_price();
2335
2336 if ( $product->is_taxable() ) {
2337
2338 if ( $this->display_prices_including_tax() ) {
2339 $row_price = wc_get_price_including_tax( $product, array( 'qty' => $quantity ) );
2340 $product_subtotal = wc_price( $row_price );
2341
2342 if ( ! wc_prices_include_tax() && $this->get_subtotal_tax() > 0 ) {
2343 $product_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
2344 }
2345 } else {
2346 $row_price = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
2347 $product_subtotal = wc_price( $row_price );
2348
2349 if ( wc_prices_include_tax() && $this->get_subtotal_tax() > 0 ) {
2350 $product_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
2351 }
2352 }
2353 } else {
2354 $row_price = (float) $price * (float) $quantity;
2355 $product_subtotal = wc_price( $row_price );
2356 }
2357
2358 return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $product, $quantity, $this );
2359 }
2360
2361 /**
2362 * Gets the cart tax (after calculation).
2363 *
2364 * @return string formatted price
2365 */
2366 public function get_cart_tax() {
2367 $cart_total_tax = wc_round_tax_total( $this->get_cart_contents_tax() + $this->get_shipping_tax() + $this->get_fee_tax() );
2368
2369 return apply_filters( 'woocommerce_get_cart_tax', $cart_total_tax ? wc_price( $cart_total_tax ) : '' );
2370 }
2371
2372 /**
2373 * Get a tax amount.
2374 *
2375 * @param string $tax_rate_id ID of the tax rate to get taxes for.
2376 * @return float amount
2377 */
2378 public function get_tax_amount( $tax_rate_id ) {
2379 $taxes = wc_array_merge_recursive_numeric( $this->get_cart_contents_taxes(), $this->get_fee_taxes() );
2380 return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
2381 }
2382
2383 /**
2384 * Get a tax amount.
2385 *
2386 * @param string $tax_rate_id ID of the tax rate to get taxes for.
2387 * @return float amount
2388 */
2389 public function get_shipping_tax_amount( $tax_rate_id ) {
2390 $taxes = $this->get_shipping_taxes();
2391 return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
2392 }
2393
2394 /**
2395 * Get tax row amounts with or without compound taxes includes.
2396 *
2397 * @param bool $compound True if getting compound taxes.
2398 * @param bool $display True if getting total to display.
2399 * @return float price
2400 */
2401 public function get_taxes_total( $compound = true, $display = true ) {
2402 $total = 0;
2403 $taxes = $this->get_taxes();
2404 foreach ( $taxes as $key => $tax ) {
2405 if ( ! $compound && WC_Tax::is_compound( $key ) ) {
2406 continue;
2407 }
2408 $total += $tax;
2409 }
2410 if ( $display ) {
2411 $total = wc_format_decimal( $total, wc_get_price_decimals() );
2412 }
2413 return apply_filters( 'woocommerce_cart_taxes_total', $total, $compound, $display, $this );
2414 }
2415
2416 /**
2417 * Gets the total discount amount.
2418 *
2419 * @return mixed formatted price or false if there are none
2420 */
2421 public function get_total_discount() {
2422 return apply_filters( 'woocommerce_cart_total_discount', $this->get_discount_total() ? wc_price( $this->get_discount_total() ) : false, $this );
2423 }
2424
2425 /**
2426 * Reset cart totals to the defaults. Useful before running calculations.
2427 *
2428 * @return void
2429 */
2430 private function reset_totals() {
2431 $this->totals = $this->default_totals;
2432 $this->fees_api->remove_all_fees();
2433 do_action( 'woocommerce_cart_reset', $this, false );
2434 }
2435
2436 /**
2437 * Returns 'incl' if tax should be included in cart, otherwise returns 'excl'.
2438 *
2439 * @return string
2440 */
2441 public function get_tax_price_display_mode() {
2442 if ( $this->get_customer() && $this->get_customer()->get_is_vat_exempt() ) {
2443 return 'excl';
2444 }
2445
2446 return get_option( 'woocommerce_tax_display_cart' );
2447 }
2448
2449 /**
2450 * Returns the hash based on cart contents.
2451 *
2452 * @since 3.6.0
2453 * @return string hash for cart content
2454 */
2455 public function get_cart_hash() {
2456 $cart_session = $this->session->get_cart_for_session();
2457 $hash = $cart_session ? md5( wp_json_encode( $cart_session ) . $this->get_total( 'edit' ) ) : '';
2458 $hash = apply_filters_deprecated( 'woocommerce_add_to_cart_hash', array( $hash, $cart_session ), '3.6.0', 'woocommerce_cart_hash' );
2459
2460 return apply_filters( 'woocommerce_cart_hash', $hash, $cart_session );
2461 }
2462 }
2463