Completed
Push — master ( 64c76e...406941 )
by Mike
10:39
created

WC_Cart::get_cart_hash()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 7
ccs 0
cts 0
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * WooCommerce cart
4
 *
5
 * The WooCommerce cart class stores cart data and active coupons as well as handling customer sessions and some cart related urls.
6
 * The cart class also has a price calculation function which calls upon other classes to calculate totals.
7
 *
8
 * @package WooCommerce/Classes
9
 * @version 2.1.0
10
 */
11
12
defined( 'ABSPATH' ) || exit;
13
14
require_once WC_ABSPATH . 'includes/legacy/class-wc-legacy-cart.php';
15
require_once WC_ABSPATH . 'includes/class-wc-cart-fees.php';
16
require_once WC_ABSPATH . 'includes/class-wc-cart-session.php';
17
18
/**
19
 * WC_Cart class.
20
 */
21
class WC_Cart extends WC_Legacy_Cart {
22
23
	/**
24
	 * Contains an array of cart items.
25
	 *
26
	 * @var array
27
	 */
28
	public $cart_contents = array();
29
30
	/**
31
	 * Contains an array of removed cart items so we can restore them if needed.
32
	 *
33
	 * @var array
34
	 */
35
	public $removed_cart_contents = array();
36
37
	/**
38
	 * Contains an array of coupon codes applied to the cart.
39
	 *
40
	 * @var array
41
	 */
42
	public $applied_coupons = array();
43
44
	/**
45
	 * Are prices in the cart displayed inc or excl tax?
46
	 *
47
	 * @var string
48
	 */
49
	public $tax_display_cart = 'incl';
50
51
	/**
52
	 * This stores the chosen shipping methods for the cart item packages.
53
	 *
54
	 * @var array
55
	 */
56
	protected $shipping_methods;
57
58
	/**
59
	 * Total defaults used to reset.
60
	 *
61
	 * @var array
62
	 */
63
	protected $default_totals = array(
64
		'subtotal'            => 0,
65
		'subtotal_tax'        => 0,
66
		'shipping_total'      => 0,
67
		'shipping_tax'        => 0,
68
		'shipping_taxes'      => array(),
69
		'discount_total'      => 0,
70
		'discount_tax'        => 0,
71
		'cart_contents_total' => 0,
72
		'cart_contents_tax'   => 0,
73
		'cart_contents_taxes' => array(),
74
		'fee_total'           => 0,
75
		'fee_tax'             => 0,
76
		'fee_taxes'           => array(),
77
		'total'               => 0,
78
		'total_tax'           => 0,
79
	);
80
	/**
81
	 * Store calculated totals.
82
	 *
83
	 * @var array
84
	 */
85
	protected $totals = array();
86
87
	/**
88
	 * Reference to the cart session handling class.
89
	 *
90
	 * @var WC_Cart_Session
91
	 */
92
	protected $session;
93
94
	/**
95
	 * Reference to the cart fees API class.
96
	 *
97
	 * @var WC_Cart_Fees
98
	 */
99
	protected $fees_api;
100
101
	/**
102
	 * Constructor for the cart class. Loads options and hooks in the init method.
103
	 */
104 1
	public function __construct() {
105 1
		$this->session          = new WC_Cart_Session( $this );
106 1
		$this->fees_api         = new WC_Cart_Fees( $this );
107 1
		$this->tax_display_cart = $this->is_tax_displayed();
108
109
		// Register hooks for the objects.
110 1
		$this->session->init();
111
112 1
		add_action( 'woocommerce_add_to_cart', array( $this, 'calculate_totals' ), 20, 0 );
113 1
		add_action( 'woocommerce_applied_coupon', array( $this, 'calculate_totals' ), 20, 0 );
114 1
		add_action( 'woocommerce_cart_item_removed', array( $this, 'calculate_totals' ), 20, 0 );
115 1
		add_action( 'woocommerce_cart_item_restored', array( $this, 'calculate_totals' ), 20, 0 );
116 1
		add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_items' ), 1 );
117 1
		add_action( 'woocommerce_check_cart_items', array( $this, 'check_cart_coupons' ), 1 );
118 1
		add_action( 'woocommerce_after_checkout_validation', array( $this, 'check_customer_coupons' ), 1 );
119
	}
120
121
	/**
122
	 * When cloning, ensure object properties are handled.
123
	 *
124
	 * These properties store a reference to the cart, so we use new instead of clone.
125
	 */
126 3
	public function __clone() {
127 3
		$this->session  = clone $this->session;
128 3
		$this->fees_api = clone $this->fees_api;
129
	}
130
131
	/*
132
	|--------------------------------------------------------------------------
133
	| Getters.
134
	|--------------------------------------------------------------------------
135
	|
136
	| Methods to retrieve class properties and avoid direct access.
137
	*/
138
139
	/**
140
	 * Gets cart contents.
141
	 *
142
	 * @since 3.2.0
143
	 * @return array of cart items
144
	 */
145 176
	public function get_cart_contents() {
146 176
		return apply_filters( 'woocommerce_get_cart_contents', (array) $this->cart_contents );
147
	}
148
149
	/**
150
	 * Return items removed from the cart.
151
	 *
152
	 * @since 3.2.0
153
	 * @return array
154
	 */
155 87
	public function get_removed_cart_contents() {
156 87
		return (array) $this->removed_cart_contents;
157
	}
158
159
	/**
160
	 * Gets the array of applied coupon codes.
161
	 *
162
	 * @return array of applied coupons
163
	 */
164 87
	public function get_applied_coupons() {
165 87
		return (array) $this->applied_coupons;
166
	}
167
168
	/**
169
	 * Return all calculated coupon totals.
170
	 *
171
	 * @since 3.2.0
172
	 * @return array
173
	 */
174 87
	public function get_coupon_discount_totals() {
175 87
		return (array) $this->coupon_discount_totals;
176
	}
177
	/**
178
	 * Return all calculated coupon tax totals.
179
	 *
180
	 * @since 3.2.0
181
	 * @return array
182
	 */
183 87
	public function get_coupon_discount_tax_totals() {
184 87
		return (array) $this->coupon_discount_tax_totals;
185
	}
186
187
	/**
188
	 * Return all calculated totals.
189
	 *
190
	 * @since 3.2.0
191
	 * @return array
192
	 */
193 87
	public function get_totals() {
194 87
		return empty( $this->totals ) ? $this->default_totals : $this->totals;
195
	}
196
197
	/**
198
	 * Get a total.
199
	 *
200
	 * @since 3.2.0
201
	 * @param string $key Key of element in $totals array.
202
	 * @return mixed
203
	 */
204 72
	protected function get_totals_var( $key ) {
205 72
		return isset( $this->totals[ $key ] ) ? $this->totals[ $key ] : $this->default_totals[ $key ];
206
	}
207
208
	/**
209
	 * Get subtotal.
210
	 *
211
	 * @since 3.2.0
212
	 * @return float
213
	 */
214 65
	public function get_subtotal() {
215 65
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'subtotal' ) );
216
	}
217
218
	/**
219
	 * Get subtotal.
220
	 *
221
	 * @since 3.2.0
222
	 * @return float
223
	 */
224 3
	public function get_subtotal_tax() {
225 3
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'subtotal_tax' ) );
226
	}
227
228
	/**
229
	 * Get discount_total.
230
	 *
231
	 * @since 3.2.0
232
	 * @return float
233
	 */
234 6
	public function get_discount_total() {
235 6
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'discount_total' ) );
236
	}
237
238
	/**
239
	 * Get discount_tax.
240
	 *
241
	 * @since 3.2.0
242
	 * @return float
243
	 */
244 2
	public function get_discount_tax() {
245 2
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'discount_tax' ) );
246
	}
247
248
	/**
249
	 * Get shipping_total.
250
	 *
251
	 * @since 3.2.0
252
	 * @return float
253
	 */
254 3
	public function get_shipping_total() {
255 3
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_total' ) );
256
	}
257
258
	/**
259
	 * Get shipping_tax.
260
	 *
261
	 * @since 3.2.0
262
	 * @return float
263
	 */
264 2
	public function get_shipping_tax() {
265 2
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_tax' ) );
266
	}
267
268
	/**
269
	 * Gets cart total. This is the total of items in the cart, but after discounts. Subtotal is before discounts.
270
	 *
271
	 * @since 3.2.0
272
	 * @return float
273
	 */
274 2
	public function get_cart_contents_total() {
275 2
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_total' ) );
276
	}
277
278
	/**
279
	 * Gets cart tax amount.
280
	 *
281
	 * @since 3.2.0
282
	 * @return float
283
	 */
284 2
	public function get_cart_contents_tax() {
285 2
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_tax' ) );
286
	}
287
288
	/**
289
	 * Gets cart total after calculation.
290
	 *
291
	 * @since 3.2.0
292
	 * @param string $context If the context is view, the value will be formatted for display. This keeps it compatible with pre-3.2 versions.
293
	 * @return float
294
	 */
295 22
	public function get_total( $context = 'view' ) {
296 22
		$total = apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'total' ) );
297 22
		return 'view' === $context ? apply_filters( 'woocommerce_cart_total', wc_price( $total ) ) : $total;
298
	}
299
300
	/**
301
	 * Get total tax amount.
302
	 *
303
	 * @since 3.2.0
304
	 * @return float
305
	 */
306 5
	public function get_total_tax() {
307 5
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'total_tax' ) );
308
	}
309
310
	/**
311
	 * Get total fee amount.
312
	 *
313
	 * @since 3.2.0
314
	 * @return float
315
	 */
316 2
	public function get_fee_total() {
317 2
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_total' ) );
318
	}
319
320
	/**
321
	 * Get total fee tax amount.
322
	 *
323
	 * @since 3.2.0
324
	 * @return float
325
	 */
326 2
	public function get_fee_tax() {
327 2
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_tax' ) );
328
	}
329
330
	/**
331
	 * Get taxes.
332
	 *
333
	 * @since 3.2.0
334
	 */
335 1
	public function get_shipping_taxes() {
336 1
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'shipping_taxes' ) );
337
	}
338
339
	/**
340
	 * Get taxes.
341
	 *
342
	 * @since 3.2.0
343
	 */
344 3
	public function get_cart_contents_taxes() {
345 3
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'cart_contents_taxes' ) );
346
	}
347
348
	/**
349
	 * Get taxes.
350
	 *
351
	 * @since 3.2.0
352
	 */
353 3
	public function get_fee_taxes() {
354 3
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, $this->get_totals_var( 'fee_taxes' ) );
355
	}
356
357
	/**
358
	 * Return whether or not the cart is displaying prices including tax, rather than excluding tax.
359
	 *
360
	 * @since 3.3.0
361
	 * @return bool
362
	 */
363 65
	public function display_prices_including_tax() {
364 65
		return apply_filters( 'woocommerce_cart_' . __FUNCTION__, 'incl' === $this->tax_display_cart );
365
	}
366
367
	/*
368
	|--------------------------------------------------------------------------
369
	| Setters.
370
	|--------------------------------------------------------------------------
371
	|
372
	| Methods to set class properties and avoid direct access.
373
	*/
374
375
	/**
376
	 * Sets the contents of the cart.
377
	 *
378
	 * @param array $value Cart array.
379
	 */
380
	public function set_cart_contents( $value ) {
381
		$this->cart_contents = (array) $value;
382
	}
383
384
	/**
385
	 * Set items removed from the cart.
386
	 *
387
	 * @since 3.2.0
388
	 * @param array $value Item array.
389
	 */
390
	public function set_removed_cart_contents( $value = array() ) {
391
		$this->removed_cart_contents = (array) $value;
392
	}
393
394
	/**
395
	 * Sets the array of applied coupon codes.
396
	 *
397
	 * @param array $value List of applied coupon codes.
398
	 */
399 49
	public function set_applied_coupons( $value = array() ) {
400 49
		$this->applied_coupons = (array) $value;
401
	}
402
403
	/**
404
	 * Sets the array of calculated coupon totals.
405
	 *
406
	 * @since 3.2.0
407
	 * @param array $value Value to set.
408
	 */
409 82
	public function set_coupon_discount_totals( $value = array() ) {
410 82
		$this->coupon_discount_totals = (array) $value;
411
	}
412
	/**
413
	 * Sets the array of calculated coupon tax totals.
414
	 *
415
	 * @since 3.2.0
416
	 * @param array $value Value to set.
417
	 */
418 82
	public function set_coupon_discount_tax_totals( $value = array() ) {
419 82
		$this->coupon_discount_tax_totals = (array) $value;
420
	}
421
422
	/**
423
	 * Set all calculated totals.
424
	 *
425
	 * @since 3.2.0
426
	 * @param array $value Value to set.
427
	 */
428
	public function set_totals( $value = array() ) {
429
		$this->totals = wp_parse_args( $value, $this->default_totals );
430
	}
431
432
	/**
433
	 * Set subtotal.
434
	 *
435
	 * @since 3.2.0
436
	 * @param string $value Value to set.
437
	 */
438 77
	public function set_subtotal( $value ) {
439 77
		$this->totals['subtotal'] = wc_format_decimal( $value, wc_get_price_decimals() );
440
	}
441
442
	/**
443
	 * Set subtotal.
444
	 *
445
	 * @since 3.2.0
446
	 * @param string $value Value to set.
447
	 */
448 77
	public function set_subtotal_tax( $value ) {
449 77
		$this->totals['subtotal_tax'] = wc_round_tax_total( $value );
450
	}
451
452
	/**
453
	 * Set discount_total.
454
	 *
455
	 * @since 3.2.0
456
	 * @param string $value Value to set.
457
	 */
458 77
	public function set_discount_total( $value ) {
459 77
		$this->totals['discount_total'] = wc_cart_round_discount( $value, wc_get_price_decimals() );
460
	}
461
462
	/**
463
	 * Set discount_tax.
464
	 *
465
	 * @since 3.2.0
466
	 * @param string $value Value to set.
467
	 */
468 77
	public function set_discount_tax( $value ) {
469 77
		$this->totals['discount_tax'] = wc_round_tax_total( $value );
470
	}
471
472
	/**
473
	 * Set shipping_total.
474
	 *
475
	 * @since 3.2.0
476
	 * @param string $value Value to set.
477
	 */
478 77
	public function set_shipping_total( $value ) {
479 77
		$this->totals['shipping_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
480
	}
481
482
	/**
483
	 * Set shipping_tax.
484
	 *
485
	 * @since 3.2.0
486
	 * @param string $value Value to set.
487
	 */
488 77
	public function set_shipping_tax( $value ) {
489 77
		$this->totals['shipping_tax'] = wc_round_tax_total( $value );
490
	}
491
492
	/**
493
	 * Set cart_contents_total.
494
	 *
495
	 * @since 3.2.0
496
	 * @param string $value Value to set.
497
	 */
498 77
	public function set_cart_contents_total( $value ) {
499 77
		$this->totals['cart_contents_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
500
	}
501
502
	/**
503
	 * Set cart tax amount.
504
	 *
505
	 * @since 3.2.0
506
	 * @param string $value Value to set.
507
	 */
508 77
	public function set_cart_contents_tax( $value ) {
509 77
		$this->totals['cart_contents_tax'] = wc_round_tax_total( $value );
510
	}
511
512
	/**
513
	 * Set cart total.
514
	 *
515
	 * @since 3.2.0
516
	 * @param string $value Value to set.
517
	 */
518 77
	public function set_total( $value ) {
519 77
		$this->totals['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
520
	}
521
522
	/**
523
	 * Set total tax amount.
524
	 *
525
	 * @since 3.2.0
526
	 * @param string $value Value to set.
527
	 */
528 77
	public function set_total_tax( $value ) {
529 77
		$this->totals['total_tax'] = wc_round_tax_total( $value );
530
	}
531
532
	/**
533
	 * Set fee amount.
534
	 *
535
	 * @since 3.2.0
536
	 * @param string $value Value to set.
537
	 */
538 77
	public function set_fee_total( $value ) {
539 77
		$this->totals['fee_total'] = wc_format_decimal( $value, wc_get_price_decimals() );
540
	}
541
542
	/**
543
	 * Set fee tax.
544
	 *
545
	 * @since 3.2.0
546
	 * @param string $value Value to set.
547
	 */
548 77
	public function set_fee_tax( $value ) {
549 77
		$this->totals['fee_tax'] = wc_round_tax_total( $value );
550
	}
551
552
	/**
553
	 * Set taxes.
554
	 *
555
	 * @since 3.2.0
556
	 * @param array $value Tax values.
557
	 */
558 77
	public function set_shipping_taxes( $value ) {
559 77
		$this->totals['shipping_taxes'] = (array) $value;
560
	}
561
562
	/**
563
	 * Set taxes.
564
	 *
565
	 * @since 3.2.0
566
	 * @param array $value Tax values.
567
	 */
568 77
	public function set_cart_contents_taxes( $value ) {
569 77
		$this->totals['cart_contents_taxes'] = (array) $value;
570
	}
571
572
	/**
573
	 * Set taxes.
574
	 *
575
	 * @since 3.2.0
576
	 * @param array $value Tax values.
577
	 */
578 77
	public function set_fee_taxes( $value ) {
579 77
		$this->totals['fee_taxes'] = (array) $value;
580
	}
581
582
	/*
583
	|--------------------------------------------------------------------------
584
	| Helper methods.
585
	|--------------------------------------------------------------------------
586
	*/
587
588
	/**
589
	 * Returns the cart and shipping taxes, merged.
590
	 *
591
	 * @return array merged taxes
592
	 */
593 1
	public function get_taxes() {
594 1
		return apply_filters( 'woocommerce_cart_get_taxes', wc_array_merge_recursive_numeric( $this->get_shipping_taxes(), $this->get_cart_contents_taxes(), $this->get_fee_taxes() ), $this );
595
	}
596
597
	/**
598
	 * Returns the contents of the cart in an array.
599
	 *
600
	 * @return array contents of the cart
601
	 */
602 176
	public function get_cart() {
603 176
		if ( ! did_action( 'wp_loaded' ) ) {
604
			wc_doing_it_wrong( __FUNCTION__, __( 'Get cart should not be called before the wp_loaded action.', 'woocommerce' ), '2.3' );
605
		}
606 176
		if ( ! did_action( 'woocommerce_load_cart_from_session' ) ) {
607
			$this->session->get_cart_from_session();
608
		}
609 176
		return array_filter( $this->get_cart_contents() );
610
	}
611
612
	/**
613
	 * Returns a specific item in the cart.
614
	 *
615
	 * @param string $item_key Cart item key.
616
	 * @return array Item data
617
	 */
618
	public function get_cart_item( $item_key ) {
619
		return isset( $this->cart_contents[ $item_key ] ) ? $this->cart_contents[ $item_key ] : array();
620
	}
621
622
	/**
623
	 * Checks if the cart is empty.
624
	 *
625
	 * @return bool
626
	 */
627 84
	public function is_empty() {
628 84
		return 0 === count( $this->get_cart() );
629
	}
630
631
	/**
632
	 * Empties the cart and optionally the persistent cart too.
633
	 *
634
	 * @param bool $clear_persistent_cart Should the persistant cart be cleared too. Defaults to true.
635
	 */
636 96
	public function empty_cart( $clear_persistent_cart = true ) {
637 96
		$this->cart_contents              = array();
638 96
		$this->removed_cart_contents      = array();
639 96
		$this->shipping_methods           = array();
640 96
		$this->coupon_discount_totals     = array();
641 96
		$this->coupon_discount_tax_totals = array();
642 96
		$this->applied_coupons            = array();
643 96
		$this->totals                     = $this->default_totals;
644
645 96
		if ( $clear_persistent_cart ) {
646 95
			$this->session->persistent_cart_destroy();
647
		}
648
649 96
		$this->fees_api->remove_all_fees();
650
651 96
		do_action( 'woocommerce_cart_emptied' );
652
	}
653
654
	/**
655
	 * Get number of items in the cart.
656
	 *
657
	 * @return int
658
	 */
659 5
	public function get_cart_contents_count() {
660 5
		return apply_filters( 'woocommerce_cart_contents_count', array_sum( wp_list_pluck( $this->get_cart(), 'quantity' ) ) );
661
	}
662
663
	/**
664
	 * Get weight of items in the cart.
665
	 *
666
	 * @since 2.5.0
667
	 * @return int
668
	 */
669 1
	public function get_cart_contents_weight() {
670 1
		$weight = 0;
671
672 1
		foreach ( $this->get_cart() as $cart_item_key => $values ) {
673 1
			if ( $values['data']->has_weight() ) {
674 1
				$weight += (float) $values['data']->get_weight() * $values['quantity'];
675
			}
676
		}
677
678 1
		return apply_filters( 'woocommerce_cart_contents_weight', $weight );
679
	}
680
681
	/**
682
	 * Get cart items quantities - merged so we can do accurate stock checks on items across multiple lines.
683
	 *
684
	 * @return array
685
	 */
686 3
	public function get_cart_item_quantities() {
687 3
		$quantities = array();
688
689 3
		foreach ( $this->get_cart() as $cart_item_key => $values ) {
690 3
			$product = $values['data'];
691 3
			$quantities[ $product->get_stock_managed_by_id() ] = isset( $quantities[ $product->get_stock_managed_by_id() ] ) ? $quantities[ $product->get_stock_managed_by_id() ] + $values['quantity'] : $values['quantity'];
692
		}
693
694 3
		return $quantities;
695
	}
696
697
	/**
698
	 * Check all cart items for errors.
699
	 */
700 1
	public function check_cart_items() {
701 1
		$return = true;
702 1
		$result = $this->check_cart_item_validity();
703
704 1
		if ( is_wp_error( $result ) ) {
705
			wc_add_notice( $result->get_error_message(), 'error' );
706
			$return = false;
707
		}
708
709 1
		$result = $this->check_cart_item_stock();
710
711 1
		if ( is_wp_error( $result ) ) {
712
			wc_add_notice( $result->get_error_message(), 'error' );
713
			$return = false;
714
		}
715
716 1
		return $return;
717
718
	}
719
720
	/**
721
	 * Check cart coupons for errors.
722
	 */
723
	public function check_cart_coupons() {
724
		foreach ( $this->get_applied_coupons() as $code ) {
725
			$coupon = new WC_Coupon( $code );
726
727
			if ( ! $coupon->is_valid() ) {
0 ignored issues
show
Deprecated Code introduced by
The method WC_Coupon::is_valid() has been deprecated with message: 3.2.0 In favor of WC_Discounts->is_coupon_valid.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
728
				$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_INVALID_REMOVED );
729
				$this->remove_coupon( $code );
730
			}
731
		}
732
	}
733
734
	/**
735
	 * Looks through cart items and checks the posts are not trashed or deleted.
736
	 *
737
	 * @return bool|WP_Error
738
	 */
739 2
	public function check_cart_item_validity() {
740 2
		$return = true;
741
742 2
		foreach ( $this->get_cart() as $cart_item_key => $values ) {
743 2
			$product = $values['data'];
744
745 2
			if ( ! $product || ! $product->exists() || 'trash' === $product->get_status() ) {
746
				$this->set_quantity( $cart_item_key, 0 );
747 2
				$return = new WP_Error( 'invalid', __( 'An item which is no longer available was removed from your cart.', 'woocommerce' ) );
748
			}
749
		}
750
751 2
		return $return;
752
	}
753
754
	/**
755
	 * Looks through the cart to check each item is in stock. If not, add an error.
756
	 *
757
	 * @return bool|WP_Error
758
	 */
759 2
	public function check_cart_item_stock() {
760 2
		$error                    = new WP_Error();
761 2
		$product_qty_in_cart      = $this->get_cart_item_quantities();
762 2
		$hold_stock_minutes       = (int) get_option( 'woocommerce_hold_stock_minutes', 0 );
763 2
		$current_session_order_id = isset( WC()->session->order_awaiting_payment ) ? absint( WC()->session->order_awaiting_payment ) : 0;
764
765 2
		foreach ( $this->get_cart() as $cart_item_key => $values ) {
766 2
			$product = $values['data'];
767
768
			// Check stock based on stock-status.
769 2
			if ( ! $product->is_in_stock() ) {
770
				/* translators: %s: product name */
771
				$error->add( 'out-of-stock', sprintf( __( 'Sorry, "%s" is not in stock. Please edit your cart and try again. We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name() ) );
772
				return $error;
773
			}
774
775
			// We only need to check products managing stock, with a limited stock qty.
776 2
			if ( ! $product->managing_stock() || $product->backorders_allowed() ) {
777 2
				continue;
778
			}
779
780
			// Check stock based on all items in the cart and consider any held stock within pending orders.
781
			$held_stock     = ( $hold_stock_minutes > 0 ) ? wc_get_held_stock_quantity( $product, $current_session_order_id ) : 0;
782
			$required_stock = $product_qty_in_cart[ $product->get_stock_managed_by_id() ];
783
784
			if ( $product->get_stock_quantity() < ( $held_stock + $required_stock ) ) {
785
				/* translators: 1: product name 2: quantity in stock */
786
				$error->add( 'out-of-stock', sprintf( __( 'Sorry, we do not have enough "%1$s" in stock to fulfill your order (%2$s available). We apologize for any inconvenience caused.', 'woocommerce' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity() - $held_stock, $product ) ) );
787
				return $error;
788
			}
789
		}
790
791 2
		return true;
792
	}
793
794
	/**
795
	 * Gets and formats a list of cart item data + variations for display on the frontend.
796
	 *
797
	 * @param array $cart_item Cart item object.
798
	 * @param bool  $flat Should the data be returned flat or in a list.
799
	 * @return string
800
	 */
801
	public function get_item_data( $cart_item, $flat = false ) {
802
		wc_deprecated_function( 'WC_Cart::get_item_data', '3.3', 'wc_get_formatted_cart_item_data' );
803
804
		return wc_get_formatted_cart_item_data( $cart_item, $flat );
805
	}
806
807
	/**
808
	 * Gets cross sells based on the items in the cart.
809
	 *
810
	 * @return array cross_sells (item ids)
811
	 */
812 1
	public function get_cross_sells() {
813 1
		$cross_sells = array();
814 1
		$in_cart     = array();
815 1
		if ( ! $this->is_empty() ) {
816 1
			foreach ( $this->get_cart() as $cart_item_key => $values ) {
817 1
				if ( $values['quantity'] > 0 ) {
818 1
					$cross_sells = array_merge( $values['data']->get_cross_sell_ids(), $cross_sells );
819 1
					$in_cart[]   = $values['product_id'];
820
				}
821
			}
822
		}
823 1
		$cross_sells = array_diff( $cross_sells, $in_cart );
824 1
		return apply_filters( 'woocommerce_cart_crosssell_ids', wp_parse_id_list( $cross_sells ), $this );
825
	}
826
827
	/**
828
	 * Gets the url to remove an item from the cart.
829
	 *
830
	 * @param string $cart_item_key contains the id of the cart item.
831
	 * @return string url to page
832
	 */
833
	public function get_remove_url( $cart_item_key ) {
834
		wc_deprecated_function( 'WC_Cart::get_remove_url', '3.3', 'wc_get_cart_remove_url' );
835
836
		return wc_get_cart_remove_url( $cart_item_key );
837
	}
838
839
	/**
840
	 * Gets the url to re-add an item into the cart.
841
	 *
842
	 * @param  string $cart_item_key Cart item key to undo.
843
	 * @return string url to page
844
	 */
845
	public function get_undo_url( $cart_item_key ) {
846
		wc_deprecated_function( 'WC_Cart::get_undo_url', '3.3', 'wc_get_cart_undo_url' );
847
848
		return wc_get_cart_undo_url( $cart_item_key );
849
	}
850
851
	/**
852
	 * Get taxes, merged by code, formatted ready for output.
853
	 *
854
	 * @return array
855
	 */
856 1
	public function get_tax_totals() {
857 1
		$taxes      = $this->get_taxes();
858 1
		$tax_totals = array();
859
860 1
		foreach ( $taxes as $key => $tax ) {
861 1
			$code = WC_Tax::get_rate_code( $key );
862
863 1
			if ( $code || apply_filters( 'woocommerce_cart_remove_taxes_zero_rate_id', 'zero-rated' ) === $key ) {
864 1 View Code Duplication
				if ( ! isset( $tax_totals[ $code ] ) ) {
865 1
					$tax_totals[ $code ]         = new stdClass();
866 1
					$tax_totals[ $code ]->amount = 0;
867
				}
868 1
				$tax_totals[ $code ]->tax_rate_id      = $key;
869 1
				$tax_totals[ $code ]->is_compound      = WC_Tax::is_compound( $key );
870 1
				$tax_totals[ $code ]->label            = WC_Tax::get_rate_label( $key );
871 1
				$tax_totals[ $code ]->amount          += wc_round_tax_total( $tax );
872 1
				$tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ) );
873
			}
874
		}
875
876 1 View Code Duplication
		if ( apply_filters( 'woocommerce_cart_hide_zero_taxes', true ) ) {
877 1
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
878 1
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
879
		}
880
881 1
		return apply_filters( 'woocommerce_cart_tax_totals', $tax_totals, $this );
882
	}
883
884
	/**
885
	 * Get all tax classes for items in the cart.
886
	 *
887
	 * @return array
888
	 */
889 View Code Duplication
	public function get_cart_item_tax_classes() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
890
		$found_tax_classes = array();
891
892
		foreach ( WC()->cart->get_cart() as $item ) {
893
			if ( $item['data'] && ( $item['data']->is_taxable() || $item['data']->is_shipping_taxable() ) ) {
894
				$found_tax_classes[] = $item['data']->get_tax_class();
895
			}
896
		}
897
898
		return array_unique( $found_tax_classes );
899
	}
900
901
	/**
902
	 * Get all tax classes for shipping based on the items in the cart.
903
	 *
904
	 * @return array
905
	 */
906 5 View Code Duplication
	public function get_cart_item_tax_classes_for_shipping() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in 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...
907 5
		$found_tax_classes = array();
908
909 5
		foreach ( WC()->cart->get_cart() as $item ) {
910 5
			if ( $item['data'] && ( $item['data']->is_shipping_taxable() ) ) {
911 5
				$found_tax_classes[] = $item['data']->get_tax_class();
912
			}
913
		}
914
915 5
		return array_unique( $found_tax_classes );
916
	}
917
918
	/**
919
	 * Determines the value that the customer spent and the subtotal
920
	 * displayed, used for things like coupon validation.
921
	 *
922
	 * Since the coupon lines are displayed based on the TAX DISPLAY value
923
	 * of cart, this is used to determine the spend.
924
	 *
925
	 * If cart totals are shown including tax, use the subtotal.
926
	 * If cart totals are shown excluding tax, use the subtotal ex tax
927
	 * (tax is shown after coupons).
928
	 *
929
	 * @since 2.6.0
930
	 * @return string
931
	 */
932 64
	public function get_displayed_subtotal() {
933 64
		return $this->display_prices_including_tax() ? $this->get_subtotal() + $this->get_subtotal_tax() : $this->get_subtotal();
934
	}
935
936
	/**
937
	 * Check if product is in the cart and return cart item key.
938
	 *
939
	 * Cart item key will be unique based on the item and its properties, such as variations.
940
	 *
941
	 * @param mixed $cart_id id of product to find in the cart.
942
	 * @return string cart item key
943
	 */
944 77
	public function find_product_in_cart( $cart_id = false ) {
945 77
		if ( false !== $cart_id ) {
946 77
			if ( is_array( $this->cart_contents ) && isset( $this->cart_contents[ $cart_id ] ) ) {
947 2
				return $cart_id;
948
			}
949
		}
950 77
		return '';
951
	}
952
953
	/**
954
	 * Generate a unique ID for the cart item being added.
955
	 *
956
	 * @param int   $product_id - id of the product the key is being generated for.
957
	 * @param int   $variation_id of the product the key is being generated for.
958
	 * @param array $variation data for the cart item.
959
	 * @param array $cart_item_data other cart item data passed which affects this items uniqueness in the cart.
960
	 * @return string cart item key
961
	 */
962 78
	public function generate_cart_id( $product_id, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
963 78
		$id_parts = array( $product_id );
964
965 78
		if ( $variation_id && 0 !== $variation_id ) {
966 6
			$id_parts[] = $variation_id;
967
		}
968
969 78
		if ( is_array( $variation ) && ! empty( $variation ) ) {
970 6
			$variation_key = '';
971 6
			foreach ( $variation as $key => $value ) {
972 6
				$variation_key .= trim( $key ) . trim( $value );
973
			}
974 6
			$id_parts[] = $variation_key;
975
		}
976
977 78
		if ( is_array( $cart_item_data ) && ! empty( $cart_item_data ) ) {
978 1
			$cart_item_data_key = '';
979 1
			foreach ( $cart_item_data as $key => $value ) {
980 1
				if ( is_array( $value ) || is_object( $value ) ) {
981 1
					$value = http_build_query( $value );
982
				}
983 1
				$cart_item_data_key .= trim( $key ) . trim( $value );
984
985
			}
986 1
			$id_parts[] = $cart_item_data_key;
987
		}
988
989 78
		return apply_filters( 'woocommerce_cart_id', md5( implode( '_', $id_parts ) ), $product_id, $variation_id, $variation, $cart_item_data );
990
	}
991
992
	/**
993
	 * Add a product to the cart.
994
	 *
995
	 * @throws Exception Plugins can throw an exception to prevent adding to cart.
996
	 * @param int   $product_id contains the id of the product to add to the cart.
997
	 * @param int   $quantity contains the quantity of the item to add.
998
	 * @param int   $variation_id ID of the variation being added to the cart.
999
	 * @param array $variation attribute values.
1000
	 * @param array $cart_item_data extra cart item data we want to pass into the item.
1001
	 * @return string|bool $cart_item_key
1002
	 */
1003 78
	public function add_to_cart( $product_id = 0, $quantity = 1, $variation_id = 0, $variation = array(), $cart_item_data = array() ) {
1004
		try {
1005 78
			$product_id   = absint( $product_id );
1006 78
			$variation_id = absint( $variation_id );
1007
1008
			// Ensure we don't add a variation to the cart directly by variation ID.
1009 78
			if ( 'product_variation' === get_post_type( $product_id ) ) {
1010
				$variation_id = $product_id;
1011
				$product_id   = wp_get_post_parent_id( $variation_id );
1012
			}
1013
1014 78
			$product_data = wc_get_product( $variation_id ? $variation_id : $product_id );
1015 78
			$quantity     = apply_filters( 'woocommerce_add_to_cart_quantity', $quantity, $product_id );
1016
1017 78
			if ( $quantity <= 0 || ! $product_data || 'trash' === $product_data->get_status() ) {
1018 1
				return false;
1019
			}
1020
1021
			// Load cart item data - may be added by other plugins.
1022 77
			$cart_item_data = (array) apply_filters( 'woocommerce_add_cart_item_data', $cart_item_data, $product_id, $variation_id, $quantity );
1023
1024
			// Generate a ID based on product ID, variation ID, variation data, and other cart item data.
1025 77
			$cart_id = $this->generate_cart_id( $product_id, $variation_id, $variation, $cart_item_data );
1026
1027
			// Find the cart item key in the existing cart.
1028 77
			$cart_item_key = $this->find_product_in_cart( $cart_id );
1029
1030
			// Force quantity to 1 if sold individually and check for existing item in cart.
1031 77
			if ( $product_data->is_sold_individually() ) {
1032 1
				$quantity      = apply_filters( 'woocommerce_add_to_cart_sold_individually_quantity', 1, $quantity, $product_id, $variation_id, $cart_item_data );
1033 1
				$found_in_cart = apply_filters( 'woocommerce_add_to_cart_sold_individually_found_in_cart', $cart_item_key && $this->cart_contents[ $cart_item_key ]['quantity'] > 0, $product_id, $variation_id, $cart_item_data, $cart_id );
1034
1035 1
				if ( $found_in_cart ) {
1036
					/* translators: %s: product name */
1037
					throw new Exception( sprintf( '<a href="%s" class="button wc-forward">%s</a> %s', wc_get_cart_url(), __( 'View cart', 'woocommerce' ), sprintf( __( 'You cannot add another "%s" to your cart.', 'woocommerce' ), $product_data->get_name() ) ) );
1038
				}
1039
			}
1040
1041 77
			if ( ! $product_data->is_purchasable() ) {
1042
				throw new Exception( __( 'Sorry, this product cannot be purchased.', 'woocommerce' ) );
1043
			}
1044
1045
			// Stock check - only check if we're managing stock and backorders are not allowed.
1046 77
			if ( ! $product_data->is_in_stock() ) {
1047
				/* translators: %s: product name */
1048
				throw new Exception( sprintf( __( 'You cannot add &quot;%s&quot; to the cart because the product is out of stock.', 'woocommerce' ), $product_data->get_name() ) );
1049
			}
1050
1051 77 View Code Duplication
			if ( ! $product_data->has_enough_stock( $quantity ) ) {
1052
				/* translators: 1: product name 2: quantity in stock */
1053
				throw new Exception( sprintf( __( 'You cannot add that amount of &quot;%1$s&quot; to the cart because there is not enough stock (%2$s remaining).', 'woocommerce' ), $product_data->get_name(), wc_format_stock_quantity_for_display( $product_data->get_stock_quantity(), $product_data ) ) );
1054
			}
1055
1056
			// Stock check - this time accounting for whats already in-cart.
1057 77
			if ( $product_data->managing_stock() ) {
1058
				$products_qty_in_cart = $this->get_cart_item_quantities();
1059
1060
				if ( isset( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] ) && ! $product_data->has_enough_stock( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ] + $quantity ) ) {
1061
					throw new Exception(
1062
						sprintf(
1063
							'<a href="%s" class="button wc-forward">%s</a> %s',
1064
							wc_get_cart_url(),
1065
							__( 'View cart', 'woocommerce' ),
1066
							/* translators: 1: quantity in stock 2: current quantity */
1067
							sprintf( __( 'You cannot add that amount to the cart &mdash; we have %1$s in stock and you already have %2$s in your cart.', 'woocommerce' ), wc_format_stock_quantity_for_display( $product_data->get_stock_quantity(), $product_data ), wc_format_stock_quantity_for_display( $products_qty_in_cart[ $product_data->get_stock_managed_by_id() ], $product_data ) )
1068
						)
1069
					);
1070
				}
1071
			}
1072
1073
			// If cart_item_key is set, the item is already in the cart.
1074 77
			if ( $cart_item_key ) {
1075 1
				$new_quantity = $quantity + $this->cart_contents[ $cart_item_key ]['quantity'];
1076 1
				$this->set_quantity( $cart_item_key, $new_quantity, false );
1077
			} else {
1078 77
				$cart_item_key = $cart_id;
1079
1080
				// Add item after merging with $cart_item_data - hook to allow plugins to modify cart item.
1081 77
				$this->cart_contents[ $cart_item_key ] = apply_filters(
1082 77
					'woocommerce_add_cart_item',
1083 77
					array_merge(
1084 77
						$cart_item_data,
1085 77
						array(
1086 77
							'key'          => $cart_item_key,
1087 77
							'product_id'   => $product_id,
1088 77
							'variation_id' => $variation_id,
1089 77
							'variation'    => $variation,
1090 77
							'quantity'     => $quantity,
1091
							'data'         => $product_data,
1092 77
							'data_hash'    => wc_get_cart_item_data_hash( $product_data ),
1093
						)
1094
					),
1095
					$cart_item_key
1096 77
				);
1097
			}
1098 77
1099
			$this->cart_contents = apply_filters( 'woocommerce_cart_contents_changed', $this->cart_contents );
1100 77
1101
			do_action( 'woocommerce_add_to_cart', $cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data );
1102
1103
			return $cart_item_key;
1104
1105
		} catch ( Exception $e ) {
1106
			if ( $e->getMessage() ) {
1107
				wc_add_notice( $e->getMessage(), 'error' );
1108
			}
1109
			return false;
1110
		}
1111
	}
1112
1113
	/**
1114
	 * Remove a cart item.
1115
	 *
1116
	 * @since  2.3.0
1117
	 * @param  string $cart_item_key Cart item key to remove from the cart.
1118
	 * @return bool
1119
	 */
1120
	public function remove_cart_item( $cart_item_key ) {
1121
		if ( isset( $this->cart_contents[ $cart_item_key ] ) ) {
1122
			$this->removed_cart_contents[ $cart_item_key ] = $this->cart_contents[ $cart_item_key ];
1123
1124
			unset( $this->removed_cart_contents[ $cart_item_key ]['data'] );
1125
1126
			do_action( 'woocommerce_remove_cart_item', $cart_item_key, $this );
1127
1128
			unset( $this->cart_contents[ $cart_item_key ] );
1129
1130
			do_action( 'woocommerce_cart_item_removed', $cart_item_key, $this );
1131
1132
			return true;
1133
		}
1134
		return false;
1135
	}
1136
1137
	/**
1138
	 * Restore a cart item.
1139
	 *
1140
	 * @param  string $cart_item_key Cart item key to restore to the cart.
1141
	 * @return bool
1142
	 */
1143
	public function restore_cart_item( $cart_item_key ) {
1144
		if ( isset( $this->removed_cart_contents[ $cart_item_key ] ) ) {
1145
			$restore_item                                  = $this->removed_cart_contents[ $cart_item_key ];
1146
			$this->cart_contents[ $cart_item_key ]         = $restore_item;
1147
			$this->cart_contents[ $cart_item_key ]['data'] = wc_get_product( $restore_item['variation_id'] ? $restore_item['variation_id'] : $restore_item['product_id'] );
1148
1149
			do_action( 'woocommerce_restore_cart_item', $cart_item_key, $this );
1150
1151
			unset( $this->removed_cart_contents[ $cart_item_key ] );
1152
1153
			do_action( 'woocommerce_cart_item_restored', $cart_item_key, $this );
1154
1155
			return true;
1156
		}
1157
		return false;
1158
	}
