Completed
Push — master ( 2e553c...66decd )
by James
10:10
created

WC_Cart::__get()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 17
Code Lines 15

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 17
rs 8.8571
cc 5
eloc 15
nc 5
nop 1
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 19 and the first side effect is on line 4.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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
	/** @var array An array of fees. */
98
	public $fees                  = array();
99
100
	/** @var boolean Prices inc tax */
101
	public $prices_include_tax;
102
103
	/** @var boolean */
104
	public $round_at_subtotal;
105
106
	/** @var string */
107
	public $tax_display_cart;
108
109
	/** @var int Prices inc tax */
110
	public $dp;
111
112
	/** @var boolean */
113
	public $display_totals_ex_tax;
114
115
	/** @var boolean */
116
	public $display_cart_ex_tax;
117
118
	/**
119
	 * Constructor for the cart class. Loads options and hooks in the init method.
120
	 */
121
	public function __construct() {
122
		$this->prices_include_tax    = wc_prices_include_tax();
123
		$this->round_at_subtotal     = get_option( 'woocommerce_tax_round_at_subtotal' ) == 'yes';
124
		$this->tax_display_cart      = get_option( 'woocommerce_tax_display_cart' );
125
		$this->dp                    = wc_get_price_decimals();
126
		$this->display_totals_ex_tax = $this->tax_display_cart == 'excl';
127
		$this->display_cart_ex_tax   = $this->tax_display_cart == 'excl';
128
129
		add_action( 'wp_loaded', array( $this, 'init' ) ); // Get cart after WP and plugins are loaded.
130
		add_action( 'wp', array( $this, 'maybe_set_cart_cookies' ), 99 ); // Set cookies
131
		add_action( 'shutdown', array( $this, 'maybe_set_cart_cookies' ), 0 ); // Set cookies before shutdown and ob flushing
132
		add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 );
133
		add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 );
134
	}
135
136
	/**
137
	 * Auto-load in-accessible properties on demand.
138
	 *
139
	 * @param mixed $key
140
	 * @return mixed
141
	 */
142
	public function __get( $key ) {
143
		switch ( $key ) {
144
			case 'cart_contents_weight' :
145
				return $this->get_cart_contents_weight();
146
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
147
			case 'cart_contents_count' :
148
				return $this->get_cart_contents_count();
149
			break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
150
			case 'tax' :
151
				_deprecated_argument( 'WC_Cart->tax', '2.3', 'Use WC_Tax:: directly' );
152
				$this->tax = new WC_Tax();
153
			return $this->tax;
154
			case 'discount_total':
155
				_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/' );
156
			return 0;
157
		}
158
	}
159
160
	/**
161
	 * Loads the cart data from the PHP session during WordPress init and hooks in other methods.
162
	 */
163
	public function init() {
164
		$this->get_cart_from_session();
165
166
		add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_items' ), 1 );
167
		add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_coupons' ), 1 );
168
		add_action( 'woocommerce_after_checkout_validation', array( $this, 'check_customer_coupons' ), 1 );
169
	}
170
171
	/**
172
	 * Will set cart cookies if needed, once, during WP hook.
173
	 */
174
	public function maybe_set_cart_cookies() {
175
		if ( ! headers_sent() && did_action( 'wp_loaded' ) ) {
176
			if ( ! $this->is_empty() ) {
177
				$this->set_cart_cookies( true );
178
			} elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) {
179
				$this->set_cart_cookies( false );
180
			}
181
		}
182
	}
183
184
	/**
185
	 * Set cart hash cookie and items in cart.
186
	 *
187
	 * @access private
188
	 * @param bool $set (default: true)
189
	 */
190
	private function set_cart_cookies( $set = true ) {
191
		if ( $set ) {
192
			wc_setcookie( 'woocommerce_items_in_cart', 1 );
193
			wc_setcookie( 'woocommerce_cart_hash', md5( json_encode( $this->get_cart_for_session() ) ) );
194
		} elseif ( isset( $_COOKIE['woocommerce_items_in_cart'] ) ) {
195
			wc_setcookie( 'woocommerce_items_in_cart', 0, time() - HOUR_IN_SECONDS );
196
			wc_setcookie( 'woocommerce_cart_hash', '', time() - HOUR_IN_SECONDS );
197
		}
198
		do_action( 'woocommerce_set_cart_cookies', $set );
199
	}
200
201
	/*-----------------------------------------------------------------------------------*/
202
	/* Cart Session Handling */
203
	/*-----------------------------------------------------------------------------------*/
204
205
		/**
206
		 * Get the cart data from the PHP session and store it in class variables.
207
		 */
208
		public function get_cart_from_session() {
209
			// Load cart session data from session
210
			foreach ( $this->cart_session_data as $key => $default ) {
211
				$this->$key = WC()->session->get( $key, $default );
212
			}
213
214
			$update_cart_session         = false;
215
			$this->removed_cart_contents = array_filter( WC()->session->get( 'removed_cart_contents', array() ) );
216
			$this->applied_coupons       = array_filter( WC()->session->get( 'applied_coupons', array() ) );
217
218
			/**
219
			 * Load the cart object. This defaults to the persistant cart if null.
220
			 */
221
			$cart = WC()->session->get( 'cart', null );
222
223
			if ( is_null( $cart ) && ( $saved_cart = get_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', true ) ) ) {
224
				$cart                = $saved_cart['cart'];
225
				$update_cart_session = true;
226
			} elseif ( is_null( $cart ) ) {
227
				$cart = array();
228
			}
229
230
			if ( is_array( $cart ) ) {
231
				foreach ( $cart as $key => $values ) {
232
					$_product = wc_get_product( $values['variation_id'] ? $values['variation_id'] : $values['product_id'] );
233
234
					if ( ! empty( $_product ) && $_product->exists() && $values['quantity'] > 0 ) {
235
236
						if ( ! $_product->is_purchasable() ) {
237
238
							// Flag to indicate the stored cart should be update
239
							$update_cart_session = true;
240
							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' );
241
							do_action( 'woocommerce_remove_cart_item_from_session', $key, $values );
242
243
						} else {
244
245
							// Put session data into array. Run through filter so other plugins can load their own session data
246
							$session_data = array_merge( $values, array( 'data' => $_product ) );
247
							$this->cart_contents[ $key ] = apply_filters( 'woocommerce_get_cart_item_from_session', $session_data, $values, $key );
248
249
						}
250
					}
251
				}
252
			}
253
254
			// Trigger action
255
			do_action( 'woocommerce_cart_loaded_from_session', $this );
256
257
			if ( $update_cart_session ) {
258
				WC()->session->cart = $this->get_cart_for_session();
259
			}
260
261
			// Queue re-calc if subtotal is not set
262
			if ( ( ! $this->subtotal && ! $this->is_empty() ) || $update_cart_session ) {
263
				$this->calculate_totals();
264
			}
265
		}
266
267
		/**
268
		 * Sets the php session data for the cart and coupons.
269
		 */
270
		public function set_session() {
271
			// Set cart and coupon session data
272
			$cart_session = $this->get_cart_for_session();
273
274
			WC()->session->set( 'cart', $cart_session );
275
			WC()->session->set( 'applied_coupons', $this->applied_coupons );
276
			WC()->session->set( 'coupon_discount_amounts', $this->coupon_discount_amounts );
277
			WC()->session->set( 'coupon_discount_tax_amounts', $this->coupon_discount_tax_amounts );
278
			WC()->session->set( 'removed_cart_contents', $this->removed_cart_contents );
279
280
			foreach ( $this->cart_session_data as $key => $default ) {
281
				WC()->session->set( $key, $this->$key );
282
			}
283
284
			if ( get_current_user_id() ) {
285
				$this->persistent_cart_update();
286
			}
287
288
			do_action( 'woocommerce_cart_updated' );
289
		}
290
291
		/**
292
		 * Empties the cart and optionally the persistent cart too.
293
		 *
294
		 * @param bool $clear_persistent_cart (default: true)
295
		 */
296
		public function empty_cart( $clear_persistent_cart = true ) {
297
			$this->cart_contents = array();
298
			$this->reset( true );
299
300
			unset( WC()->session->order_awaiting_payment, WC()->session->applied_coupons, WC()->session->coupon_discount_amounts, WC()->session->coupon_discount_tax_amounts, WC()->session->cart );
301
302
			if ( $clear_persistent_cart && get_current_user_id() ) {
303
				$this->persistent_cart_destroy();
304
			}
305
306
			do_action( 'woocommerce_cart_emptied' );
307
		}
308
309
	/*-----------------------------------------------------------------------------------*/
310
	/* Persistent cart handling */
311
	/*-----------------------------------------------------------------------------------*/
312
313
		/**
314
		 * Save the persistent cart when the cart is updated.
315
		 */
316
		public function persistent_cart_update() {
317
			update_user_meta( get_current_user_id(), '_woocommerce_persistent_cart', array(
318
				'cart' => WC()->session->get( 'cart' )
319
			) );
320
		}
321
322
		/**
323
		 * Delete the persistent cart permanently.
324
		 */
325
		public function persistent_cart_destroy() {
326
			delete_user_meta( get_current_user_id(), '_woocommerce_persistent_cart' );
327
		}
328
329
	/*-----------------------------------------------------------------------------------*/
330
	/* Cart Data Functions */
331
	/*-----------------------------------------------------------------------------------*/
332
333
		/**
334
		 * Coupons enabled function. Filterable.
335
		 *
336
		 * @deprecated 2.5.0 in favor to wc_coupons_enabled()
337
		 *
338
		 * @return bool
339
		 */
340
		public function coupons_enabled() {
341
			return wc_coupons_enabled();
342
		}
343
344
		/**
345
		 * Get number of items in the cart.
346
		 * @return int
347
		 */
348
		public function get_cart_contents_count() {
349
			return apply_filters( 'woocommerce_cart_contents_count', array_sum( wp_list_pluck( $this->get_cart(), 'quantity' ) ) );
350
		}
351
352
		/**
353
		 * Get weight of items in the cart.
354
		 * @since 2.5.0
355
		 * @return int
356
		 */
357
		public function get_cart_contents_weight() {
358
			$weight = 0;
359
360
			foreach ( $this->get_cart() as $cart_item_key => $values ) {
361
				$weight += $values['data']->get_weight() * $values['quantity'];
362
			}
363
364
			return apply_filters( 'woocommerce_cart_contents_weight', $weight );
365
		}
366
367
		/**
368
		* Checks if the cart is empty.
369
		*
370
		* @return bool
371
		*/
372
		public function is_empty() {
373
			return 0 === sizeof( $this->get_cart() );
374
		}
375
376
		/**
377
		 * Check all cart items for errors.
378
		 */
379
		public function check_cart_items() {
380
381
			// Result
382
			$return = true;
383
384
			// Check cart item validity
385
			$result = $this->check_cart_item_validity();
386
387
			if ( is_wp_error( $result ) ) {
388
				wc_add_notice( $result->get_error_message(), 'error' );
389
				$return = false;
390
			}
391
392
			// Check item stock
393
			$result = $this->check_cart_item_stock();
394
395
			if ( is_wp_error( $result ) ) {
396
				wc_add_notice( $result->get_error_message(), 'error' );
397
				$return = false;
398
			}
399
400
			return $return;
401
402
		}
403
404
		/**
405
		 * Check cart coupons for errors.
406
		 */
407
		public function check_cart_coupons() {
408
			foreach ( $this->applied_coupons as $code ) {
409
				$coupon = new WC_Coupon( $code );
410
411 View Code Duplication
				if ( ! $coupon->is_valid() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
412
					// Error message
413
					$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED );
414
415
					// Remove the coupon
416
					$this->remove_coupon( $code );
417
418
					// Flag totals for refresh
419
					WC()->session->set( 'refresh_totals', true );
420
				}
421
			}
422
		}
