This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | if ( ! defined( 'ABSPATH' ) ) { |
||
4 | exit; // Exit if accessed directly |
||
5 | } |
||
6 | |||
7 | /** |
||
8 | * WooCommerce cart |
||
9 | * |
||
10 | * The WooCommerce cart class stores cart data and active coupons as well as handling customer sessions and some cart related urls. |
||
11 | * The cart class also has a price calculation function which calls upon other classes to calculate totals. |
||
12 | * |
||
13 | * @class WC_Cart |
||
14 | * @version 2.1.0 |
||
15 | * @package WooCommerce/Classes |
||
16 | * @category Class |
||
17 | * @author WooThemes |
||
18 | */ |
||
19 | class WC_Cart { |
||
20 | |||
21 | /** @var array Contains an array of cart items. */ |
||
22 | public $cart_contents = array(); |
||
23 | |||
24 | /** @var array Contains an array of removed cart items. */ |
||
25 | public $removed_cart_contents = array(); |
||
26 | |||
27 | /** @var array Contains an array of coupon codes applied to the cart. */ |
||
28 | public $applied_coupons = array(); |
||
29 | |||
30 | /** @var array Contains an array of coupon code discounts after they have been applied. */ |
||
31 | public $coupon_discount_amounts = array(); |
||
32 | |||
33 | /** @var array Contains an array of coupon code discount taxes. Used for tax incl pricing. */ |
||
34 | public $coupon_discount_tax_amounts = array(); |
||
35 | |||
36 | /** @var array Contains an array of coupon usage counts after they have been applied. */ |
||
37 | public $coupon_applied_count = array(); |
||
38 | |||
39 | /** @var array Array of coupons */ |
||
40 | public $coupons = array(); |
||
41 | |||
42 | /** @var float The total cost of the cart items. */ |
||
43 | public $cart_contents_total; |
||
44 | |||
45 | /** @var float Cart grand total. */ |
||
46 | public $total; |
||
47 | |||
48 | /** @var float Cart subtotal. */ |
||
49 | public $subtotal; |
||
50 | |||
51 | /** @var float Cart subtotal without tax. */ |
||
52 | public $subtotal_ex_tax; |
||
53 | |||
54 | /** @var float Total cart tax. */ |
||
55 | public $tax_total; |
||
56 | |||
57 | /** @var array An array of taxes/tax rates for the cart. */ |
||
58 | public $taxes; |
||
59 | |||
60 | /** @var array An array of taxes/tax rates for the shipping. */ |
||
61 | public $shipping_taxes; |
||
62 | |||
63 | /** @var float Discount amount before tax */ |
||
64 | public $discount_cart; |
||
65 | |||
66 | /** @var float Discounted tax amount. Used predominantly for displaying tax inclusive prices correctly */ |
||
67 | public $discount_cart_tax; |
||
68 | |||
69 | /** @var float Total for additional fees. */ |
||
70 | public $fee_total; |
||
71 | |||
72 | /** @var float Shipping cost. */ |
||
73 | public $shipping_total; |
||
74 | |||
75 | /** @var float Shipping tax. */ |
||
76 | public $shipping_tax_total; |
||
77 | |||
78 | /** @var array cart_session_data. Array of data the cart calculates and stores in the session with defaults */ |
||
79 | public $cart_session_data = array( |
||
80 | 'cart_contents_total' => 0, |
||
81 | 'total' => 0, |
||
82 | 'subtotal' => 0, |
||
83 | 'subtotal_ex_tax' => 0, |
||
84 | 'tax_total' => 0, |
||
85 | 'taxes' => array(), |
||
86 | 'shipping_taxes' => array(), |
||
87 | 'discount_cart' => 0, |
||
88 | 'discount_cart_tax' => 0, |
||
89 | 'shipping_total' => 0, |
||
90 | 'shipping_tax_total' => 0, |
||
91 | 'coupon_discount_amounts' => array(), |
||
92 | 'coupon_discount_tax_amounts' => array(), |
||
93 | 'fee_total' => 0, |
||
94 | 'fees' => array() |
||
95 | ); |
||
96 | |||
97 | /** |
||
98 | * An array of fees. |
||
99 | * |
||
100 | * @var array |
||
101 | */ |
||
102 | public $fees = array(); |
||
103 | |||
104 | /** |
||
105 | * Constructor for the cart class. Loads options and hooks in the init method. |
||
106 | */ |
||
107 | public function __construct() { |
||
108 | add_action( 'wp_loaded', array( $this, 'init' ) ); // Get cart after WP and plugins are loaded. |
||
109 | add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 ); // Set cookies |
||
110 | add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 ); // Set cookies before shutdown and ob flushing |
||
111 | add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 ); |
||
112 | add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 ); |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Auto-load in-accessible properties on demand. |
||
117 | * |
||
118 | * @param mixed $key |
||
119 | * @return mixed |
||
120 | */ |
||
121 | public function __get( $key ) { |
||
122 | switch ( $key ) { |
||
123 | case 'prices_include_tax' : |
||
124 | return wc_prices_include_tax(); |
||
125 | break; |
||
126 | case 'round_at_subtotal' : |
||
127 | return 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ); |
||
128 | break; |
||
129 | case 'tax_display_cart' : |
||
130 | return get_option( 'woocommerce_tax_display_cart' ); |
||
131 | break; |
||
132 | case 'dp' : |
||
133 | return wc_get_price_decimals(); |
||
134 | break; |
||
135 | case 'display_totals_ex_tax' : |
||
136 | case 'display_cart_ex_tax' : |
||
137 | return $this->tax_display_cart === 'excl'; |
||
138 | break; |
||
139 | case 'cart_contents_weight' : |
||
140 | return $this->get_cart_contents_weight(); |
||
141 | break; |
||
142 | case 'cart_contents_count' : |
||
143 | return $this->get_cart_contents_count(); |
||
144 | break; |
||
145 | case 'tax' : |
||
146 | _deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax:: directly' ); |
||
147 | $this->tax = new WC_Tax(); |
||
148 | return $this->tax; |
||
149 | case 'discount_total': |
||
150 | _deprecated_argument( 'WC_Cart->discount_total', '2.3', 'After tax coupons are no longer supported. For more information see: https://woocommerce.wordpress.com/2014/12/upcoming-coupon-changes-in-woocommerce-2-3/' ); |
||
151 | return 0; |
||
152 | } |
||
153 | } |
||
154 | |||
155 | /** |
||
156 | * Loads the cart data from the PHP session during WordPress init and hooks in other methods. |
||
157 | */ |
||
158 | public function init() { |
||
159 | $this->get_cart_from_session(); |
||
160 | |||
161 | add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_items' ), 1 ); |
||
162 | add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_coupons' ), 1 ); |
||
163 | add_action( 'woocommerce_after_checkout_validation', array( $this, 'check_customer_coupons' ), 1 ); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | * Will set cart cookies if needed, once, during WP hook. |
||
168 | */ |
||
169 | public function maybe_set_cart_cookies() { |
||
170 | if ( ! headers_sent() && did_action( 'wp_loaded' ) ) { |
||
171 | if ( ! $this->is_empty() ) { |
||
172 | $this->set_cart_cookies( true ); |
||
173 | } elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) { |
||
174 | $this->set_cart_cookies( false ); |
||
175 | } |
||
176 | } |
||
177 | } |
||
178 | |||
179 | /** |
||
180 | * Set cart hash cookie and items in cart. |
||
181 | * |
||
182 | * @access private |
||
183 | * @param bool $set (default: true) |
||
184 | */ |
||
185 | private function set_cart_cookies( $set = true ) { |
||
186 | if ( $set ) { |
||
187 | wc_setcookie( 'woocommerce_items_in_cart', 1 ); |
||
188 | wc_setcookie( 'woocommerce_cart_hash', md5( json_encode( $this->get_cart_for_session() ) ) ); |
||
189 | } elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) { |
||
190 | wc_setcookie( 'woocommerce_items_in_cart', 0, time() - HOUR_IN_SECONDS ); |
||
191 | wc_setcookie( 'woocommerce_cart_hash', '', time() - HOUR_IN_SECONDS ); |
||
192 | } |
||
193 | do_action( 'woocommerce_set_cart_cookies', $set ); |
||
194 | } |
||
195 | |||
196 | /*-----------------------------------------------------------------------------------*/ |
||
197 | /* Cart Session Handling */ |
||
198 | /*-----------------------------------------------------------------------------------*/ |
||
199 | |||
200 | /** |
||
201 | * Get the cart data from the PHP session and store it in class variables. |
||
202 | */ |
||
203 | public function get_cart_from_session() { |
||
204 | // Load cart session data from session |
||
205 | foreach ( $this->cart_session_data as $key => $default ) { |
||
206 | $this->$key = WC()->session->get( $key, $default ); |
||
207 | } |
||
208 | |||
209 | $update_cart_session = false; |
||
210 | $this->removed_cart_contents = array_filter( WC()->session->get( 'removed_cart_contents', array() ) ); |
||
211 | $this->applied_coupons = array_filter( WC()->session->get( 'applied_coupons', array() ) ); |
||
212 | |||
213 | /** |
||
214 | * Load the cart object. This defaults to the persistent cart if null. |
||
215 | */ |
||
216 | $cart = WC()->session->get( 'cart', null ); |
||
217 | |||
218 | if ( is_null( $cart ) && ( $saved_cart = get_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', true ) ) ) { |
||
219 | $cart = $saved_cart['cart']; |
||
220 | $update_cart_session = true; |
||
221 | } elseif ( is_null( $cart ) ) { |
||
222 | $cart = array(); |
||
223 | } |
||
224 | |||
225 | if ( is_array( $cart ) ) { |
||
226 | // Prime meta cache to reduce future queries |
||
227 | update_meta_cache( 'post', wp_list_pluck( $cart, 'product_id' ) ); |
||
228 | |||
229 | foreach ( $cart as $key => $values ) { |
||
230 | $_product = wc_get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] ); |
||
231 | |||
232 | if ( ! empty( $_product ) && $_product->exists() && $values['quantity'] > 0 ) { |
||
233 | |||
234 | if ( ! $_product->is_purchasable() ) { |
||
235 | |||
236 | // Flag to indicate the stored cart should be update |
||
237 | $update_cart_session = true; |
||
238 | wc_add_notice( sprintf( __( '%s has been removed from your cart because it can no longer be purchased. Please contact us if you need assistance.', 'woocommerce' ), $_product->get_title() ), 'error' ); |
||
239 | do_action( 'woocommerce_remove_cart_item_from_session', $key, $values ); |
||
240 | |||
241 | } else { |
||
242 | |||
243 | // Put session data into array. Run through filter so other plugins can load their own session data |
||
244 | $session_data = array_merge( $values, array( 'data' => $_product ) ); |
||
245 | $this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key ); |
||
246 | |||
247 | } |
||
248 | } |
||
249 | } |
||
250 | } |
||
251 | |||
252 | // Trigger action |
||
253 | do_action( 'woocommerce_cart_loaded_from_session', $this ); |
||
254 | |||
255 | if ( $update_cart_session ) { |
||
256 | WC()->session->cart = $this->get_cart_for_session(); |
||
257 | } |
||
258 | |||
259 | // Queue re-calc if subtotal is not set |
||
260 | if ( ( ! $this->subtotal && ! $this->is_empty() ) || $update_cart_session ) { |
||
261 | $this->calculate_totals(); |
||
262 | } |
||
263 | } |
||
264 | |||
265 | /** |
||
266 | * Sets the php session data for the cart and coupons. |
||
267 | */ |
||
268 | public function set_session() { |
||
269 | // Set cart and coupon session data |
||
270 | $cart_session = $this->get_cart_for_session(); |
||
271 | |||
272 | WC()->session->set( 'cart', $cart_session ); |
||
273 | WC()->session->set( 'applied_coupons', $this->applied_coupons ); |
||
274 | WC()->session->set( 'coupon_discount_amounts', $this->coupon_discount_amounts ); |
||
275 | WC()->session->set( 'coupon_discount_tax_amounts', $this->coupon_discount_tax_amounts ); |
||
276 | WC()->session->set( 'removed_cart_contents', $this->removed_cart_contents ); |
||
277 | |||
278 | foreach ( $this->cart_session_data as $key => $default ) { |
||
279 | WC()->session->set( $key, $this->$key ); |
||
280 | } |
||
281 | |||
282 | if ( get_current_user_id() ) { |
||
283 | $this->persistent_cart_update(); |
||
284 | } |
||
285 | |||
286 | do_action( 'woocommerce_cart_updated' ); |
||
287 | } |
||
288 | |||
289 | /** |
||
290 | * Empties the cart and optionally the persistent cart too. |
||
291 | * |
||
292 | * @param bool $clear_persistent_cart (default: true) |
||
293 | */ |
||
294 | public function empty_cart( $clear_persistent_cart = true ) { |
||
295 | $this->cart_contents = array(); |
||
296 | $this->reset( true ); |
||
297 | |||
298 | unset( WC()->session->order_awaiting_payment, WC()->session->applied_coupons, WC()->session->coupon_discount_amounts, WC()->session->coupon_discount_tax_amounts, WC()->session->cart ); |
||
299 | |||
300 | if ( $clear_persistent_cart && get_current_user_id() ) { |
||
301 | $this->persistent_cart_destroy(); |
||
302 | } |
||
303 | |||
304 | do_action( 'woocommerce_cart_emptied' ); |
||
305 | } |
||
306 | |||
307 | /*-----------------------------------------------------------------------------------*/ |
||
308 | /* Persistent cart handling */ |
||
309 | /*-----------------------------------------------------------------------------------*/ |
||
310 | |||
311 | /** |
||
312 | * Save the persistent cart when the cart is updated. |
||
313 | */ |
||
314 | public function persistent_cart_update() { |
||
315 | update_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', array( |
||
316 | 'cart' => WC()->session->get( 'cart' ) |
||
317 | ) ); |
||
318 | } |
||
319 | |||
320 | /** |
||
321 | * Delete the persistent cart permanently. |
||
322 | */ |
||
323 | public function persistent_cart_destroy() { |
||
324 | delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart' ); |
||
325 | } |
||
326 | |||
327 | /*-----------------------------------------------------------------------------------*/ |
||
328 | /* Cart Data Functions */ |
||
329 | /*-----------------------------------------------------------------------------------*/ |
||
330 | |||
331 | /** |
||
332 | * Coupons enabled function. Filterable. |
||
333 | * |
||
334 | * @deprecated 2.5.0 in favor to wc_coupons_enabled() |
||
335 | * |
||
336 | * @return bool |
||
337 | */ |
||
338 | public function coupons_enabled() { |
||
339 | return wc_coupons_enabled(); |
||
340 | } |
||
341 | |||
342 | /** |
||
343 | * Get number of items in the cart. |
||
344 | * @return int |
||
345 | */ |
||
346 | public function get_cart_contents_count() { |
||
347 | return apply_filters( 'woocommerce_cart_contents_count', array_sum( wp_list_pluck( $this->get_cart(), 'quantity' ) ) ); |
||
348 | } |
||
349 | |||
350 | /** |
||
351 | * Get weight of items in the cart. |
||
352 | * @since 2.5.0 |
||
353 | * @return int |
||
354 | */ |
||
355 | public function get_cart_contents_weight() { |
||
356 | $weight = 0; |
||
357 | |||
358 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
359 | $weight += $values['data']->get_weight() * $values['quantity']; |
||
360 | } |
||
361 | |||
362 | return apply_filters( 'woocommerce_cart_contents_weight', $weight ); |
||
363 | } |
||
364 | |||
365 | /** |
||
366 | * Checks if the cart is empty. |
||
367 | * |
||
368 | * @return bool |
||
369 | */ |
||
370 | public function is_empty() { |
||
371 | return 0 === sizeof( $this->get_cart() ); |
||
372 | } |
||
373 | |||
374 | /** |
||
375 | * Check all cart items for errors. |
||
376 | */ |
||
377 | public function check_cart_items() { |
||
378 | |||
379 | // Result |
||
380 | $return = true; |
||
381 | |||
382 | // Check cart item validity |
||
383 | $result = $this->check_cart_item_validity(); |
||
384 | |||
385 | if ( is_wp_error( $result ) ) { |
||
386 | wc_add_notice( $result->get_error_message(), 'error' ); |
||
387 | $return = false; |
||
388 | } |
||
389 | |||
390 | // Check item stock |
||
391 | $result = $this->check_cart_item_stock(); |
||
392 | |||
393 | if ( is_wp_error( $result ) ) { |
||
394 | wc_add_notice( $result->get_error_message(), 'error' ); |
||
395 | $return = false; |
||
396 | } |
||
397 | |||
398 | return $return; |
||
399 | |||
400 | } |
||
401 | |||
402 | /** |
||
403 | * Check cart coupons for errors. |
||
404 | */ |
||
405 | public function check_cart_coupons() { |
||
406 | foreach ( $this->applied_coupons as $code ) { |
||
407 | $coupon = new WC_Coupon( $code ); |
||
408 | |||
409 | View Code Duplication | if ( ! $coupon->is_valid() ) { |
|
410 | // Error message |
||
411 | $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED ); |
||
412 | |||
413 | // Remove the coupon |
||
414 | $this->remove_coupon( $code ); |
||
415 | |||
416 | // Flag totals for refresh |
||
417 | WC()->session->set( 'refresh_totals', true ); |
||
418 | } |
||
419 | } |
||
420 | } |
||
421 | |||
422 | /** |
||
423 | * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines. |
||
424 | * |
||
425 | * @return array |
||
426 | */ |
||
427 | public function get_cart_item_quantities() { |
||
428 | $quantities = array(); |
||
429 | |||
430 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
431 | $_product = $values['data']; |
||
432 | |||
433 | if ( $_product->is_type( 'variation' ) && true === $_product->managing_stock() ) { |
||
434 | // Variation has stock levels defined so its handled individually |
||
435 | $quantities[ $values['variation_id'] ] = isset( $quantities[ $values['variation_id'] ] ) ? $quantities[ $values['variation_id'] ] + $values['quantity'] : $values['quantity']; |
||
436 | } else { |
||
437 | $quantities[ $values['product_id'] ] = isset( $quantities[ $values['product_id'] ] ) ? $quantities[ $values['product_id'] ] + $values['quantity'] : $values['quantity']; |
||
438 | } |
||
439 | } |
||
440 | |||
441 | return $quantities; |
||
442 | } |
||
443 | |||
444 | /** |
||
445 | * Looks through cart items and checks the posts are not trashed or deleted. |
||
446 | * |
||
447 | * @return bool|WP_Error |
||
448 | */ |
||
449 | public function check_cart_item_validity() { |
||
450 | $return = true; |
||
451 | |||
452 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
453 | $_product = $values['data']; |
||
454 | |||
455 | if ( ! $_product || ! $_product->exists() || 'trash' === $_product->post->post_status ) { |
||
456 | $this->set_quantity( $cart_item_key, 0 ); |
||
457 | $return = new WP_Error( 'invalid', __( 'An item which is no longer available was removed from your cart.', 'woocommerce' ) ); |
||
458 | } |
||
459 | } |
||
460 | |||
461 | return $return; |
||
462 | } |
||
463 | |||
464 | /** |
||
465 | * Looks through the cart to check each item is in stock. If not, add an error. |
||
466 | * |
||
467 | * @return bool|WP_Error |
||
468 | */ |
||
469 | public function check_cart_item_stock() { |
||
470 | global $wpdb; |
||
471 | |||
472 | $error = new WP_Error(); |
||
473 | $product_qty_in_cart = $this->get_cart_item_quantities(); |
||
474 | |||
475 | // First stock check loop |
||
476 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
477 | $_product = $values['data']; |
||
478 | |||
479 | /** |
||
480 | * Check stock based on stock-status. |
||
481 | */ |
||
482 | if ( ! $_product->is_in_stock() ) { |
||
483 | $error->add( 'out-of-stock', sprintf(__( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title() ) ); |
||
484 | return $error; |
||
485 | } |
||
486 | |||
487 | if ( ! $_product->managing_stock() ) { |
||
488 | continue; |
||
489 | } |
||
490 | |||
491 | $check_qty = $_product->is_type( 'variation' ) && true === $_product->managing_stock() ? $product_qty_in_cart[ $values['variation_id'] ] : $product_qty_in_cart[ $values['product_id'] ]; |
||
492 | |||
493 | /** |
||
494 | * Check stock based on all items in the cart. |
||
495 | */ |
||
496 | if ( ! $_product->has_enough_stock( $check_qty ) ) { |
||
497 | $error->add( 'out-of-stock', sprintf(__( 'Sorry, we do not have enough "%s" in stock to fulfill your order (%s in stock). Please edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), $_product->get_stock_quantity() ) ); |
||
498 | return $error; |
||
499 | } |
||
500 | |||
501 | /** |
||
502 | * Finally consider any held stock, from pending orders. |
||
503 | */ |
||
504 | if ( get_option( 'woocommerce_hold_stock_minutes' ) > 0 && ! $_product->backorders_allowed() ) { |
||
505 | $order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0; |
||
506 | $held_stock = $wpdb->get_var( |
||
507 | $wpdb->prepare( " |
||
508 | SELECT SUM( order_item_meta.meta_value ) AS held_qty |
||
509 | FROM {$wpdb->posts} AS posts |
||
510 | LEFT JOIN {$wpdb->prefix}woocommerce_order_items as order_items ON posts.ID = order_items.order_id |
||
511 | LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id |
||
512 | LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta2 ON order_items.order_item_id = order_item_meta2.order_item_id |
||
513 | WHERE order_item_meta.meta_key = '_qty' |
||
514 | AND order_item_meta2.meta_key = %s AND order_item_meta2.meta_value = %d |
||
515 | AND posts.post_type IN ( '" . implode( "','", wc_get_order_types() ) . "' ) |
||
516 | AND posts.post_status = 'wc-pending' |
||
517 | AND posts.ID != %d;", |
||
518 | $_product->is_type( 'variation' ) && true === $_product->managing_stock() ? '_variation_id' : '_product_id', |
||
519 | $_product->is_type( 'variation' ) && true === $_product->managing_stock() ? $values['variation_id'] : $values['product_id'], |
||
520 | $order_id |
||
521 | ) |
||
522 | ); |
||
523 | |||
524 | $not_enough_stock = false; |
||
525 | |||
526 | if ( $_product->is_type( 'variation' ) && 'parent' === $_product->managing_stock() && $_product->parent->get_stock_quantity() < ( $held_stock + $check_qty ) ) { |
||
527 | $not_enough_stock = true; |
||
528 | } elseif ( $_product->get_stock_quantity() < ( $held_stock + $check_qty ) ) { |
||
529 | $not_enough_stock = true; |
||
530 | } |
||
531 | if ( $not_enough_stock ) { |
||
532 | $error->add( 'out-of-stock', sprintf(__( 'Sorry, we do not have enough "%s" in stock to fulfill your order right now. Please try again in %d minutes or edit your cart and try again. We apologise for any inconvenience caused.', 'woocommerce' ), $_product->get_title(), get_option( 'woocommerce_hold_stock_minutes' ) ) ); |
||
533 | return $error; |
||
534 | } |
||
535 | } |
||
536 | } |
||
537 | |||
538 | return true; |
||
539 | } |
||
540 | |||
541 | /** |
||
542 | * Gets and formats a list of cart item data + variations for display on the frontend. |
||
543 | * |
||
544 | * @param array $cart_item |
||
545 | * @param bool $flat (default: false) |
||
546 | * @return string |
||
547 | */ |
||
548 | public function get_item_data( $cart_item, $flat = false ) { |
||
549 | $item_data = array(); |
||
550 | |||
551 | // Variation data |
||
552 | if ( ! empty( $cart_item['data']->variation_id ) && is_array( $cart_item['variation'] ) ) { |
||
553 | |||
554 | foreach ( $cart_item['variation'] as $name => $value ) { |
||
555 | |||
556 | if ( '' === $value ) |
||
557 | continue; |
||
558 | |||
559 | $taxonomy = wc_attribute_taxonomy_name( str_replace( 'attribute_pa_', '', urldecode( $name ) ) ); |
||
560 | |||
561 | // If this is a term slug, get the term's nice name |
||
562 | if ( taxonomy_exists( $taxonomy ) ) { |
||
563 | $term = get_term_by( 'slug', $value, $taxonomy ); |
||
564 | if ( ! is_wp_error( $term ) && $term && $term->name ) { |
||
565 | $value = $term->name; |
||
566 | } |
||
567 | $label = wc_attribute_label( $taxonomy ); |
||
568 | |||
569 | // If this is a custom option slug, get the options name |
||
570 | } else { |
||
571 | $value = apply_filters( 'woocommerce_variation_option_name', $value ); |
||
572 | $product_attributes = $cart_item['data']->get_attributes(); |
||
573 | if ( isset( $product_attributes[ str_replace( 'attribute_', '', $name ) ] ) ) { |
||
574 | $label = wc_attribute_label( $product_attributes[ str_replace( 'attribute_', '', $name ) ]['name'] ); |
||
575 | } else { |
||
576 | $label = $name; |
||
577 | } |
||
578 | } |
||
579 | |||
580 | $item_data[] = array( |
||
581 | 'key' => $label, |
||
582 | 'value' => $value |
||
583 | ); |
||
584 | } |
||
585 | } |
||
586 | |||
587 | // Filter item data to allow 3rd parties to add more to the array |
||
588 | $item_data = apply_filters( 'woocommerce_get_item_data', $item_data, $cart_item ); |
||
589 | |||
590 | // Format item data ready to display |
||
591 | foreach ( $item_data as $key => $data ) { |
||
592 | // Set hidden to true to not display meta on cart. |
||
593 | if ( ! empty( $data['hidden'] ) ) { |
||
594 | unset( $item_data[ $key ] ); |
||
595 | continue; |
||
596 | } |
||
597 | $item_data[ $key ]['key'] = ! empty( $data['key'] ) ? $data['key'] : $data['name']; |
||
598 | $item_data[ $key ]['display'] = ! empty( $data['display'] ) ? $data['display'] : $data['value']; |
||
599 | } |
||
600 | |||
601 | // Output flat or in list format |
||
602 | if ( sizeof( $item_data ) > 0 ) { |
||
603 | ob_start(); |
||
604 | |||
605 | if ( $flat ) { |
||
606 | foreach ( $item_data as $data ) { |
||
607 | echo esc_html( $data['key'] ) . ': ' . wp_kses_post( $data['display'] ) . "\n"; |
||
608 | } |
||
609 | } else { |
||
610 | wc_get_template( 'cart/cart-item-data.php', array( 'item_data' => $item_data ) ); |
||
611 | } |
||
612 | |||
613 | return ob_get_clean(); |
||
614 | } |
||
615 | |||
616 | return ''; |
||
617 | } |
||
618 | |||
619 | /** |
||
620 | * Gets cross sells based on the items in the cart. |
||
621 | * |
||
622 | * @return array cross_sells (item ids) |
||
623 | */ |
||
624 | public function get_cross_sells() { |
||
625 | $cross_sells = array(); |
||
626 | $in_cart = array(); |
||
627 | if ( ! $this->is_empty() ) { |
||
628 | foreach ( $this->get_cart() as $cart_item_key => $values ) { |
||
629 | if ( $values['quantity'] > 0 ) { |
||
630 | $cross_sells = array_merge( $values['data']->get_cross_sells(), $cross_sells ); |
||
631 | $in_cart[] = $values['product_id']; |
||
632 | } |
||
633 | } |
||
634 | } |
||
635 | $cross_sells = array_diff( $cross_sells, $in_cart ); |
||
636 | return $cross_sells; |
||
637 | } |
||
638 | |||
639 | /** |
||
640 | * Gets the url to the cart page. |
||
641 | * |
||
642 | * @deprecated 2.5.0 in favor to wc_get_cart_url() |
||
643 | * |
||
644 | * @return string url to page |
||
645 | */ |
||
646 | public function get_cart_url() { |
||
647 | return wc_get_cart_url(); |
||
648 | } |
||
649 | |||
650 | /** |
||
651 | * Gets the url to the checkout page. |
||
652 | * |
||
653 | * @deprecated 2.5.0 in favor to wc_get_checkout_url() |
||
654 | * |
||
655 | * @return string url to page |
||
656 | */ |
||
657 | public function get_checkout_url() { |
||
658 | return wc_get_checkout_url(); |
||
659 | } |
||
660 | |||
661 | /** |
||
662 | * Gets the url to remove an item from the cart. |
||
663 | * |
||
664 | * @param string $cart_item_key contains the id of the cart item |
||
665 | * @return string url to page |
||
666 | */ |
||
667 | public function get_remove_url( $cart_item_key ) { |
||
668 | $cart_page_url = wc_get_page_permalink( 'cart' ); |
||
669 | return apply_filters( 'woocommerce_get_remove_url', $cart_page_url ? wp_nonce_url( add_query_arg( 'remove_item', $cart_item_key, $cart_page_url ), 'woocommerce-cart' ) : '' ); |
||
670 | } |
||
671 | |||
672 | /** |
||
673 | * Gets the url to re-add an item into the cart. |
||
674 | * |
||
675 | * @param string $cart_item_key |
||
676 | * @return string url to page |
||
677 | */ |
||
678 | public function get_undo_url( $cart_item_key ) { |
||
679 | $cart_page_url = wc_get_page_permalink( 'cart' ); |
||
680 | |||
681 | $query_args = array( |
||
682 | 'undo_item' => $cart_item_key, |
||
683 | ); |
||
684 | |||
685 | return apply_filters( 'woocommerce_get_undo_url', $cart_page_url ? wp_nonce_url( add_query_arg( $query_args, $cart_page_url ), 'woocommerce-cart' ) : '', $cart_item_key ); |
||
686 | } |
||
687 | |||
688 | /** |
||
689 | * Returns the contents of the cart in an array. |
||
690 | * |
||
691 | * @return array contents of the cart |
||
692 | */ |
||
693 | public function get_cart() { |
||
694 | if ( ! did_action( 'wp_loaded' ) ) { |
||
695 | _doing_it_wrong( __FUNCTION__, __( 'Get cart should not be called before the wp_loaded action.', 'woocommerce' ), '2.3' ); |
||
696 | } |
||
697 | if ( ! did_action( 'woocommerce_cart_loaded_from_session' ) ) { |
||
698 | $this->get_cart_from_session(); |
||
699 | } |
||
700 | return array_filter( (array) $this->cart_contents ); |
||
701 | } |
||
702 | |||
703 | /** |
||
704 | * Returns the contents of the cart in an array without the 'data' element. |
||
705 | * |
||
706 | * @return array contents of the cart |
||
707 | */ |
||
708 | public function get_cart_for_session() { |
||
709 | $cart_session = array(); |
||
710 | |||
711 | if ( $this->get_cart() ) { |
||
712 | foreach ( $this->get_cart() as $key => $values ) { |
||
713 | $cart_session[ $key ] = $values; |
||
714 | unset( $cart_session[ $key ]['data'] ); // Unset product object |
||
715 | } |
||
716 | } |
||
717 | |||
718 | return $cart_session; |
||
719 | } |
||
720 | |||
721 | /** |
||
722 | * Returns a specific item in the cart. |
||
723 | * |
||
724 | * @param string $item_key Cart item key. |
||
725 | * @return array Item data |
||
726 | */ |
||
727 | public function get_cart_item( $item_key ) { |
||
728 | if ( isset( $this->cart_contents[ $item_key ] ) ) { |
||
729 | return $this->cart_contents[ $item_key ]; |
||
730 | } |
||
731 | |||
732 | return array(); |
||
733 | } |
||
734 | |||
735 | /** |
||
736 | * Returns the cart and shipping taxes, merged. |
||
737 | * |
||
738 | * @return array merged taxes |
||
739 | */ |
||
740 | public function get_taxes() { |
||
741 | $taxes = array(); |
||
742 | |||
743 | // Merge |
||
744 | foreach ( array_keys( $this->taxes + $this->shipping_taxes ) as $key ) { |
||
745 | $taxes[ $key ] = ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); |
||
746 | } |
||
747 | |||
748 | return apply_filters( 'woocommerce_cart_get_taxes', $taxes, $this ); |
||
749 | } |
||
750 | |||
751 | /** |
||
752 | * Get taxes, merged by code, formatted ready for output. |
||
753 | * |
||
754 | * @return array |
||
755 | */ |
||
756 | public function get_tax_totals() { |
||
757 | $taxes = $this->get_taxes(); |
||
758 | $tax_totals = array(); |
||
759 | |||
760 | foreach ( $taxes as $key => $tax ) { |
||
761 | $code = WC_Tax::get_rate_code( $key ); |
||
762 | |||
763 | if ( $code || $key === apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) ) { |
||
764 | View Code Duplication | if ( ! isset( $tax_totals[ $code ] ) ) { |
|
765 | $tax_totals[ $code ] = new stdClass(); |
||
766 | $tax_totals[ $code ]->amount = 0; |
||
767 | } |
||
768 | $tax_totals[ $code ]->tax_rate_id = $key; |
||
769 | $tax_totals[ $code ]->is_compound = WC_Tax::is_compound( $key ); |
||
770 | $tax_totals[ $code ]->label = WC_Tax::get_rate_label( $key ); |
||
771 | $tax_totals[ $code ]->amount += wc_round_tax_total( $tax ); |
||
772 | $tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ) ); |
||
773 | } |
||
774 | } |
||
775 | |||
776 | View Code Duplication | if ( apply_filters( 'woocommerce_cart_hide_zero_taxes', true ) ) { |
|
777 | $amounts = array_filter( wp_list_pluck( $tax_totals, 'amount' ) ); |
||
778 | $tax_totals = array_intersect_key( $tax_totals, $amounts ); |
||
779 | } |
||
780 | |||
781 | return apply_filters( 'woocommerce_cart_tax_totals', $tax_totals, $this ); |
||
782 | } |
||
783 | |||
784 | /** |
||
785 | * Get all tax classes for items in the cart. |
||
786 | * @return array |
||
787 | */ |
||
788 | public function get_cart_item_tax_classes() { |
||
789 | $found_tax_classes = array(); |
||
790 | |||
791 | foreach ( WC()->cart->get_cart() as $item ) { |
||
792 | $found_tax_classes[] = $item['data']->get_tax_class(); |
||
793 | } |
||
794 | |||
795 | return array_unique( $found_tax_classes ); |
||
796 | } |
||
797 | |||
798 | /** |
||
799 | * Determines the value that the customer spent and the subtotal |
||
800 | * displayed, used for things like coupon validation. |
||
801 | * |
||
802 | * Since the coupon lines are displayed based on the TAX DISPLAY value |
||
803 | * of cart, this is used to determine the spend. |
||
804 | * |
||
805 | * If cart totals are shown including tax, use the subtotal. |
||
806 | * If cart totals are shown excluding tax, use the subtotal ex tax |
||
807 | * (tax is shown after coupons). |
||
808 | * |
||
809 | * @since 2.6.0 |
||
810 | * @return string |
||
811 | */ |
||
812 | public function get_displayed_subtotal() { |
||
813 | if ( 'incl' === $this->tax_display_cart ) { |
||
814 | return wc_format_decimal( $this->subtotal ); |
||
815 | } elseif ( 'excl' === $this->tax_display_cart ) { |
||
816 | return wc_format_decimal( $this->subtotal_ex_tax ); |
||
817 | } |
||
818 | } |
||
819 | |||
820 | /*-----------------------------------------------------------------------------------*/ |
||
821 | /* Add to cart handling */ |
||
822 | /*-----------------------------------------------------------------------------------*/ |
||
823 | |||
824 | /** |
||
825 | * Check if product is in the cart and return cart item key. |
||
826 | * |
||
827 | * Cart item key will be unique based on the item and its properties, such as variations. |
||
828 | * |
||
829 | * @param mixed id of product to find in the cart |
||
830 | * @return string cart item key |
||
831 | */ |
||
832 | public function find_product_in_cart( $cart_id = false ) { |
||
833 | if ( $cart_id !== false ) { |
||
834 | if ( is_array( $this->cart_contents ) ) { |
||
835 | foreach ( $this->cart_contents as $cart_item_key => $cart_item ) { |
||
836 | if ( $cart_item_key == $cart_id ) { |
||
837 | return $cart_item_key; |
||
838 | } |
||
839 | } |
||
840 | } |
||
841 | } |
||
842 | return ''; |
||
843 | } |
||
844 | |||
845 | /** |
||
846 | * Generate a unique ID for the cart item being added. |
||
847 | * |
||
848 | * @param int $product_id - id of the product the key is being generated for |
||
849 | * @param int $variation_id of the product the key is being generated for |
||
850 | * @param array $variation data for the cart item |
||
851 | * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart |
||
852 | * @return string cart item key |
||
853 | */ |
||
854 | public function generate_cart_id( $product_id, $variation_id = 0, $variation = array(), $cart_item_data = array() ) { |
||
855 | $id_parts = array( $product_id ); |
||
856 | |||
857 | if ( $variation_id && 0 != $variation_id ) { |
||
858 | $id_parts[] = $variation_id; |
||
859 | } |
||
860 | |||
861 | if ( is_array( $variation ) && ! empty( $variation ) ) { |
||
862 | $variation_key = ''; |
||
863 | foreach ( $variation as $key => $value ) { |
||
864 | $variation_key .= trim( $key ) . trim( $value ); |
||
865 | } |
||
866 | $id_parts[] = $variation_key; |
||
867 | } |
||
868 | |||
869 | if ( is_array( $cart_item_data ) && ! empty( $cart_item_data ) ) { |
||
870 | $cart_item_data_key = ''; |
||
871 | foreach ( $cart_item_data as $key => $value ) { |
||
872 | |||
873 | if ( is_array( $value ) ) { |
||
874 | $value = http_build_query( $value ); |
||
875 | } |
||
876 | $cart_item_data_key .= trim( $key ) . trim( $value ); |
||
877 | |||
878 | } |
||
879 | $id_parts[] = $cart_item_data_key; |
||
880 | } |
||
881 | |||
882 | return md5( implode( '_', $id_parts ) ); |
||
883 | } |
||
884 | |||
885 | /** |
||
886 | * Add a product to the cart. |
||
887 | * |
||
888 | * @param int $product_id contains the id of the product to add to the cart |
||
889 | * @param int $quantity contains the quantity of the item to add |
||
890 | * @param int $variation_id |
||
891 | * @param array $variation attribute values |
||
892 | * @param array $cart_item_data extra cart item data we want to pass into the item |
||
893 | * @return string|bool $cart_item_key |
||
894 | */ |
||
895 | public function add_to_cart( $product_id = 0, $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() ) { |
||
896 | // Wrap in try catch so plugins can throw an exception to prevent adding to cart |
||
897 | try { |
||
898 | $product_id = absint( $product_id ); |
||
899 | $variation_id = absint( $variation_id ); |
||
900 | |||
901 | // Ensure we don't add a variation to the cart directly by variation ID |
||
902 | if ( 'product_variation' == get_post_type( $product_id ) ) { |
||
903 | $variation_id = $product_id; |
||
904 | $product_id = wp_get_post_parent_id( $variation_id ); |
||
905 | } |
||
906 | |||
907 | // Get the product |
||
908 | $product_data = wc_get_product( $variation_id ? $variation_id : $product_id ); |
||
909 | |||
910 | // Sanity check |
||
911 | if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->post->post_status ) { |
||
912 | throw new Exception(); |
||
913 | } |
||
914 | |||
915 | // Load cart item data - may be added by other plugins |
||
916 | $cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id ); |
||
917 | |||
918 | // Generate a ID based on product ID, variation ID, variation data, and other cart item data |
||
919 | $cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data ); |
||
920 | |||
921 | // Find the cart item key in the existing cart |
||
922 | $cart_item_key = $this->find_product_in_cart( $cart_id ); |
||
0 ignored issues
–
show
|
|||
923 | |||
924 | // Force quantity to 1 if sold individually and check for existing item in cart |
||
925 | if ( $product_data->is_sold_individually() ) { |
||
926 | $quantity = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data ); |
||
927 | $in_cart_quantity = $cart_item_key ? $this->cart_contents[ $cart_item_key ]['quantity'] : 0; |
||
928 | |||
929 | if ( $in_cart_quantity > 0 ) { |
||
930 | throw new Exception( sprintf( '<a href="%s" class="button wc-forward">%s</a> %s', wc_get_cart_url(), __( 'View Cart', 'woocommerce' ), sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_title() ) ) ); |
||
931 | } |
||
932 | } |
||
933 | |||
934 | // Check product is_purchasable |
||
935 | if ( ! $product_data->is_purchasable() ) { |
||
936 | throw new Exception( __( 'Sorry, this product cannot be purchased.', 'woocommerce' ) ); |
||
937 | } |
||
938 | |||
939 | // Stock check - only check if we're managing stock and backorders are not allowed |
||
940 | if ( ! $product_data->is_in_stock() ) { |
||
941 | throw new Exception( sprintf( __( 'You cannot add "%s" to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_title() ) ); |
||
942 | } |
||
943 | |||
944 | if ( ! $product_data->has_enough_stock( $quantity ) ) { |
||
945 | throw new Exception( sprintf(__( 'You cannot add that amount of "%s" to the cart because there is not enough stock (%s remaining).', 'woocommerce' ), $product_data->get_title(), $product_data->get_stock_quantity() ) ); |
||
946 | } |
||
947 | |||
948 | // Stock check - this time accounting for whats already in-cart |
||
949 | if ( $managing_stock = $product_data->managing_stock() ) { |
||
950 | $products_qty_in_cart = $this->get_cart_item_quantities(); |
||
951 | |||
952 | if ( $product_data->is_type( 'variation' ) && true === $managing_stock ) { |
||
953 | $check_qty = isset( $products_qty_in_cart[ $variation_id ] ) ? $products_qty_in_cart[ $variation_id ] : 0; |
||
954 | } else { |
||
955 | $check_qty = isset( $products_qty_in_cart[ $product_id ] ) ? $products_qty_in_cart[ $product_id ] : 0; |
||
956 | } |
||
957 | |||
958 | /** |
||
959 | * Check stock based on all items in the cart. |
||
960 | */ |
||
961 | if ( ! $product_data->has_enough_stock( $check_qty + $quantity ) ) { |
||
962 | throw new Exception( sprintf( |
||
963 | '<a href="%s" class="button wc-forward">%s</a> %s', |
||
964 | wc_get_cart_url(), |
||
965 | __( 'View Cart', 'woocommerce' ), |
||
966 | sprintf( __( 'You cannot add that amount to the cart — we have %s in stock and you already have %s in your cart.', 'woocommerce' ), $product_data->get_stock_quantity(), $check_qty ) |
||
967 | ) ); |
||
968 | } |
||
969 | } |
||
970 | |||
971 | // If cart_item_key is set, the item is already in the cart |
||
972 | if ( $cart_item_key ) { |
||
973 | $new_quantity = $quantity + $this->cart_contents[ $cart_item_key ]['quantity']; |
||
974 | $this->set_quantity( $cart_item_key, $new_quantity, false ); |
||
975 | } else { |
||
976 | $cart_item_key = $cart_id; |
||
977 | |||
978 | // Add item after merging with $cart_item_data - hook to allow plugins to modify cart item |
||
979 | $this->cart_contents[ $cart_item_key ] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array( |
||
980 | 'product_id' => $product_id, |
||
981 | 'variation_id' => $variation_id, |
||
982 | 'variation' => $variation, |
||
983 | 'quantity' => $quantity, |
||
984 | 'data' => $product_data |
||
985 | ) ), $cart_item_key ); |
||
986 | } |
||
987 | |||
988 | if ( did_action( 'wp' ) ) { |
||
989 | $this->set_cart_cookies( ! $this->is_empty() ); |
||
990 | } |
||
991 | |||
992 | do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data ); |
||
993 | |||
994 | return $cart_item_key; |
||
995 | |||
996 | } catch ( Exception $e ) { |
||
997 | if ( $e->getMessage() ) { |
||
998 | wc_add_notice( $e->getMessage(), 'error' ); |
||
999 | } |
||
1000 | return false; |
||
1001 | } |
||
1002 | } |
||
1003 | |||
1004 | /** |
||
1005 | * Remove a cart item. |
||
1006 | * |
||
1007 | * @since 2.3.0 |
||
1008 | * @param string $cart_item_key |
||
1009 | * @return bool |
||
1010 | */ |
||
1011 | public function remove_cart_item( $cart_item_key ) { |
||
1012 | if ( isset( $this->cart_contents[ $cart_item_key ] ) ) { |
||
1013 | $this->removed_cart_contents[ $cart_item_key ] = $this->cart_contents[ $cart_item_key ]; |
||
1014 | unset( $this->removed_cart_contents[ $cart_item_key ]['data'] ); |
||
1015 | |||
1016 | do_action( 'woocommerce_remove_cart_item', $cart_item_key, $this ); |
||
1017 | |||
1018 | unset( $this->cart_contents[ $cart_item_key ] ); |
||
1019 | |||
1020 | do_action( 'woocommerce_cart_item_removed', $cart_item_key, $this ); |
||
1021 | |||
1022 | $this->calculate_totals(); |
||
1023 | |||
1024 | return true; |
||
1025 | } |
||
1026 | |||
1027 | return false; |
||
1028 | } |
||
1029 | |||
1030 | /** |
||
1031 | * Restore a cart item. |
||
1032 | * |
||
1033 | * @param string $cart_item_key |
||
1034 | * @return bool |
||
1035 | */ |
||
1036 | public function restore_cart_item( $cart_item_key ) { |
||
1037 | if ( isset( $this->removed_cart_contents[ $cart_item_key ] ) ) { |
||
1038 | $this->cart_contents[ $cart_item_key ] = $this->removed_cart_contents[ $cart_item_key ]; |
||
1039 | $this->cart_contents[ $cart_item_key ]['data'] = wc_get_product( $this->cart_contents[ $cart_item_key ]['variation_id'] ? $this->cart_contents[ $cart_item_key ]['variation_id'] : $this->cart_contents[ $cart_item_key ]['product_id'] ); |
||
1040 | |||
1041 | do_action( 'woocommerce_restore_cart_item', $cart_item_key, $this ); |
||
1042 | |||
1043 | unset( $this->removed_cart_contents[ $cart_item_key ] ); |
||
1044 | |||
1045 | do_action( 'woocommerce_cart_item_restored', $cart_item_key, $this ); |
||
1046 | |||
1047 | $this->calculate_totals(); |
||
1048 | |||
1049 | return true; |
||
1050 | } |
||
1051 | |||
1052 | return false; |
||
1053 | } |
||
1054 | |||
1055 | /** |
||
1056 | * Set the quantity for an item in the cart. |
||
1057 | * |
||
1058 | * @param string $cart_item_key contains the id of the cart item |
||
1059 | * @param int $quantity contains the quantity of the item |
||
1060 | * @param bool $refresh_totals whether or not to calculate totals after setting the new qty |
||
1061 | * |
||
1062 | * @return bool |
||
1063 | */ |
||
1064 | public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) { |
||
1065 | if ( $quantity == 0 || $quantity < 0 ) { |
||
1066 | do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key ); |
||
1067 | unset( $this->cart_contents[ $cart_item_key ] ); |
||
1068 | } else { |
||
1069 | $old_quantity = $this->cart_contents[ $cart_item_key ]['quantity']; |
||
1070 | $this->cart_contents[ $cart_item_key ]['quantity'] = $quantity; |
||
1071 | do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity ); |
||
1072 | } |
||
1073 | |||
1074 | if ( $refresh_totals ) { |
||
1075 | $this->calculate_totals(); |
||
1076 | } |
||
1077 | |||
1078 | return true; |
||
1079 | } |
||
1080 | |||
1081 | /*-----------------------------------------------------------------------------------*/ |
||
1082 | /* Cart Calculation Functions */ |
||
1083 | /*-----------------------------------------------------------------------------------*/ |
||
1084 | |||
1085 | /** |
||
1086 | * Reset cart totals to the defaults. Useful before running calculations. |
||
1087 | * |
||
1088 | * @param bool $unset_session If true, the session data will be forced unset. |
||
1089 | * @access private |
||
1090 | */ |
||
1091 | private function reset( $unset_session = false ) { |
||
1092 | foreach ( $this->cart_session_data as $key => $default ) { |
||
1093 | $this->$key = $default; |
||
1094 | if ( $unset_session ) { |
||
1095 | unset( WC()->session->$key ); |
||
1096 | } |
||
1097 | } |
||
1098 | do_action( 'woocommerce_cart_reset', $this, $unset_session ); |
||
1099 | } |
||
1100 | |||
1101 | /** |
||
1102 | * Sort by subtotal. |
||
1103 | * @param array $a |
||
1104 | * @param array $b |
||
1105 | * @return int |
||
1106 | */ |
||
1107 | private function sort_by_subtotal( $a, $b ) { |
||
1108 | $first_item_subtotal = isset( $a['line_subtotal'] ) ? $a['line_subtotal'] : 0; |
||
1109 | $second_item_subtotal = isset( $b['line_subtotal'] ) ? $b['line_subtotal'] : 0; |
||
1110 | if ( $first_item_subtotal === $second_item_subtotal ) { |
||
1111 | return 0; |
||
1112 | } |
||
1113 | return ( $first_item_subtotal < $second_item_subtotal ) ? 1 : -1; |
||
1114 | } |
||
1115 | |||
1116 | /** |
||
1117 | * Calculate totals for the items in the cart. |
||
1118 | */ |
||
1119 | public function calculate_totals() { |
||
1120 | $this->reset(); |
||
1121 | $this->coupons = $this->get_coupons(); |
||
1122 | |||
1123 | do_action( 'woocommerce_before_calculate_totals', $this ); |
||
1124 | |||
1125 | if ( $this->is_empty() ) { |
||
1126 | $this->set_session(); |
||
1127 | return; |
||
1128 | } |
||
1129 | |||
1130 | $tax_rates = array(); |
||
1131 | $shop_tax_rates = array(); |
||
1132 | $cart = $this->get_cart(); |
||
1133 | |||
1134 | /** |
||
1135 | * Calculate subtotals for items. This is done first so that discount logic can use the values. |
||
1136 | */ |
||
1137 | foreach ( $cart as $cart_item_key => $values ) { |
||
1138 | $_product = $values['data']; |
||
1139 | $line_price = $_product->get_price() * $values['quantity']; |
||
1140 | $line_subtotal = 0; |
||
1141 | $line_subtotal_tax = 0; |
||
1142 | |||
1143 | /** |
||
1144 | * No tax to calculate. |
||
1145 | */ |
||
1146 | if ( ! $_product->is_taxable() ) { |
||
1147 | |||
1148 | // Subtotal is the undiscounted price |
||
1149 | $this->subtotal += $line_price; |
||
1150 | $this->subtotal_ex_tax += $line_price; |
||
1151 | |||
1152 | /** |
||
1153 | * Prices include tax. |
||
1154 | * |
||
1155 | * To prevent rounding issues we need to work with the inclusive price where possible. |
||
1156 | * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would. |
||
1157 | * be 8.325 leading to totals being 1p off. |
||
1158 | * |
||
1159 | * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated. |
||
1160 | * afterwards. |
||
1161 | * |
||
1162 | * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that. |
||
1163 | */ |
||
1164 | } elseif ( $this->prices_include_tax ) { |
||
1165 | |||
1166 | // Get base tax rates |
||
1167 | if ( empty( $shop_tax_rates[ $_product->tax_class ] ) ) { |
||
1168 | $shop_tax_rates[ $_product->tax_class ] = WC_Tax::get_base_tax_rates( $_product->tax_class ); |
||
1169 | } |
||
1170 | |||
1171 | // Get item tax rates |
||
1172 | if ( empty( $tax_rates[ $_product->get_tax_class() ] ) ) { |
||
1173 | $tax_rates[ $_product->get_tax_class() ] = WC_Tax::get_rates( $_product->get_tax_class() ); |
||
1174 | } |
||
1175 | |||
1176 | $base_tax_rates = $shop_tax_rates[ $_product->tax_class ]; |
||
1177 | $item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; |
||
1178 | |||
1179 | /** |
||
1180 | * ADJUST TAX - Calculations when base tax is not equal to the item tax. |
||
1181 | * |
||
1182 | * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. |
||
1183 | * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. |
||
1184 | * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. |
||
1185 | */ |
||
1186 | if ( $item_tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { |
||
1187 | |||
1188 | // Work out a new base price without the shop's base tax |
||
1189 | $taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true, true ); |
||
1190 | |||
1191 | // Now we have a new item price (excluding TAX) |
||
1192 | $line_subtotal = $line_price - array_sum( $taxes ); |
||
1193 | |||
1194 | // Now add modified taxes |
||
1195 | $tax_result = WC_Tax::calc_tax( $line_subtotal, $item_tax_rates ); |
||
1196 | $line_subtotal_tax = array_sum( $tax_result ); |
||
1197 | |||
1198 | /** |
||
1199 | * Regular tax calculation (customer inside base and the tax class is unmodified. |
||
1200 | */ |
||
1201 | } else { |
||
1202 | |||
1203 | // Calc tax normally |
||
1204 | $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates, true ); |
||
1205 | $line_subtotal_tax = array_sum( $taxes ); |
||
1206 | $line_subtotal = $line_price - array_sum( $taxes ); |
||
1207 | } |
||
1208 | |||
1209 | /** |
||
1210 | * Prices exclude tax. |
||
1211 | * |
||
1212 | * This calculation is simpler - work with the base, untaxed price. |
||
1213 | */ |
||
1214 | } else { |
||
1215 | |||
1216 | // Get item tax rates |
||
1217 | if ( empty( $tax_rates[ $_product->get_tax_class() ] ) ) { |
||
1218 | $tax_rates[ $_product->get_tax_class() ] = WC_Tax::get_rates( $_product->get_tax_class() ); |
||
1219 | } |
||
1220 | |||
1221 | $item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; |
||
1222 | |||
1223 | // Base tax for line before discount - we will store this in the order data |
||
1224 | $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates ); |
||
1225 | $line_subtotal_tax = array_sum( $taxes ); |
||
1226 | |||
1227 | $line_subtotal = $line_price; |
||
1228 | } |
||
1229 | |||
1230 | // Add to main subtotal |
||
1231 | $this->subtotal += $line_subtotal + $line_subtotal_tax; |
||
1232 | $this->subtotal_ex_tax += $line_subtotal; |
||
1233 | } |
||
1234 | |||
1235 | // Order cart items by price so coupon logic is 'fair' for customers and not based on order added to cart. |
||
1236 | uasort( $cart, array( $this, 'sort_by_subtotal' ) ); |
||
1237 | |||
1238 | /** |
||
1239 | * Calculate totals for items. |
||
1240 | */ |
||
1241 | foreach ( $cart as $cart_item_key => $values ) { |
||
1242 | |||
1243 | $_product = $values['data']; |
||
1244 | |||
1245 | // Prices |
||
1246 | $base_price = $_product->get_price(); |
||
1247 | $line_price = $_product->get_price() * $values['quantity']; |
||
1248 | |||
1249 | // Tax data |
||
1250 | $taxes = array(); |
||
1251 | $discounted_taxes = array(); |
||
1252 | |||
1253 | /** |
||
1254 | * No tax to calculate. |
||
1255 | */ |
||
1256 | if ( ! $_product->is_taxable() ) { |
||
1257 | |||
1258 | // Discounted Price (price with any pre-tax discounts applied) |
||
1259 | $discounted_price = $this->get_discounted_price( $values, $base_price, true ); |
||
1260 | $line_subtotal_tax = 0; |
||
1261 | $line_subtotal = $line_price; |
||
1262 | $line_tax = 0; |
||
1263 | $line_total = round( $discounted_price * $values['quantity'], WC_ROUNDING_PRECISION ); |
||
1264 | |||
1265 | /** |
||
1266 | * Prices include tax. |
||
1267 | */ |
||
1268 | } elseif ( $this->prices_include_tax ) { |
||
1269 | |||
1270 | $base_tax_rates = $shop_tax_rates[ $_product->tax_class ]; |
||
1271 | $item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; |
||
1272 | |||
1273 | /** |
||
1274 | * ADJUST TAX - Calculations when base tax is not equal to the item tax. |
||
1275 | * |
||
1276 | * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. |
||
1277 | * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. |
||
1278 | * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. |
||
1279 | */ |
||
1280 | if ( $item_tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { |
||
1281 | |||
1282 | // Work out a new base price without the shop's base tax |
||
1283 | $taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true, true ); |
||
1284 | |||
1285 | // Now we have a new item price (excluding TAX) |
||
1286 | $line_subtotal = round( $line_price - array_sum( $taxes ), WC_ROUNDING_PRECISION ); |
||
1287 | $taxes = WC_Tax::calc_tax( $line_subtotal, $item_tax_rates ); |
||
1288 | $line_subtotal_tax = array_sum( $taxes ); |
||
1289 | |||
1290 | // Adjusted price (this is the price including the new tax rate) |
||
1291 | $adjusted_price = ( $line_subtotal + $line_subtotal_tax ) / $values['quantity']; |
||
1292 | |||
1293 | // Apply discounts and get the discounted price FOR A SINGLE ITEM |
||
1294 | $discounted_price = $this->get_discounted_price( $values, $adjusted_price, true ); |
||
1295 | |||
1296 | // Convert back to line price and round nicely |
||
1297 | $discounted_line_price = round( $discounted_price * $values['quantity'], $this->dp ); |
||
1298 | |||
1299 | // Now use rounded line price to get taxes. |
||
1300 | $discounted_taxes = WC_Tax::calc_tax( $discounted_line_price, $item_tax_rates, true ); |
||
1301 | $line_tax = array_sum( $discounted_taxes ); |
||
1302 | $line_total = $discounted_line_price - $line_tax; |
||
1303 | |||
1304 | /** |
||
1305 | * Regular tax calculation (customer inside base and the tax class is unmodified. |
||
1306 | */ |
||
1307 | } else { |
||
1308 | |||
1309 | // Work out a new base price without the item tax |
||
1310 | $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates, true ); |
||
1311 | |||
1312 | // Now we have a new item price (excluding TAX) |
||
1313 | $line_subtotal = $line_price - array_sum( $taxes ); |
||
1314 | $line_subtotal_tax = array_sum( $taxes ); |
||
1315 | |||
1316 | // Calc prices and tax (discounted) |
||
1317 | $discounted_price = $this->get_discounted_price( $values, $base_price, true ); |
||
1318 | |||
1319 | // Convert back to line price and round nicely |
||
1320 | $discounted_line_price = round( $discounted_price * $values['quantity'], $this->dp ); |
||
1321 | |||
1322 | // Now use rounded line price to get taxes. |
||
1323 | $discounted_taxes = WC_Tax::calc_tax( $discounted_line_price, $item_tax_rates, true ); |
||
1324 | $line_tax = array_sum( $discounted_taxes ); |
||
1325 | $line_total = $discounted_line_price - $line_tax; |
||
1326 | } |
||
1327 | |||
1328 | // Tax rows - merge the totals we just got |
||
1329 | View Code Duplication | foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) { |
|
1330 | $this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); |
||
1331 | } |
||
1332 | |||
1333 | /** |
||
1334 | * Prices exclude tax. |
||
1335 | */ |
||
1336 | } else { |
||
1337 | |||
1338 | $item_tax_rates = $tax_rates[ $_product->get_tax_class() ]; |
||
1339 | |||
1340 | // Work out a new base price without the shop's base tax |
||
1341 | $taxes = WC_Tax::calc_tax( $line_price, $item_tax_rates ); |
||
1342 | |||
1343 | // Now we have the item price (excluding TAX) |
||
1344 | $line_subtotal = $line_price; |
||
1345 | $line_subtotal_tax = array_sum( $taxes ); |
||
1346 | |||
1347 | // Now calc product rates |
||
1348 | $discounted_price = $this->get_discounted_price( $values, $base_price, true ); |
||
1349 | $discounted_taxes = WC_Tax::calc_tax( $discounted_price * $values['quantity'], $item_tax_rates ); |
||
1350 | $discounted_tax_amount = array_sum( $discounted_taxes ); |
||
1351 | $line_tax = $discounted_tax_amount; |
||
1352 | $line_total = $discounted_price * $values['quantity']; |
||
1353 | |||
1354 | // Tax rows - merge the totals we just got |
||
1355 | View Code Duplication | foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) { |
|
1356 | $this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); |
||
1357 | } |
||
1358 | } |
||
1359 | |||
1360 | // Cart contents total is based on discounted prices and is used for the final total calculation |
||
1361 | $this->cart_contents_total += $line_total; |
||
1362 | |||
1363 | // Store costs + taxes for lines |
||
1364 | $this->cart_contents[ $cart_item_key ]['line_total'] = $line_total; |
||
1365 | $this->cart_contents[ $cart_item_key ]['line_tax'] = $line_tax; |
||
1366 | $this->cart_contents[ $cart_item_key ]['line_subtotal'] = $line_subtotal; |
||
1367 | $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $line_subtotal_tax; |
||
1368 | |||
1369 | // Store rates ID and costs - Since 2.2 |
||
1370 | $this->cart_contents[ $cart_item_key ]['line_tax_data'] = array( 'total' => $discounted_taxes, 'subtotal' => $taxes ); |
||
1371 | } |
||
1372 | |||
1373 | // Only calculate the grand total + shipping if on the cart/checkout |
||
1374 | if ( is_checkout() || is_cart() || defined('WOOCOMMERCE_CHECKOUT') || defined('WOOCOMMERCE_CART') ) { |
||
1375 | |||
1376 | // Calculate the Shipping |
||
1377 | $this->calculate_shipping(); |
||
1378 | |||
1379 | // Trigger the fees API where developers can add fees to the cart |
||
1380 | $this->calculate_fees(); |
||
1381 | |||
1382 | // Total up/round taxes and shipping taxes |
||
1383 | if ( $this->round_at_subtotal ) { |
||
1384 | $this->tax_total = WC_Tax::get_tax_total( $this->taxes ); |
||
0 ignored issues
–
show
It seems like
\WC_Tax::get_tax_total($this->taxes) can also be of type integer . However, the property $tax_total is declared as type double . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
1385 | $this->shipping_tax_total = WC_Tax::get_tax_total( $this->shipping_taxes ); |
||
0 ignored issues
–
show
It seems like
\WC_Tax::get_tax_total($this->shipping_taxes) can also be of type integer . However, the property $shipping_tax_total is declared as type double . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
1386 | $this->taxes = array_map( array( 'WC_Tax', 'round' ), $this->taxes ); |
||
1387 | $this->shipping_taxes = array_map( array( 'WC_Tax', 'round' ), $this->shipping_taxes ); |
||
1388 | } else { |
||
1389 | $this->tax_total = array_sum( $this->taxes ); |
||
0 ignored issues
–
show
It seems like
array_sum($this->taxes) can also be of type integer . However, the property $tax_total is declared as type double . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
1390 | $this->shipping_tax_total = array_sum( $this->shipping_taxes ); |
||
0 ignored issues
–
show
It seems like
array_sum($this->shipping_taxes) can also be of type integer . However, the property $shipping_tax_total is declared as type double . Maybe add an additional type check?
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly. For example, imagine you have a variable Either this assignment is in error or a type check should be added for that assignment. class Id
{
public $id;
public function __construct($id)
{
$this->id = $id;
}
}
class Account
{
/** @var Id $id */
public $id;
}
$account_id = false;
if (starsAreRight()) {
$account_id = new Id(42);
}
$account = new Account();
if ($account instanceof Id)
{
$account->id = $account_id;
}
![]() |
|||
1391 | } |
||
1392 | |||
1393 | // VAT exemption done at this point - so all totals are correct before exemption |
||
1394 | if ( WC()->customer->is_vat_exempt() ) { |
||
1395 | $this->remove_taxes(); |
||
1396 | } |
||
1397 | |||
1398 | // Allow plugins to hook and alter totals before final total is calculated |
||
1399 | do_action( 'woocommerce_calculate_totals', $this ); |
||
1400 | |||
1401 | // Grand Total - Discounted product prices, discounted tax, shipping cost + tax |
||
1402 | $this->total = max( 0, apply_filters( 'woocommerce_calculated_total', round( $this->cart_contents_total + $this->tax_total + $this->shipping_tax_total + $this->shipping_total + $this->fee_total, $this->dp ), $this ) ); |
||
1403 | |||
1404 | } else { |
||
1405 | |||
1406 | // Set tax total to sum of all tax rows |
||
1407 | $this->tax_total = WC_Tax::get_tax_total( $this->taxes ); |
||
1408 | |||
1409 | // VAT exemption done at this point - so all totals are correct before exemption |
||
1410 | if ( WC()->customer->is_vat_exempt() ) { |
||
1411 | $this->remove_taxes(); |
||
1412 | } |
||
1413 | } |
||
1414 | |||
1415 | do_action( 'woocommerce_after_calculate_totals', $this ); |
||
1416 | |||
1417 | $this->set_session(); |
||
1418 | } |
||
1419 | |||
1420 | /** |
||
1421 | * Remove taxes. |
||
1422 | */ |
||
1423 | public function remove_taxes() { |
||
1424 | $this->shipping_tax_total = $this->tax_total = 0; |
||
0 ignored issues
–
show
The property
$tax_total was declared of type double , but 0 is of type integer . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() The property
$shipping_tax_total was declared of type double , but $this->tax_total = 0 is of type integer . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() |
|||
1425 | $this->subtotal = $this->subtotal_ex_tax; |
||
1426 | |||
1427 | foreach ( $this->cart_contents as $cart_item_key => $item ) { |
||
1428 | $this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $this->cart_contents[ $cart_item_key ]['line_tax'] = 0; |
||
1429 | $this->cart_contents[ $cart_item_key ]['line_tax_data'] = array( 'total' => array(), 'subtotal' => array() ); |
||
1430 | } |
||
1431 | |||
1432 | // If true, zero rate is applied so '0' tax is displayed on the frontend rather than nothing. |
||
1433 | if ( apply_filters( 'woocommerce_cart_remove_taxes_apply_zero_rate', true ) ) { |
||
1434 | $this->taxes = $this->shipping_taxes = array( apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) => 0 ); |
||
1435 | } else { |
||
1436 | $this->taxes = $this->shipping_taxes = array(); |
||
1437 | } |
||
1438 | } |
||
1439 | |||
1440 | /** |
||
1441 | * Looks at the totals to see if payment is actually required. |
||
1442 | * |
||
1443 | * @return bool |
||
1444 | */ |
||
1445 | public function needs_payment() { |
||
1446 | return apply_filters( 'woocommerce_cart_needs_payment', $this->total > 0, $this ); |
||
1447 | } |
||
1448 | |||
1449 | /*-----------------------------------------------------------------------------------*/ |
||
1450 | /* Shipping related functions */ |
||
1451 | /*-----------------------------------------------------------------------------------*/ |
||
1452 | |||
1453 | /** |
||
1454 | * Uses the shipping class to calculate shipping then gets the totals when its finished. |
||
1455 | */ |
||
1456 | public function calculate_shipping() { |
||
1457 | if ( $this->needs_shipping() && $this->show_shipping() ) { |
||
1458 | WC()->shipping->calculate_shipping( $this->get_shipping_packages() ); |
||
1459 | } else { |
||
1460 | WC()->shipping->reset_shipping(); |
||
1461 | } |
||
1462 | |||
1463 | // Get totals for the chosen shipping method |
||
1464 | $this->shipping_total = WC()->shipping->shipping_total; // Shipping Total |
||
1465 | $this->shipping_taxes = WC()->shipping->shipping_taxes; // Shipping Taxes |
||
1466 | } |
||
1467 | |||
1468 | /** |
||
1469 | * Get packages to calculate shipping for. |
||
1470 | * |
||
1471 | * This lets us calculate costs for carts that are shipped to multiple locations. |
||
1472 | * |
||
1473 | * Shipping methods are responsible for looping through these packages. |
||
1474 | * |
||
1475 | * By default we pass the cart itself as a package - plugins can change this. |
||
1476 | * through the filter and break it up. |
||
1477 | * |
||
1478 | * @since 1.5.4 |
||
1479 | * @return array of cart items |
||
1480 | */ |
||
1481 | public function get_shipping_packages() { |
||
1482 | // Packages array for storing 'carts' |
||
1483 | $packages = array(); |
||
1484 | |||
1485 | $packages[0]['contents'] = $this->get_cart(); // Items in the package |
||
1486 | $packages[0]['contents_cost'] = 0; // Cost of items in the package, set below |
||
1487 | $packages[0]['applied_coupons'] = $this->applied_coupons; |
||
1488 | $packages[0]['user']['ID'] = get_current_user_id(); |
||
1489 | $packages[0]['destination']['country'] = WC()->customer->get_shipping_country(); |
||
1490 | $packages[0]['destination']['state'] = WC()->customer->get_shipping_state(); |
||
1491 | $packages[0]['destination']['postcode'] = WC()->customer->get_shipping_postcode(); |
||
1492 | $packages[0]['destination']['city'] = WC()->customer->get_shipping_city(); |
||
1493 | $packages[0]['destination']['address'] = WC()->customer->get_shipping_address(); |
||
1494 | $packages[0]['destination']['address_2'] = WC()->customer->get_shipping_address_2(); |
||
1495 | |||
1496 | foreach ( $this->get_cart() as $item ) { |
||
1497 | if ( $item['data']->needs_shipping() ) { |
||
1498 | if ( isset( $item['line_total'] ) ) { |
||
1499 | $packages[0]['contents_cost'] += $item['line_total']; |
||
1500 | } |
||
1501 | } |
||
1502 | } |
||
1503 | |||
1504 | return apply_filters( 'woocommerce_cart_shipping_packages', $packages ); |
||
1505 | } |
||
1506 | |||
1507 | /** |
||
1508 | * Looks through the cart to see if shipping is actually required. |
||
1509 | * |
||
1510 | * @return bool whether or not the cart needs shipping |
||
1511 | */ |
||
1512 | public function needs_shipping() { |
||
1513 | // If shipping is disabled or not yet configured, we can skip this. |
||
1514 | if ( ! wc_shipping_enabled() || 0 === wc_get_shipping_method_count( true ) ) { |
||
1515 | return false; |
||
1516 | } |
||
1517 | |||
1518 | $needs_shipping = false; |
||
1519 | |||
1520 | if ( $this->cart_contents ) { |
||
1521 | foreach ( $this->cart_contents as $cart_item_key => $values ) { |
||
1522 | $_product = $values['data']; |
||
1523 | if ( $_product->needs_shipping() ) { |
||
1524 | $needs_shipping = true; |
||
1525 | } |
||
1526 | } |
||
1527 | } |
||
1528 | |||
1529 | return apply_filters( 'woocommerce_cart_needs_shipping', $needs_shipping ); |
||
1530 | } |
||
1531 | |||
1532 | /** |
||
1533 | * Should the shipping address form be shown. |
||
1534 | * |
||
1535 | * @return bool |
||
1536 | */ |
||
1537 | public function needs_shipping_address() { |
||
1538 | |||
1539 | $needs_shipping_address = false; |
||
1540 | |||
1541 | if ( $this->needs_shipping() === true && ! wc_ship_to_billing_address_only() ) { |
||
1542 | $needs_shipping_address = true; |
||
1543 | } |
||
1544 | |||
1545 | return apply_filters( 'woocommerce_cart_needs_shipping_address', $needs_shipping_address ); |
||
1546 | } |
||
1547 | |||
1548 | /** |
||
1549 | * Sees if the customer has entered enough data to calc the shipping yet. |
||
1550 | * |
||
1551 | * @return bool |
||
1552 | */ |
||
1553 | public function show_shipping() { |
||
1554 | if ( ! wc_shipping_enabled() || ! is_array( $this->cart_contents ) ) |
||
1555 | return false; |
||
1556 | |||
1557 | if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) { |
||
1558 | if ( ! WC()->customer->has_calculated_shipping() ) { |
||
1559 | if ( ! WC()->customer->get_shipping_country() || ( ! WC()->customer->get_shipping_state() && ! WC()->customer->get_shipping_postcode() ) ) { |
||
1560 | return false; |
||
1561 | } |
||
1562 | } |
||
1563 | } |
||
1564 | |||
1565 | return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', true ); |
||
1566 | } |
||
1567 | |||
1568 | /** |
||
1569 | * Sees if we need a shipping address. |
||
1570 | * |
||
1571 | * @deprecated 2.5.0 in favor to wc_ship_to_billing_address_only() |
||
1572 | * |
||
1573 | * @return bool |
||
1574 | */ |
||
1575 | public function ship_to_billing_address_only() { |
||
1576 | return wc_ship_to_billing_address_only(); |
||
1577 | } |
||
1578 | |||
1579 | /** |
||
1580 | * Gets the shipping total (after calculation). |
||
1581 | * |
||
1582 | * @return string price or string for the shipping total |
||
1583 | */ |
||
1584 | public function get_cart_shipping_total() { |
||
1585 | if ( isset( $this->shipping_total ) ) { |
||
1586 | if ( $this->shipping_total > 0 ) { |
||
1587 | |||
1588 | // Display varies depending on settings |
||
1589 | if ( $this->tax_display_cart == 'excl' ) { |
||
1590 | |||
1591 | $return = wc_price( $this->shipping_total ); |
||
1592 | |||
1593 | if ( $this->shipping_tax_total > 0 && $this->prices_include_tax ) { |
||
1594 | $return .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; |
||
1595 | } |
||
1596 | |||
1597 | return $return; |
||
1598 | |||
1599 | } else { |
||
1600 | |||
1601 | $return = wc_price( $this->shipping_total + $this->shipping_tax_total ); |
||
1602 | |||
1603 | if ( $this->shipping_tax_total > 0 && ! $this->prices_include_tax ) { |
||
1604 | $return .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>'; |
||
1605 | } |
||
1606 | |||
1607 | return $return; |
||
1608 | |||
1609 | } |
||
1610 | |||
1611 | } else { |
||
1612 | return __( 'Free!', 'woocommerce' ); |
||
1613 | } |
||
1614 | } |
||
1615 | |||
1616 | return ''; |
||
1617 | } |
||
1618 | |||
1619 | /*-----------------------------------------------------------------------------------*/ |
||
1620 | /* Coupons/Discount related functions */ |
||
1621 | /*-----------------------------------------------------------------------------------*/ |
||
1622 | |||
1623 | /** |
||
1624 | * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error. |
||
1625 | * |
||
1626 | * Checks two types of coupons: |
||
1627 | * 1. Where a list of customer emails are set (limits coupon usage to those defined). |
||
1628 | * 2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email). |
||
1629 | * |
||
1630 | * @param array $posted |
||
1631 | */ |
||
1632 | public function check_customer_coupons( $posted ) { |
||
1633 | if ( ! empty( $this->applied_coupons ) ) { |
||
1634 | foreach ( $this->applied_coupons as $code ) { |
||
1635 | $coupon = new WC_Coupon( $code ); |
||
1636 | |||
1637 | if ( $coupon->is_valid() ) { |
||
1638 | |||
1639 | // Limit to defined email addresses |
||
1640 | if ( is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) { |
||
1641 | $check_emails = array(); |
||
1642 | $coupon->customer_email = array_map( 'sanitize_email', $coupon->customer_email ); |
||
1643 | |||
1644 | if ( is_user_logged_in() ) { |
||
1645 | $current_user = wp_get_current_user(); |
||
1646 | $check_emails[] = $current_user->user_email; |
||
1647 | } |
||
1648 | $check_emails[] = $posted['billing_email']; |
||
1649 | $check_emails = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) ); |
||
1650 | |||
1651 | if ( 0 == sizeof( array_intersect( $check_emails, $coupon->customer_email ) ) ) { |
||
1652 | $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED ); |
||
1653 | |||
1654 | // Remove the coupon |
||
1655 | $this->remove_coupon( $code ); |
||
1656 | |||
1657 | // Flag totals for refresh |
||
1658 | WC()->session->set( 'refresh_totals', true ); |
||
1659 | } |
||
1660 | } |
||
1661 | |||
1662 | // Usage limits per user - check against billing and user email and user ID |
||
1663 | if ( $coupon->usage_limit_per_user > 0 ) { |
||
1664 | $check_emails = array(); |
||
1665 | $used_by = $coupon->get_used_by(); |
||
1666 | |||
1667 | if ( is_user_logged_in() ) { |
||
1668 | $current_user = wp_get_current_user(); |
||
1669 | $check_emails[] = sanitize_email( $current_user->user_email ); |
||
1670 | $usage_count = sizeof( array_keys( $used_by, get_current_user_id() ) ); |
||
1671 | } else { |
||
1672 | $check_emails[] = sanitize_email( $posted['billing_email'] ); |
||
1673 | $user = get_user_by( 'email', $posted['billing_email'] ); |
||
1674 | if ( $user ) { |
||
1675 | $usage_count = sizeof( array_keys( $used_by, $user->ID ) ); |
||
1676 | } else { |
||
1677 | $usage_count = 0; |
||
1678 | } |
||
1679 | } |
||
1680 | |||
1681 | foreach ( $check_emails as $check_email ) { |
||
1682 | $usage_count = $usage_count + sizeof( array_keys( $used_by, $check_email ) ); |
||
1683 | } |
||
1684 | |||
1685 | View Code Duplication | if ( $usage_count >= $coupon->usage_limit_per_user ) { |
|
1686 | $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED ); |
||
1687 | |||
1688 | // Remove the coupon |
||
1689 | $this->remove_coupon( $code ); |
||
1690 | |||
1691 | // Flag totals for refresh |
||
1692 | WC()->session->set( 'refresh_totals', true ); |
||
1693 | } |
||
1694 | } |
||
1695 | } |
||
1696 | } |
||
1697 | } |
||
1698 | } |
||
1699 | |||
1700 | /** |
||
1701 | * Returns whether or not a discount has been applied. |
||
1702 | * @param string $coupon_code |
||
1703 | * @return bool |
||
1704 | */ |
||
1705 | public function has_discount( $coupon_code = '' ) { |
||
1706 | return $coupon_code ? in_array( apply_filters( 'woocommerce_coupon_code', $coupon_code ), $this->applied_coupons ) : sizeof( $this->applied_coupons ) > 0; |
||
1707 | } |
||
1708 | |||
1709 | /** |
||
1710 | * Applies a coupon code passed to the method. |
||
1711 | * |
||
1712 | * @param string $coupon_code - The code to apply |
||
1713 | * @return bool True if the coupon is applied, false if it does not exist or cannot be applied |
||
1714 | */ |
||
1715 | public function add_discount( $coupon_code ) { |
||
1716 | // Coupons are globally disabled |
||
1717 | if ( ! wc_coupons_enabled() ) { |
||
1718 | return false; |
||
1719 | } |
||
1720 | |||
1721 | // Sanitize coupon code |
||
1722 | $coupon_code = apply_filters( 'woocommerce_coupon_code', $coupon_code ); |
||
1723 | |||
1724 | // Get the coupon |
||
1725 | $the_coupon = new WC_Coupon( $coupon_code ); |
||
1726 | |||
1727 | // Check it can be used with cart |
||
1728 | if ( ! $the_coupon->is_valid() ) { |
||
1729 | wc_add_notice( $the_coupon->get_error_message(), 'error' ); |
||
1730 | return false; |
||
1731 | } |
||
1732 | |||
1733 | // Check if applied |
||
1734 | if ( $this->has_discount( $coupon_code ) ) { |
||
1735 | $the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED ); |
||
1736 | return false; |
||
1737 | } |
||
1738 | |||
1739 | // If its individual use then remove other coupons |
||
1740 | if ( $the_coupon->individual_use == 'yes' ) { |
||
1741 | $this->applied_coupons = apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $the_coupon, $this->applied_coupons ); |
||
1742 | } |
||
1743 | |||
1744 | if ( $this->applied_coupons ) { |
||
1745 | foreach ( $this->applied_coupons as $code ) { |
||
1746 | $coupon = new WC_Coupon( $code ); |
||
1747 | |||
1748 | if ( $coupon->individual_use == 'yes' && false === apply_filters( 'woocommerce_apply_with_individual_use_coupon', false, $the_coupon, $coupon, $this->applied_coupons ) ) { |
||
1749 | |||
1750 | // Reject new coupon |
||
1751 | $coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED_INDIV_USE_ONLY ); |
||
1752 | |||
1753 | return false; |
||
1754 | } |
||
1755 | } |
||
1756 | } |
||
1757 | |||
1758 | $this->applied_coupons[] = $coupon_code; |
||
1759 | |||
1760 | // Choose free shipping |
||
1761 | if ( $the_coupon->enable_free_shipping() ) { |
||
1762 | $packages = WC()->shipping->get_packages(); |
||
1763 | $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' ); |
||
1764 | |||
1765 | foreach ( $packages as $i => $package ) { |
||
1766 | $chosen_shipping_methods[ $i ] = 'free_shipping'; |
||
1767 | } |
||
1768 | |||
1769 | WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods ); |
||
1770 | } |
||
1771 | |||
1772 | $the_coupon->add_coupon_message( WC_Coupon::WC_COUPON_SUCCESS ); |
||
1773 | |||
1774 | do_action( 'woocommerce_applied_coupon', $coupon_code ); |
||
1775 | |||
1776 | return true; |
||
1777 | } |
||
1778 | |||
1779 | /** |
||
1780 | * Get array of applied coupon objects and codes. |
||
1781 | * @return array of applied coupons |
||
1782 | */ |
||
1783 | public function get_coupons( $deprecated = null ) { |
||
1784 | $coupons = array(); |
||
1785 | |||
1786 | if ( 'order' === $deprecated ) { |
||
1787 | return $coupons; |
||
1788 | } |
||
1789 | |||
1790 | foreach ( $this->get_applied_coupons() as $code ) { |
||
1791 | $coupon = new WC_Coupon( $code ); |
||
1792 | $coupons[ $code ] = $coupon; |
||
1793 | } |
||
1794 | |||
1795 | return $coupons; |
||
1796 | } |
||
1797 | |||
1798 | /** |
||
1799 | * Gets the array of applied coupon codes. |
||
1800 | * |
||
1801 | * @return array of applied coupons |
||
1802 | */ |
||
1803 | public function get_applied_coupons() { |
||
1804 | return $this->applied_coupons; |
||
1805 | } |
||
1806 | |||
1807 | /** |
||
1808 | * Get the discount amount for a used coupon. |
||
1809 | * @param string $code coupon code |
||
1810 | * @param bool $ex_tax inc or ex tax |
||
1811 | * @return float discount amount |
||
1812 | */ |
||
1813 | public function get_coupon_discount_amount( $code, $ex_tax = true ) { |
||
1814 | $discount_amount = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] : 0; |
||
1815 | |||
1816 | if ( ! $ex_tax ) { |
||
1817 | $discount_amount += $this->get_coupon_discount_tax_amount( $code ); |
||
1818 | } |
||
1819 | |||
1820 | return wc_cart_round_discount( $discount_amount, $this->dp ); |
||
1821 | } |
||
1822 | |||
1823 | /** |
||
1824 | * Get the discount tax amount for a used coupon (for tax inclusive prices). |
||
1825 | * @param string $code coupon code |
||
1826 | * @param bool inc or ex tax |
||
1827 | * @return float discount amount |
||
1828 | */ |
||
1829 | public function get_coupon_discount_tax_amount( $code ) { |
||
1830 | return wc_cart_round_discount( isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] : 0, $this->dp ); |
||
1831 | } |
||
1832 | |||
1833 | /** |
||
1834 | * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax. |
||
1835 | */ |
||
1836 | public function remove_coupons( $deprecated = null ) { |
||
1837 | $this->applied_coupons = $this->coupon_discount_amounts = $this->coupon_discount_tax_amounts = $this->coupon_applied_count = array(); |
||
1838 | WC()->session->set( 'applied_coupons', array() ); |
||
1839 | WC()->session->set( 'coupon_discount_amounts', array() ); |
||
1840 | WC()->session->set( 'coupon_discount_tax_amounts', array() ); |
||
1841 | } |
||
1842 | |||
1843 | /** |
||
1844 | * Remove a single coupon by code. |
||
1845 | * @param string $coupon_code Code of the coupon to remove |
||
1846 | * @return bool |
||
1847 | */ |
||
1848 | public function remove_coupon( $coupon_code ) { |
||
1849 | // Coupons are globally disabled |
||
1850 | if ( ! wc_coupons_enabled() ) { |
||
1851 | return false; |
||
1852 | } |
||
1853 | |||
1854 | // Get the coupon |
||
1855 | $coupon_code = apply_filters( 'woocommerce_coupon_code', $coupon_code ); |
||
1856 | $position = array_search( $coupon_code, $this->applied_coupons ); |
||
1857 | |||
1858 | if ( $position !== false ) { |
||
1859 | unset( $this->applied_coupons[ $position ] ); |
||
1860 | } |
||
1861 | |||
1862 | WC()->session->set( 'applied_coupons', $this->applied_coupons ); |
||
1863 | |||
1864 | do_action( 'woocommerce_removed_coupon', $coupon_code ); |
||
1865 | |||
1866 | return true; |
||
1867 | } |
||
1868 | |||
1869 | /** |
||
1870 | * Function to apply discounts to a product and get the discounted price (before tax is applied). |
||
1871 | * |
||
1872 | * @param mixed $values |
||
1873 | * @param mixed $price |
||
1874 | * @param bool $add_totals (default: false) |
||
1875 | * @return float price |
||
1876 | */ |
||
1877 | public function get_discounted_price( $values, $price, $add_totals = false ) { |
||
1878 | if ( ! $price ) { |
||
1879 | return $price; |
||
1880 | } |
||
1881 | |||
1882 | $undiscounted_price = $price; |
||
1883 | |||
1884 | if ( ! empty( $this->coupons ) ) { |
||
1885 | $product = $values['data']; |
||
1886 | |||
1887 | foreach ( $this->coupons as $code => $coupon ) { |
||
1888 | if ( $coupon->is_valid() && ( $coupon->is_valid_for_product( $product, $values ) || $coupon->is_valid_for_cart() ) ) { |
||
1889 | $discount_amount = $coupon->get_discount_amount( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ? $price : $undiscounted_price, $values, true ); |
||
1890 | $discount_amount = min( $price, $discount_amount ); |
||
1891 | $price = max( $price - $discount_amount, 0 ); |
||
1892 | |||
1893 | // Store the totals for DISPLAY in the cart |
||
1894 | if ( $add_totals ) { |
||
1895 | $total_discount = $discount_amount * $values['quantity']; |
||
1896 | $total_discount_tax = 0; |
||
1897 | |||
1898 | if ( wc_tax_enabled() ) { |
||
1899 | $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); |
||
1900 | $taxes = WC_Tax::calc_tax( $discount_amount, $tax_rates, $this->prices_include_tax ); |
||
1901 | $total_discount_tax = WC_Tax::get_tax_total( $taxes ) * $values['quantity']; |
||
1902 | $total_discount = $this->prices_include_tax ? $total_discount - $total_discount_tax : $total_discount; |
||
1903 | $this->discount_cart_tax += $total_discount_tax; |
||
1904 | } |
||
1905 | |||
1906 | $this->discount_cart += $total_discount; |
||
1907 | $this->increase_coupon_discount_amount( $code, $total_discount, $total_discount_tax ); |
||
1908 | $this->increase_coupon_applied_count( $code, $values['quantity'] ); |
||
1909 | } |
||
1910 | } |
||
1911 | |||
1912 | // If the price is 0, we can stop going through coupons because there is nothing more to discount for this product. |
||
1913 | if ( 0 >= $price ) { |
||
1914 | break; |
||
1915 | } |
||
1916 | } |
||
1917 | } |
||
1918 | |||
1919 | return apply_filters( 'woocommerce_get_discounted_price', $price, $values, $this ); |
||
1920 | } |
||
1921 | |||
1922 | /** |
||
1923 | * Store how much discount each coupon grants. |
||
1924 | * |
||
1925 | * @access private |
||
1926 | * @param string $code |
||
1927 | * @param double $amount |
||
1928 | * @param double $tax |
||
1929 | */ |
||
1930 | private function increase_coupon_discount_amount( $code, $amount, $tax ) { |
||
1931 | $this->coupon_discount_amounts[ $code ] = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] + $amount : $amount; |
||
1932 | $this->coupon_discount_tax_amounts[ $code ] = isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] + $tax : $tax; |
||
1933 | } |
||
1934 | |||
1935 | /** |
||
1936 | * Store how many times each coupon is applied to cart/items. |
||
1937 | * |
||
1938 | * @access private |
||
1939 | * @param string $code |
||
1940 | * @param int $count |
||
1941 | */ |
||
1942 | private function increase_coupon_applied_count( $code, $count = 1 ) { |
||
1943 | if ( empty( $this->coupon_applied_count[ $code ] ) ) { |
||
1944 | $this->coupon_applied_count[ $code ] = 0; |
||
1945 | } |
||
1946 | $this->coupon_applied_count[ $code ] += $count; |
||
1947 | } |
||
1948 | |||
1949 | /*-----------------------------------------------------------------------------------*/ |
||
1950 | /* Fees API to add additional costs to orders */ |
||
1951 | /*-----------------------------------------------------------------------------------*/ |
||
1952 | |||
1953 | /** |
||
1954 | * Add additional fee to the cart. |
||
1955 | * |
||
1956 | * @param string $name Unique name for the fee. Multiple fees of the same name cannot be added. |
||
1957 | * @param float $amount Fee amount. |
||
1958 | * @param bool $taxable (default: false) Is the fee taxable? |
||
1959 | * @param string $tax_class (default: '') The tax class for the fee if taxable. A blank string is standard tax class. |
||
1960 | */ |
||
1961 | public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) { |
||
1962 | |||
1963 | $new_fee_id = sanitize_title( $name ); |
||
1964 | |||
1965 | // Only add each fee once |
||
1966 | foreach ( $this->fees as $fee ) { |
||
1967 | if ( $fee->id == $new_fee_id ) { |
||
1968 | return; |
||
1969 | } |
||
1970 | } |
||
1971 | |||
1972 | $new_fee = new stdClass(); |
||
1973 | $new_fee->id = $new_fee_id; |
||
1974 | $new_fee->name = esc_attr( $name ); |
||
1975 | $new_fee->amount = (float) esc_attr( $amount ); |
||
1976 | $new_fee->tax_class = $tax_class; |
||
1977 | $new_fee->taxable = $taxable ? true : false; |
||
1978 | $new_fee->tax = 0; |
||
1979 | $new_fee->tax_data = array(); |
||
1980 | $this->fees[] = $new_fee; |
||
1981 | } |
||
1982 | |||
1983 | /** |
||
1984 | * Get fees. |
||
1985 | * |
||
1986 | * @return array |
||
1987 | */ |
||
1988 | public function get_fees() { |
||
1989 | return array_filter( (array) $this->fees ); |
||
1990 | } |
||
1991 | |||
1992 | /** |
||
1993 | * Calculate fees. |
||
1994 | */ |
||
1995 | public function calculate_fees() { |
||
1996 | // Reset fees before calculation |
||
1997 | $this->fee_total = 0; |
||
0 ignored issues
–
show
The property
$fee_total was declared of type double , but 0 is of type integer . Maybe add a type cast?
This check looks for assignments to scalar types that may be of the wrong type. To ensure the code behaves as expected, it may be a good idea to add an explicit type cast. $answer = 42;
$correct = false;
$correct = (bool) $answer;
![]() |
|||
1998 | $this->fees = array(); |
||
1999 | |||
2000 | // Fire an action where developers can add their fees |
||
2001 | do_action( 'woocommerce_cart_calculate_fees', $this ); |
||
2002 | |||
2003 | // If fees were added, total them and calculate tax |
||
2004 | if ( ! empty( $this->fees ) ) { |
||
2005 | foreach ( $this->fees as $fee_key => $fee ) { |
||
2006 | $this->fee_total += $fee->amount; |
||
2007 | |||
2008 | if ( $fee->taxable ) { |
||
2009 | // Get tax rates |
||
2010 | $tax_rates = WC_Tax::get_rates( $fee->tax_class ); |
||
2011 | $fee_taxes = WC_Tax::calc_tax( $fee->amount, $tax_rates, false ); |
||
2012 | |||
2013 | if ( ! empty( $fee_taxes ) ) { |
||
2014 | // Set the tax total for this fee |
||
2015 | $this->fees[ $fee_key ]->tax = array_sum( $fee_taxes ); |
||
2016 | |||
2017 | // Set tax data - Since 2.2 |
||
2018 | $this->fees[ $fee_key ]->tax_data = $fee_taxes; |
||
2019 | |||
2020 | // Tax rows - merge the totals we just got |
||
2021 | View Code Duplication | foreach ( array_keys( $this->taxes + $fee_taxes ) as $key ) { |
|
2022 | $this->taxes[ $key ] = ( isset( $fee_taxes[ $key ] ) ? $fee_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 ); |
||
2023 | } |
||
2024 | } |
||
2025 | } |
||
2026 | } |
||
2027 | } |
||
2028 | } |
||
2029 | |||
2030 | /*-----------------------------------------------------------------------------------*/ |
||
2031 | /* Get Formatted Totals */ |
||
2032 | /*-----------------------------------------------------------------------------------*/ |
||
2033 | |||
2034 | /** |
||
2035 | * Gets the order total (after calculation). |
||
2036 | * |
||
2037 | * @return string formatted price |
||
2038 | */ |
||
2039 | public function get_total() { |
||
2040 | return apply_filters( 'woocommerce_cart_total', wc_price( $this->total ) ); |
||
2041 | } |
||
2042 | |||
2043 | /** |
||
2044 | * Gets the total excluding taxes. |
||
2045 | * |
||
2046 | * @return string formatted price |
||
2047 | */ |
||
2048 | public function get_total_ex_tax() { |
||
2049 | $total = $this->total - $this->tax_total - $this->shipping_tax_total; |
||
2050 | if ( $total < 0 ) { |
||
2051 | $total = 0; |
||
2052 | } |
||
2053 | return apply_filters( 'woocommerce_cart_total_ex_tax', wc_price( $total ) ); |
||
2054 | } |
||
2055 | |||
2056 | /** |
||
2057 | * Gets the cart contents total (after calculation). |
||
2058 | * |
||
2059 | * @return string formatted price |
||
2060 | */ |
||
2061 | public function get_cart_total() { |
||
2062 | if ( ! $this->prices_include_tax ) { |
||
2063 | $cart_contents_total = wc_price( $this->cart_contents_total ); |
||
2064 | } else { |
||
2065 | $cart_contents_total = wc_price( $this->cart_contents_total + $this->tax_total ); |
||
2066 | } |
||
2067 | |||
2068 | return apply_filters( 'woocommerce_cart_contents_total', $cart_contents_total ); |
||
2069 | } |
||
2070 | |||
2071 | /** |
||
2072 | * Gets the sub total (after calculation). |
||
2073 | * |
||
2074 | * @param bool $compound whether to include compound taxes |
||
2075 | * @return string formatted price |
||
2076 | */ |
||
2077 | public function get_cart_subtotal( $compound = false ) { |
||
2078 | |||
2079 | // If the cart has compound tax, we want to show the subtotal as |
||
2080 | // cart + shipping + non-compound taxes (after discount) |
||
2081 | if ( $compound ) { |
||
2082 | |||
2083 | $cart_subtotal = wc_price( $this->cart_contents_total + $this->shipping_total + $this->get_taxes_total( false, false ) ); |
||
2084 | |||
2085 | // Otherwise we show cart items totals only (before discount) |
||
2086 | } else { |
||
2087 | |||
2088 | // Display varies depending on settings |
||
2089 | if ( $this->tax_display_cart == 'excl' ) { |
||
2090 | |||
2091 | $cart_subtotal = wc_price( $this->subtotal_ex_tax ); |
||
2092 | |||
2093 | if ( $this->tax_total > 0 && $this->prices_include_tax ) { |
||
2094 | $cart_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; |
||
2095 | } |
||
2096 | |||
2097 | } else { |
||
2098 | |||
2099 | $cart_subtotal = wc_price( $this->subtotal ); |
||
2100 | |||
2101 | if ( $this->tax_total > 0 && !$this->prices_include_tax ) { |
||
2102 | $cart_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>'; |
||
2103 | } |
||
2104 | |||
2105 | } |
||
2106 | } |
||
2107 | |||
2108 | return apply_filters( 'woocommerce_cart_subtotal', $cart_subtotal, $compound, $this ); |
||
2109 | } |
||
2110 | |||
2111 | /** |
||
2112 | * Get the product row price per item. |
||
2113 | * |
||
2114 | * @param WC_Product $_product |
||
2115 | * @return string formatted price |
||
2116 | */ |
||
2117 | public function get_product_price( $_product ) { |
||
2118 | if ( $this->tax_display_cart == 'excl' ) { |
||
2119 | $product_price = $_product->get_price_excluding_tax(); |
||
2120 | } else { |
||
2121 | $product_price = $_product->get_price_including_tax(); |
||
2122 | } |
||
2123 | |||
2124 | return apply_filters( 'woocommerce_cart_product_price', wc_price( $product_price ), $_product ); |
||
2125 | } |
||
2126 | |||
2127 | /** |
||
2128 | * Get the product row subtotal. |
||
2129 | * |
||
2130 | * Gets the tax etc to avoid rounding issues. |
||
2131 | * |
||
2132 | * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate. |
||
2133 | * |
||
2134 | * @param WC_Product $_product |
||
2135 | * @param int $quantity |
||
2136 | * @return string formatted price |
||
2137 | */ |
||
2138 | public function get_product_subtotal( $_product, $quantity ) { |
||
2139 | |||
2140 | $price = $_product->get_price(); |
||
2141 | $taxable = $_product->is_taxable(); |
||
2142 | |||
2143 | // Taxable |
||
2144 | if ( $taxable ) { |
||
2145 | |||
2146 | if ( $this->tax_display_cart == 'excl' ) { |
||
2147 | |||
2148 | $row_price = $_product->get_price_excluding_tax( $quantity ); |
||
2149 | $product_subtotal = wc_price( $row_price ); |
||
2150 | |||
2151 | if ( $this->prices_include_tax && $this->tax_total > 0 ) { |
||
2152 | $product_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; |
||
2153 | } |
||
2154 | |||
2155 | } else { |
||
2156 | |||
2157 | $row_price = $_product->get_price_including_tax( $quantity ); |
||
2158 | $product_subtotal = wc_price( $row_price ); |
||
2159 | |||
2160 | if ( ! $this->prices_include_tax && $this->tax_total > 0 ) { |
||
2161 | $product_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>'; |
||
2162 | } |
||
2163 | |||
2164 | } |
||
2165 | |||
2166 | // Non-taxable |
||
2167 | } else { |
||
2168 | |||
2169 | $row_price = $price * $quantity; |
||
2170 | $product_subtotal = wc_price( $row_price ); |
||
2171 | |||
2172 | } |
||
2173 | |||
2174 | return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $_product, $quantity, $this ); |
||
2175 | } |
||
2176 | |||
2177 | /** |
||
2178 | * Gets the cart tax (after calculation). |
||
2179 | * |
||
2180 | * @return string formatted price |
||
2181 | */ |
||
2182 | public function get_cart_tax() { |
||
2183 | $cart_total_tax = wc_round_tax_total( $this->tax_total + $this->shipping_tax_total ); |
||
2184 | |||
2185 | return apply_filters( 'woocommerce_get_cart_tax', $cart_total_tax ? wc_price( $cart_total_tax ) : '' ); |
||
2186 | } |
||
2187 | |||
2188 | /** |
||
2189 | * Get a tax amount. |
||
2190 | * @param string $tax_rate_id |
||
2191 | * @return float amount |
||
2192 | */ |
||
2193 | public function get_tax_amount( $tax_rate_id ) { |
||
2194 | return isset( $this->taxes[ $tax_rate_id ] ) ? $this->taxes[ $tax_rate_id ] : 0; |
||
2195 | } |
||
2196 | |||
2197 | /** |
||
2198 | * Get a tax amount. |
||
2199 | * @param string $tax_rate_id |
||
2200 | * @return float amount |
||
2201 | */ |
||
2202 | public function get_shipping_tax_amount( $tax_rate_id ) { |
||
2203 | return isset( $this->shipping_taxes[ $tax_rate_id ] ) ? $this->shipping_taxes[ $tax_rate_id ] : 0; |
||
2204 | } |
||
2205 | |||
2206 | /** |
||
2207 | * Get tax row amounts with or without compound taxes includes. |
||
2208 | * |
||
2209 | * @param bool $compound True if getting compound taxes |
||
2210 | * @param bool $display True if getting total to display |
||
2211 | * @return float price |
||
2212 | */ |
||
2213 | public function get_taxes_total( $compound = true, $display = true ) { |
||
2214 | $total = 0; |
||
2215 | View Code Duplication | foreach ( $this->taxes as $key => $tax ) { |
|
2216 | if ( ! $compound && WC_Tax::is_compound( $key ) ) continue; |
||
2217 | $total += $tax; |
||
2218 | } |
||
2219 | View Code Duplication | foreach ( $this->shipping_taxes as $key => $tax ) { |
|
2220 | if ( ! $compound && WC_Tax::is_compound( $key ) ) continue; |
||
2221 | $total += $tax; |
||
2222 | } |
||
2223 | if ( $display ) { |
||
2224 | $total = wc_round_tax_total( $total ); |
||
2225 | } |
||
2226 | return apply_filters( 'woocommerce_cart_taxes_total', $total, $compound, $display, $this ); |
||
2227 | } |
||
2228 | |||
2229 | /** |
||
2230 | * Get the total of all cart discounts. |
||
2231 | * |
||
2232 | * @return float |
||
2233 | */ |
||
2234 | public function get_cart_discount_total() { |
||
2235 | return wc_cart_round_discount( $this->discount_cart, $this->dp ); |
||
2236 | } |
||
2237 | |||
2238 | /** |
||
2239 | * Get the total of all cart tax discounts (used for discounts on tax inclusive prices). |
||
2240 | * |
||
2241 | * @return float |
||
2242 | */ |
||
2243 | public function get_cart_discount_tax_total() { |
||
2244 | return wc_cart_round_discount( $this->discount_cart_tax, $this->dp ); |
||
2245 | } |
||
2246 | |||
2247 | /** |
||
2248 | * Gets the total discount amount - both kinds. |
||
2249 | * |
||
2250 | * @return mixed formatted price or false if there are none |
||
2251 | */ |
||
2252 | public function get_total_discount() { |
||
2253 | if ( $this->get_cart_discount_total() ) { |
||
2254 | $total_discount = wc_price( $this->get_cart_discount_total() ); |
||
2255 | } else { |
||
2256 | $total_discount = false; |
||
2257 | } |
||
2258 | return apply_filters( 'woocommerce_cart_total_discount', $total_discount, $this ); |
||
2259 | } |
||
2260 | |||
2261 | /** |
||
2262 | * Gets the total (product) discount amount - these are applied before tax. |
||
2263 | * |
||
2264 | * @deprecated Order discounts (after tax) removed in 2.3 so multiple methods for discounts are no longer required. |
||
2265 | * @return mixed formatted price or false if there are none |
||
2266 | */ |
||
2267 | public function get_discounts_before_tax() { |
||
2268 | _deprecated_function( 'get_discounts_before_tax', '2.3', 'get_total_discount' ); |
||
2269 | if ( $this->get_cart_discount_total() ) { |
||
2270 | $discounts_before_tax = wc_price( $this->get_cart_discount_total() ); |
||
2271 | } else { |
||
2272 | $discounts_before_tax = false; |
||
2273 | } |
||
2274 | return apply_filters( 'woocommerce_cart_discounts_before_tax', $discounts_before_tax, $this ); |
||
2275 | } |
||
2276 | |||
2277 | /** |
||
2278 | * Get the total of all order discounts (after tax discounts). |
||
2279 | * |
||
2280 | * @deprecated Order discounts (after tax) removed in 2.3 |
||
2281 | * @return int |
||
2282 | */ |
||
2283 | public function get_order_discount_total() { |
||
2284 | _deprecated_function( 'get_order_discount_total', '2.3' ); |
||
2285 | return 0; |
||
2286 | } |
||
2287 | |||
2288 | /** |
||
2289 | * Function to apply cart discounts after tax. |
||
2290 | * @deprecated Coupons can not be applied after tax |
||
2291 | */ |
||
2292 | public function apply_cart_discounts_after_tax( $values, $price ) { |
||
2293 | _deprecated_function( 'apply_cart_discounts_after_tax', '2.3' ); |
||
2294 | } |
||
2295 | |||
2296 | /** |
||
2297 | * Function to apply product discounts after tax. |
||
2298 | * @deprecated Coupons can not be applied after tax |
||
2299 | */ |
||
2300 | public function apply_product_discounts_after_tax( $values, $price ) { |
||
2301 | _deprecated_function( 'apply_product_discounts_after_tax', '2.3' ); |
||
2302 | } |
||
2303 | |||
2304 | /** |
||
2305 | * Gets the order discount amount - these are applied after tax. |
||
2306 | * @deprecated Coupons can not be applied after tax |
||
2307 | */ |
||
2308 | public function get_discounts_after_tax() { |
||
2309 | _deprecated_function( 'get_discounts_after_tax', '2.3' ); |
||
2310 | } |
||
2311 | } |
||
2312 |
It seems like the type of the argument is not accepted by the function/method which you are calling.
In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.
We suggest to add an explicit type cast like in the following example: