Issues (942)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/abstracts/abstract-wc-order.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * Abstract Order
4
 *
5
 * Handles generic order data and database interaction which is extended by both
6
 * WC_Order (regular orders) and WC_Order_Refund (refunds are negative orders).
7
 *
8
 * @class       WC_Abstract_Order
9
 * @version     3.0.0
10
 * @package     WooCommerce/Classes
11
 */
12
13
defined( 'ABSPATH' ) || exit;
14
15
require_once WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php';
16
17
/**
18
 * WC_Abstract_Order class.
19
 */
20
abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
21
22
	/**
23
	 * Order Data array. This is the core order data exposed in APIs since 3.0.0.
24
	 *
25
	 * Notes: cart_tax = cart_tax is the new name for the legacy 'order_tax'
26
	 * which is the tax for items only, not shipping.
27
	 *
28
	 * @since 3.0.0
29
	 * @var array
30
	 */
31
	protected $data = array(
32
		'parent_id'          => 0,
33
		'status'             => '',
34
		'currency'           => '',
35
		'version'            => '',
36
		'prices_include_tax' => false,
37
		'date_created'       => null,
38
		'date_modified'      => null,
39
		'discount_total'     => 0,
40
		'discount_tax'       => 0,
41
		'shipping_total'     => 0,
42
		'shipping_tax'       => 0,
43
		'cart_tax'           => 0,
44
		'total'              => 0,
45
		'total_tax'          => 0,
46
	);
47
48
	/**
49
	 * Order items will be stored here, sometimes before they persist in the DB.
50
	 *
51
	 * @since 3.0.0
52
	 * @var array
53
	 */
54
	protected $items = array();
55
56
	/**
57
	 * Order items that need deleting are stored here.
58
	 *
59
	 * @since 3.0.0
60
	 * @var array
61
	 */
62
	protected $items_to_delete = array();
63
64
	/**
65
	 * Stores meta in cache for future reads.
66
	 *
67
	 * A group must be set to to enable caching.
68
	 *
69
	 * @var string
70
	 */
71
	protected $cache_group = 'orders';
72
73
	/**
74
	 * Which data store to load.
75
	 *
76
	 * @var string
77
	 */
78
	protected $data_store_name = 'order';
79
80
	/**
81
	 * This is the name of this object type.
82
	 *
83
	 * @var string
84
	 */
85
	protected $object_type = 'order';
86
87
	/**
88
	 * Get the order if ID is passed, otherwise the order is new and empty.
89
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
90
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
91
	 * methods that will be maintained going forward.
92
	 *
93
	 * @param  int|object|WC_Order $order Order to read.
94
	 */
95 212
	public function __construct( $order = 0 ) {
96 212
		parent::__construct( $order );
97
98 212 View Code Duplication
		if ( is_numeric( $order ) && $order > 0 ) {
99 58
			$this->set_id( $order );
100 212
		} elseif ( $order instanceof self ) {
101
			$this->set_id( $order->get_id() );
102 212
		} elseif ( ! empty( $order->ID ) ) {
103
			$this->set_id( $order->ID );
104
		} else {
105 212
			$this->set_object_read( true );
106
		}
107
108 212
		$this->data_store = WC_Data_Store::load( $this->data_store_name );
109
110 212
		if ( $this->get_id() > 0 ) {
111 58
			$this->data_store->read( $this );
112
		}
113
	}
114
115
	/**
116
	 * Get internal type.
117
	 *
118
	 * @return string
119
	 */
120 130
	public function get_type() {
121 130
		return 'shop_order';
122
	}
123
124
	/**
125
	 * Get all class data in array format.
126
	 *
127
	 * @since 3.0.0
128
	 * @return array
129
	 */
130 1 View Code Duplication
	public function get_data() {
131 1
		return array_merge(
132
			array(
133 1
				'id' => $this->get_id(),
134
			),
135 1
			$this->data,
136
			array(
137 1
				'meta_data'      => $this->get_meta_data(),
138 1
				'line_items'     => $this->get_items( 'line_item' ),
139 1
				'tax_lines'      => $this->get_items( 'tax' ),
140 1
				'shipping_lines' => $this->get_items( 'shipping' ),
141 1
				'fee_lines'      => $this->get_items( 'fee' ),
142 1
				'coupon_lines'   => $this->get_items( 'coupon' ),
143
			)
144
		);
145
	}
146
147
	/*
148
	|--------------------------------------------------------------------------
149
	| CRUD methods
150
	|--------------------------------------------------------------------------
151
	|
152
	| Methods which create, read, update and delete orders from the database.
153
	| Written in abstract fashion so that the way orders are stored can be
154
	| changed more easily in the future.
155
	|
156
	| A save method is included for convenience (chooses update or create based
157
	| on if the order exists yet).
158
	|
159
	*/
160
161
	/**
162
	 * Save data to the database.
163
	 *
164
	 * @since 3.0.0
165
	 * @return int order ID
166
	 */
167 129
	public function save() {
168 129
		if ( ! $this->data_store ) {
169
			return $this->get_id();
170
		}
171
172
		try {
173
			/**
174
			 * Trigger action before saving to the DB. Allows you to adjust object props before save.
175
			 *
176
			 * @param WC_Data          $this The object being saved.
177
			 * @param WC_Data_Store_WP $data_store THe data store persisting the data.
178
			 */
179 129
			do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
180
181 129
			if ( $this->get_id() ) {
182 88
				$this->data_store->update( $this );
183
			} else {
184 129
				$this->data_store->create( $this );
185
			}
186
187 129
			$this->save_items();
188
189
			/**
190
			 * Trigger action after saving to the DB.
191
			 *
192
			 * @param WC_Data          $this The object being saved.
193
			 * @param WC_Data_Store_WP $data_store THe data store persisting the data.
194
			 */
195 129
			do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store );
196
197 1
		} catch ( Exception $e ) {
198 1
			$this->handle_exception( $e, __( 'Error saving order.', 'woocommerce' ) );
199
		}
200
201 129
		return $this->get_id();
202
	}
203
204
	/**
205
	 * Log an error about this order is exception is encountered.
206
	 *
207
	 * @param Exception $e Exception object.
208
	 * @param string    $message Message regarding exception thrown.
209
	 * @since 3.7.0
210
	 */
211
	protected function handle_exception( $e, $message = 'Error' ) {
212
		wc_get_logger()->error(
213
			$message,
214
			array(
215
				'order' => $this,
216
				'error' => $e,
217
			)
218
		);
219
	}
220
221
	/**
222
	 * Save all order items which are part of this order.
223
	 */
224 129
	protected function save_items() {
225 129
		$items_changed = false;
226
227 129
		foreach ( $this->items_to_delete as $item ) {
228 8
			$item->delete();
229 8
			$items_changed = true;
230
		}
231 129
		$this->items_to_delete = array();
232
233
		// Add/save items.
234 129
		foreach ( $this->items as $item_group => $items ) {
235 97
			if ( is_array( $items ) ) {
236 97
				$items = array_filter( $items );
237 97
				foreach ( $items as $item_key => $item ) {
238 86
					$item->set_order_id( $this->get_id() );
239
240 86
					$item_id = $item->save();
241
242
					// If ID changed (new item saved to DB)...
243 86
					if ( $item_id !== $item_key ) {
244 82
						$this->items[ $item_group ][ $item_id ] = $item;
245
246 82
						unset( $this->items[ $item_group ][ $item_key ] );
247
248 82
						$items_changed = true;
249
					}
250
				}
251
			}
252
		}
253
254 129
		if ( $items_changed ) {
255 84
			delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
256
		}
257
	}
258
259
	/*
260
	|--------------------------------------------------------------------------
261
	| Getters
262
	|--------------------------------------------------------------------------
263
	*/
264
265
	/**
266
	 * Get parent order ID.
267
	 *
268
	 * @since 3.0.0
269
	 * @param  string $context View or edit context.
270
	 * @return integer
271
	 */
272 129
	public function get_parent_id( $context = 'view' ) {
273 129
		return $this->get_prop( 'parent_id', $context );
274
	}
275
276
	/**
277
	 * Gets order currency.
278
	 *
279
	 * @param  string $context View or edit context.
280
	 * @return string
281
	 */
282 131
	public function get_currency( $context = 'view' ) {
283 131
		return $this->get_prop( 'currency', $context );
284
	}
285
286
	/**
287
	 * Get order_version.
288
	 *
289
	 * @param  string $context View or edit context.
290
	 * @return string
291
	 */
292 130
	public function get_version( $context = 'view' ) {
293 130
		return $this->get_prop( 'version', $context );
294
	}
295
296
	/**
297
	 * Get prices_include_tax.
298
	 *
299
	 * @param  string $context View or edit context.
300
	 * @return bool
301
	 */
302 130
	public function get_prices_include_tax( $context = 'view' ) {
303 130
		return $this->get_prop( 'prices_include_tax', $context );
304
	}
305
306
	/**
307
	 * Get date_created.
308
	 *
309
	 * @param  string $context View or edit context.
310
	 * @return WC_DateTime|NULL object if the date is set or null if there is no date.
311
	 */
312 131
	public function get_date_created( $context = 'view' ) {
313 131
		return $this->get_prop( 'date_created', $context );
314
	}
315
316
	/**
317
	 * Get date_modified.
318
	 *
319
	 * @param  string $context View or edit context.
320
	 * @return WC_DateTime|NULL object if the date is set or null if there is no date.
321
	 */
322 1
	public function get_date_modified( $context = 'view' ) {
323 1
		return $this->get_prop( 'date_modified', $context );
324
	}
325
326
	/**
327
	 * Return the order statuses without wc- internal prefix.
328
	 *
329
	 * @param  string $context View or edit context.
330
	 * @return string
331
	 */
332 135
	public function get_status( $context = 'view' ) {
333 135
		$status = $this->get_prop( 'status', $context );
334
335 135
		if ( empty( $status ) && 'view' === $context ) {
336
			// In view context, return the default status if no status has been set.
337 102
			$status = apply_filters( 'woocommerce_default_order_status', 'pending' );
338
		}
339 135
		return $status;
340
	}
341
342
	/**
343
	 * Get discount_total.
344
	 *
345
	 * @param  string $context View or edit context.
346
	 * @return string
347
	 */
348 131
	public function get_discount_total( $context = 'view' ) {
349 131
		return $this->get_prop( 'discount_total', $context );
350
	}
351
352
	/**
353
	 * Get discount_tax.
354
	 *
355
	 * @param  string $context View or edit context.
356
	 * @return string
357
	 */
358 131
	public function get_discount_tax( $context = 'view' ) {
359 131
		return $this->get_prop( 'discount_tax', $context );
360
	}
361
362
	/**
363
	 * Get shipping_total.
364
	 *
365
	 * @param  string $context View or edit context.
366
	 * @return string
367
	 */
368 130
	public function get_shipping_total( $context = 'view' ) {
369 130
		return $this->get_prop( 'shipping_total', $context );
370
	}
371
372
	/**
373
	 * Get shipping_tax.
374
	 *
375
	 * @param  string $context View or edit context.
376
	 * @return string
377
	 */
378 132
	public function get_shipping_tax( $context = 'view' ) {
379 132
		return $this->get_prop( 'shipping_tax', $context );
380
	}
381
382
	/**
383
	 * Gets cart tax amount.
384
	 *
385
	 * @param  string $context View or edit context.
386
	 * @return float
387
	 */
388 132
	public function get_cart_tax( $context = 'view' ) {
389 132
		return $this->get_prop( 'cart_tax', $context );
390
	}
391
392
	/**
393
	 * Gets order grand total. incl. taxes. Used in gateways.
394
	 *
395
	 * @param  string $context View or edit context.
396
	 * @return float
397
	 */
398 132
	public function get_total( $context = 'view' ) {
399 132
		return $this->get_prop( 'total', $context );
400
	}
401
402
	/**
403
	 * Get total tax amount. Alias for get_order_tax().
404
	 *
405
	 * @param  string $context View or edit context.
406
	 * @return float
407
	 */
408 5
	public function get_total_tax( $context = 'view' ) {
409 5
		return $this->get_prop( 'total_tax', $context );
410
	}
411
412
	/*
413
	|--------------------------------------------------------------------------
414
	| Non-CRUD Getters
415
	|--------------------------------------------------------------------------
416
	*/
417
418
	/**
419
	 * Gets the total discount amount.
420
	 *
421
	 * @param  bool $ex_tax Show discount excl any tax.
422
	 * @return float
423
	 */
424 2
	public function get_total_discount( $ex_tax = true ) {
425 2
		if ( $ex_tax ) {
426 1
			$total_discount = $this->get_discount_total();
427
		} else {
428 2
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
429
		}
430 2
		return apply_filters( 'woocommerce_order_get_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
431
	}
432
433
	/**
434
	 * Gets order subtotal.
435
	 *
436
	 * @return float
437
	 */
438 9
	public function get_subtotal() {
439 9
		$subtotal = 0;
440
441 9
		foreach ( $this->get_items() as $item ) {
442 9
			$subtotal += $item->get_subtotal();
443
		}
444
445 9
		return apply_filters( 'woocommerce_order_get_subtotal', (float) $subtotal, $this );
446
	}
447
448
	/**
449
	 * Get taxes, merged by code, formatted ready for output.
450
	 *
451
	 * @return array
452
	 */
453 1
	public function get_tax_totals() {
454 1
		$tax_totals = array();
455
456 1
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
457
			$code = $tax->get_rate_code();
458
459 View Code Duplication
			if ( ! isset( $tax_totals[ $code ] ) ) {
460
				$tax_totals[ $code ]         = new stdClass();
461
				$tax_totals[ $code ]->amount = 0;
462
			}
463
464
			$tax_totals[ $code ]->id               = $key;
465
			$tax_totals[ $code ]->rate_id          = $tax->get_rate_id();
466
			$tax_totals[ $code ]->is_compound      = $tax->is_compound();
467
			$tax_totals[ $code ]->label            = $tax->get_label();
468
			$tax_totals[ $code ]->amount          += (float) $tax->get_tax_total() + (float) $tax->get_shipping_tax_total();
469
			$tax_totals[ $code ]->formatted_amount = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
470
		}
471
472 1 View Code Duplication
		if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
473 1
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
474 1
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
475
		}
476
477 1
		return apply_filters( 'woocommerce_order_get_tax_totals', $tax_totals, $this );
478
	}
479
480
	/**
481
	 * Get all valid statuses for this order
482
	 *
483
	 * @since 3.0.0
484
	 * @return array Internal status keys e.g. 'wc-processing'
485
	 */
486 83
	protected function get_valid_statuses() {
487 83
		return array_keys( wc_get_order_statuses() );
488
	}
489
490
	/**
491
	 * Get user ID. Used by orders, not other order types like refunds.
492
	 *
493
	 * @param  string $context View or edit context.
494
	 * @return int
495
	 */
496
	public function get_user_id( $context = 'view' ) {
497
		return 0;
498
	}
499
500
	/**
501
	 * Get user. Used by orders, not other order types like refunds.
502
	 *
503
	 * @return WP_User|false
504
	 */
505
	public function get_user() {
506
		return false;
507
	}
508
509
	/*
510
	|--------------------------------------------------------------------------
511
	| Setters
512
	|--------------------------------------------------------------------------
513
	|
514
	| Functions for setting order data. These should not update anything in the
515
	| database itself and should only change what is stored in the class
516
	| object. However, for backwards compatibility pre 3.0.0 some of these
517
	| setters may handle both.
518
	*/
519
520
	/**
521
	 * Set parent order ID.
522
	 *
523
	 * @since 3.0.0
524
	 * @param int $value Value to set.
525
	 * @throws WC_Data_Exception Exception thrown if parent ID does not exist or is invalid.
526
	 */
527 58
	public function set_parent_id( $value ) {
528 58
		if ( $value && ( $value === $this->get_id() || ! wc_get_order( $value ) ) ) {
529
			$this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
530
		}
531 58
		$this->set_prop( 'parent_id', absint( $value ) );
532
	}
533
534
	/**
535
	 * Set order status.
536
	 *
537
	 * @since 3.0.0
538
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
539
	 * @return array details of change
540
	 */
541 101
	public function set_status( $new_status ) {
542 101
		$old_status = $this->get_status();
543 101
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
544
545
		// If setting the status, ensure it's set to a valid status.
546 101
		if ( true === $this->object_read ) {
547
			// Only allow valid new status.
548 83 View Code Duplication
			if ( ! in_array( 'wc-' . $new_status, $this->get_valid_statuses(), true ) && 'trash' !== $new_status ) {
549
				$new_status = 'pending';
550
			}
551
552
			// If the old status is set but unknown (e.g. draft) assume its pending for action usage.
553 83 View Code Duplication
			if ( $old_status && ! in_array( 'wc-' . $old_status, $this->get_valid_statuses(), true ) && 'trash' !== $old_status ) {
554
				$old_status = 'pending';
555
			}
556
		}
557
558 101
		$this->set_prop( 'status', $new_status );
559
560
		return array(
561 101
			'from' => $old_status,
562 101
			'to'   => $new_status,
563
		);
564
	}
565
566
	/**
567
	 * Set order_version.
568
	 *
569
	 * @param string $value Value to set.
570
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
571
	 */
572 130
	public function set_version( $value ) {
573 130
		$this->set_prop( 'version', $value );
574
	}
575
576
	/**
577
	 * Set order_currency.
578
	 *
579
	 * @param string $value Value to set.
580
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
581
	 */
582 131
	public function set_currency( $value ) {
583 131
		if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ), true ) ) {
584
			$this->error( 'order_invalid_currency', __( 'Invalid currency code', 'woocommerce' ) );
585
		}
586 131
		$this->set_prop( 'currency', $value ? $value : get_woocommerce_currency() );
587
	}
588
589
	/**
590
	 * Set prices_include_tax.
591
	 *
592
	 * @param bool $value Value to set.
593
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
594
	 */
595 96
	public function set_prices_include_tax( $value ) {
596 96
		$this->set_prop( 'prices_include_tax', (bool) $value );
597
	}
598
599
	/**
600
	 * Set date_created.
601
	 *
602
	 * @param  string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
603
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
604
	 */
605 131
	public function set_date_created( $date = null ) {
606 131
		$this->set_date_prop( 'date_created', $date );
607
	}
608
609
	/**
610
	 * Set date_modified.
611
	 *
612
	 * @param  string|integer|null $date UTC timestamp, or ISO 8601 DateTime. If the DateTime string has no timezone or offset, WordPress site timezone will be assumed. Null if there is no date.
613
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
614
	 */
615 59
	public function set_date_modified( $date = null ) {
616 59
		$this->set_date_prop( 'date_modified', $date );
617
	}
618
619
	/**
620
	 * Set discount_total.
621
	 *
622
	 * @param string $value Value to set.
623
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
624
	 */
625 102
	public function set_discount_total( $value ) {
626 102
		$this->set_prop( 'discount_total', wc_format_decimal( $value ) );
627
	}
628
629
	/**
630
	 * Set discount_tax.
631
	 *
632
	 * @param string $value Value to set.
633
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
634
	 */
635 102
	public function set_discount_tax( $value ) {
636 102
		$this->set_prop( 'discount_tax', wc_format_decimal( $value ) );
637
	}
638
639
	/**
640
	 * Set shipping_total.
641
	 *
642
	 * @param string $value Value to set.
643
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
644
	 */
645 102
	public function set_shipping_total( $value ) {
646 102
		$this->set_prop( 'shipping_total', wc_format_decimal( $value ) );
647
	}
648
649
	/**
650
	 * Set shipping_tax.
651
	 *
652
	 * @param string $value Value to set.
653
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
654
	 */
655 105
	public function set_shipping_tax( $value ) {
656 105
		$this->set_prop( 'shipping_tax', wc_format_decimal( $value ) );
657 105
		$this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() );
658
	}
659
660
	/**
661
	 * Set cart tax.
662
	 *
663
	 * @param string $value Value to set.
664
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
665
	 */
666 105
	public function set_cart_tax( $value ) {
667 105
		$this->set_prop( 'cart_tax', wc_format_decimal( $value ) );
668 105
		$this->set_total_tax( (float) $this->get_cart_tax() + (float) $this->get_shipping_tax() );
669
	}
670
671
	/**
672
	 * Sets order tax (sum of cart and shipping tax). Used internally only.
673
	 *
674
	 * @param string $value Value to set.
675
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
676
	 */
677 106
	protected function set_total_tax( $value ) {
678 106
		$this->set_prop( 'total_tax', wc_format_decimal( $value ) );
679
	}
680
681
	/**
682
	 * Set total.
683
	 *
684
	 * @param string $value Value to set.
685
	 * @param string $deprecated Function used to set different totals based on this.
686
	 *
687
	 * @return bool|void
688
	 * @throws WC_Data_Exception Exception may be thrown if value is invalid.
689
	 */
690 103
	public function set_total( $value, $deprecated = '' ) {
691 103
		if ( $deprecated ) {
692
			wc_deprecated_argument( 'total_type', '3.0', 'Use dedicated total setter methods instead.' );
693
			return $this->legacy_set_total( $value, $deprecated );
694
		}
695 103
		$this->set_prop( 'total', wc_format_decimal( $value, wc_get_price_decimals() ) );
696
	}
697
698
	/*
699
	|--------------------------------------------------------------------------
700
	| Order Item Handling
701
	|--------------------------------------------------------------------------
702
	|
703
	| Order items are used for products, taxes, shipping, and fees within
704
	| each order.
705
	*/
706
707
	/**
708
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
709
	 *
710
	 * @param string $type Order item type. Default null.
711
	 */
712 1
	public function remove_order_items( $type = null ) {
713 1
		if ( ! empty( $type ) ) {
714
			$this->data_store->delete_items( $this, $type );
715
716
			$group = $this->type_to_group( $type );
717
718
			if ( $group ) {
719
				unset( $this->items[ $group ] );
720
			}
721
		} else {
722 1
			$this->data_store->delete_items( $this );
723 1
			$this->items = array();
724
		}
725
	}
726
727
	/**
728
	 * Convert a type to a types group.
729
	 *
730
	 * @param string $type type to lookup.
731
	 * @return string
732
	 */
733 108
	protected function type_to_group( $type ) {
734 108
		$type_to_group = apply_filters(
735 108
			'woocommerce_order_type_to_group',
736
			array(
737 108
				'line_item' => 'line_items',
738
				'tax'       => 'tax_lines',
739
				'shipping'  => 'shipping_lines',
740
				'fee'       => 'fee_lines',
741
				'coupon'    => 'coupon_lines',
742
			)
743
		);
744 108
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
745
	}
746
747
	/**
748
	 * Return an array of items/products within this order.
749
	 *
750
	 * @param string|array $types Types of line items to get (array or string).
751
	 * @return WC_Order_Item[]
752
	 */
753 108
	public function get_items( $types = 'line_item' ) {
754 108
		$items = array();
755 108
		$types = array_filter( (array) $types );
756
757 108
		foreach ( $types as $type ) {
758 108
			$group = $this->type_to_group( $type );
759
760 108
			if ( $group ) {
761 108
				if ( ! isset( $this->items[ $group ] ) ) {
762 108
					$this->items[ $group ] = array_filter( $this->data_store->read_items( $this, $type ) );
763
				}
764
				// Don't use array_merge here because keys are numeric.
765 108
				$items = $items + $this->items[ $group ];
766
			}
767
		}
768
769 108
		return apply_filters( 'woocommerce_order_get_items', $items, $this, $types );
770
	}
771
772
	/**
773
	 * Return an array of coupons within this order.
774
	 *
775
	 * @since  3.7.0
776
	 * @return WC_Order_Item_Coupon[]
777
	 */
778 2
	public function get_coupons() {
779 2
		return $this->get_items( 'coupon' );
780
	}
781
782
	/**
783
	 * Return an array of fees within this order.
784
	 *
785
	 * @return WC_Order_item_Fee[]
786
	 */
787 23
	public function get_fees() {
788 23
		return $this->get_items( 'fee' );
789
	}
790
791
	/**
792
	 * Return an array of taxes within this order.
793
	 *
794
	 * @return WC_Order_Item_Tax[]
795
	 */
796 25
	public function get_taxes() {
797 25
		return $this->get_items( 'tax' );
798
	}
799
800
	/**
801
	 * Return an array of shipping costs within this order.
802
	 *
803
	 * @return WC_Order_Item_Shipping[]
804
	 */
805 30
	public function get_shipping_methods() {
806 30
		return $this->get_items( 'shipping' );
807
	}
808
809
	/**
810
	 * Gets formatted shipping method title.
811
	 *
812
	 * @return string
813
	 */
814 1
	public function get_shipping_method() {
815 1
		$names = array();
816 1
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
817 1
			$names[] = $shipping_method->get_name();
818
		}
819 1
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
820
	}
821
822
	/**
823
	 * Get used coupon codes only.
824
	 *
825
	 * @since 3.7.0
826
	 * @return array
827
	 */
828 18
	public function get_coupon_codes() {
829 18
		$coupon_codes = array();
830 18
		$coupons      = $this->get_items( 'coupon' );
831
832 18
		if ( $coupons ) {
833 2
			foreach ( $coupons as $coupon ) {
834 2
				$coupon_codes[] = $coupon->get_code();
835
			}
836
		}
837 18
		return $coupon_codes;
838
	}
839
840
	/**
841
	 * Gets the count of order items of a certain type.
842
	 *
843
	 * @param string $item_type Item type to lookup.
844
	 * @return int|string
845
	 */
846 9
	public function get_item_count( $item_type = '' ) {
847 9
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
848 9
		$count = 0;
849
850 9
		foreach ( $items as $item ) {
851 6
			$count += $item->get_quantity();
852
		}
853
854 9
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
855
	}
856
857
	/**
858
	 * Get an order item object, based on its type.
859
	 *
860
	 * @since  3.0.0
861
	 * @param  int  $item_id ID of item to get.
862
	 * @param  bool $load_from_db Prior to 3.2 this item was loaded direct from WC_Order_Factory, not this object. This param is here for backwards compatility with that. If false, uses the local items variable instead.
863
	 * @return WC_Order_Item|false
864
	 */
865 15
	public function get_item( $item_id, $load_from_db = true ) {
866 15
		if ( $load_from_db ) {
867 1
			return WC_Order_Factory::get_order_item( $item_id );
868
		}
869
870
		// Search for item id.
871 14
		if ( $this->items ) {
872 14
			foreach ( $this->items as $group => $items ) {
873 14
				if ( isset( $items[ $item_id ] ) ) {
874 14
					return $items[ $item_id ];
875
				}
876
			}
877
		}
878
879
		// Load all items of type and cache.
880
		$type = $this->data_store->get_order_item_type( $this, $item_id );
881
882
		if ( ! $type ) {
883
			return false;
884
		}
885
886
		$items = $this->get_items( $type );
887
888
		return ! empty( $items[ $item_id ] ) ? $items[ $item_id ] : false;
889
	}
890
891
	/**
892
	 * Get key for where a certain item type is stored in _items.
893
	 *
894
	 * @since  3.0.0
895
	 * @param  string $item object Order item (product, shipping, fee, coupon, tax).
896
	 * @return string
897
	 */
898 89
	protected function get_items_key( $item ) {
899 89
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
900 81
			return 'line_items';
901 81
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
902 3
			return 'fee_lines';
903 78
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
904 65
			return 'shipping_lines';
905 20
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
906 10
			return 'tax_lines';
907 15
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
908 15
			return 'coupon_lines';
909
		}
910 1
		return apply_filters( 'woocommerce_get_items_key', '', $item );
911
	}
912
913
	/**
914
	 * Remove item from the order.
915
	 *
916
	 * @param int $item_id Item ID to delete.
917
	 * @return false|void
918
	 */
919 8
	public function remove_item( $item_id ) {
920 8
		$item      = $this->get_item( $item_id, false );
921 8
		$items_key = $item ? $this->get_items_key( $item ) : false;
922
923 8
		if ( ! $items_key ) {
924
			return false;
925
		}
926
927
		// Unset and remove later.
928 8
		$this->items_to_delete[] = $item;
929 8
		unset( $this->items[ $items_key ][ $item->get_id() ] );
930
	}
931
932
	/**
933
	 * Adds an order item to this order. The order item will not persist until save.
934
	 *
935
	 * @since 3.0.0
936
	 * @param WC_Order_Item $item Order item object (product, shipping, fee, coupon, tax).
937
	 * @return false|void
938
	 */
939 89
	public function add_item( $item ) {
940 89
		$items_key = $this->get_items_key( $item );
941
942 89
		if ( ! $items_key ) {
943 1
			return false;
944
		}
945
946
		// Make sure existing items are loaded so we can append this new one.
947 89
		if ( ! isset( $this->items[ $items_key ] ) ) {
948 88
			$this->items[ $items_key ] = $this->get_items( $item->get_type() );
949
		}
950
951
		// Set parent.
952 89
		$item->set_order_id( $this->get_id() );
953
954
		// Append new row with generated temporary ID.
955 89
		$item_id = $item->get_id();
956
957 89
		if ( $item_id ) {
958 75
			$this->items[ $items_key ][ $item_id ] = $item;
959
		} else {
960 84
			$this->items[ $items_key ][ 'new:' . $items_key . count( $this->items[ $items_key ] ) ] = $item;
961
		}
962
	}
963
964
	/**
965
	 * Apply a coupon to the order and recalculate totals.
966
	 *
967
	 * @since 3.2.0
968
	 * @param string|WC_Coupon $raw_coupon Coupon code or object.
969
	 * @return true|WP_Error True if applied, error if not.
970
	 */
971 8
	public function apply_coupon( $raw_coupon ) {
972 8
		if ( is_a( $raw_coupon, 'WC_Coupon' ) ) {
973 1
			$coupon = $raw_coupon;
974 7
		} elseif ( is_string( $raw_coupon ) ) {
975 7
			$code   = wc_format_coupon_code( $raw_coupon );
976 7
			$coupon = new WC_Coupon( $code );
977
978 7
			if ( $coupon->get_code() !== $code ) {
979
				return new WP_Error( 'invalid_coupon', __( 'Invalid coupon code', 'woocommerce' ) );
980
			}
981
982 7
			$discounts = new WC_Discounts( $this );
983 7
			$valid     = $discounts->is_coupon_valid( $coupon );
984
985 7
			if ( is_wp_error( $valid ) ) {
986 7
				return $valid;
987
			}
988
		} else {
989
			return new WP_Error( 'invalid_coupon', __( 'Invalid coupon', 'woocommerce' ) );
990
		}
991
992
		// Check to make sure coupon is not already applied.
993 8
		$applied_coupons = $this->get_items( 'coupon' );
994 8
		foreach ( $applied_coupons as $applied_coupon ) {
995 4
			if ( $applied_coupon->get_code() === $coupon->get_code() ) {
0 ignored issues
show
It seems like you code against a specific sub-type and not the parent class WC_Order_Item as the method get_code() does only exist in the following sub-classes of WC_Order_Item: WC_Order_Item_Coupon. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
996
				return new WP_Error( 'invalid_coupon', __( 'Coupon code already applied!', 'woocommerce' ) );
997
			}
998
		}
999
1000 8
		$discounts = new WC_Discounts( $this );
1001 8
		$applied   = $discounts->apply_coupon( $coupon );
1002
1003 8
		if ( is_wp_error( $applied ) ) {
1004
			return $applied;
1005
		}
1006
1007 8
		$data_store = $coupon->get_data_store();
1008
1009
		// Check specific for guest checkouts here as well since WC_Cart handles that seperately in check_customer_coupons.
1010 8
		if ( $data_store && 0 === $this->get_customer_id() ) {
1011 1
			$usage_count = $data_store->get_usage_by_email( $coupon, $this->get_billing_email() );
1012 1
			if ( 0 < $coupon->get_usage_limit_per_user() && $usage_count >= $coupon->get_usage_limit_per_user() ) {
1013 1
				return new WP_Error(
1014 1
					'invalid_coupon',
1015 1
					$coupon->get_coupon_error( 106 ),
1016
					array(
1017 1
						'status' => 400,
1018
					)
1019
				);
1020
			}
1021
		}
1022
1023 8
		$this->set_coupon_discount_amounts( $discounts );
1024 8
		$this->save();
1025
1026
		// Recalculate totals and taxes.
1027 8
		$this->recalculate_coupons();
1028
1029
		// Record usage so counts and validation is correct.
1030 8
		$used_by = $this->get_user_id();
1031
1032 8
		if ( ! $used_by ) {
1033 1
			$used_by = $this->get_billing_email();
1034
		}
1035
1036 8
		$coupon->increase_usage_count( $used_by );
1037
1038 8
		return true;
1039
	}
1040
1041
	/**
1042
	 * Remove a coupon from the order and recalculate totals.
1043
	 *
1044
	 * Coupons affect line item totals, but there is no relationship between
1045
	 * coupon and line total, so to remove a coupon we need to work from the
1046
	 * line subtotal (price before discount) and re-apply all coupons in this
1047
	 * order.
1048
	 *
1049
	 * Manual discounts are not affected; those are separate and do not affect
1050
	 * stored line totals.
1051
	 *
1052
	 * @since  3.2.0
1053
	 * @param  string $code Coupon code.
1054
	 * @return void
1055
	 */
1056 6
	public function remove_coupon( $code ) {
1057 6
		$coupons = $this->get_items( 'coupon' );
1058
1059
		// Remove the coupon line.
1060 6
		foreach ( $coupons as $item_id => $coupon ) {
1061 6
			if ( $coupon->get_code() === $code ) {
1062 6
				$this->remove_item( $item_id );
1063 6
				$coupon_object = new WC_Coupon( $code );
1064 6
				$coupon_object->decrease_usage_count( $this->get_user_id() );
1065 6
				$this->recalculate_coupons();
1066 6
				break;
1067
			}
1068
		}
1069
	}
1070
1071
	/**
1072
	 * Apply all coupons in this order again to all line items.
1073
	 * This method is public since WooCommerce 3.8.0.
1074
	 *
1075
	 * @since 3.2.0
1076
	 */
1077 12
	public function recalculate_coupons() {
1078
		// Reset line item totals.
1079 12
		foreach ( $this->get_items() as $item ) {
1080 12
			$item->set_total( $item->get_subtotal() );
1081 12
			$item->set_total_tax( $item->get_subtotal_tax() );
1082
		}
1083
1084 12
		$discounts = new WC_Discounts( $this );
1085
1086 12
		foreach ( $this->get_items( 'coupon' ) as $coupon_item ) {
1087 12
			$coupon_code = $coupon_item->get_code();
1088 12
			$coupon_id   = wc_get_coupon_id_by_code( $coupon_code );
1089
1090
			// If we have a coupon ID (loaded via wc_get_coupon_id_by_code) we can simply load the new coupon object using the ID.
1091 12
			if ( $coupon_id ) {
1092 12
				$coupon_object = new WC_Coupon( $coupon_id );
1093
1094
			} else {
1095
1096
				// If we do not have a coupon ID (was it virtual? has it been deleted?) we must create a temporary coupon using what data we have stored during checkout.
1097 8
				$coupon_object = new WC_Coupon();
1098 8
				$coupon_object->set_props( (array) $coupon_item->get_meta( 'coupon_data', true ) );
1099 8
				$coupon_object->set_code( $coupon_code );
1100 8
				$coupon_object->set_virtual( true );
1101
1102
				// If there is no coupon amount (maybe dynamic?), set it to the given **discount** amount so the coupon's same value is applied.
1103 8
				if ( ! $coupon_object->get_amount() ) {
1104
1105
					// If the order originally had prices including tax, remove the discount + discount tax.
1106 8
					if ( $this->get_prices_include_tax() ) {
1107 4
						$coupon_object->set_amount( $coupon_item->get_discount() + $coupon_item->get_discount_tax() );
1108
					} else {
1109 4
						$coupon_object->set_amount( $coupon_item->get_discount() );
1110
					}
1111 8
					$coupon_object->set_discount_type( 'fixed_cart' );
1112
				}
1113
			}
1114
1115
			/**
1116
			 * Allow developers to filter this coupon before it get's re-applied to the order.
1117
			 *
1118
			 * @since 3.2.0
1119
			 */
1120 12
			$coupon_object = apply_filters( 'woocommerce_order_recalculate_coupons_coupon_object', $coupon_object, $coupon_code, $coupon_item, $this );
1121
1122 12
			if ( $coupon_object ) {
1123 12
				$discounts->apply_coupon( $coupon_object, false );
1124
			}
1125
		}
1126
1127 12
		$this->set_coupon_discount_amounts( $discounts );
1128 12
		$this->set_item_discount_amounts( $discounts );
1129
1130
		// Recalculate totals and taxes.
1131 12
		$this->calculate_totals( true );
1132
	}
1133
1134
	/**
1135
	 * After applying coupons via the WC_Discounts class, update line items.
1136
	 *
1137
	 * @since 3.2.0
1138
	 * @param WC_Discounts $discounts Discounts class.
1139
	 */
1140 12
	protected function set_item_discount_amounts( $discounts ) {
1141 12
		$item_discounts = $discounts->get_discounts_by_item();
1142
1143 12
		if ( $item_discounts ) {
1144 12
			foreach ( $item_discounts as $item_id => $amount ) {
1145 12
				$item = $this->get_item( $item_id, false );
1146
1147
				// If the prices include tax, discounts should be taken off the tax inclusive prices like in the cart.
1148 12
				if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
1149 2
					$taxes = WC_Tax::calc_tax( $amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
1150
1151 2
					if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1152 2
						$taxes = array_map( 'wc_round_tax_total', $taxes );
1153
					}
1154
1155 2
					$amount = $amount - array_sum( $taxes );
1156 2
					$item->set_total( max( 0, round( $item->get_total() - $amount, wc_get_price_decimals() ) ) );
1157
				} else {
1158 10
					$item->set_total( max( 0, $item->get_total() - $amount ) );
1159
				}
1160
			}
1161
		}
1162
	}
1163
1164
	/**
1165
	 * After applying coupons via the WC_Discounts class, update or create coupon items.
1166
	 *
1167
	 * @since 3.2.0
1168
	 * @param WC_Discounts $discounts Discounts class.
1169
	 */
1170 12
	protected function set_coupon_discount_amounts( $discounts ) {
1171 12
		$coupons           = $this->get_items( 'coupon' );
1172 12
		$coupon_code_to_id = wc_list_pluck( $coupons, 'get_id', 'get_code' );
1173 12
		$all_discounts     = $discounts->get_discounts();
1174 12
		$coupon_discounts  = $discounts->get_discounts_by_coupon();
1175
1176 12
		if ( $coupon_discounts ) {
1177 12
			foreach ( $coupon_discounts as $coupon_code => $amount ) {
1178 12
				$item_id = isset( $coupon_code_to_id[ $coupon_code ] ) ? $coupon_code_to_id[ $coupon_code ] : 0;
1179
1180 12
				if ( ! $item_id ) {
1181 8
					$coupon_item = new WC_Order_Item_Coupon();
1182 8
					$coupon_item->set_code( $coupon_code );
1183
				} else {
1184 12
					$coupon_item = $this->get_item( $item_id, false );
1185
				}
1186
1187 12
				$discount_tax = 0;
1188
1189
				// Work out how much tax has been removed as a result of the discount from this coupon.
1190 12
				foreach ( $all_discounts[ $coupon_code ] as $item_id => $item_discount_amount ) {
1191 12
					$item = $this->get_item( $item_id, false );
1192
1193 12
					if ( $this->get_prices_include_tax() && wc_tax_enabled() ) {
1194 2
						$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ), true );
1195
1196 2
						if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1197 2
							$taxes = array_map( 'wc_round_tax_total', $taxes );
1198
						}
1199
1200 2
						$discount_tax += array_sum( $taxes );
1201 2
						$amount        = $amount - array_sum( $taxes );
1202
					} else {
1203 10
						$taxes = WC_Tax::calc_tax( $item_discount_amount, WC_Tax::get_rates( $item->get_tax_class() ) );
1204
1205 10
						if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1206 10
							$taxes = array_map( 'wc_round_tax_total', $taxes );
1207
						}
1208
1209 10
						$discount_tax += array_sum( $taxes );
1210
					}
1211
				}
1212
1213 12
				$coupon_item->set_discount( $amount );
1214 12
				$coupon_item->set_discount_tax( $discount_tax );
1215
1216 12
				$this->add_item( $coupon_item );
1217
			}
1218
		}
1219
	}
1220
1221
	/**
1222
	 * Add a product line item to the order. This is the only line item type with
1223
	 * its own method because it saves looking up order amounts (costs are added up for you).
1224
	 *
1225
	 * @param  WC_Product $product Product object.
1226
	 * @param  int        $qty Quantity to add.
1227
	 * @param  array      $args Args for the added product.
1228
	 * @return int
1229
	 * @throws WC_Data_Exception Exception thrown if the item cannot be added to the cart.
1230
	 */
1231 7
	public function add_product( $product, $qty = 1, $args = array() ) {
1232 7
		if ( $product ) {
1233
			$default_args = array(
1234 7
				'name'         => $product->get_name(),
1235 7
				'tax_class'    => $product->get_tax_class(),
1236 7
				'product_id'   => $product->is_type( 'variation' ) ? $product->get_parent_id() : $product->get_id(),
1237 7
				'variation_id' => $product->is_type( 'variation' ) ? $product->get_id() : 0,
1238 7
				'variation'    => $product->is_type( 'variation' ) ? $product->get_attributes() : array(),
1239 7
				'subtotal'     => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
1240 7
				'total'        => wc_get_price_excluding_tax( $product, array( 'qty' => $qty ) ),
1241 7
				'quantity'     => $qty,
1242
			);
1243
		} else {
1244
			$default_args = array(
1245
				'quantity' => $qty,
1246
			);
1247
		}
1248
1249 7
		$args = wp_parse_args( $args, $default_args );
1250
1251
		// BW compatibility with old args.
1252 7
		if ( isset( $args['totals'] ) ) {
1253
			foreach ( $args['totals'] as $key => $value ) {
1254
				if ( 'tax' === $key ) {
1255
					$args['total_tax'] = $value;
1256
				} elseif ( 'tax_data' === $key ) {
1257
					$args['taxes'] = $value;
1258
				} else {
1259
					$args[ $key ] = $value;
1260
				}
1261
			}
1262
		}
1263
1264 7
		$item = new WC_Order_Item_Product();
1265 7
		$item->set_props( $args );
1266 7
		$item->set_backorder_meta();
1267 7
		$item->set_order_id( $this->get_id() );
1268 7
		$item->save();
1269 7
		$this->add_item( $item );
1270 7
		wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '3.0', 'woocommerce_new_order_item action instead' );
1271 7
		delete_transient( 'wc_order_' . $this->get_id() . '_needs_processing' );
1272 7
		return $item->get_id();
1273
	}
1274
1275
	/*
1276
	|--------------------------------------------------------------------------
1277
	| Payment Token Handling
1278
	|--------------------------------------------------------------------------
1279
	|
1280
	| Payment tokens are hashes used to take payments by certain gateways.
1281
	|
1282
	*/