423
424
		/**
425
		 * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines.
426
		 *
427
		 * @return array
428
		 */
429
		public function get_cart_item_quantities() {
430
			$quantities = array();
431
432
			foreach ( $this->get_cart() as $cart_item_key => $values ) {
433
				$_product = $values['data'];
434
435
				if ( $_product->is_type( 'variation' ) && true === $_product->managing_stock() ) {
436
					// Variation has stock levels defined so its handled individually
437
					$quantities[ $values['variation_id'] ] = isset( $quantities[ $values['variation_id'] ] ) ? $quantities[ $values['variation_id'] ] + $values['quantity'] : $values['quantity'];
438
				} else {
439
					$quantities[ $values['product_id'] ] = isset( $quantities[ $values['product_id'] ] ) ? $quantities[ $values['product_id'] ] + $values['quantity'] : $values['quantity'];
440
				}
441
			}
442
443
			return $quantities;
444
		}
445
446
		/**
447
		 * Looks through cart items and checks the posts are not trashed or deleted.
448
		 *
449
		 * @return bool|WP_Error
450
		 */
451
		public function check_cart_item_validity() {
452
			foreach ( $this->get_cart() as $cart_item_key => $values ) {
453
454
				$_product = $values['data'];
455
456
				if ( ! $_product || ! $_product->exists() || $_product->post->post_status == 'trash' ) {
457
					$this->set_quantity( $cart_item_key, 0 );
458
459
					return new WP_Error( 'invalid', __( 'An item which is no longer available was removed from your cart.', 'woocommerce' ) );
460
				}
461
			}
462
463
			return true;
464
		}
465
466
		/**
467
		 * Looks through the cart to check each item is in stock. If not, add an error.
468
		 *
469
		 * @return bool|WP_Error
470
		 */
471
		public function check_cart_item_stock() {
472
			global $wpdb;
473
474
			$error               = new WP_Error();
475
			$product_qty_in_cart = $this->get_cart_item_quantities();
476
477
			// First stock check loop
478
			foreach ( $this->get_cart() as $cart_item_key => $values ) {
479
				$_product = $values['data'];
480
481
				/**
482
				 * Check stock based on stock-status.
483
				 */
484
				if ( ! $_product->is_in_stock() ) {
485
					$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() ) );
486
					return $error;
487
				}
488
489
				if ( ! $_product->managing_stock() ) {
490
					continue;
491
				}
492
493
				$check_qty = $_product->is_type( 'variation' ) && true === $_product->managing_stock() ? $product_qty_in_cart[ $values['variation_id'] ] : $product_qty_in_cart[ $values['product_id'] ];
494
495
				/**
496
				 * Check stock based on all items in the cart.
497
				 */
498
				if ( ! $_product->has_enough_stock( $check_qty ) ) {
499
					$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() ) );
500
					return $error;
501
				}
502
503
				/**
504
				 * Finally consider any held stock, from pending orders.
505
				 */
506
				if ( get_option( 'woocommerce_hold_stock_minutes' ) > 0 && ! $_product->backorders_allowed() ) {
507
					$order_id   = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0;
508
					$held_stock = $wpdb->get_var(
509
						$wpdb->prepare( "
510
							SELECT SUM( order_item_meta.meta_value ) AS held_qty
511
							FROM {$wpdb->posts} AS posts
512
							LEFT JOIN {$wpdb->prefix}woocommerce_order_items as order_items ON posts.ID = order_items.order_id
513
							LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta ON order_items.order_item_id = order_item_meta.order_item_id
514
							LEFT JOIN {$wpdb->prefix}woocommerce_order_itemmeta as order_item_meta2 ON order_items.order_item_id = order_item_meta2.order_item_id
515
							WHERE 	order_item_meta.meta_key   = '_qty'
516
							AND 	order_item_meta2.meta_key  = %s AND order_item_meta2.meta_value  = %d
517
							AND 	posts.post_type            IN ( '" . implode( "','", wc_get_order_types() ) . "' )
518
							AND 	posts.post_status          = 'wc-pending'
519
							AND		posts.ID                   != %d;",
520
							$_product->is_type( 'variation' ) && true === $_product->managing_stock() ? '_variation_id' : '_product_id',
521
							$_product->is_type( 'variation' ) && true === $_product->managing_stock() ? $values['variation_id'] : $values['product_id'],
522
							$order_id
523
						)
524
					);
525
526
					$not_enough_stock = false;
527
528
					if ( $_product->is_type( 'variation' ) && 'parent' === $_product->managing_stock() && $_product->parent->get_stock_quantity() < ( $held_stock + $check_qty ) ) {
529
						$not_enough_stock = true;
530
					} elseif ( $_product->get_stock_quantity() < ( $held_stock + $check_qty ) ) {
531
						$not_enough_stock = true;
532
					}
533
					if ( $not_enough_stock ) {
534
						$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' ) ) );
535
						return $error;
536
					}
537
				}
538
			}
539
540
			return true;
541
		}
542
543
		/**
544
		 * Gets and formats a list of cart item data + variations for display on the frontend.
545
		 *
546
		 * @param array $cart_item
547
		 * @param bool $flat (default: false)
548
		 * @return string
549
		 */
550
		public function get_item_data( $cart_item, $flat = false ) {
551
			$item_data = array();
552
553
			// Variation data
554
			if ( ! empty( $cart_item['data']->variation_id ) && is_array( $cart_item['variation'] ) ) {
555
556
				foreach ( $cart_item['variation'] as $name => $value ) {
557
558
					if ( '' === $value )
559
						continue;
560
561
					$taxonomy = wc_attribute_taxonomy_name( str_replace( 'attribute_pa_', '', urldecode( $name ) ) );
562
563
					// If this is a term slug, get the term's nice name
564
					if ( taxonomy_exists( $taxonomy ) ) {
565
						$term = get_term_by( 'slug', $value, $taxonomy );
566
						if ( ! is_wp_error( $term ) && $term && $term->name ) {
567
							$value = $term->name;
568
						}
569
						$label = wc_attribute_label( $taxonomy );
570
571
					// If this is a custom option slug, get the options name
572
					} else {
573
						$value              = apply_filters( 'woocommerce_variation_option_name', $value );
574
						$product_attributes = $cart_item['data']->get_attributes();
575
						if ( isset( $product_attributes[ str_replace( 'attribute_', '', $name ) ] ) ) {
576
							$label = wc_attribute_label( $product_attributes[ str_replace( 'attribute_', '', $name ) ]['name'] );
577
						} else {
578
							$label = $name;
579
						}
580
					}
581
582
					$item_data[] = array(
583
						'key'   => $label,
584
						'value' => $value
585
					);
586
				}
587
			}
588
589
			// Filter item data to allow 3rd parties to add more to the array
590
			$item_data = apply_filters( 'woocommerce_get_item_data', $item_data, $cart_item );
591
592
			// Format item data ready to display
593
			foreach ( $item_data as $key => $data ) {
594
				// Set hidden to true to not display meta on cart.
595
				if ( ! empty( $data['hidden'] ) ) {
596
					unset( $item_data[ $key ] );
597
					continue;
598
				}
599
				$item_data[ $key ]['key']     = ! empty( $data['key'] ) ? $data['key'] : $data['name'];
600
				$item_data[ $key ]['display'] = ! empty( $data['display'] ) ? $data['display'] : $data['value'];
601
			}
602
603
			// Output flat or in list format
604
			if ( sizeof( $item_data ) > 0 ) {
605
				ob_start();
606
607
				if ( $flat ) {
608
					foreach ( $item_data as $data ) {
609
						echo esc_html( $data['key'] ) . ': ' . wp_kses_post( $data['display'] ) . "\n";
610
					}
611
				} else {
612
					wc_get_template( 'cart/cart-item-data.php', array( 'item_data' => $item_data ) );
613
				}
614
615
				return ob_get_clean();
616
			}
617
618
			return '';
619
		}
620
621
		/**
622
		 * Gets cross sells based on the items in the cart.
623
		 *
624
		 * @return array cross_sells (item ids)
625
		 */
626
		public function get_cross_sells() {
627
			$cross_sells = array();
628
			$in_cart = array();
629
			if ( ! $this->is_empty() ) {
630
				foreach ( $this->get_cart() as $cart_item_key => $values ) {
631
					if ( $values['quantity'] > 0 ) {
632
						$cross_sells = array_merge( $values['data']->get_cross_sells(), $cross_sells );
633
						$in_cart[] = $values['product_id'];
634
					}
635
				}
636
			}
637
			$cross_sells = array_diff( $cross_sells, $in_cart );
638
			return $cross_sells;
639
		}
640
641
		/**
642
		 * Gets the url to the cart page.
643
		 *
644
		 * @deprecated 2.5.0 in favor to wc_get_cart_url()
645
		 *
646
		 * @return string url to page
647
		 */
648
		public function get_cart_url() {
649
			return wc_get_cart_url();
650
		}
651
652
		/**
653
		 * Gets the url to the checkout page.
654
		 *
655
		 * @deprecated 2.5.0 in favor to wc_get_checkout_url()
656
		 *
657
		 * @return string url to page
658
		 */
659
		public function get_checkout_url() {
660
			return wc_get_checkout_url();
661
		}
662
663
		/**
664
		 * Gets the url to remove an item from the cart.
665
		 *
666
		 * @param string cart_item_key contains the id of the cart item
667
		 * @return string url to page
668
		 */