1159
1160
	/**
1161
	 * Set the quantity for an item in the cart.
1162
	 *
1163
	 * @param string $cart_item_key contains the id of the cart item.
1164
	 * @param int    $quantity contains the quantity of the item.
1165 2
	 * @param bool   $refresh_totals whether or not to calculate totals after setting the new qty.
1166 2
	 * @return bool
1167 1
	 */
1168 1
	public function set_quantity( $cart_item_key, $quantity = 1, $refresh_totals = true ) {
1169
		if ( 0 === $quantity || $quantity < 0 ) {
1170 2
			do_action( 'woocommerce_before_cart_item_quantity_zero', $cart_item_key, $this );
1171 2
			unset( $this->cart_contents[ $cart_item_key ] );
1172 2
		} else {
1173
			$old_quantity                                      = $this->cart_contents[ $cart_item_key ]['quantity'];
1174
			$this->cart_contents[ $cart_item_key ]['quantity'] = $quantity;
1175 2
			do_action( 'woocommerce_after_cart_item_quantity_update', $cart_item_key, $quantity, $old_quantity, $this );
1176 1
		}
1177
1178
		if ( $refresh_totals ) {
1179 2
			$this->calculate_totals();
1180
		}
1181
1182
		return true;
1183
	}
1184
1185
	/**
1186
	 * Get cart's owner.
1187
	 *
1188 28
	 * @since  3.2.0
1189 28
	 * @return WC_Customer
1190
	 */
1191
	public function get_customer() {
1192
		return WC()->customer;
1193
	}
1194
1195
	/**
1196
	 * Calculate totals for the items in the cart.
1197 84
	 *
1198 84
	 * @uses WC_Cart_Totals
1199
	 */
1200 84
	public function calculate_totals() {
1201 9
		$this->reset_totals();
1202 9
1203
		if ( $this->is_empty() ) {
1204
			$this->session->set_session();
1205 77
			return;
1206
		}
1207 77
1208
		do_action( 'woocommerce_before_calculate_totals', $this );
1209 77
1210
		new WC_Cart_Totals( $this );
1211
1212
		do_action( 'woocommerce_after_calculate_totals', $this );
1213
	}
1214
1215
	/**
1216
	 * Looks at the totals to see if payment is actually required.
1217
	 *
1218
	 * @return bool
1219
	 */
1220
	public function needs_payment() {
1221
		return apply_filters( 'woocommerce_cart_needs_payment', 0 < $this->get_total( 'edit' ), $this );
1222
	}
1223
1224
	/*
1225
	 * Shipping related functions.
1226
	 */
1227
1228 77
	/**
1229 77
	 * Uses the shipping class to calculate shipping then gets the totals when its finished.
1230
	 */
1231 77
	public function calculate_shipping() {
1232 77
		$this->shipping_methods = $this->needs_shipping() ? $this->get_chosen_shipping_methods( WC()->shipping()->calculate_shipping( $this->get_shipping_packages() ) ) : array();
1233 77
1234 9
		$shipping_taxes = wp_list_pluck( $this->shipping_methods, 'taxes' );
1235 5
		$merged_taxes   = array();
1236 5 View Code Duplication
		foreach ( $shipping_taxes as $taxes ) {
1237
			foreach ( $taxes as $tax_id => $tax_amount ) {
1238 9
				if ( ! isset( $merged_taxes[ $tax_id ] ) ) {
1239
					$merged_taxes[ $tax_id ] = 0;
1240
				}
1241
				$merged_taxes[ $tax_id ] += $tax_amount;
1242 77
			}
1243 77
		}
1244 77
1245
		$this->set_shipping_total( array_sum( wp_list_pluck( $this->shipping_methods, 'cost' ) ) );
1246 77
		$this->set_shipping_tax( array_sum( $merged_taxes ) );
1247
		$this->set_shipping_taxes( $merged_taxes );
1248
1249
		return $this->shipping_methods;
1250
	}
1251
1252
	/**
1253
	 * Given a set of packages with rates, get the chosen ones only.
1254
	 *
1255
	 * @since 3.2.0
1256 15
	 * @param array $calculated_shipping_packages Array of packages.
1257 15
	 * @return array
1258
	 */
1259 15
	protected function get_chosen_shipping_methods( $calculated_shipping_packages = array() ) {
1260 15
		$chosen_methods = array();
1261 15
		// Get chosen methods for each package to get our totals.
1262 15
		foreach ( $calculated_shipping_packages as $key => $package ) {
1263
			$chosen_method = wc_get_chosen_shipping_method_for_package( $key, $package );
1264
			if ( $chosen_method ) {
1265 15
				$chosen_methods[ $key ] = $package['rates'][ $chosen_method ];
1266
			}
1267
		}
1268
		return $chosen_methods;
1269
	}