1283
1284
	/**
1285
	 * Add a payment token to an order
1286
	 *
1287
	 * @since 2.6
1288
	 * @param WC_Payment_Token $token Payment token object.
1289
	 * @return boolean|int The new token ID or false if it failed.
1290
	 */
1291 3
	public function add_payment_token( $token ) {
1292 3
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1293 1
			return false;
1294
		}
1295
1296 3
		$token_ids   = $this->data_store->get_payment_token_ids( $this );
1297 3
		$token_ids[] = $token->get_id();
1298 3
		$this->data_store->update_payment_token_ids( $this, $token_ids );
1299
1300 3
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1301 3
		return $token->get_id();
1302
	}
1303
1304
	/**
1305
	 * Returns a list of all payment tokens associated with the current order
1306
	 *
1307
	 * @since 2.6
1308
	 * @return array An array of payment token objects
1309
	 */
1310 4
	public function get_payment_tokens() {
1311 4
		return $this->data_store->get_payment_token_ids( $this );
1312
	}
1313
1314
	/*
1315
	|--------------------------------------------------------------------------
1316
	| Calculations.
1317
	|--------------------------------------------------------------------------
1318
	|
1319
	| These methods calculate order totals and taxes based on the current data.
1320
	|
1321
	*/
1322
1323
	/**
1324
	 * Calculate shipping total.
1325
	 *
1326
	 * @since 2.2
1327
	 * @return float
1328
	 */
1329 1
	public function calculate_shipping() {
1330 1
		$shipping_total = 0;
1331
1332 1
		foreach ( $this->get_shipping_methods() as $shipping ) {
1333 1
			$shipping_total += $shipping->get_total();
1334
		}
1335
1336 1
		$this->set_shipping_total( $shipping_total );
1337 1
		$this->save();
1338
1339 1
		return $this->get_shipping_total();
1340
	}
1341
1342
	/**
1343
	 * Get all tax classes for items in the order.
1344
	 *
1345
	 * @since 2.6.3
1346
	 * @return array
1347
	 */
1348 21
	public function get_items_tax_classes() {
1349 21
		$found_tax_classes = array();
1350
1351 21
		foreach ( $this->get_items() as $item ) {
1352 21
			if ( is_callable( array( $item, 'get_tax_status' ) ) && in_array( $item->get_tax_status(), array( 'taxable', 'shipping' ), true ) ) {
1353 21
				$found_tax_classes[] = $item->get_tax_class();
1354
			}
1355
		}
1356
1357 21
		return array_unique( $found_tax_classes );
1358
	}
1359
1360
	/**
1361
	 * Get tax location for this order.
1362
	 *
1363
	 * @since 3.2.0
1364
	 * @param array $args array Override the location.
1365
	 * @return array
1366
	 */
1367 20
	protected function get_tax_location( $args = array() ) {
1368 20
		$tax_based_on = get_option( 'woocommerce_tax_based_on' );
1369
1370 20
		if ( 'shipping' === $tax_based_on && ! $this->get_shipping_country() ) {
1371 11
			$tax_based_on = 'billing';
1372
		}
1373
1374 20
		$args = wp_parse_args(
1375 20
			$args,
1376
			array(
1377 20
				'country'  => 'billing' === $tax_based_on ? $this->get_billing_country() : $this->get_shipping_country(),
1378 20
				'state'    => 'billing' === $tax_based_on ? $this->get_billing_state() : $this->get_shipping_state(),
1379 20
				'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
1380 20
				'city'     => 'billing' === $tax_based_on ? $this->get_billing_city() : $this->get_shipping_city(),
1381
			)
1382
		);
1383
1384
		// Default to base.
1385 20
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1386 16
			$args['country']  = WC()->countries->get_base_country();
1387 16
			$args['state']    = WC()->countries->get_base_state();
1388 16
			$args['postcode'] = WC()->countries->get_base_postcode();
1389 16
			$args['city']     = WC()->countries->get_base_city();
1390
		}
1391
1392 20
		return $args;
1393
	}
1394
1395
	/**
1396
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1397
	 *
1398
	 * If by default the taxes are based on the shipping address and the current order doesn't
1399
	 * have any, it would use the billing address rather than using the Shopping base location.
1400
	 *
1401
	 * Will use the base country unless customer addresses are set.
1402
	 *
1403
	 * @param array $args Added in 3.0.0 to pass things like location.
1404
	 */
1405 20
	public function calculate_taxes( $args = array() ) {
1406 20
		do_action( 'woocommerce_order_before_calculate_taxes', $args, $this );
1407
1408 20
		$calculate_tax_for  = $this->get_tax_location( $args );
1409 20
		$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1410
1411 20
		if ( 'inherit' === $shipping_tax_class ) {
1412 20
			$found_classes      = array_intersect( array_merge( array( '' ), WC_Tax::get_tax_class_slugs() ), $this->get_items_tax_classes() );
1413 20
			$shipping_tax_class = count( $found_classes ) ? current( $found_classes ) : false;
1414
		}
1415
1416 20
		$is_vat_exempt = apply_filters( 'woocommerce_order_is_vat_exempt', 'yes' === $this->get_meta( 'is_vat_exempt' ), $this );
1417
1418
		// Trigger tax recalculation for all items.
1419 20
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1420 20
			if ( ! $is_vat_exempt ) {
1421 20
				$item->calculate_taxes( $calculate_tax_for );
1422
			} else {
1423 1
				$item->set_taxes( false );
1424
			}
1425
		}
1426
1427 20
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1428 7
			if ( false !== $shipping_tax_class && ! $is_vat_exempt ) {
1429 7
				$item->calculate_taxes( array_merge( $calculate_tax_for, array( 'tax_class' => $shipping_tax_class ) ) );
1430
			} else {
1431 1
				$item->set_taxes( false );
1432
			}
1433
		}
1434
1435 20
		$this->update_taxes();
1436
	}
1437
1438
	/**
1439
	 * Update tax lines for the order based on the line item taxes themselves.
1440
	 */
1441 25
	public function update_taxes() {
1442 25
		$cart_taxes     = array();
1443 25
		$shipping_taxes = array();
1444 25
		$existing_taxes = $this->get_taxes();
1445 25
		$saved_rate_ids = array();
1446
1447 25 View Code Duplication
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1448 20
			$taxes = $item->get_taxes();
1449 20
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1450 10
				$tax_amount = (float) $tax;
1451
1452 10
				if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1453 10
					$tax_amount = wc_round_tax_total( $tax_amount );
1454
				}
1455
1456 10
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
1457
			}
1458
		}
1459
1460 25 View Code Duplication
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1461 7
			$taxes = $item->get_taxes();
1462 7
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1463 4
				$tax_amount = (float) $tax;
1464
1465 4
				if ( 'yes' !== get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1466 4
					$tax_amount = wc_round_tax_total( $tax_amount );
1467
				}
1468
1469 4
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax_amount : $tax_amount;
1470
			}
1471
		}
1472
1473 25
		foreach ( $existing_taxes as $tax ) {
1474
			// Remove taxes which no longer exist for cart/shipping.
1475 5
			if ( ( ! array_key_exists( $tax->get_rate_id(), $cart_taxes ) && ! array_key_exists( $tax->get_rate_id(), $shipping_taxes ) ) || in_array( $tax->get_rate_id(), $saved_rate_ids, true ) ) {
1476 1
				$this->remove_item( $tax->get_id() );
1477 1
				continue;
1478
			}
1479 4
			$saved_rate_ids[] = $tax->get_rate_id();
1480 4
			$tax->set_tax_total( isset( $cart_taxes[ $tax->get_rate_id() ] ) ? $cart_taxes[ $tax->get_rate_id() ] : 0 );
1481 4
			$tax->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax->get_rate_id() ] ) ? $shipping_taxes[ $tax->get_rate_id() ] : 0 );
1482 4
			$tax->save();
1483
		}
1484
1485 25
		$new_rate_ids = wp_parse_id_list( array_diff( array_keys( $cart_taxes + $shipping_taxes ), $saved_rate_ids ) );
1486
1487
		// New taxes.