669
		public function get_remove_url( $cart_item_key ) {
670
			$cart_page_url = wc_get_page_permalink( 'cart' );
671
			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' ) : '' );
672
		}
673
674
		/**
675
		 * Gets the url to re-add an item into the cart.
676
		 *
677
		 * @param  string $cart_item_key
678
		 * @return string url to page
679
		 */
680
		public function get_undo_url( $cart_item_key ) {
681
			$cart_page_url = wc_get_page_permalink( 'cart' );
682
683
			$query_args = array(
684
				'undo_item' => $cart_item_key,
685
			);
686
687
			return apply_filters( 'woocommerce_get_undo_url', $cart_page_url ? wp_nonce_url( add_query_arg( $query_args, $cart_page_url ), 'woocommerce-cart' ) : '' );
688
		}
689
690
		/**
691
		 * Returns the contents of the cart in an array.
692
		 *
693
		 * @return array contents of the cart
694
		 */
695
		public function get_cart() {
696
			if ( ! did_action( 'wp_loaded' ) ) {
697
				_doing_it_wrong( __FUNCTION__, __( 'Get cart should not be called before the wp_loaded action.', 'woocommerce' ), '2.3' );
698
			}
699
			if ( ! did_action( 'woocommerce_cart_loaded_from_session' ) ) {
700
				$this->get_cart_from_session();
701
			}
702
			return array_filter( (array) $this->cart_contents );
703
		}
704
705
		/**
706
		 * Returns the contents of the cart in an array without the 'data' element.
707
		 *
708
		 * @return array contents of the cart
709
		 */
710
		public function get_cart_for_session() {
711
			$cart_session = array();
712
713
			if ( $this->get_cart() ) {
714
				foreach ( $this->get_cart() as $key => $values ) {
715
					$cart_session[ $key ] = $values;
716
					unset( $cart_session[ $key ]['data'] ); // Unset product object
717
				}
718
			}
719
720
			return $cart_session;
721
		}
722
723
		/**
724
		 * Returns a specific item in the cart.
725
		 *
726
		 * @return array item data
727
		 */
728
		public function get_cart_item( $item_key ) {
729
			if ( isset( $this->cart_contents[ $item_key ] ) ) {
730
				return $this->cart_contents[ $item_key ];
731
			}
732
733
			return array();
734
		}
735
736
		/**
737
		 * Returns the cart and shipping taxes, merged.
738
		 *
739
		 * @return array merged taxes
740
		 */
741
		public function get_taxes() {
742
			$taxes = array();
743
744
			// Merge
745
			foreach ( array_keys( $this->taxes + $this->shipping_taxes ) as $key ) {
746
				$taxes[ $key ] = ( isset( $this->shipping_taxes[ $key ] ) ? $this->shipping_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
747
			}
748
749
			return apply_filters( 'woocommerce_cart_get_taxes', $taxes, $this );
750
		}
751
752
		/**
753
		 * Get taxes, merged by code, formatted ready for output.
754
		 *
755
		 * @return array
756
		 */
757
		public function get_tax_totals() {
758
			$taxes      = $this->get_taxes();
759
			$tax_totals = array();
760
761
			foreach ( $taxes as $key => $tax ) {
762
				$code = WC_Tax::get_rate_code( $key );
763
764
				if ( $code || $key === apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) ) {
765 View Code Duplication
					if ( ! isset( $tax_totals[ $code ] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
766
						$tax_totals[ $code ] = new stdClass();
767
						$tax_totals[ $code ]->amount = 0;
768
					}
769
					$tax_totals[ $code ]->tax_rate_id       = $key;
770
					$tax_totals[ $code ]->is_compound       = WC_Tax::is_compound( $key );
771
					$tax_totals[ $code ]->label             = WC_Tax::get_rate_label( $key );
772
					$tax_totals[ $code ]->amount           += wc_round_tax_total( $tax );
773
					$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ) );
774
				}
775
			}
776
777
			return apply_filters( 'woocommerce_cart_tax_totals', $tax_totals, $this );
778
		}
779
780
		/**
781
		 * Get all tax classes for items in the cart.
782
		 * @return array
783
		 */
784
		public function get_cart_item_tax_classes() {
785
			$found_tax_classes = array();
786
787
			foreach ( WC()->cart->get_cart() as $item ) {
788
				$found_tax_classes[] = $item['data']->get_tax_class();
789
			}
790
791
			return array_unique( $found_tax_classes );
792
		}
793
794
	/*-----------------------------------------------------------------------------------*/
795
	/* Add to cart handling */
796
	/*-----------------------------------------------------------------------------------*/
797
798
		/**
799
		 * Check if product is in the cart and return cart item key.
800
		 *
801
		 * Cart item key will be unique based on the item and its properties, such as variations.
802
		 *
803
		 * @param mixed id of product to find in the cart
804
		 * @return string cart item key
805
		 */
806
		public function find_product_in_cart( $cart_id = false ) {
807
			if ( $cart_id !== false ) {
808
				if ( is_array( $this->cart_contents ) ) {
809
					foreach ( $this->cart_contents as $cart_item_key => $cart_item ) {
810
						if ( $cart_item_key == $cart_id ) {
811
							return $cart_item_key;
812
						}
813
					}
814
				}
815
			}
816
			return '';
817
		}
818
819
		/**
820
		 * Generate a unique ID for the cart item being added.
821
		 *
822
		 * @param int $product_id - id of the product the key is being generated for
823
		 * @param int $variation_id of the product the key is being generated for
824
		 * @param array $variation data for the cart item
825
		 * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart
826
		 * @return string cart item key
827
		 */
828
		public function generate_cart_id( $product_id, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
829
			$id_parts = array( $product_id );
830
831
			if ( $variation_id && 0 != $variation_id ) {
832
				$id_parts[] = $variation_id;
833
			}
834
835
			if ( is_array( $variation ) && ! empty( $variation ) ) {
836
				$variation_key = '';
837
				foreach ( $variation as $key => $value ) {
838
					$variation_key .= trim( $key ) . trim( $value );
839
				}
840
				$id_parts[] = $variation_key;
841
			}
842
843
			if ( is_array( $cart_item_data ) && ! empty( $cart_item_data ) ) {
844
				$cart_item_data_key = '';
845
				foreach ( $cart_item_data as $key => $value ) {
846
847
					if ( is_array( $value ) ) {
848
						$value = http_build_query( $value );
849
					}
850
					$cart_item_data_key .= trim( $key ) . trim( $value );
851
852
				}
853
				$id_parts[] = $cart_item_data_key;
854
			}
855
856
			return md5( implode( '_', $id_parts ) );
857
		}
858
859
		/**
860
		 * Add a product to the cart.
861
		 *
862
		 * @param integer $product_id contains the id of the product to add to the cart
863
		 * @param integer $quantity contains the quantity of the item to add
864
		 * @param integer $variation_id
865
		 * @param array $variation attribute values
866
		 * @param array $cart_item_data extra cart item data we want to pass into the item
867
		 * @return string $cart_item_key
868
		 */
869
		public function add_to_cart( $product_id = 0, $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
870
			// Wrap in try catch so plugins can throw an exception to prevent adding to cart
871
			try {
872
				$product_id   = absint( $product_id );
873
				$variation_id = absint( $variation_id );
874
875
				// Ensure we don't add a variation to the cart directly by variation ID
876
				if ( 'product_variation' == get_post_type( $product_id ) ) {
877
					$variation_id = $product_id;
878
					$product_id   = wp_get_post_parent_id( $variation_id );
879
				}
880
881
				// Get the product
882
				$product_data = wc_get_product( $variation_id ? $variation_id : $product_id );
883
884
				// Sanitity check
885
				if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->post->post_status  ) {
886
					throw new Exception();
887
				}
888
889
				// Load cart item data - may be added by other plugins
890
				$cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id );
891
892
				// Generate a ID based on product ID, variation ID, variation data, and other cart item data
893
				$cart_id        = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data );
894
895
				// Find the cart item key in the existing cart
896
				$cart_item_key  = $this->find_product_in_cart( $cart_id );
0 ignored issues
show
Documentation introduced by
$cart_id is of type string, but the function expects a boolean.

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:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
897
898
				// Force quantity to 1 if sold individually and check for exisitng item in cart
899
				if ( $product_data->is_sold_individually() ) {
900
					$quantity         = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data );
901
					$in_cart_quantity = $cart_item_key ? $this->cart_contents[ $cart_item_key ]['quantity'] : 0;
902
903
					if ( $in_cart_quantity > 0 ) {
904
						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 &quot;%s&quot; to your cart.', 'woocommerce' ), $product_data->get_title() ) ) );
905
					}
906
				}
907
908
				// Check product is_purchasable
909
				if ( ! $product_data->is_purchasable() ) {
910
					throw new Exception( __( 'Sorry, this product cannot be purchased.', 'woocommerce' ) );
911
				}
912
913
				// Stock check - only check if we're managing stock and backorders are not allowed
914
				if ( ! $product_data->is_in_stock() ) {
915
					throw new Exception( sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_title() ) );
916
				}
917
918
				if ( ! $product_data->has_enough_stock( $quantity ) ) {
919
					throw new Exception( sprintf(__( 'You cannot add that amount of &quot;%s&quot; to the cart because there is not enough stock (%s remaining).', 'woocommerce' ), $product_data->get_title(), $product_data->get_stock_quantity() ) );
920
				}
921
922
				// Stock check - this time accounting for whats already in-cart
923
				if ( $managing_stock = $product_data->managing_stock() ) {
924
					$products_qty_in_cart = $this->get_cart_item_quantities();
925
926
					if ( $product_data->is_type( 'variation' ) && true === $managing_stock ) {
927
						$check_qty = isset( $products_qty_in_cart[ $variation_id ] ) ? $products_qty_in_cart[ $variation_id ] : 0;
928
					} else {
929
						$check_qty = isset( $products_qty_in_cart[ $product_id ] ) ? $products_qty_in_cart[ $product_id ] : 0;
930
					}
931
932
					/**
933
					 * Check stock based on all items in the cart.
934
					 */
935
					if ( ! $product_data->has_enough_stock( $check_qty + $quantity ) ) {
936
						throw new Exception( sprintf(
937
							'<a href="%s" class="button wc-forward">%s</a> %s',
938
							wc_get_cart_url(),
939
							__( 'View Cart', 'woocommerce' ),
940
							sprintf( __( 'You cannot add that amount to the cart &mdash; we have %s in stock and you already have %s in your cart.', 'woocommerce' ), $product_data->get_stock_quantity(), $check_qty )
941
						) );
942
					}
943
				}