1270
1271
	/**
1272
	 * Filter items needing shipping callback.
1273
	 *
1274
	 * @since  3.0.0
1275 15
	 * @param  array $item Item to check for shipping.
1276 15
	 * @return bool
1277 15
	 */
1278
	protected function filter_items_needing_shipping( $item ) {
1279
		$product = $item['data'];
1280
		return $product && $product->needs_shipping();
1281
	}
1282
1283
	/**
1284
	 * Get only items that need shipping.
1285
	 *
1286 15
	 * @since  3.0.0
1287 15
	 * @return array
1288
	 */
1289
	protected function get_items_needing_shipping() {
1290
		return array_filter( $this->get_cart(), array( $this, 'filter_items_needing_shipping' ) );
1291
	}
1292
1293
	/**
1294
	 * Get packages to calculate shipping for.
1295
	 *
1296
	 * This lets us calculate costs for carts that are shipped to multiple locations.
1297
	 *
1298
	 * Shipping methods are responsible for looping through these packages.
1299
	 *
1300
	 * By default we pass the cart itself as a package - plugins can change this.
1301
	 * through the filter and break it up.
1302
	 *
1303 15
	 * @since 1.5.4
1304 15
	 * @return array of cart items
1305 15
	 */
1306
	public function get_shipping_packages() {
1307
		return apply_filters(
1308 15
			'woocommerce_cart_shipping_packages',
1309 15
			array(
1310 15
				array(
1311
					'contents'        => $this->get_items_needing_shipping(),
1312 15
					'contents_cost'   => array_sum( wp_list_pluck( $this->get_items_needing_shipping(), 'line_total' ) ),
1313
					'applied_coupons' => $this->get_applied_coupons(),
1314
					'user'            => array(
1315 15
						'ID' => get_current_user_id(),
1316 15
					),
1317 15
					'destination'     => array(
1318 15
						'country'   => $this->get_customer()->get_shipping_country(),
1319 15
						'state'     => $this->get_customer()->get_shipping_state(),
1320 15
						'postcode'  => $this->get_customer()->get_shipping_postcode(),
1321 15
						'city'      => $this->get_customer()->get_shipping_city(),
1322
						'address'   => $this->get_customer()->get_shipping_address(),
1323 15
						'address_1' => $this->get_customer()->get_shipping_address(), // Provide both address and address_1 for backwards compatibility.
1324
						'address_2' => $this->get_customer()->get_shipping_address_2(),
1325
					),
1326
					'cart_subtotal'   => $this->get_displayed_subtotal(),
1327
				),
1328
			)
1329
		);
1330
	}
1331
1332
	/**
1333
	 * Looks through the cart to see if shipping is actually required.
1334 79
	 *
1335 79
	 * @return bool whether or not the cart needs shipping
1336 64
	 */
1337
	public function needs_shipping() {
1338 15
		if ( ! wc_shipping_enabled() || 0 === wc_get_shipping_method_count( true ) ) {
1339
			return false;
1340 15
		}
1341 15
		$needs_shipping = false;
1342 15
1343 15
		foreach ( $this->get_cart_contents() as $cart_item_key => $values ) {
1344
			if ( $values['data']->needs_shipping() ) {
1345
				$needs_shipping = true;
1346
				break;
1347 15
			}
1348
		}
1349
1350
		return apply_filters( 'woocommerce_cart_needs_shipping', $needs_shipping );
1351
	}
1352
1353
	/**
1354
	 * Should the shipping address form be shown.
1355 1
	 *
1356 1
	 * @return bool
1357
	 */
1358
	public function needs_shipping_address() {
1359
		return apply_filters( 'woocommerce_cart_needs_shipping_address', true === $this->needs_shipping() && ! wc_ship_to_billing_address_only() );
1360
	}
1361
1362
	/**
1363
	 * Sees if the customer has entered enough data to calc the shipping yet.
1364 77
	 *
1365 77
	 * @return bool
1366
	 */
1367
	public function show_shipping() {
1368
		if ( ! wc_shipping_enabled() || ! $this->get_cart_contents() ) {
1369 77
			return false;
1370
		}
1371
1372
		if ( 'yes' === get_option( 'woocommerce_shipping_cost_requires_address' ) ) {
1373
			if ( ! $this->get_customer()->has_calculated_shipping() ) {
1374
				if ( ! $this->get_customer()->get_shipping_country() || ( ! $this->get_customer()->get_shipping_state() && ! $this->get_customer()->get_shipping_postcode() ) ) {
1375
					return false;
1376
				}
1377 77
			}
1378
		}
1379
1380
		return apply_filters( 'woocommerce_cart_ready_to_calc_shipping', true );
1381
	}
1382
1383
	/**
1384
	 * Gets the shipping total (after calculation).
1385
	 *
1386
	 * @return string price or string for the shipping total
1387
	 */
1388
	public function get_cart_shipping_total() {
1389
1390
		// Default total assumes Free shipping.
1391
		$total = __( 'Free!', 'woocommerce' );
1392
1393
		if ( 0 < $this->get_shipping_total() ) {
1394
1395
			if ( $this->display_prices_including_tax() ) {
1396
				$total = wc_price( $this->shipping_total + $this->shipping_tax_total );
1397
1398
				if ( $this->shipping_tax_total > 0 && ! wc_prices_include_tax() ) {
1399
					$total .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
1400
				}
1401
			} else {
1402
				$total = wc_price( $this->shipping_total );
1403
1404
				if ( $this->shipping_tax_total > 0 && wc_prices_include_tax() ) {
1405
					$total .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1406
				}
1407
			}
1408
		}
1409
		return apply_filters( 'woocommerce_cart_shipping_total', $total, $this );
1410
	}
1411
1412
	/**
1413
	 * Check for user coupons (now that we have billing email). If a coupon is invalid, add an error.
1414
	 *
1415
	 * Checks two types of coupons:
1416
	 *  1. Where a list of customer emails are set (limits coupon usage to those defined).
1417
	 *  2. Where a usage_limit_per_user is set (limits coupon usage to a number based on user ID and email).
1418
	 *
1419
	 * @param array $posted Post data.
1420
	 */
1421
	public function check_customer_coupons( $posted ) {
1422
		foreach ( $this->get_applied_coupons() as $code ) {
1423
			$coupon = new WC_Coupon( $code );
1424
1425
			if ( $coupon->is_valid() ) {
0 ignored issues
show
Deprecated Code introduced by
The method WC_Coupon::is_valid() has been deprecated with message: 3.2.0 In favor of WC_Discounts->is_coupon_valid.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1426
1427
				// Get user and posted emails to compare.
1428
				$current_user = wp_get_current_user();
1429
				$billing_email = isset( $posted['billing_email'] ) ? $posted['billing_email'] : '';
1430
				$check_emails  = array_unique(
1431
					array_filter(
1432
						array_map(
1433
							'strtolower',
1434
							array_map(
1435
								'sanitize_email',
1436
								array(
1437
									$billing_email,
1438
									$current_user->user_email,
1439
								)
1440
							)
1441
						)
1442
					)
1443
				);
1444
1445
				// Limit to defined email addresses.
1446
				$restrictions = $coupon->get_email_restrictions();
1447
1448
				if ( is_array( $restrictions ) && 0 < count( $restrictions ) && ! $this->is_coupon_emails_allowed( $check_emails, $restrictions ) ) {
1449
					$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_YOURS_REMOVED );
1450
					$this->remove_coupon( $code );
1451
				}
1452
1453
				// Usage limits per user - check against billing and user email and user ID.
1454
				$limit_per_user = $coupon->get_usage_limit_per_user();
1455
1456
				if ( 0 < $limit_per_user ) {
1457
					$used_by         = $coupon->get_used_by();
1458
					$usage_count     = 0;
1459
					$user_id_matches = array( get_current_user_id() );
1460
1461
					// Check usage against emails.
1462
					foreach ( $check_emails as $check_email ) {
1463
						$usage_count      += count( array_keys( $used_by, $check_email, true ) );
1464
						$user              = get_user_by( 'email', $check_email );
1465
						$user_id_matches[] = $user ? $user->ID : 0;
1466
					}
1467
1468
					// Check against billing emails of existing users.
1469
					$users_query = new WP_User_Query(
1470
						array(
1471
							'fields'     => 'ID',
1472
							'meta_query' => array(
0 ignored issues
show
introduced by
Detected usage of meta_query, possible slow query.
Loading history...
1473
								array(
1474
									'key'     => '_billing_email',
1475
									'value'   => $check_emails,
1476
									'compare' => 'IN',
1477
								),
1478
							),
1479
						)
1480
					); // WPCS: slow query ok.
1481
1482
					$user_id_matches = array_unique( array_filter( array_merge( $user_id_matches, $users_query->get_results() ) ) );
1483
1484
					foreach ( $user_id_matches as $user_id ) {
1485
						$usage_count += count( array_keys( $used_by, (string) $user_id, true ) );
1486
					}
1487
1488
					if ( $usage_count >= $coupon->get_usage_limit_per_user() ) {
1489
						$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_USAGE_LIMIT_REACHED );
1490
						$this->remove_coupon( $code );
1491
					}
1492
				}
1493
			}
1494
		}
1495
	}
1496
1497
	/**
1498
	 * Checks if the given email address(es) matches the ones specified on the coupon.
1499 1
	 *
1500
	 * @param array $check_emails Array of customer email addresses.
1501 1
	 * @param array $restrictions Array of allowed email addresses.
1502
	 * @return bool
1503 1
	 */