1488 25
		foreach ( $new_rate_ids as $tax_rate_id ) {
1489 10
			$item = new WC_Order_Item_Tax();
1490 10
			$item->set_rate( $tax_rate_id );
1491 10
			$item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
1492 10
			$item->set_shipping_tax_total( ! empty( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
1493 10
			$this->add_item( $item );
1494
		}
1495
1496 25
		$this->set_shipping_tax( wc_round_tax_total( array_sum( $shipping_taxes ) ) );
1497 25
		$this->set_cart_tax( wc_round_tax_total( array_sum( $cart_taxes ) ) );
1498 25
		$this->save();
1499
	}
1500
1501
	/**
1502
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1503
	 *
1504
	 * @since 2.2
1505
	 * @param  bool $and_taxes Calc taxes if true.
1506
	 * @return float calculated grand total.
1507
	 */
1508 22
	public function calculate_totals( $and_taxes = true ) {
1509 22
		do_action( 'woocommerce_order_before_calculate_totals', $and_taxes, $this );
1510
1511 22
		$cart_subtotal     = 0;
1512 22
		$cart_total        = 0;
1513 22
		$fees_total         = 0;
1514 22
		$shipping_total    = 0;
1515 22
		$cart_subtotal_tax = 0;
1516 22
		$cart_total_tax    = 0;
1517
1518
		// Sum line item costs.
1519 22
		foreach ( $this->get_items() as $item ) {
1520 17
			$cart_subtotal += round( $item->get_subtotal(), wc_get_price_decimals() );
1521 17
			$cart_total    += round( $item->get_total(), wc_get_price_decimals() );
1522
		}
1523
1524
		// Sum shipping costs.
1525 22
		foreach ( $this->get_shipping_methods() as $shipping ) {
1526 5
			$shipping_total += round( $shipping->get_total(), wc_get_price_decimals() );
1527
		}
1528
1529 22
		$this->set_shipping_total( $shipping_total );
1530
1531
		// Sum fee costs.
1532 22
		foreach ( $this->get_fees() as $item ) {
1533 1
			$fee_total = $item->get_total();
1534
1535 1
			if ( 0 > $fee_total ) {
1536 1
				$max_discount = round( $cart_total + $fees_total + $shipping_total, wc_get_price_decimals() ) * -1;
1537
1538 1
				if ( $fee_total < $max_discount && 0 > $max_discount ) {
1539
					$item->set_total( $max_discount );
1540
				}
1541
			}
1542
1543 1
			$fees_total += $item->get_total();
1544
		}
1545
1546
		// Calculate taxes for items, shipping, discounts. Note; this also triggers save().
1547 22
		if ( $and_taxes ) {
1548 17
			$this->calculate_taxes();
1549
		}
1550
1551
		// Sum taxes again so we can work out how much tax was discounted. This uses original values, not those possibly rounded to 2dp.
1552 22
		foreach ( $this->get_items() as $item ) {
1553 17
			$taxes = $item->get_taxes();
1554
1555 17
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
1556 7
				$cart_total_tax += (float) $tax;
1557
			}
1558
1559 17
			foreach ( $taxes['subtotal'] as $tax_rate_id => $tax ) {
1560 7
				$cart_subtotal_tax += (float) $tax;
1561
			}
1562
		}
1563
1564 22
		$this->set_discount_total( $cart_subtotal - $cart_total );
1565 22
		$this->set_discount_tax( wc_round_tax_total( $cart_subtotal_tax - $cart_total_tax ) );
1566 22
		$this->set_total( round( $cart_total + $fees_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() ) );
1567
1568 22
		do_action( 'woocommerce_order_after_calculate_totals', $and_taxes, $this );
1569
1570 22
		$this->save();
1571
1572 22
		return $this->get_total();
1573
	}
1574
1575
	/**
1576
	 * Get item subtotal - this is the cost before discount.
1577
	 *
1578
	 * @param object $item Item to get total from.
1579
	 * @param bool   $inc_tax (default: false).
1580
	 * @param bool   $round (default: true).
1581
	 * @return float
1582
	 */
1583 View Code Duplication
	public function get_item_subtotal( $item, $inc_tax = false, $round = true ) {
1584
		$subtotal = 0;
1585
1586
		if ( is_callable( array( $item, 'get_subtotal' ) ) && $item->get_quantity() ) {
1587
			if ( $inc_tax ) {
1588
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / $item->get_quantity();
1589
			} else {
1590
				$subtotal = floatval( $item->get_subtotal() ) / $item->get_quantity();
1591
			}
1592
1593
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1594
		}
1595
1596
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1597
	}
1598
1599
	/**
1600
	 * Get line subtotal - this is the cost before discount.
1601
	 *
1602
	 * @param object $item Item to get total from.
1603
	 * @param bool   $inc_tax (default: false).
1604
	 * @param bool   $round (default: true).
1605
	 * @return float
1606
	 */
1607 View Code Duplication
	public function get_line_subtotal( $item, $inc_tax = false, $round = true ) {
1608
		$subtotal = 0;
1609
1610
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1611
			if ( $inc_tax ) {
1612
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1613
			} else {
1614
				$subtotal = $item->get_subtotal();
1615
			}
1616
1617
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1618
		}
1619
1620
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1621
	}
1622
1623
	/**
1624
	 * Calculate item cost - useful for gateways.
1625
	 *
1626
	 * @param object $item Item to get total from.
1627
	 * @param bool   $inc_tax (default: false).
1628
	 * @param bool   $round (default: true).
1629
	 * @return float
1630
	 */
1631 View Code Duplication
	public function get_item_total( $item, $inc_tax = false, $round = true ) {
1632
		$total = 0;
1633
1634
		if ( is_callable( array( $item, 'get_total' ) ) && $item->get_quantity() ) {
1635
			if ( $inc_tax ) {
1636
				$total = ( $item->get_total() + $item->get_total_tax() ) / $item->get_quantity();
1637
			} else {
1638
				$total = floatval( $item->get_total() ) / $item->get_quantity();
1639
			}
1640
1641
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1642
		}
1643
1644
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1645
	}
1646
1647
	/**
1648
	 * Calculate line total - useful for gateways.
1649
	 *
1650
	 * @param object $item Item to get total from.
1651
	 * @param bool   $inc_tax (default: false).
1652
	 * @param bool   $round (default: true).
1653
	 * @return float
1654
	 */
1655 View Code Duplication
	public function get_line_total( $item, $inc_tax = false, $round = true ) {
1656
		$total = 0;
1657
1658
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1659
			// Check if we need to add line tax to the line total.
1660
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1661
1662
			// Check if we need to round.
1663
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1664
		}
1665
1666
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1667
	}
1668
1669
	/**
1670
	 * Get item tax - useful for gateways.
1671
	 *
1672
	 * @param mixed $item Item to get total from.
1673
	 * @param bool  $round (default: true).
1674
	 * @return float
1675
	 */
1676
	public function get_item_tax( $item, $round = true ) {
1677
		$tax = 0;
1678
1679
		if ( is_callable( array( $item, 'get_total_tax' ) ) && $item->get_quantity() ) {
1680
			$tax = $item->get_total_tax() / $item->get_quantity();
1681
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1682
		}
1683
1684
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1685
	}
1686
1687
	/**
1688
	 * Get line tax - useful for gateways.
1689
	 *
1690
	 * @param mixed $item Item to get total from.
1691
	 * @return float
1692
	 */
1693
	public function get_line_tax( $item ) {
1694
		return apply_filters( 'woocommerce_order_amount_line_tax', is_callable( array( $item, 'get_total_tax' ) ) ? wc_round_tax_total( $item->get_total_tax() ) : 0, $item, $this );
1695
	}
1696
1697
	/**
1698
	 * Gets line subtotal - formatted for display.
1699
	 *
1700
	 * @param object $item Item to get total from.
1701
	 * @param string $tax_display Incl or excl tax display mode.
1702
	 * @return string
1703
	 */
1704
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1705
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1706
1707
		if ( 'excl' === $tax_display ) {
1708
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1709
1710
			$subtotal = wc_price(
1711
				$this->get_line_subtotal( $item ),
1712
				array(
1713
					'ex_tax_label' => $ex_tax_label,
1714
					'currency'     => $this->get_currency(),
1715
				)
1716
			);
1717
		} else {
1718
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array( 'currency' => $this->get_currency() ) );
1719
		}
1720
1721
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1722
	}
1723
1724
	/**
1725
	 * Gets order total - formatted for display.
1726
	 *
1727
	 * @return string
1728
	 */
1729
	public function get_formatted_order_total() {
1730
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1731
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1732
	}
1733
1734
	/**
1735
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1736
	 *
1737
	 * @param bool   $compound (default: false).
1738
	 * @param string $tax_display (default: the tax_display_cart value).
1739
	 * @return string
1740
	 */
1741
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1742
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1743
		$subtotal    = 0;
1744
1745
		if ( ! $compound ) {
1746
			foreach ( $this->get_items() as $item ) {
1747
				$subtotal += $item->get_subtotal();
1748
1749
				if ( 'incl' === $tax_display ) {
1750
					$subtotal += $item->get_subtotal_tax();
1751
				}
1752
			}
1753
1754
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1755
1756 View Code Duplication
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() && wc_tax_enabled() ) {
1757
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1758
			}