944
945
				// If cart_item_key is set, the item is already in the cart
946
				if ( $cart_item_key ) {
947
					$new_quantity = $quantity + $this->cart_contents[ $cart_item_key ]['quantity'];
948
					$this->set_quantity( $cart_item_key, $new_quantity, false );
949
				} else {
950
					$cart_item_key = $cart_id;
951
952
					// Add item after merging with $cart_item_data - hook to allow plugins to modify cart item
953
					$this->cart_contents[ $cart_item_key ] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array(
954
						'product_id'	=> $product_id,
955
						'variation_id'	=> $variation_id,
956
						'variation' 	=> $variation,
957
						'quantity' 		=> $quantity,
958
						'data'			=> $product_data
959
					) ), $cart_item_key );
960
				}
961
962
				if ( did_action( 'wp' ) ) {
963
					$this->set_cart_cookies( ! $this->is_empty() );
964
				}
965
966
				do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data );
967
968
				return $cart_item_key;
969
970
			} catch ( Exception $e ) {
971
				if ( $e->getMessage() ) {
972
					wc_add_notice( $e->getMessage(), 'error' );
973
				}
974
				return false;
975
			}
976
		}
977
978
		/**
979
		 * Remove a cart item.
980
		 *
981
		 * @since  2.3.0
982
		 * @param  string $cart_item_key
983
		 * @return bool
984
		 */
985
		public function remove_cart_item( $cart_item_key ) {
986
			if ( isset( $this->cart_contents[ $cart_item_key ] ) ) {
987
				$this->removed_cart_contents[ $cart_item_key ] = $this->cart_contents[ $cart_item_key ];
988
				unset( $this->removed_cart_contents[ $cart_item_key ]['data'] );
989
990
				do_action( 'woocommerce_remove_cart_item', $cart_item_key, $this );
991
992
				unset( $this->cart_contents[ $cart_item_key ] );
993
994
				do_action( 'woocommerce_cart_item_removed', $cart_item_key, $this );
995
996
				$this->calculate_totals();
997
998
				return true;
999
			}
1000
1001
			return false;
1002
		}
1003
1004
		/**
1005
		 * Restore a cart item.
1006
		 *
1007
		 * @param  string $cart_item_key
1008
		 * @return bool
1009
		 */
1010
		public function restore_cart_item( $cart_item_key ) {
1011
			if ( isset( $this->removed_cart_contents[ $cart_item_key ] ) ) {
1012
				$this->cart_contents[ $cart_item_key ] = $this->removed_cart_contents[ $cart_item_key ];
1013
				$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'] );
1014
1015
				do_action( 'woocommerce_restore_cart_item', $cart_item_key, $this );
1016
1017
				unset( $this->removed_cart_contents[ $cart_item_key ] );
1018
1019
				do_action( 'woocommerce_cart_item_restored', $cart_item_key, $this );
1020
1021
				$this->calculate_totals();
1022
1023
				return true;
1024
			}
1025
1026
			return false;
1027
		}
1028
1029
		/**
1030
		 * Set the quantity for an item in the cart.
1031
		 *
1032
		 * @param string	cart_item_key	contains the id of the cart item
1033
		 * @param string	quantity		contains the quantity of the item
1034
		 * @param boolean 	$refresh_totals	whether or not to calculate totals after setting the new qty
1035
		 *
1036
		 * @return bool
1037
		 */
1038
		public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) {
1039
			if ( $quantity == 0 || $quantity < 0 ) {
1040
				do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key );
1041
				unset( $this->cart_contents[ $cart_item_key ] );
1042
			} else {
1043
				$old_quantity = $this->cart_contents[ $cart_item_key ]['quantity'];
1044
				$this->cart_contents[ $cart_item_key ]['quantity'] = $quantity;
1045
				do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity );
1046
			}
1047
1048
			if ( $refresh_totals ) {
1049
				$this->calculate_totals();
1050
			}
1051
1052
			return true;
1053
		}
1054
1055
	/*-----------------------------------------------------------------------------------*/
1056
	/* Cart Calculation Functions */
1057
	/*-----------------------------------------------------------------------------------*/
1058
1059
		/**
1060
		 * Reset cart totals to the defaults. Useful before running calculations.
1061
		 *
1062
		 * @param  bool  	$unset_session If true, the session data will be forced unset.
1063
		 * @access private
1064
		 */
1065
		private function reset( $unset_session = false ) {
1066
			foreach ( $this->cart_session_data as $key => $default ) {
1067
				$this->$key = $default;
1068
				if ( $unset_session ) {
1069
					unset( WC()->session->$key );
1070
				}
1071
			}
1072
			do_action( 'woocommerce_cart_reset', $this, $unset_session );
1073
		}
1074
1075
		/**
1076
		 * Sort by subtotal.
1077
		 * @param  array $a
1078
		 * @param  array $b
1079
		 * @return int
1080
		 */
1081
		private function sort_by_subtotal( $a, $b ) {
1082
			$first_item_subtotal  = isset( $a['line_subtotal'] ) ? $a['line_subtotal'] : 0;
1083
			$second_item_subtotal = isset( $b['line_subtotal'] ) ? $b['line_subtotal'] : 0;
1084
			if ( $first_item_subtotal === $second_item_subtotal ) {
1085
				return 0;
1086
			}
1087
			return ( $first_item_subtotal < $second_item_subtotal ) ? 1 : -1;
1088
		}
1089
1090
		/**
1091
		 * Calculate totals for the items in the cart.
1092
		 */