1504 1
	public function is_coupon_emails_allowed( $check_emails, $restrictions ) {
1505
1506
		foreach ( $check_emails as $check_email ) {
1507
			// With a direct match we return true.
1508 1
			if ( in_array( $check_email, $restrictions, true ) ) {
1509
				return true;
1510 1
			}
1511 1
1512 1
			// Go through the allowed emails and return true if the email matches a wildcard.
1513 1
			foreach ( $restrictions as $restriction ) {
1514
				// Convert to PHP-regex syntax.
1515
				$regex = '/^' . str_replace( '*', '(.+)?', $restriction ) . '$/';
1516
				preg_match( $regex, $check_email, $match );
1517
				if ( ! empty( $match ) ) {
1518
					return true;
1519 1
				}
1520
			}
1521
		}
1522
1523
		// No matches, this one isn't allowed.
1524
		return false;
1525
	}
1526
1527
1528
	/**
1529 23
	 * Returns whether or not a discount has been applied.
1530 23
	 *
1531
	 * @param string $coupon_code Coupon code to check.
1532
	 * @return bool
1533
	 */
1534
	public function has_discount( $coupon_code = '' ) {
1535
		return $coupon_code ? in_array( wc_format_coupon_code( $coupon_code ), $this->applied_coupons, true ) : count( $this->applied_coupons ) > 0;
1536
	}
1537
1538
	/**
1539 23
	 * Applies a coupon code passed to the method.
1540
	 *
1541 23
	 * @param string $coupon_code - The code to apply.
1542
	 * @return bool True if the coupon is applied, false if it does not exist or cannot be applied.
1543
	 */
1544
	public function apply_coupon( $coupon_code ) {
1545
		// Coupons are globally disabled.
1546 23
		if ( ! wc_coupons_enabled() ) {
1547
			return false;
1548
		}
1549 23
1550
		// Sanitize coupon code.
1551
		$coupon_code = wc_format_coupon_code( $coupon_code );
1552 23
1553 1
		// Get the coupon.
1554 1
		$the_coupon = new WC_Coupon( $coupon_code );
1555 1
1556
		// Prevent adding coupons by post ID.
1557
		if ( $the_coupon->get_code() !== $coupon_code ) {
1558
			$the_coupon->set_code( $coupon_code );
1559 23
			$the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_NOT_EXIST );
1560
			return false;
1561
		}
1562
1563
		// Check it can be used with cart.
1564
		if ( ! $the_coupon->is_valid() ) {
0 ignored issues
show
Deprecated Code introduced by
The method WC_Coupon::is_valid() has been deprecated with message: 3.2.0 In favor of WC_Discounts->is_coupon_valid.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
1565 23
			wc_add_notice( $the_coupon->get_error_message(), 'error' );
1566 1
			return false;
1567 1
		}
1568
1569
		// Check if applied.
1570
		if ( $this->has_discount( $coupon_code ) ) {
1571 23
			$the_coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED );
1572 3
			return false;
1573
		}
1574 3
1575 2
		// If its individual use then remove other coupons.
1576 2
		if ( $the_coupon->get_individual_use() ) {
1577 2
			$coupons_to_keep = apply_filters( 'woocommerce_apply_individual_use_coupon', array(), $the_coupon, $this->applied_coupons );
1578
1579 2
			foreach ( $this->applied_coupons as $applied_coupon ) {
1580
				$keep_key = array_search( $applied_coupon, $coupons_to_keep, true );
1581
				if ( false === $keep_key ) {
1582
					$this->remove_coupon( $applied_coupon );
1583 3
				} else {
1584
					unset( $coupons_to_keep[ $keep_key ] );
1585
				}
1586
			}
1587
1588
			if ( ! empty( $coupons_to_keep ) ) {
1589 23
				$this->applied_coupons += $coupons_to_keep;
1590 1
			}
1591 1
		}
1592
1593 1
		// Check to see if an individual use coupon is set.
1594
		if ( $this->applied_coupons ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->applied_coupons 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...
1595
			foreach ( $this->applied_coupons as $code ) {
1596 1
				$coupon = new WC_Coupon( $code );
1597
1598 1
				if ( $coupon->get_individual_use() && false === apply_filters( 'woocommerce_apply_with_individual_use_coupon', false, $the_coupon, $coupon, $this->applied_coupons ) ) {
1599
1600
					// Reject new coupon.
1601
					$coupon->add_coupon_message( WC_Coupon::E_WC_COUPON_ALREADY_APPLIED_INDIV_USE_ONLY );
1602
1603 23
					return false;
1604
				}
1605
			}
1606 23
		}
1607
1608
		$this->applied_coupons[] = $coupon_code;
1609
1610
		// Choose free shipping.
1611
		if ( $the_coupon->get_free_shipping() ) {
1612
			$packages                = WC()->shipping()->get_packages();
1613
			$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
1614
1615
			foreach ( $packages as $i => $package ) {
1616
				$chosen_shipping_methods[ $i ] = 'free_shipping';
1617 23
			}
1618
1619 23
			WC()->session->set( 'chosen_shipping_methods', $chosen_shipping_methods );
1620
		}
1621 23
1622
		$the_coupon->add_coupon_message( WC_Coupon::WC_COUPON_SUCCESS );
1623
1624
		do_action( 'woocommerce_applied_coupon', $coupon_code );
1625
1626
		return true;
1627
	}
1628
1629
	/**
1630 81
	 * Get array of applied coupon objects and codes.
1631 81
	 *
1632
	 * @param null $deprecated No longer used.
1633 81
	 * @return array of applied coupons
1634
	 */
1635
	public function get_coupons( $deprecated = null ) {
1636
		$coupons = array();
1637 81
1638 20
		if ( 'order' === $deprecated ) {
1639 20
			return $coupons;
1640
		}
1641
1642 81
		foreach ( $this->get_applied_coupons() as $code ) {
1643
			$coupon           = new WC_Coupon( $code );
1644
			$coupons[ $code ] = $coupon;
1645
		}
1646
1647
		return $coupons;
1648
	}
1649
1650
	/**
1651
	 * Get the discount amount for a used coupon.
1652
	 *
1653
	 * @param  string $code coupon code.
1654
	 * @param  bool   $ex_tax inc or ex tax.
1655
	 * @return float discount amount
1656
	 */
1657
	public function get_coupon_discount_amount( $code, $ex_tax = true ) {
1658
		$totals          = $this->get_coupon_discount_totals();
1659
		$discount_amount = isset( $totals[ $code ] ) ? $totals[ $code ] : 0;
1660
1661
		if ( ! $ex_tax ) {
1662
			$discount_amount += $this->get_coupon_discount_tax_amount( $code );
1663
		}
1664
1665
		return wc_cart_round_discount( $discount_amount, wc_get_price_decimals() );
1666
	}
1667
1668
	/**
1669
	 * Get the discount tax amount for a used coupon (for tax inclusive prices).
1670
	 *
1671
	 * @param  string $code coupon code.
1672
	 * @return float discount amount
1673
	 */
1674
	public function get_coupon_discount_tax_amount( $code ) {
1675
		$totals = $this->get_coupon_discount_tax_totals();
1676
		return wc_cart_round_discount( isset( $totals[ $code ] ) ? $totals[ $code ] : 0, wc_get_price_decimals() );
1677
	}
1678
1679 49
	/**
1680 49
	 * Remove coupons from the cart of a defined type. Type 1 is before tax, type 2 is after tax.
1681 49
	 *
1682 49
	 * @param null $deprecated No longer used.
1683 49
	 */
1684
	public function remove_coupons( $deprecated = null ) {
1685
		$this->set_coupon_discount_totals( array() );
1686
		$this->set_coupon_discount_tax_totals( array() );
1687
		$this->set_applied_coupons( array() );
1688
		$this->session->set_session();
1689
	}
1690
1691
	/**
1692 2
	 * Remove a single coupon by code.
1693 2
	 *
1694 2
	 * @param  string $coupon_code Code of the coupon to remove.
1695
	 * @return bool
1696 2
	 */
1697 2
	public function remove_coupon( $coupon_code ) {
1698
		$coupon_code = wc_format_coupon_code( $coupon_code );
1699
		$position    = array_search( $coupon_code, $this->get_applied_coupons(), true );
1700 2
1701
		if ( false !== $position ) {
1702 2
			unset( $this->applied_coupons[ $position ] );
1703
		}
1704 2
1705
		WC()->session->set( 'refresh_totals', true );
1706
1707
		do_action( 'woocommerce_removed_coupon', $coupon_code );
1708
1709
		return true;
1710
	}
1711
1712 77
	/**
1713 77
	 * Trigger an action so 3rd parties can add custom fees.
1714
	 *
1715
	 * @since 2.0.0
1716
	 */
1717
	public function calculate_fees() {
1718
		do_action( 'woocommerce_cart_calculate_fees', $this );
1719
	}
1720
1721
	/**
1722 78
	 * Return reference to fees API.
1723 78
	 *
1724
	 * @since  3.2.0
1725
	 * @return WC_Cart_Fees
1726
	 */
1727
	public function fees_api() {
1728
		return $this->fees_api;
1729
	}
1730
1731
	/**
1732
	 * Add additional fee to the cart.
1733
	 *
1734
	 * This method should be called on a callback attached to the
1735
	 * woocommerce_cart_calculate_fees action during cart/checkout. Fees do not
1736
	 * persist.
1737
	 *
1738
	 * @uses WC_Cart_Fees::add_fee
1739 11
	 * @param string $name      Unique name for the fee. Multiple fees of the same name cannot be added.
1740 11
	 * @param float  $amount    Fee amount (do not enter negative amounts).
1741
	 * @param bool   $taxable   Is the fee taxable? (default: false).
1742 11
	 * @param string $tax_class The tax class for the fee if taxable. A blank string is standard tax class. (default: '').
1743 11
	 */