1759
		} else {
1760
			if ( 'incl' === $tax_display ) {
1761
				return '';
1762
			}
1763
1764
			foreach ( $this->get_items() as $item ) {
1765
				$subtotal += $item->get_subtotal();
1766
			}
1767
1768
			// Add Shipping Costs.
1769
			$subtotal += $this->get_shipping_total();
1770
1771
			// Remove non-compound taxes.
1772
			foreach ( $this->get_taxes() as $tax ) {
1773
				if ( $tax->is_compound() ) {
1774
					continue;
1775
				}
1776
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1777
			}
1778
1779
			// Remove discounts.
1780
			$subtotal = $subtotal - $this->get_total_discount();
1781
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1782
		}
1783
1784
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1785
	}
1786
1787
	/**
1788
	 * Gets shipping (formatted).
1789
	 *
1790
	 * @param string $tax_display Excl or incl tax display mode.
1791
	 * @return string
1792
	 */
1793
	public function get_shipping_to_display( $tax_display = '' ) {
1794
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1795
1796
		if ( 0 < abs( (float) $this->get_shipping_total() ) ) {
1797
1798
			if ( 'excl' === $tax_display ) {
1799
1800
				// Show shipping excluding tax.
1801
				$shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
1802
1803 View Code Duplication
				if ( (float) $this->get_shipping_tax() > 0 && $this->get_prices_include_tax() ) {
1804
					$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display );
1805
				}
1806
			} else {
1807
1808
				// Show shipping including tax.
1809
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
1810
1811 View Code Duplication
				if ( (float) $this->get_shipping_tax() > 0 && ! $this->get_prices_include_tax() ) {
1812
					$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display );
1813
				}
1814
			}
1815
1816
			/* translators: %s: method */
1817
			$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_shipped_via', '&nbsp;<small class="shipped_via">' . sprintf( __( 'via %s', 'woocommerce' ), $this->get_shipping_method() ) . '</small>', $this );
1818
1819
		} elseif ( $this->get_shipping_method() ) {
1820
			$shipping = $this->get_shipping_method();
1821
		} else {
1822
			$shipping = __( 'Free!', 'woocommerce' );
1823
		}
1824
1825
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this, $tax_display );
1826
	}
1827
1828
	/**
1829
	 * Get the discount amount (formatted).
1830
	 *
1831
	 * @since  2.3.0
1832
	 * @param string $tax_display Excl or incl tax display mode.
1833
	 * @return string
1834
	 */
1835
	public function get_discount_to_display( $tax_display = '' ) {
1836
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1837
		return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( 'excl' === $tax_display && 'excl' === get_option( 'woocommerce_tax_display_cart' ) ), array( 'currency' => $this->get_currency() ) ), $this );
1838
	}
1839
1840
	/**
1841
	 * Add total row for subtotal.
1842
	 *
1843
	 * @param array  $total_rows Reference to total rows array.
1844
	 * @param string $tax_display Excl or incl tax display mode.
1845
	 */
1846
	protected function add_order_item_totals_subtotal_row( &$total_rows, $tax_display ) {
1847
		$subtotal = $this->get_subtotal_to_display( false, $tax_display );
1848
1849
		if ( $subtotal ) {
1850
			$total_rows['cart_subtotal'] = array(
1851
				'label' => __( 'Subtotal:', 'woocommerce' ),
1852
				'value' => $subtotal,
1853
			);
1854
		}
1855
	}
1856
1857
	/**
1858
	 * Add total row for discounts.
1859
	 *
1860
	 * @param array  $total_rows Reference to total rows array.
1861
	 * @param string $tax_display Excl or incl tax display mode.
1862
	 */
1863
	protected function add_order_item_totals_discount_row( &$total_rows, $tax_display ) {
1864
		if ( $this->get_total_discount() > 0 ) {
1865
			$total_rows['discount'] = array(
1866
				'label' => __( 'Discount:', 'woocommerce' ),
1867
				'value' => '-' . $this->get_discount_to_display( $tax_display ),
1868
			);
1869
		}
1870
	}
1871
1872
	/**
1873
	 * Add total row for shipping.
1874
	 *
1875
	 * @param array  $total_rows Reference to total rows array.
1876
	 * @param string $tax_display Excl or incl tax display mode.
1877
	 */
1878
	protected function add_order_item_totals_shipping_row( &$total_rows, $tax_display ) {
1879
		if ( $this->get_shipping_method() ) {
1880
			$total_rows['shipping'] = array(
1881
				'label' => __( 'Shipping:', 'woocommerce' ),
1882
				'value' => $this->get_shipping_to_display( $tax_display ),
1883
			);
1884
		}
1885
	}
1886
1887
	/**
1888
	 * Add total row for fees.
1889
	 *
1890
	 * @param array  $total_rows Reference to total rows array.
1891
	 * @param string $tax_display Excl or incl tax display mode.
1892
	 */
1893
	protected function add_order_item_totals_fee_rows( &$total_rows, $tax_display ) {
1894
		$fees = $this->get_fees();
1895
1896
		if ( $fees ) {
1897
			foreach ( $fees as $id => $fee ) {
1898
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1899
					continue;
1900
				}
1901
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1902
					'label' => $fee->get_name() . ':',
1903
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1904
				);
1905
			}
1906
		}
1907
	}
1908
1909
	/**
1910
	 * Add total row for taxes.
1911
	 *
1912
	 * @param array  $total_rows Reference to total rows array.
1913
	 * @param string $tax_display Excl or incl tax display mode.
1914
	 */
1915
	protected function add_order_item_totals_tax_rows( &$total_rows, $tax_display ) {
1916
		// Tax for tax exclusive prices.
1917
		if ( 'excl' === $tax_display && wc_tax_enabled() ) {
1918
			if ( 'itemized' === get_option( 'woocommerce_tax_total_display' ) ) {
1919
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1920
					$total_rows[ sanitize_title( $code ) ] = array(
1921
						'label' => $tax->label . ':',
1922
						'value' => $tax->formatted_amount,
1923
					);
1924
				}
1925
			} else {
1926
				$total_rows['tax'] = array(
1927
					'label' => WC()->countries->tax_or_vat() . ':',
1928
					'value' => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1929
				);
1930
			}
1931
		}
1932
	}
1933
1934
	/**
1935
	 * Add total row for grand total.
1936
	 *
1937
	 * @param array  $total_rows Reference to total rows array.
1938
	 * @param string $tax_display Excl or incl tax display mode.
1939
	 */
1940
	protected function add_order_item_totals_total_row( &$total_rows, $tax_display ) {
1941
		$total_rows['order_total'] = array(
1942
			'label' => __( 'Total:', 'woocommerce' ),
1943
			'value' => $this->get_formatted_order_total( $tax_display ),
1944
		);
1945
	}
1946
1947
	/**
1948
	 * Get totals for display on pages and in emails.
1949
	 *
1950
	 * @param mixed $tax_display Excl or incl tax display mode.
1951
	 * @return array
1952
	 */
1953
	public function get_order_item_totals( $tax_display = '' ) {
1954
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1955
		$total_rows  = array();
1956
1957
		$this->add_order_item_totals_subtotal_row( $total_rows, $tax_display );
1958
		$this->add_order_item_totals_discount_row( $total_rows, $tax_display );
1959
		$this->add_order_item_totals_shipping_row( $total_rows, $tax_display );
1960
		$this->add_order_item_totals_fee_rows( $total_rows, $tax_display );
1961
		$this->add_order_item_totals_tax_rows( $total_rows, $tax_display );
1962
		$this->add_order_item_totals_total_row( $total_rows, $tax_display );
1963
1964
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this, $tax_display );
1965
	}
1966
1967
	/*
1968
	|--------------------------------------------------------------------------
1969
	| Conditionals
1970
	|--------------------------------------------------------------------------
1971
	|
1972
	| Checks if a condition is true or false.
1973
	|
1974
	*/
1975
1976
	/**
1977
	 * Checks the order status against a passed in status.
1978
	 *
1979
	 * @param array|string $status Status to check.
1980
	 * @return bool
1981
	 */
1982 26
	public function has_status( $status ) {
1983 26
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status, true ) ) || $this->get_status() === $status, $this, $status );
1984
	}
1985
1986
	/**
1987
	 * Check whether this order has a specific shipping method or not.
1988
	 *
1989
	 * @param string $method_id Method ID to check.
1990
	 * @return bool
1991
	 */
1992 1
	public function has_shipping_method( $method_id ) {
1993 1
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1994 1
			if ( strpos( $shipping_method->get_method_id(), $method_id ) === 0 ) {
1995 1
				return true;
1996
			}
1997
		}
1998 1
		return false;
1999
	}
2000
2001
	/**
2002
	 * Returns true if the order contains a free product.
2003
	 *
2004
	 * @since 2.5.0
2005
	 * @return bool
2006
	 */
2007 2
	public function has_free_item() {
2008 2
		foreach ( $this->get_items() as $item ) {
2009 1
			if ( ! $item->get_total() ) {
2010 1
				return true;
2011
			}
2012
		}
2013 2
		return false;
2014
	}
2015
}
2016