1093
		public function calculate_totals() {
1094
			$this->reset();
1095
			$this->coupons = $this->get_coupons();
1096
1097
			do_action( 'woocommerce_before_calculate_totals', $this );
1098
1099
			if ( $this->is_empty() ) {
1100
				$this->set_session();
1101
				return;
1102
			}
1103
1104
			$tax_rates      = array();
1105
			$shop_tax_rates = array();
1106
			$cart           = $this->get_cart();
1107
1108
			/**
1109
			 * Calculate subtotals for items. This is done first so that discount logic can use the values.
1110
			 */
1111
			foreach ( $cart as $cart_item_key => $values ) {
1112
				$_product          = $values['data'];
1113
				$line_price        = $_product->get_price() * $values['quantity'];
1114
				$line_subtotal     = 0;
1115
				$line_subtotal_tax = 0;
1116
1117
				/**
1118
				 * No tax to calculate.
1119
				 */
1120
				if ( ! $_product->is_taxable() ) {
1121
1122
					// Subtotal is the undiscounted price
1123
					$this->subtotal += $line_price;
1124
					$this->subtotal_ex_tax += $line_price;
1125
1126
				/**
1127
				 * Prices include tax.
1128
				 *
1129
				 * To prevent rounding issues we need to work with the inclusive price where possible.
1130
				 * otherwise we'll see errors such as when working with a 9.99 inc price, 20% VAT which would.
1131
				 * be 8.325 leading to totals being 1p off.
1132
				 *
1133
				 * Pre tax coupons come off the price the customer thinks they are paying - tax is calculated.
1134
				 * afterwards.
1135
				 *
1136
				 * e.g. $100 bike with $10 coupon = customer pays $90 and tax worked backwards from that.
1137
				 */
1138
				} elseif ( $this->prices_include_tax ) {
1139
1140
					// Get base tax rates
1141
					if ( empty( $shop_tax_rates[ $_product->tax_class ] ) ) {
1142
						$shop_tax_rates[ $_product->tax_class ] = WC_Tax::get_base_tax_rates( $_product->tax_class );
1143
					}
1144
1145
					// Get item tax rates
1146
					if ( empty( $tax_rates[ $_product->get_tax_class() ] ) ) {
1147
						$tax_rates[ $_product->get_tax_class() ] = WC_Tax::get_rates( $_product->get_tax_class() );
1148
					}
1149
1150
					$base_tax_rates = $shop_tax_rates[ $_product->tax_class ];
1151
					$item_tax_rates = $tax_rates[ $_product->get_tax_class() ];
1152
1153
					/**
1154
					 * ADJUST TAX - Calculations when base tax is not equal to the item tax.
1155
					 *
1156
 					 * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations.
1157
 					 * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes.
1158
 					 * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk.
1159
 					 */
1160
					if ( $item_tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
1161
1162
						// Work out a new base price without the shop's base tax
1163
						$taxes                 = WC_Tax::calc_tax( $line_price, $base_tax_rates, true, true );
1164
1165
						// Now we have a new item price (excluding TAX)
1166
						$line_subtotal         = $line_price - array_sum( $taxes );
1167
1168
						// Now add modified taxes
1169
						$tax_result            = WC_Tax::calc_tax( $line_subtotal, $item_tax_rates );
1170
						$line_subtotal_tax     = array_sum( $tax_result );
1171
1172
					/**
1173
					 * Regular tax calculation (customer inside base and the tax class is unmodified.
1174
					 */
1175
					} else {
1176
1177
						// Calc tax normally
1178
						$taxes                 = WC_Tax::calc_tax( $line_price, $item_tax_rates, true );
1179
						$line_subtotal_tax     = array_sum( $taxes );
1180
						$line_subtotal         = $line_price - array_sum( $taxes );
1181
					}
1182
1183
				/**
1184
				 * Prices exclude tax.
1185
				 *
1186
				 * This calculation is simpler - work with the base, untaxed price.
1187
				 */
1188
				} else {
1189
1190
					// Get item tax rates
1191
					if ( empty( $tax_rates[ $_product->get_tax_class() ] ) ) {
1192
						$tax_rates[ $_product->get_tax_class() ] = WC_Tax::get_rates( $_product->get_tax_class() );
1193
					}
1194
1195
					$item_tax_rates        = $tax_rates[ $_product->get_tax_class() ];
1196
1197
					// Base tax for line before discount - we will store this in the order data
1198
					$taxes                 = WC_Tax::calc_tax( $line_price, $item_tax_rates );
1199
					$line_subtotal_tax     = array_sum( $taxes );
1200
1201
					$line_subtotal         = $line_price;
1202
				}
1203
1204
				// Add to main subtotal
1205
				$this->subtotal        += $line_subtotal + $line_subtotal_tax;
1206
				$this->subtotal_ex_tax += $line_subtotal;
1207
			}
1208
1209
			// Order cart items by price so coupon logic is 'fair' for customers and not based on order added to cart.
1210
			uasort( $cart, array( $this, 'sort_by_subtotal' ) );
1211
1212
			/**
1213
			 * Calculate totals for items.
1214
			 */
1215
			foreach ( $cart as $cart_item_key => $values ) {
1216
1217
				$_product = $values['data'];
1218
1219
				// Prices
1220
				$base_price = $_product->get_price();
1221
				$line_price = $_product->get_price() * $values['quantity'];
1222
1223
				// Tax data
1224
				$taxes = array();
1225
				$discounted_taxes = array();
1226
1227
				/**
1228
				 * No tax to calculate.
1229
				 */
1230
				if ( ! $_product->is_taxable() ) {
1231
1232
					// Discounted Price (price with any pre-tax discounts applied)
1233
					$discounted_price      = $this->get_discounted_price( $values, $base_price, true );
1234
					$line_subtotal_tax     = 0;
1235
					$line_subtotal         = $line_price;
1236
					$line_tax              = 0;
1237
					$line_total            = WC_Tax::round( $discounted_price * $values['quantity'] );
1238
1239
				/**
1240
				 * Prices include tax.
1241
				 */
1242
				} elseif ( $this->prices_include_tax ) {
1243
1244
					$base_tax_rates = $shop_tax_rates[ $_product->tax_class ];
1245
					$item_tax_rates = $tax_rates[ $_product->get_tax_class() ];
1246
1247
					/**
1248
					 * ADJUST TAX - Calculations when base tax is not equal to the item tax.
1249
					 *
1250
 					 * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations.
1251
 					 * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes.
1252
 					 * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk.
1253
 					 */
1254
					if ( $item_tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
1255
1256
						// Work out a new base price without the shop's base tax
1257
						$taxes             = WC_Tax::calc_tax( $line_price, $base_tax_rates, true, true );
1258
1259
						// Now we have a new item price (excluding TAX)
1260
						$line_subtotal     = round( $line_price - array_sum( $taxes ), WC_ROUNDING_PRECISION );
1261
						$taxes             = WC_Tax::calc_tax( $line_subtotal, $item_tax_rates );
1262
						$line_subtotal_tax = array_sum( $taxes );
1263
1264
						// Adjusted price (this is the price including the new tax rate)
1265
						$adjusted_price    = ( $line_subtotal + $line_subtotal_tax ) / $values['quantity'];
1266
1267
						// Apply discounts
1268
						$discounted_price  = $this->get_discounted_price( $values, $adjusted_price, true );
1269
						$discounted_taxes  = WC_Tax::calc_tax( $discounted_price * $values['quantity'], $item_tax_rates, true );
1270
						$line_tax          = array_sum( $discounted_taxes );
1271
						$line_total        = ( $discounted_price * $values['quantity'] ) - $line_tax;
1272
1273
					/**
1274
					 * Regular tax calculation (customer inside base and the tax class is unmodified.
1275
					 */
1276
					} else {
1277
1278
						// Work out a new base price without the item tax
1279
						$taxes             = WC_Tax::calc_tax( $line_price, $item_tax_rates, true );
1280
1281
						// Now we have a new item price (excluding TAX)
1282
						$line_subtotal     = $line_price - array_sum( $taxes );
1283
						$line_subtotal_tax = array_sum( $taxes );
1284
1285
						// Calc prices and tax (discounted)
1286
						$discounted_price = $this->get_discounted_price( $values, $base_price, true );
1287
						$discounted_taxes = WC_Tax::calc_tax( $discounted_price * $values['quantity'], $item_tax_rates, true );
1288
						$line_tax         = array_sum( $discounted_taxes );
1289
						$line_total       = ( $discounted_price * $values['quantity'] ) - $line_tax;
1290
					}
1291
1292
					// Tax rows - merge the totals we just got
1293 View Code Duplication
					foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1294
						$this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
1295
					}
1296
1297
				/**
1298
				 * Prices exclude tax.
1299
				 */
1300
				} else {
1301
1302
					$item_tax_rates        = $tax_rates[ $_product->get_tax_class() ];
1303
1304
					// Work out a new base price without the shop's base tax
1305
					$taxes                 = WC_Tax::calc_tax( $line_price, $item_tax_rates );
1306
1307
					// Now we have the item price (excluding TAX)
1308
					$line_subtotal         = $line_price;
1309
					$line_subtotal_tax     = array_sum( $taxes );
1310
1311
					// Now calc product rates
1312
					$discounted_price      = $this->get_discounted_price( $values, $base_price, true );
1313
					$discounted_taxes      = WC_Tax::calc_tax( $discounted_price * $values['quantity'], $item_tax_rates );
1314
					$discounted_tax_amount = array_sum( $discounted_taxes );
1315
					$line_tax              = $discounted_tax_amount;
1316
					$line_total            = $discounted_price * $values['quantity'];
1317
1318
					// Tax rows - merge the totals we just got
1319 View Code Duplication
					foreach ( array_keys( $this->taxes + $discounted_taxes ) as $key ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1320
						$this->taxes[ $key ] = ( isset( $discounted_taxes[ $key ] ) ? $discounted_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
1321
					}
1322
				}
1323
1324
				// Cart contents total is based on discounted prices and is used for the final total calculation
1325
				$this->cart_contents_total += $line_total;
1326
1327
				// Store costs + taxes for lines
1328
				$this->cart_contents[ $cart_item_key ]['line_total']        = $line_total;
1329
				$this->cart_contents[ $cart_item_key ]['line_tax']          = $line_tax;
1330
				$this->cart_contents[ $cart_item_key ]['line_subtotal']     = $line_subtotal;
1331
				$this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $line_subtotal_tax;
1332
1333
				// Store rates ID and costs - Since 2.2
1334
				$this->cart_contents[ $cart_item_key ]['line_tax_data']     = array( 'total' => $discounted_taxes, 'subtotal' => $taxes );
1335
			}
1336
1337
			// Only calculate the grand total + shipping if on the cart/checkout
1338
			if ( is_checkout() || is_cart() || defined('WOOCOMMERCE_CHECKOUT') || defined('WOOCOMMERCE_CART') ) {
1339
1340
				// Calculate the Shipping
1341
				$this->calculate_shipping();
1342
1343
				// Trigger the fees API where developers can add fees to the cart
1344
				$this->calculate_fees();
1345
1346
				// Total up/round taxes and shipping taxes
1347
				if ( $this->round_at_subtotal ) {
1348
					$this->tax_total          = WC_Tax::get_tax_total( $this->taxes );
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
1349
					$this->shipping_tax_total = WC_Tax::get_tax_total( $this->shipping_taxes );
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
1350
					$this->taxes              = array_map( array( 'WC_Tax', 'round' ), $this->taxes );
1351
					$this->shipping_taxes     = array_map( array( 'WC_Tax', 'round' ), $this->shipping_taxes );
1352
				} else {
1353
					$this->tax_total          = array_sum( $this->taxes );
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
1354
					$this->shipping_tax_total = array_sum( $this->shipping_taxes );
0 ignored issues
show
Documentation Bug introduced by
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 $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

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;
}
Loading history...
1355
				}
1356
1357
				// VAT exemption done at this point - so all totals are correct before exemption
1358
				if ( WC()->customer->is_vat_exempt() ) {
1359
					$this->remove_taxes();
1360
				}
1361
1362
				// Allow plugins to hook and alter totals before final total is calculated
1363
				do_action( 'woocommerce_calculate_totals', $this );
1364
1365
				// Grand Total - Discounted product prices, discounted tax, shipping cost + tax
1366
				$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 ) );
1367
1368
			} else {
1369
1370
				// Set tax total to sum of all tax rows
1371
				$this->tax_total = WC_Tax::get_tax_total( $this->taxes );
1372
1373
				// VAT exemption done at this point - so all totals are correct before exemption
1374
				if ( WC()->customer->is_vat_exempt() ) {
1375
					$this->remove_taxes();
1376
				}
1377
			}
1378
1379
			do_action( 'woocommerce_after_calculate_totals', $this );
1380
1381
			$this->set_session();
1382
		}
1383
1384
		/**
1385
		 * remove_taxes function.
1386
		 */
1387
		public function remove_taxes() {
1388
			$this->shipping_tax_total = $this->tax_total = 0;
0 ignored issues
show
Documentation Bug introduced by
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;
Loading history...
Documentation Bug introduced by
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;
Loading history...
1389
			$this->subtotal           = $this->subtotal_ex_tax;
1390
1391
			foreach ( $this->cart_contents as $cart_item_key => $item ) {
1392
				$this->cart_contents[ $cart_item_key ]['line_subtotal_tax'] = $this->cart_contents[ $cart_item_key ]['line_tax'] = 0;
1393
				$this->cart_contents[ $cart_item_key ]['line_tax_data']     = array( 'total' => array(), 'subtotal' => array() );
1394
			}
1395
1396
			// If true, zero rate is applied so '0' tax is displayed on the frontend rather than nothing.
1397
			if ( apply_filters( 'woocommerce_cart_remove_taxes_apply_zero_rate', true ) ) {
1398
				$this->taxes = $this->shipping_taxes = array( apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) => 0 );
1399
			} else {
1400
				$this->taxes = $this->shipping_taxes = array();
1401
			}
1402
		}
1403
1404
		/**
1405
		 * looks at the totals to see if payment is actually required.
1406
		 *
1407
		 * @return bool
1408
		 */
1409
		public function needs_payment() {
1410
			return apply_filters( 'woocommerce_cart_needs_payment', $this->total > 0, $this );
1411
		}
1412
1413
	/*-----------------------------------------------------------------------------------*/
1414
	/* Shipping related functions */
1415
	/*-----------------------------------------------------------------------------------*/
1416
1417
		/**
1418
		 * Uses the shipping class to calculate shipping then gets the totals when its finished.
1419
		 */
1420
		public function calculate_shipping() {
1421
			if ( $this->needs_shipping() && $this->show_shipping() ) {
1422
				WC()->shipping->calculate_shipping( $this->get_shipping_packages() );
1423
			} else {
1424
				WC()->shipping->reset_shipping();
1425
			}
1426
1427
			// Get totals for the chosen shipping method
1428
			$this->shipping_total 		= WC()->shipping->shipping_total;	// Shipping Total
1429
			$this->shipping_taxes		= WC()->shipping->shipping_taxes;	// Shipping Taxes
1430
		}
1431
1432
		/**
1433
		 * Get packages to calculate shipping for.
1434
		 *
1435
		 * This lets us calculate costs for carts that are shipped to multiple locations.
1436
		 *
1437
		 * Shipping methods are responsible for looping through these packages.
1438
		 *
1439
		 * By default we pass the cart itself as a package - plugins can change this.
1440
		 * through the filter and break it up.
1441
		 *
1442
		 * @since 1.5.4
1443
		 * @return array of cart items
1444
		 */