1744 11
	public function add_fee( $name, $amount, $taxable = false, $tax_class = '' ) {
1745 11
		$this->fees_api()->add_fee(
1746
			array(
1747
				'name'      => $name,
1748
				'amount'    => (float) $amount,
1749
				'taxable'   => $taxable,
1750
				'tax_class' => $tax_class,
1751
			)
1752
		);
1753
	}
1754
1755
	/**
1756 77
	 * Return all added fees from the Fees API.
1757 77
	 *
1758
	 * @uses WC_Cart_Fees::get_fees
1759 77
	 * @return array
1760
	 */
1761
	public function get_fees() {
1762 77
		$fees = $this->fees_api()->get_fees();
1763
1764
		if ( property_exists( $this, 'fees' ) ) {
1765
			$fees = $fees + (array) $this->fees;
1766
		}
1767
		return $fees;
1768
	}
1769
1770 1
	/**
1771 1
	 * Gets the total excluding taxes.
1772
	 *
1773
	 * @return string formatted price
1774
	 */
1775
	public function get_total_ex_tax() {
1776
		return apply_filters( 'woocommerce_cart_total_ex_tax', wc_price( max( 0, $this->get_total( 'edit' ) - $this->get_total_tax() ) ) );
1777
	}
1778
1779
	/**
1780
	 * Gets the cart contents total (after calculation).
1781
	 *
1782
	 * @return string formatted price
1783
	 */
1784
	public function get_cart_total() {
1785
		return apply_filters( 'woocommerce_cart_contents_total', wc_price( wc_prices_include_tax() ? $this->get_cart_contents_total() + $this->get_cart_contents_tax() : $this->get_cart_contents_total() ) );
1786
	}
1787
1788
	/**
1789 1
	 * Gets the sub total (after calculation).
1790
	 *
1791
	 * @param bool $compound whether to include compound taxes.
1792
	 * @return string formatted price
1793 1
	 */
1794
	public function get_cart_subtotal( $compound = false ) {
1795
		/**
1796 1
		 * If the cart has compound tax, we want to show the subtotal as cart + shipping + non-compound taxes (after discount).
1797
		 */
1798
		if ( $compound ) {
1799
			$cart_subtotal = wc_price( $this->get_cart_contents_total() + $this->get_shipping_total() + $this->get_taxes_total( false, false ) );
1800
1801
		} elseif ( $this->display_prices_including_tax() ) {
1802
			$cart_subtotal = wc_price( $this->get_subtotal() + $this->get_subtotal_tax() );
1803 1
1804
			if ( $this->get_subtotal_tax() > 0 && ! wc_prices_include_tax() ) {
1805 1
				$cart_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
1806
			}
1807
		} else {
1808
			$cart_subtotal = wc_price( $this->get_subtotal() );
1809
1810 1
			if ( $this->get_subtotal_tax() > 0 && wc_prices_include_tax() ) {
1811
				$cart_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1812
			}
1813
		}
1814
1815
		return apply_filters( 'woocommerce_cart_subtotal', $cart_subtotal, $compound, $this );
1816
	}
1817
1818
	/**
1819
	 * Get the product row price per item.
1820
	 *
1821
	 * @param WC_Product $product Product object.
1822
	 * @return string formatted price
1823
	 */
1824
	public function get_product_price( $product ) {
1825
		if ( $this->display_prices_including_tax() ) {
1826
			$product_price = wc_get_price_including_tax( $product );
1827
		} else {
1828
			$product_price = wc_get_price_excluding_tax( $product );
1829
		}
1830
		return apply_filters( 'woocommerce_cart_product_price', wc_price( $product_price ), $product );
1831
	}
1832
1833
	/**
1834
	 * Get the product row subtotal.
1835
	 *
1836
	 * Gets the tax etc to avoid rounding issues.
1837
	 *
1838
	 * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate.
1839
	 *
1840
	 * @param WC_Product $product Product object.
1841
	 * @param int        $quantity Quantity being purchased.
1842
	 * @return string formatted price
1843
	 */
1844
	public function get_product_subtotal( $product, $quantity ) {
1845
		$price = $product->get_price();
1846
1847
		if ( $product->is_taxable() ) {
1848
1849
			if ( $this->display_prices_including_tax() ) {
1850
				$row_price        = wc_get_price_including_tax( $product, array( 'qty' => $quantity ) );
1851
				$product_subtotal = wc_price( $row_price );
1852
1853
				if ( ! wc_prices_include_tax() && $this->get_subtotal_tax() > 0 ) {
1854
					$product_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
1855
				}
1856
			} else {
1857
				$row_price        = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) );
1858
				$product_subtotal = wc_price( $row_price );
1859
1860
				if ( wc_prices_include_tax() && $this->get_subtotal_tax() > 0 ) {
1861
					$product_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1862
				}
1863
			}
1864
		} else {
1865
			$row_price        = $price * $quantity;
1866
			$product_subtotal = wc_price( $row_price );
1867
		}
1868
1869
		return apply_filters( 'woocommerce_cart_product_subtotal', $product_subtotal, $product, $quantity, $this );
1870
	}
1871
1872
	/**
1873
	 * Gets the cart tax (after calculation).
1874
	 *
1875
	 * @return string formatted price
1876
	 */
1877
	public function get_cart_tax() {
1878
		$cart_total_tax = wc_round_tax_total( $this->get_cart_contents_tax() + $this->get_shipping_tax() + $this->get_fee_tax() );
1879
1880
		return apply_filters( 'woocommerce_get_cart_tax', $cart_total_tax ? wc_price( $cart_total_tax ) : '' );
1881
	}
1882
1883
	/**
1884
	 * Get a tax amount.
1885
	 *
1886
	 * @param  string $tax_rate_id ID of the tax rate to get taxes for.
1887
	 * @return float amount
1888
	 */
1889
	public function get_tax_amount( $tax_rate_id ) {
1890
		$taxes = wc_array_merge_recursive_numeric( $this->get_cart_contents_taxes(), $this->get_fee_taxes() );
1891
		return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
1892
	}
1893
1894
	/**
1895
	 * Get a tax amount.
1896
	 *
1897
	 * @param  string $tax_rate_id ID of the tax rate to get taxes for.
1898
	 * @return float amount
1899
	 */
1900
	public function get_shipping_tax_amount( $tax_rate_id ) {
1901
		$taxes = $this->get_shipping_taxes();
1902
		return isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0;
1903
	}
1904
1905
	/**
1906
	 * Get tax row amounts with or without compound taxes includes.
1907
	 *
1908
	 * @param  bool $compound True if getting compound taxes.
1909
	 * @param  bool $display  True if getting total to display.
1910
	 * @return float price
1911
	 */
1912
	public function get_taxes_total( $compound = true, $display = true ) {
1913
		$total = 0;
1914
		$taxes = $this->get_taxes();
1915
		foreach ( $taxes as $key => $tax ) {
1916
			if ( ! $compound && WC_Tax::is_compound( $key ) ) {
1917
				continue;
1918
			}
1919
			$total += $tax;
1920
		}
1921
		if ( $display ) {
1922
			$total = wc_round_tax_total( $total );
1923
		}
1924
		return apply_filters( 'woocommerce_cart_taxes_total', $total, $compound, $display, $this );
1925
	}
1926
1927
	/**
1928
	 * Gets the total discount amount.
1929
	 *
1930
	 * @return mixed formatted price or false if there are none
1931
	 */
1932
	public function get_total_discount() {
1933
		return apply_filters( 'woocommerce_cart_total_discount', $this->get_discount_total() ? wc_price( $this->get_discount_total() ) : false, $this );
1934 84
	}
1935 84
1936 84
	/**
1937 84
	 * Reset cart totals to the defaults. Useful before running calculations.
1938
	 */
1939
	private function reset_totals() {
1940
		$this->totals = $this->default_totals;
1941
		$this->fees_api->remove_all_fees();
1942
		do_action( 'woocommerce_cart_reset', $this, false );
1943
	}
1944
1945 1
	/**
1946 1
	 * Returns 'incl' if tax should be included in cart, otherwise returns 'excl'.
1947
	 *
1948
	 * @return string
1949
	 */
1950 1
	private function is_tax_displayed() {
1951
		if ( $this->get_customer() && $this->get_customer()->get_is_vat_exempt() ) {
1952
			return 'excl';
1953
		}
1954
1955
		return get_option( 'woocommerce_tax_display_cart' );
1956
	}
1957
1958
	/**
1959
	 * Returns the hash based on cart contents.
1960
	 *
1961
	 * @since 3.6.0
1962
	 * @return string hash for cart content
1963
	 */
1964
	public function get_cart_hash() {
1965
		$cart_session = $this->session->get_cart_for_session();
1966
		$hash         = $cart_session ? md5( wp_json_encode( $cart_session ) . $this->get_total( 'edit' ) ) : '';
1967
		$hash         = apply_filters_deprecated( 'woocommerce_add_to_cart_hash', array( $hash, $cart_session ), '3.6.0', 'woocommerce_cart_hash' );
1968
1969
		return apply_filters( 'woocommerce_cart_hash', $hash, $cart_session );
1970
	}
1971
}
1972