1445
		public function get_shipping_packages() {
1446
			// Packages array for storing 'carts'
1447
			$packages = array();
1448
1449
			$packages[0]['contents']                 = $this->get_cart();		// Items in the package
1450
			$packages[0]['contents_cost']            = 0;						// Cost of items in the package, set below
1451
			$packages[0]['applied_coupons']          = $this->applied_coupons;
1452
			$packages[0]['user']['ID']               = get_current_user_id();
1453
			$packages[0]['destination']['country']   = WC()->customer->get_shipping_country();
1454
			$packages[0]['destination']['state']     = WC()->customer->get_shipping_state();
1455
			$packages[0]['destination']['postcode']  = WC()->customer->get_shipping_postcode();
1456
			$packages[0]['destination']['city']      = WC()->customer->get_shipping_city();
1457
			$packages[0]['destination']['address']   = WC()->customer->get_shipping_address();
1458
			$packages[0]['destination']['address_2'] = WC()->customer->get_shipping_address_2();
1459
1460
			foreach ( $this->get_cart() as $item ) {
1461
				if ( $item['data']->needs_shipping() ) {
1462
					if ( isset( $item['line_total'] ) ) {
1463
						$packages[0]['contents_cost'] += $item['line_total'];
1464
					}
1465
				}
1466
			}
1467
1468
			return apply_filters( 'woocommerce_cart_shipping_packages', $packages );
1469
		}
1470
1471
		/**
1472
		 * Looks through the cart to see if shipping is actually required.
1473
		 *
1474
		 * @return bool whether or not the cart needs shipping
1475
		 */
1476
		public function needs_shipping() {
1477
			if ( get_option( 'woocommerce_calc_shipping' ) === 'no' ) {
1478
				return false;
1479
			}
1480
1481
			$needs_shipping = false;
1482
1483
			if ( $this->cart_contents ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->cart_contents of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1484
				foreach ( $this->cart_contents as $cart_item_key => $values ) {
1485
					$_product = $values['data'];
1486
					if ( $_product->needs_shipping() ) {
1487
						$needs_shipping = true;
1488
					}
1489
				}
1490
			}
1491
1492
			return apply_filters( 'woocommerce_cart_needs_shipping', $needs_shipping );
1493
		}
1494
1495
		/**
1496
		 * Should the shipping address form be shown.
1497
		 *
1498
		 * @return bool
1499
		 */
1500
		function needs_shipping_address() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1501
1502
			$needs_shipping_address = false;
1503
1504
			if ( $this->needs_shipping() === true && ! wc_ship_to_billing_address_only() ) {
1505
				$needs_shipping_address = true;
1506
			}
1507
1508
			return apply_filters( 'woocommerce_cart_needs_shipping_address', $needs_shipping_address );
1509
		}
1510
1511
		/**
1512
		 * Sees if the customer has entered enough data to calc the shipping yet.
1513
		 *
1514
		 * @return bool
1515
		 */
1516
		public function show_shipping() {
1517
			if ( get_option('woocommerce_calc_shipping') == 'no' || ! is_array( $this->cart_contents ) )
1518
				return false;
1519
1520
			if ( get_option( 'woocommerce_shipping_cost_requires_address' ) == 'yes' ) {
1521
				if ( ! WC()->customer->has_calculated_shipping() ) {
1522
					if ( ! WC()->customer->get_shipping_country() || ( ! WC()->customer->get_shipping_state() && ! WC()->customer->get_shipping_postcode() ) )
1523
						return false;
1524
				}
1525
			}
1526
1527
			$show_shipping = true;
1528
1529
			return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', $show_shipping );
1530
1531
		}
1532
1533
		/**
1534
		 * Sees if we need a shipping address.
1535
		 *
1536
		 * @deprecated 2.5.0 in favor to wc_ship_to_billing_address_only()
1537
		 *
1538
		 * @return bool
1539
		 */
1540
		public function ship_to_billing_address_only() {
1541
			return wc_ship_to_billing_address_only();
1542
		}
1543
1544
		/**
1545
		 * Gets the shipping total (after calculation).
1546
		 *
1547
		 * @return string price or string for the shipping total
1548
		 */
1549
		public function get_cart_shipping_total() {
1550
			if ( isset( $this->shipping_total ) ) {
1551
				if ( $this->shipping_total > 0 ) {
1552
1553
					// Display varies depending on settings
1554
					if ( $this->tax_display_cart == 'excl' ) {
1555
1556
						$return = wc_price( $this->shipping_total );
1557
1558
						if ( $this->shipping_tax_total > 0 && $this->prices_include_tax ) {
1559
							$return .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1560
						}
1561
1562
						return $return;
1563
1564
					} else {
1565
1566
						$return = wc_price( $this->shipping_total + $this->shipping_tax_total );
1567
1568
						if ( $this->shipping_tax_total > 0 && ! $this->prices_include_tax ) {
1569
							$return .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
1570
						}
1571
1572
						return $return;
1573
1574
					}
1575
1576
				} else {
1577
					return __( 'Free!', 'woocommerce' );
1578
				}
1579
			}
1580
1581
			return '';
1582
		}
1583
1584
	/*-----------------------------------------------------------------------------------*/
1585
	/* Coupons/Discount related functions */
1586
	/*-----------------------------------------------------------------------------------*/
1587
1588
		/**
1589
		 * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error.
1590
		 *
1591
		 * Checks two types of coupons:
1592
		 *  1. Where a list of customer emails are set (limits coupon usage to those defined).
1593
		 *  2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email).
1594
		 *
1595
		 * @param array $posted
1596
		 */
1597
		public function check_customer_coupons( $posted ) {
1598
			if ( ! empty( $this->applied_coupons ) ) {
1599
				foreach ( $this->applied_coupons as $code ) {
1600
					$coupon = new WC_Coupon( $code );
1601
1602
					if ( $coupon->is_valid() ) {
1603
1604
						// Limit to defined email addresses
1605
						if ( is_array( $coupon->customer_email ) && sizeof( $coupon->customer_email ) > 0 ) {
1606
							$check_emails           = array();
1607
							$coupon->customer_email = array_map( 'sanitize_email', $coupon->customer_email );
1608
1609
							if ( is_user_logged_in() ) {
1610
								$current_user   = wp_get_current_user();
1611
								$check_emails[] = $current_user->user_email;
1612
							}
1613
							$check_emails[] = $posted['billing_email'];
1614
							$check_emails   = array_map( 'sanitize_email', array_map( 'strtolower', $check_emails ) );
1615
1616
							if ( 0 == sizeof( array_intersect( $check_emails, $coupon->customer_email ) ) ) {
1617
								$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
1618
1619
								// Remove the coupon
1620
								$this->remove_coupon( $code );
1621
1622
								// Flag totals for refresh
1623
								WC()->session->set( 'refresh_totals', true );
1624
							}
1625
						}
1626
1627
						// Usage limits per user - check against billing and user email and user ID
1628
						if ( $coupon->usage_limit_per_user > 0 ) {
1629
							$check_emails = array();
1630
							$used_by      = $coupon->get_used_by();
1631
1632
							if ( is_user_logged_in() ) {
1633
								$current_user   = wp_get_current_user();
1634
								$check_emails[] = sanitize_email( $current_user->user_email );
1635
								$usage_count    = sizeof( array_keys( $used_by, get_current_user_id() ) );
1636
							} else {
1637
								$check_emails[] = sanitize_email( $posted['billing_email'] );
1638
								$user           = get_user_by( 'email', $posted['billing_email'] );
1639
								if ( $user ) {
1640
									$usage_count = sizeof( array_keys( $used_by, $user->ID ) );
1641
								} else {
1642
									$usage_count = 0;
1643
								}
1644
							}
1645
1646
							foreach ( $check_emails as $check_email ) {
1647
								$usage_count = $usage_count + sizeof( array_keys( $used_by, $check_email ) );
1648
							}
1649
1650 View Code Duplication
							if ( $usage_count >= $coupon->usage_limit_per_user ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1651
								$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
1652
1653
								// Remove the coupon
1654
								$this->remove_coupon( $code );
1655
1656
								// Flag totals for refresh
1657
								WC()->session->set( 'refresh_totals', true );
1658
							}
1659
						}
1660
					}
1661
				}
1662
			}
1663
		}
1664
1665
		/**
1666
		 * Returns whether or not a discount has been applied.
1667
		 *
1668
		 * @return bool
1669
		 */
1670
		public function has_discount( $coupon_code ) {
1671
			return in_array( apply_filters( 'woocommerce_coupon_code', $coupon_code ), $this->applied_coupons );
1672
		}
1673
1674
		/**
1675
		 * Applies a coupon code passed to the method.
1676
		 *
1677
		 * @param string $coupon_code - The code to apply
1678
		 * @return bool	True if the coupon is applied, false if it does not exist or cannot be applied
1679
		 */
1680
		public function add_discount( $coupon_code ) {
1681
			// Coupons are globally disabled
1682
			if ( ! wc_coupons_enabled() ) {
1683
				return false;
1684
			}
1685
1686
			// Sanitize coupon code
1687
			$coupon_code = apply_filters( 'woocommerce_coupon_code', $coupon_code );
1688
1689
			// Get the coupon
1690
			$the_coupon = new WC_Coupon( $coupon_code );
1691
1692
			// Check it can be used with cart
1693
			if ( ! $the_coupon->is_valid() ) {
1694
				wc_add_notice( $the_coupon->get_error_message(), 'error' );
1695
				return false;
1696
			}
1697
1698
			// Check if applied
1699
			if ( $this->has_discount( $coupon_code ) ) {
1700
				$the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED );
1701
				return false;
1702
			}
1703
1704
			// If its individual use then remove other coupons
1705
			if ( $the_coupon->individual_use == 'yes' ) {
1706
				$this->applied_coupons = apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $the_coupon, $this->applied_coupons );
1707
			}
1708
1709
			if ( $this->applied_coupons ) {
1710
				foreach ( $this->applied_coupons as $code ) {
1711
					$coupon = new WC_Coupon( $code );
1712
1713
					if ( $coupon->individual_use == 'yes' && false === apply_filters( 'woocommerce_apply_with_individual_use_coupon', false, $the_coupon, $coupon, $this->applied_coupons ) ) {
1714
1715
						// Reject new coupon
1716
						$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED_INDIV_USE_ONLY );
1717
1718
						return false;
1719
					}
1720
				}
1721
			}
1722
1723
			$this->applied_coupons[] = $coupon_code;
1724
1725
			// Choose free shipping
1726
			if ( $the_coupon->enable_free_shipping() ) {
1727
				$packages = WC()->shipping->get_packages();
1728
				$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
1729
1730
				foreach ( $packages as $i => $package ) {
1731
					$chosen_shipping_methods[ $i ] = 'free_shipping';
1732
				}
1733
1734
				WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
1735
			}
1736
1737
			$the_coupon->add_coupon_message( WC_Coupon::WC_COUPON_SUCCESS );
1738
1739
			do_action( 'woocommerce_applied_coupon', $coupon_code );
1740
1741
			return true;
1742
		}
1743
1744
		/**
1745
		 * Get array of applied coupon objects and codes.
1746
		 * @return array of applied coupons
1747
		 */
1748
		public function get_coupons( $deprecated = null ) {
1749
			$coupons = array();
1750
1751
			if ( 'order' === $deprecated ) {
1752
				return $coupons;
1753
			}
1754
1755
			foreach ( $this->get_applied_coupons() as $code ) {
1756
				$coupon = new WC_Coupon( $code );
1757
				$coupons[ $code ] = $coupon;
1758
			}
1759
1760
			return $coupons;
1761
		}
1762
1763
		/**
1764
		 * Gets the array of applied coupon codes.
1765
		 *
1766
		 * @return array of applied coupons
1767
		 */
1768
		public function get_applied_coupons() {
1769
			return $this->applied_coupons;
1770
		}
1771
1772
		/**
1773
		 * Get the discount amount for a used coupon.
1774
		 * @param  string $code coupon code
1775
		 * @param  bool inc or ex tax
1776
		 * @return float discount amount
1777
		 */
1778
		public function get_coupon_discount_amount( $code, $ex_tax = true ) {
1779
			$discount_amount = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] : 0;
1780
1781
			if ( ! $ex_tax ) {
1782
				$discount_amount += $this->get_coupon_discount_tax_amount( $code );
1783
			}
1784
1785
			return wc_cart_round_discount( $discount_amount, $this->dp );
1786
		}
1787
1788
		/**
1789
		 * Get the discount tax amount for a used coupon (for tax inclusive prices).
1790
		 * @param  string $code coupon code
1791
		 * @param  bool inc or ex tax
1792
		 * @return float discount amount
1793
		 */
1794
		public function get_coupon_discount_tax_amount( $code ) {
1795
			return wc_cart_round_discount( isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] : 0, $this->dp );
1796
		}
1797
1798
		/**
1799
		 * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax.
1800
		 */
1801
		public function remove_coupons( $deprecated = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $deprecated is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1802
			$this->applied_coupons = $this->coupon_discount_amounts = $this->coupon_discount_tax_amounts = $this->coupon_applied_count = array();
1803
			WC()->session->set( 'applied_coupons', array() );
1804
			WC()->session->set( 'coupon_discount_amounts', array() );
1805
			WC()->session->set( 'coupon_discount_tax_amounts', array() );
1806
		}
1807
1808
		/**
1809
		 * Remove a single coupon by code.
1810
		 * @param  string $coupon_code Code of the coupon to remove
1811
		 * @return bool
1812
		 */
1813
		public function remove_coupon( $coupon_code ) {
1814
			// Coupons are globally disabled
1815
			if ( ! wc_coupons_enabled() ) {
1816
				return false;
1817
			}
1818
1819
			// Get the coupon
1820
			$coupon_code  = apply_filters( 'woocommerce_coupon_code', $coupon_code );
1821
			$position     = array_search( $coupon_code, $this->applied_coupons );
1822
1823
			if ( $position !== false ) {
1824
				unset( $this->applied_coupons[ $position ] );
1825
			}
1826
1827
			WC()->session->set( 'applied_coupons', $this->applied_coupons );
1828
1829
			do_action( 'woocommerce_removed_coupon', $coupon_code );
1830
1831
			return true;
1832
		}
1833
1834
		/**
1835
		 * Function to apply discounts to a product and get the discounted price (before tax is applied).
1836
		 *
1837
		 * @param mixed $values
1838
		 * @param mixed $price
1839
		 * @param bool $add_totals (default: false)
1840
		 * @return float price
1841
		 */
1842
		public function get_discounted_price( $values, $price, $add_totals = false ) {
1843
			if ( ! $price ) {
1844
				return $price;
1845
			}
1846
1847
			$undiscounted_price = $price;
1848
1849
			if ( ! empty( $this->coupons ) ) {
1850
				$product = $values['data'];
1851
1852
				foreach ( $this->coupons as $code => $coupon ) {
1853
					if ( $coupon->is_valid() && ( $coupon->is_valid_for_product( $product, $values ) || $coupon->is_valid_for_cart() ) ) {
1854
						$discount_amount = $coupon->get_discount_amount( ( 'yes' === get_option( 'woocommerce_calc_discounts_sequentially', 'no' ) ? $price : $undiscounted_price ), $values, true );
1855
						$discount_amount = min( $price, $discount_amount );
1856
						$price           = max( $price - $discount_amount, 0 );
1857
1858
						// Store the totals for DISPLAY in the cart
1859
						if ( $add_totals ) {
1860
							$total_discount     = $discount_amount * $values['quantity'];
1861
							$total_discount_tax = 0;
1862
1863
							if ( wc_tax_enabled() ) {
1864
								$tax_rates          = WC_Tax::get_rates( $product->get_tax_class() );
1865
								$taxes              = WC_Tax::calc_tax( $discount_amount, $tax_rates, $this->prices_include_tax );
1866
								$total_discount_tax = WC_Tax::get_tax_total( $taxes ) * $values['quantity'];
1867
								$total_discount     = $this->prices_include_tax ? $total_discount - $total_discount_tax : $total_discount;
1868
								$this->discount_cart_tax += $total_discount_tax;
1869
							}
1870
1871
							$this->discount_cart     += $total_discount;
1872
							$this->increase_coupon_discount_amount( $code, $total_discount, $total_discount_tax );
1873
							$this->increase_coupon_applied_count( $code, $values['quantity'] );
1874
						}
1875
					}
1876
				}
1877
			}
1878
1879
			return apply_filters( 'woocommerce_get_discounted_price', $price, $values, $this );
1880
		}
1881
1882
		/**
1883
		 * Store how much discount each coupon grants.
1884
		 *
1885
		 * @access private
1886
		 * @param string $code
1887
		 * @param double $amount
1888
		 * @param double $tax
1889
		 */
1890
		private function increase_coupon_discount_amount( $code, $amount, $tax ) {
1891
			$this->coupon_discount_amounts[ $code ]     = isset( $this->coupon_discount_amounts[ $code ] ) ? $this->coupon_discount_amounts[ $code ] + $amount : $amount;
1892
			$this->coupon_discount_tax_amounts[ $code ] = isset( $this->coupon_discount_tax_amounts[ $code ] ) ? $this->coupon_discount_tax_amounts[ $code ] + $tax : $tax;
1893
		}
1894
1895
		/**
1896
		 * Store how many times each coupon is applied to cart/items.
1897
		 *
1898
		 * @access private
1899
		 * @param string $code
1900
		 * @param integer $count
1901
		 */
1902
		private function increase_coupon_applied_count( $code, $count = 1 ) {
1903
			if ( empty( $this->coupon_applied_count[ $code ] ) ) {
1904
				$this->coupon_applied_count[ $code ] = 0;
1905
			}
1906
			$this->coupon_applied_count[ $code ] += $count;
1907
		}
1908
1909
	/*-----------------------------------------------------------------------------------*/
1910
	/* Fees API to add additional costs to orders */
1911
	/*-----------------------------------------------------------------------------------*/
1912
1913
		/**
1914
		 * Add additional fee to the cart.
1915
		 *
1916
		 * @param string $name Unique name for the fee. Multiple fees of the same name cannot be added.
1917
		 * @param float $amount Fee amount.
1918
		 * @param bool $taxable (default: false) Is the fee taxable?
1919
		 * @param string $tax_class (default: '') The tax class for the fee if taxable. A blank string is standard tax class.
1920
		 */
1921
		public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) {
1922
1923
			$new_fee_id = sanitize_title( $name );
1924
1925
			// Only add each fee once
1926
			foreach ( $this->fees as $fee ) {
1927
				if ( $fee->id == $new_fee_id ) {
1928
					return;
1929
				}
1930
			}
1931
1932
			$new_fee            = new stdClass();
1933
			$new_fee->id        = $new_fee_id;
1934
			$new_fee->name      = esc_attr( $name );
1935
			$new_fee->amount    = (float) esc_attr( $amount );
1936
			$new_fee->tax_class = $tax_class;
1937
			$new_fee->taxable   = $taxable ? true : false;
1938
			$new_fee->tax       = 0;
1939
			$new_fee->tax_data  = array();
1940
			$this->fees[]       = $new_fee;
1941
		}
1942
1943
		/**
1944
		 * get_fees function.
1945
		 *
1946
		 * @return array
1947
		 */
1948
		public function get_fees() {
1949
			return array_filter( (array) $this->fees );
1950
		}
1951
1952
		/**
1953
		 * Calculate fees.
1954
		 */
1955
		public function calculate_fees() {
1956
			// Reset fees before calculation
1957
			$this->fee_total = 0;
0 ignored issues
show
Documentation Bug introduced by
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;
Loading history...
1958
			$this->fees      = array();
1959
1960
			// Fire an action where developers can add their fees
1961
			do_action( 'woocommerce_cart_calculate_fees', $this );
1962
1963
			// If fees were added, total them and calculate tax
1964
			if ( ! empty( $this->fees ) ) {
1965
				foreach ( $this->fees as $fee_key => $fee ) {
1966
					$this->fee_total += $fee->amount;
1967
1968
					if ( $fee->taxable ) {
1969
						// Get tax rates
1970
						$tax_rates = WC_Tax::get_rates( $fee->tax_class );
1971
						$fee_taxes = WC_Tax::calc_tax( $fee->amount, $tax_rates, false );
1972
1973
						if ( ! empty( $fee_taxes ) ) {
1974
							// Set the tax total for this fee
1975
							$this->fees[ $fee_key ]->tax = array_sum( $fee_taxes );
1976
1977
							// Set tax data - Since 2.2
1978
							$this->fees[ $fee_key ]->tax_data = $fee_taxes;
1979
1980
							// Tax rows - merge the totals we just got
1981 View Code Duplication
							foreach ( array_keys( $this->taxes + $fee_taxes ) as $key ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1982
								$this->taxes[ $key ] = ( isset( $fee_taxes[ $key ] ) ? $fee_taxes[ $key ] : 0 ) + ( isset( $this->taxes[ $key ] ) ? $this->taxes[ $key ] : 0 );
1983
							}
1984
						}
1985
					}
1986
				}
1987
			}
1988
		}
1989
1990
	/*-----------------------------------------------------------------------------------*/
1991
	/* Get Formatted Totals */
1992
	/*-----------------------------------------------------------------------------------*/
1993
1994
		/**
1995
		 * Gets the order total (after calculation).
1996
		 *
1997
		 * @return string formatted price
1998
		 */
1999
		public function get_total() {
2000
			return apply_filters( 'woocommerce_cart_total', wc_price( $this->total ) );
2001
		}
2002
2003
		/**
2004
		 * Gets the total excluding taxes.
2005
		 *
2006
		 * @return string formatted price
2007
		 */
2008
		public function get_total_ex_tax() {
2009
			$total = $this->total - $this->tax_total - $this->shipping_tax_total;
2010
			if ( $total < 0 ) {
2011
				$total = 0;
2012
			}
2013
			return apply_filters( 'woocommerce_cart_total_ex_tax', wc_price( $total ) );
2014
		}
2015
2016
		/**
2017
		 * Gets the cart contents total (after calculation).
2018
		 *
2019
		 * @return string formatted price
2020
		 */
2021
		public function get_cart_total() {
2022
			if ( ! $this->prices_include_tax ) {
2023
				$cart_contents_total = wc_price( $this->cart_contents_total );
2024
			} else {
2025
				$cart_contents_total = wc_price( $this->cart_contents_total + $this->tax_total );
2026
			}
2027
2028
			return apply_filters( 'woocommerce_cart_contents_total', $cart_contents_total );
2029
		}
2030
2031
		/**
2032
		 * Gets the sub total (after calculation).
2033
		 *
2034
		 * @params bool whether to include compound taxes
2035
		 * @return string formatted price
2036
		 */
2037
		public function get_cart_subtotal( $compound = false ) {
2038
2039
			// If the cart has compound tax, we want to show the subtotal as
2040
			// cart + shipping + non-compound taxes (after discount)
2041
			if ( $compound ) {
2042
2043
				$cart_subtotal = wc_price( $this->cart_contents_total + $this->shipping_total + $this->get_taxes_total( false, false ) );
2044
2045
			// Otherwise we show cart items totals only (before discount)
2046
			} else {
2047
2048
				// Display varies depending on settings
2049
				if ( $this->tax_display_cart == 'excl' ) {
2050
2051
					$cart_subtotal = wc_price( $this->subtotal_ex_tax );
2052
2053
					if ( $this->tax_total > 0 && $this->prices_include_tax ) {
2054
						$cart_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
2055
					}
2056
2057
				} else {
2058
2059
					$cart_subtotal = wc_price( $this->subtotal );
2060
2061
					if ( $this->tax_total > 0 && !$this->prices_include_tax ) {
2062
						$cart_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
2063
					}
2064
2065
				}
2066
			}
2067
2068
			return apply_filters( 'woocommerce_cart_subtotal', $cart_subtotal, $compound, $this );
2069
		}
2070
2071
		/**
2072
		 * Get the product row price per item.
2073
		 *
2074
		 * @param WC_Product $_product
2075
		 * @return string formatted price
2076
		 */
2077
		public function get_product_price( $_product ) {
2078
			if ( $this->tax_display_cart == 'excl' ) {
2079
				$product_price = $_product->get_price_excluding_tax();
2080
			} else {
2081
				$product_price = $_product->get_price_including_tax();
2082
			}
2083
2084
			return apply_filters( 'woocommerce_cart_product_price', wc_price( $product_price ), $_product );
2085
		}
2086
2087
		/**
2088
		 * Get the product row subtotal.
2089
		 *
2090
		 * Gets the tax etc to avoid rounding issues.
2091
		 *
2092
		 * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate.
2093
		 *
2094
		 * @param WC_Product $_product
2095
		 * @param int quantity
2096
		 * @return string formatted price
2097
		 */
2098
		public function get_product_subtotal( $_product, $quantity ) {
2099
2100
			$price 			= $_product->get_price();
2101
			$taxable 		= $_product->is_taxable();
2102
2103
			// Taxable
2104
			if ( $taxable ) {
2105
2106
				if ( $this->tax_display_cart == 'excl' ) {
2107
2108
					$row_price        = $_product->get_price_excluding_tax( $quantity );
2109
					$product_subtotal = wc_price( $row_price );
2110
2111
					if ( $this->prices_include_tax && $this->tax_total > 0 ) {
2112
						$product_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
2113
					}
2114
2115
				} else {
2116
2117
					$row_price        = $_product->get_price_including_tax( $quantity );
2118
					$product_subtotal = wc_price( $row_price );
2119
2120
					if ( ! $this->prices_include_tax && $this->tax_total > 0 ) {
2121
						$product_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
2122
					}
2123
2124
				}
2125
2126
			// Non-taxable
2127
			} else {
2128
2129
				$row_price        = $price * $quantity;
2130
				$product_subtotal = wc_price( $row_price );
2131
2132
			}
2133
2134
			return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $_product, $quantity, $this );
2135
		}
2136
2137
		/**
2138
		 * Gets the cart tax (after calculation).
2139
		 *
2140
		 * @return string formatted price
2141
		 */
2142
		public function get_cart_tax() {
2143
			$cart_total_tax = wc_round_tax_total( $this->tax_total + $this->shipping_tax_total );
2144
2145
			return apply_filters( 'woocommerce_get_cart_tax', $cart_total_tax ? wc_price( $cart_total_tax ) : '' );
2146
		}
2147
2148
		/**
2149
		 * Get a tax amount.
2150
		 * @param  string $tax_rate_id
2151
		 * @return float amount
2152
		 */
2153
		public function get_tax_amount( $tax_rate_id ) {
2154
			return isset( $this->taxes[ $tax_rate_id ] ) ? $this->taxes[ $tax_rate_id ] : 0;
2155
		}
2156
2157
		/**
2158
		 * Get a tax amount.
2159
		 * @param  string $tax_rate_id
2160
		 * @return float amount
2161
		 */
2162
		public function get_shipping_tax_amount( $tax_rate_id ) {
2163
			return isset( $this->shipping_taxes[ $tax_rate_id ] ) ? $this->shipping_taxes[ $tax_rate_id ] : 0;
2164
		}
2165
2166
		/**
2167
		 * Get tax row amounts with or without compound taxes includes.
2168
		 *
2169
		 * @param  boolean $compound True if getting compound taxes
2170
		 * @param  boolean $display  True if getting total to display
2171
		 * @return float price
2172
		 */
2173
		public function get_taxes_total( $compound = true, $display = true ) {
2174
			$total = 0;
2175 View Code Duplication
			foreach ( $this->taxes as $key => $tax ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2176
				if ( ! $compound && WC_Tax::is_compound( $key ) ) continue;
2177
				$total += $tax;
2178
			}
2179 View Code Duplication
			foreach ( $this->shipping_taxes as $key => $tax ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2180
				if ( ! $compound && WC_Tax::is_compound( $key ) ) continue;
2181
				$total += $tax;
2182
			}
2183
			if ( $display ) {
2184
				$total = wc_round_tax_total( $total );
2185
			}
2186
			return apply_filters( 'woocommerce_cart_taxes_total', $total, $compound, $display, $this );
2187
		}
2188
2189
		/**
2190
		 * Get the total of all cart discounts.
2191
		 *
2192
		 * @return float
2193
		 */
2194
		public function get_cart_discount_total() {
2195
			return wc_cart_round_discount( $this->discount_cart, $this->dp );
2196
		}
2197
2198
		/**
2199
		 * Get the total of all cart tax discounts (used for discounts on tax inclusive prices).
2200
		 *
2201
		 * @return float
2202
		 */
2203
		public function get_cart_discount_tax_total() {
2204
			return wc_cart_round_discount( $this->discount_cart_tax, $this->dp );
2205
		}
2206
2207
		/**
2208
		 * Gets the total discount amount - both kinds.
2209
		 *
2210
		 * @return mixed formatted price or false if there are none
2211
		 */
2212
		public function get_total_discount() {
2213
			if ( $this->get_cart_discount_total() ) {
2214
				$total_discount = wc_price( $this->get_cart_discount_total() );
2215
			} else {
2216
				$total_discount = false;
2217
			}
2218
			return apply_filters( 'woocommerce_cart_total_discount', $total_discount, $this );
2219
		}
2220
2221
		/**
2222
		 * Gets the total (product) discount amount - these are applied before tax.
2223
		 *
2224
		 * @deprecated Order discounts (after tax) removed in 2.3 so multiple methods for discounts are no longer required.
2225
		 * @return mixed formatted price or false if there are none
2226
		 */
2227
		public function get_discounts_before_tax() {
2228
			_deprecated_function( 'get_discounts_before_tax', '2.3', 'get_total_discount' );
2229
			if ( $this->get_cart_discount_total() ) {
2230
				$discounts_before_tax = wc_price( $this->get_cart_discount_total() );
2231
			} else {
2232
				$discounts_before_tax = false;
2233
			}
2234
			return apply_filters( 'woocommerce_cart_discounts_before_tax', $discounts_before_tax, $this );
2235
		}
2236
2237
		/**
2238
		 * Get the total of all order discounts (after tax discounts).
2239
		 *
2240
		 * @deprecated Order discounts (after tax) removed in 2.3
2241
		 * @return integer
2242
		 */
2243
		public function get_order_discount_total() {
2244
			_deprecated_function( 'get_order_discount_total', '2.3' );
2245
			return 0;
2246
		}
2247
2248
		/**
2249
		 * Function to apply cart discounts after tax.
2250
 		 * @deprecated Coupons can not be applied after tax
2251
		 */
2252
		public function apply_cart_discounts_after_tax( $values, $price ) {
0 ignored issues
show
Unused Code introduced by
The parameter $values is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $price is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2253
			_deprecated_function( 'apply_cart_discounts_after_tax', '2.3' );
2254
		}
2255
2256
		/**
2257
		 * Function to apply product discounts after tax.
2258
		 * @deprecated Coupons can not be applied after tax
2259
		 */
2260
		public function apply_product_discounts_after_tax( $values, $price ) {
0 ignored issues
show
Unused Code introduced by
The parameter $values is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $price is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2261
			_deprecated_function( 'apply_product_discounts_after_tax', '2.3' );
2262
		}
2263
2264
		/**
2265
		 * Gets the order discount amount - these are applied after tax.
2266
		 * @deprecated Coupons can not be applied after tax
2267
		 */
2268
		public function get_discounts_after_tax() {
2269
			_deprecated_function( 'get_discounts_after_tax', '2.3' );
2270
		}
2271
}
2272