Completed
Pull Request — master (#11889)
by Mike
12:12
created

WC_Abstract_Order::get_tax_totals()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 10
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
eloc 6
nc 2
nop 0
dl 10
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
include_once( WC_ABSPATH . 'includes/legacy/abstract-wc-legacy-order.php' );
7
8
/**
9
 * Abstract Order
10
 *
11
 * Handles generic order data and database interaction which is extended by both
12
 * WC_Order (regular orders) and WC_Order_Refund (refunds are negative orders).
13
 *
14
 * @class       WC_Abstract_Order
15
 * @version     2.7.0
16
 * @package     WooCommerce/Classes
17
 * @category    Class
18
 * @author      WooThemes
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 2.7.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
	 * @since 2.7.0
28
	 * @var array
29
	 */
30
	protected $data = array(
31
		'parent_id'          => 0,
32
		'status'             => '',
33
		'currency'           => '',
34
		'version'            => '',
35
		'prices_include_tax' => false,
36
		'date_created'       => '',
37
		'date_modified'      => '',
38
		'discount_total'     => 0,
39
		'discount_tax'       => 0,
40
		'shipping_total'     => 0,
41
		'shipping_tax'       => 0,
42
		'cart_tax'           => 0,
43
		'total'              => 0,
44
		'total_tax'          => 0,
45
	);
46
47
	/**
48
	 * Data stored in meta keys, but not considered "meta" for an order.
49
	 * @since 2.7.0
50
	 * @var array
51
	 */
52
	protected $internal_meta_keys = array(
53
		'_order_currency',
54
		'_cart_discount',
55
		'_cart_discount_tax',
56
		'_order_shipping',
57
		'_order_shipping_tax',
58
		'_order_tax',
59
		'_order_total',
60
		'_order_version',
61
		'_prices_include_tax',
62
		'_payment_tokens',
63
	);
64
65
	/**
66
	 * Order items will be stored here, sometimes before they persist in the DB.
67
	 * @since 2.7.0
68
	 * @var array
69
	 */
70
	protected $items = array(
71
		'line_items'     => null,
72
		'coupon_lines'   => null,
73
		'shipping_lines' => null,
74
		'fee_lines'      => null,
75
		'tax_lines'      => null,
76
	);
77
78
	/**
79
	 * Order items that need deleting are stored here.
80
	 * @since 2.7.0
81
	 * @var array
82
	 */
83
	protected $items_to_delete = array();
84
85
	/**
86
	 *  Internal meta type used to store order data.
87
	 * @var string
88
	 */
89
	protected $meta_type = 'post';
90
91
	/**
92
	 * Stores meta in cache for future reads.
93
	 * A group must be set to to enable caching.
94
	 * @var string
95
	 */
96
	protected $cache_group = 'order';
97
98
	/**
99
	 * Get the order if ID is passed, otherwise the order is new and empty.
100
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
101
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
102
	 * methods that will be maintained going forward.
103
	 *
104
	 * @param  int|object|WC_Order $read Order to init.
105
	 */
106
	public function __construct( $read = 0 ) {
107
		parent::__construct( $read );
108
109 View Code Duplication
		if ( is_numeric( $read ) && $read > 0 ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
110
			$this->read( $read );
111
		} elseif ( $read instanceof self ) {
112
			$this->read( absint( $read->get_id() ) );
113
		} elseif ( ! empty( $read->ID ) ) {
114
			$this->read( absint( $read->ID ) );
115
		}
116
		// Set default status if none were read.
117
		if ( ! $this->get_status() ) {
118
			$this->set_status( apply_filters( 'woocommerce_default_order_status', 'pending' ) );
119
		}
120
	}
121
122
	/*
123
	|--------------------------------------------------------------------------
124
	| CRUD methods
125
	|--------------------------------------------------------------------------
126
	|
127
	| Methods which create, read, update and delete orders from the database.
128
	| Written in abstract fashion so that the way orders are stored can be
129
	| changed more easily in the future.
130
	|
131
	| A save method is included for convenience (chooses update or create based
132
	| on if the order exists yet).
133
	|
134
	*/
135
136
	/**
137
	 * Get internal type (post type.)
138
	 * @return string
139
	 */
140
	public function get_type() {
141
		return 'shop_order';
142
	}
143
144
	/**
145
	 * Get a title for the new post type.
146
	 */
147
	protected function get_post_title() {
148
		// @codingStandardsIgnoreStart
149
		return sprintf( __( 'Order &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
150
		// @codingStandardsIgnoreEnd
151
	}
152
153
	/**
154
	 * Insert data into the database.
155
	 * @since 2.7.0
156
	 */
157
	public function create() {
158
		$this->set_date_created( current_time( 'timestamp' ) );
159
		$this->set_currency( $this->get_currency() ? $this->get_currency() : get_woocommerce_currency() );
160
161
		$order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', array(
162
			'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
163
			'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
164
			'post_type'     => $this->get_type(),
165
			'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
166
			'ping_status'   => 'closed',
167
			'post_author'   => 1,
168
			'post_title'    => $this->get_post_title(),
169
			'post_password' => uniqid( 'order_' ),
170
			'post_parent'   => $this->get_parent_id(),
171
		) ), true );
172
173
		if ( $order_id ) {
174
			$this->set_id( $order_id );
175
176
			// Set meta data
177
			$this->update_post_meta( '_order_currency', $this->get_currency() );
178
			$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
179
			$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
180
			$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
181
			$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
182
			$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
183
			$this->update_post_meta( '_order_total', $this->get_total( true ) );
184
			$this->update_post_meta( '_order_version', $this->get_version() );
185
			$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
186
			$this->save_meta_data();
187
		}
188
	}
189
190
	/**
191
	 * Read from the database.
192
	 * @since 2.7.0
193
	 * @param int $id ID of object to read.
194
	 */
195
	public function read( $id ) {
196
		$this->set_defaults();
197
198
		if ( empty( $id ) || ! ( $post_object = get_post( $id ) ) ) {
199
			return;
200
		}
201
202
		$this->set_id( $id );
203
		$this->set_props( array(
204
			'parent_id'          => $post_object->post_parent,
205
			'date_created'       => $post_object->post_date,
206
			'date_modified'      => $post_object->post_modified,
207
			'status'             => $post_object->post_status,
208
			'currency'           => get_post_meta( $id, '_order_currency', true ),
209
			'discount_total'     => get_post_meta( $id, '_cart_discount', true ),
210
			'discount_tax'       => get_post_meta( $id, '_cart_discount_tax', true ),
211
			'shipping_total'     => get_post_meta( $id, '_order_shipping', true ),
212
			'shipping_tax'       => get_post_meta( $id, '_order_shipping_tax', true ),
213
			'cart_tax'           => get_post_meta( $id, '_order_tax', true ),
214
			'total'              => get_post_meta( $id, '_order_total', true ),
215
			'version'            => get_post_meta( $id, '_order_version', true ),
216
			'prices_include_tax' => metadata_exists( 'post', $id, '_prices_include_tax' ) ? 'yes' === get_post_meta( $id, '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ),
217
		) );
218
219
		$this->read_meta_data();
220
	}
221
222
	/**
223
	 * Post meta update wrapper. Sets or deletes based on value.
224
	 * @since 2.7.0
225
	 * @return bool Was it changed?
226
	 */
227
	protected function update_post_meta( $key, $value ) {
228
		if ( '' !== $value ) {
229
			return update_post_meta( $this->get_id(), $key, $value );
230
		} else {
231
			return delete_post_meta( $this->get_id(), $key );
232
		}
233
	}
234
235
	/**
236
	 * Update data in the database.
237
	 * @since 2.7.0
238
	 */
239
	public function update() {
240
		global $wpdb;
241
242
		$order_id = $this->get_id();
243
244
		$wpdb->update(
245
			$wpdb->posts,
246
			array(
247
				'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
248
				'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
249
				'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
250
				'post_parent'   => $this->get_parent_id(),
251
			),
252
			array(
253
				'ID' => $order_id,
254
			)
255
		);
256
257
		// Update meta data
258
		$this->update_post_meta( '_order_currency', $this->get_currency() );
259
		$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
260
		$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
261
		$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
262
		$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
263
		$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
264
		$this->update_post_meta( '_order_total', $this->get_total( true ) );
265
		$this->update_post_meta( '_order_version', $this->get_version() );
266
		$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
267
		$this->save_meta_data();
268
	}
269
270
	/**
271
	 * Delete data from the database.
272
	 * @since 2.7.0
273
	 */
274
	public function delete() {
275
		wp_delete_post( $this->get_id() );
276
	}
277
278
	/**
279
	 * Save data to the database.
280
	 * @since 2.7.0
281
	 * @return int order ID
282
	 */
283
	public function save() {
284
		$this->set_version( WC_VERSION );
285
286
		if ( ! $this->get_id() ) {
287
			$this->create();
288
		} else {
289
			$this->update();
290
		}
291
292
		$this->save_items();
293
		clean_post_cache( $this->get_id() );
294
		wc_delete_shop_order_transients( $this->get_id() );
295
296
		return $this->get_id();
297
	}
298
299
	/**
300
	 * Save all order items which are part of this order.
301
	 */
302
	protected function save_items() {
303
		// remove items
304
		foreach ( $this->items_to_delete as $item ) {
305
			$item->delete();
306
		}
307
308
		$this->items_to_delete = array();
309
310
		// Add/save items
311
		foreach ( $this->items as $item_group => $items ) {
312
			if ( is_array( $items ) ) {
313
				foreach ( $items as $item_key => $item ) {
314
					$item->set_order_id( $this->get_id() );
315
					$item_id = $item->save();
316
317
					// If ID changed (new item saved to DB)...
318
					if ( $item_id !== $item_key ) {
319
						$this->items[ $item_group ][ $item_id ] = $item;
320
						unset( $this->items[ $item_group ][ $item_key ] );
321
322
						// Legacy action handler
323
						switch ( $item_group ) {
324
							case 'fee_lines' :
325
								if ( isset( $item->legacy_fee, $item->legacy_fee_key ) ) {
326
									wc_do_deprecated_action( 'woocommerce_add_order_fee_meta', array( $this->get_id(), $item_id, $item->legacy_fee, $item->legacy_fee_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
327
								}
328
							break;
329
							case 'shipping_lines' :
330
								if ( isset( $item->legacy_package_key ) ) {
331
									wc_do_deprecated_action( 'woocommerce_add_shipping_order_item', array( $item_id, $item->legacy_package_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
332
								}
333
							break;
334
							case 'line_items' :
335
								if ( isset( $item->legacy_values, $item->legacy_cart_item_key ) ) {
336
									wc_do_deprecated_action( 'woocommerce_add_order_item_meta', array( $item_id, $item->legacy_values, $item->legacy_cart_item_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
337
								}
338
							break;
339
						}
340
					}
341
				}
342
			}
343
		}
344
	}
345
346
	/*
347
	|--------------------------------------------------------------------------
348
	| Getters
349
	|--------------------------------------------------------------------------
350
	|
351
	| Methods for getting data from the order object.
352
	|
353
	*/
354
355
	/**
356
	 * Get all class data in array format.
357
	 * @since 2.7.0
358
	 * @return array
359
	 */
360 View Code Duplication
	public function get_data() {
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...
361
		return array_merge(
362
			$this->data,
363
			array(
364
				'meta_data'      => $this->get_meta_data(),
365
				'line_items'     => $this->get_items( 'line_item' ),
366
				'tax_lines'      => $this->get_items( 'tax' ),
367
				'shipping_lines' => $this->get_items( 'shipping' ),
368
				'fee_lines'      => $this->get_items( 'fee' ),
369
				'coupon_lines'   => $this->get_items( 'coupon' ),
370
			)
371
		);
372
	}
373
374
	/**
375
	 * Get parent order ID.
376
	 * @since 2.7.0
377
	 * @return integer
378
	 */
379
	public function get_parent_id() {
380
		return $this->data['parent_id'];
381
	}
382
383
	/**
384
	 * Gets order currency.
385
	 * @return string
386
	 */
387
	public function get_currency() {
388
		return apply_filters( 'woocommerce_get_currency', $this->data['currency'], $this );
389
	}
390
391
	/**
392
	 * Get order_version
393
	 * @return string
394
	 */
395
	public function get_version() {
396
		return $this->data['version'];
397
	}
398
399
	/**
400
	 * Get prices_include_tax
401
	 * @return bool
402
	 */
403
	public function get_prices_include_tax() {
404
		return $this->data['prices_include_tax'];
405
	}
406
407
	/**
408
	 * Get date_created
409
	 * @return int
410
	 */
411
	public function get_date_created() {
412
		return $this->data['date_created'];
413
	}
414
415
	/**
416
	 * Get date_modified
417
	 * @return int
418
	 */
419
	public function get_date_modified() {
420
		return $this->data['date_modified'];
421
	}
422
423
	/**
424
	 * Return the order statuses without wc- internal prefix.
425
	 * @return string
426
	 */
427
	public function get_status() {
428
		$status = $this->data['status'];
429
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $status, 0, 3 ) ? substr( $status, 3 ) : $status, $this );
430
	}
431
432
	/**
433
	 * Get discount_total
434
	 * @param bool $raw Gets raw unfiltered value.
435
	 * @return string
436
	 */
437
	public function get_discount_total( $raw = false ) {
438
		return $raw ? $this->data['discount_total'] : apply_filters( 'woocommerce_order_amount_discount_total', $this->data['discount_total'], $this );
439
	}
440
441
	/**
442
	 * Get discount_tax
443
	 * @param bool $raw Gets raw unfiltered value.
444
	 * @return string
445
	 */
446
	public function get_discount_tax( $raw = false ) {
447
		return $raw ? $this->data['discount_tax'] : apply_filters( 'woocommerce_order_amount_discount_tax', $this->data['discount_tax'], $this );
448
	}
449
450
	/**
451
	 * Get shipping_total
452
	 * @param bool $raw Gets raw unfiltered value.
453
	 * @return string
454
	 */
455
	public function get_shipping_total( $raw = false ) {
456
		return $raw ? $this->data['shipping_total'] : apply_filters( 'woocommerce_order_amount_shipping_total', $this->data['shipping_total'], $this );
457
	}
458
459
	/**
460
	 * Get shipping_tax.
461
	 * @param bool $raw Gets raw unfiltered value.
462
	 * @return string
463
	 */
464
	public function get_shipping_tax( $raw = false ) {
465
		return $raw ? $this->data['shipping_tax'] : apply_filters( 'woocommerce_order_amount_shipping_tax', $this->data['shipping_tax'], $this );
466
	}
467
468
	/**
469
	 * Gets cart tax amount.
470
	 * @param bool $raw Gets raw unfiltered value.
471
	 * @return float
472
	 */
473
	public function get_cart_tax( $raw = false ) {
474
		return $raw ? $this->data['cart_tax'] : apply_filters( 'woocommerce_order_amount_cart_tax', $this->data['cart_tax'], $this );
475
	}
476
477
	/**
478
	 * Gets order grand total. incl. taxes. Used in gateways. Filtered.
479
	 * @param bool $raw Gets raw unfiltered value.
480
	 * @return float
481
	 */
482
	public function get_total( $raw = false ) {
483
		return $raw ? $this->data['total'] : apply_filters( 'woocommerce_order_amount_total', $this->data['total'], $this );
484
	}
485
486
	/**
487
	 * Get total tax amount. Alias for get_order_tax().
488
	 *
489
	 * @since 2.7.0 woocommerce_order_amount_total_tax filter has been removed to avoid
490
	 * these values being modified and then saved back to the DB. There are
491
	 * other, later hooks available to change totals on display. e.g.
492
	 * woocommerce_get_order_item_totals.
493
	 * @param bool $raw Gets raw unfiltered value.
494
	 * @return float
495
	 */
496
	public function get_total_tax( $raw = false ) {
497
		return $raw ? $this->data['total_tax'] : apply_filters( 'woocommerce_order_amount_total_tax', $this->data['total_tax'], $this );
498
	}
499
500
	/**
501
	 * Gets the total discount amount.
502
	 * @param  bool $ex_tax Show discount excl any tax.
503
	 * @return float
504
	 */
505
	public function get_total_discount( $ex_tax = true ) {
506
		if ( $ex_tax ) {
507
			$total_discount = $this->get_discount_total();
508
		} else {
509
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
510
		}
511
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
512
	}
513
514
	/**
515
	 * Gets order subtotal.
516
	 * @return float
517
	 */
518
	public function get_subtotal() {
519
		$subtotal = 0;
520
521
		foreach ( $this->get_items() as $item ) {
522
			$subtotal += $item->get_subtotal();
523
		}
524
525
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
526
	}
527
528
	/**
529
	 * Get taxes, merged by code, formatted ready for output.
530
	 *
531
	 * @return array
532
	 */
533 View Code Duplication
	public function get_tax_totals() {
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...
534
		$taxes = $this->get_items( 'tax' );
535
536
		if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
537
			$zero_amounts = array_filter( wp_list_pluck( $taxes, 'amount' ) );
538
			$taxes        = array_intersect_key( $taxes, $zero_amounts );
539
		}
540
541
		return apply_filters( 'woocommerce_order_formatted_tax_totals', $taxes );
542
	}
543
544
	/*
545
	|--------------------------------------------------------------------------
546
	| Setters
547
	|--------------------------------------------------------------------------
548
	|
549
	| Functions for setting order data. These should not update anything in the
550
	| database itself and should only change what is stored in the class
551
	| object. However, for backwards compatibility pre 2.7.0 some of these
552
	| setters may handle both.
553
	|
554
	*/
555
556
	/**
557
	 * Set parent order ID.
558
	 * @since 2.7.0
559
	 * @param int $value
560
	 * @throws WC_Data_Exception
561
	 */
562
	public function set_parent_id( $value ) {
563
		if ( $value && ! get_post( $value ) ) {
564
			$this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
565
		}
566
		$this->data['parent_id'] = absint( $value );
567
	}
568
569
	/**
570
	 * Set order status.
571
	 * @since 2.7.0
572
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
573
	 * @return array details of change
574
	 */
575
	 public function set_status( $new_status ) {
576
		$old_status = $this->get_status();
577
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
578
579
		// Only allow valid new status
580
		if ( ! in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) ) {
581
			$new_status = 'pending';
582
		}
583
584
		$this->data['status'] = 'wc-' . $new_status;
585
586
		// If the old status is set but unknown (e.g. draft) assume its pending for action usage.
587
		if ( $old_status && ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
588
			$old_status = 'pending';
589
		}
590
591
		return array(
592
			'from' => $old_status,
593
			'to'   => $new_status,
594
		);
595
	 }
596
597
	/**
598
	 * Set order_version
599
	 * @param string $value
600
	 * @throws WC_Data_Exception
601
	 */
602
	public function set_version( $value ) {
603
		$this->data['version'] = $value;
604
	}
605
606
	/**
607
	 * Set order_currency
608
	 * @param string $value
609
	 * @throws WC_Data_Exception
610
	 */
611
	public function set_currency( $value ) {
612
		if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ) ) ) {
613
			$this->error( 'order_invalid_currency', __( 'Invalid currency code', 'woocommerce' ) );
614
		}
615
		$this->data['currency'] = $value ? $value : get_woocommerce_currency();
616
	}
617
618
	/**
619
	 * Set prices_include_tax
620
	 * @param bool $value
621
	 * @throws WC_Data_Exception
622
	 */
623
	public function set_prices_include_tax( $value ) {
624
		$this->data['prices_include_tax'] = (bool) $value;
625
	}
626
627
	/**
628
	 * Set date_created
629
	 * @param string $timestamp Timestamp
630
	 * @throws WC_Data_Exception
631
	 */
632
	public function set_date_created( $timestamp ) {
633
		$this->data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
634
	}
635
636
	/**
637
	 * Set date_modified
638
	 * @param string $timestamp
639
	 * @throws WC_Data_Exception
640
	 */
641
	public function set_date_modified( $timestamp ) {
642
		$this->data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
643
	}
644
645
	/**
646
	 * Set discount_total
647
	 * @param string $value
648
	 * @throws WC_Data_Exception
649
	 */
650
	public function set_discount_total( $value ) {
651
		$this->data['discount_total'] = wc_format_decimal( $value );
652
	}
653
654
	/**
655
	 * Set discount_tax
656
	 * @param string $value
657
	 * @throws WC_Data_Exception
658
	 */
659
	public function set_discount_tax( $value ) {
660
		$this->data['discount_tax'] = wc_format_decimal( $value );
661
	}
662
663
	/**
664
	 * Set shipping_total
665
	 * @param string $value
666
	 * @throws WC_Data_Exception
667
	 */
668
	public function set_shipping_total( $value ) {
669
		$this->data['shipping_total'] = wc_format_decimal( $value );
670
	}
671
672
	/**
673
	 * Set shipping_tax
674
	 * @param string $value
675
	 * @throws WC_Data_Exception
676
	 */
677
	public function set_shipping_tax( $value ) {
678
		$this->data['shipping_tax'] = wc_format_decimal( $value );
679
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
680
	}
681
682
	/**
683
	 * Set cart tax
684
	 * @param string $value
685
	 * @throws WC_Data_Exception
686
	 */
687
	public function set_cart_tax( $value ) {
688
		$this->data['cart_tax'] = wc_format_decimal( $value );
689
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
690
	}
691
692
	/**
693
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
694
	 * @param string $value
695
	 * @throws WC_Data_Exception
696
	 */
697
	protected function set_total_tax( $value ) {
698
		$this->data['total_tax'] = wc_format_decimal( $value );
699
	}
700
701
	/**
702
	 * Set total
703
	 * @param string $value
704
	 * @param string $deprecated Function used to set different totals based on this.
705
	 * @throws WC_Data_Exception
706
	 */
707
	public function set_total( $value, $deprecated = '' ) {
708
		if ( $deprecated ) {
709
			_deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' );
710
			return $this->legacy_set_total( $value, $deprecated );
711
		}
712
		$this->data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
713
	}
714
715
	/*
716
	|--------------------------------------------------------------------------
717
	| Order Item Handling
718
	|--------------------------------------------------------------------------
719
	|
720
	| Order items are used for products, taxes, shipping, and fees within
721
	| each order.
722
	|
723
	*/
724
725
	/**
726
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
727
	 * @param string $type Order item type. Default null.
728
	 */
729
	public function remove_order_items( $type = null ) {
730
		global $wpdb;
731
		if ( ! empty( $type ) ) {
732
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id AND items.order_id = %d AND items.order_item_type = %s", $this->get_id(), $type ) );
733
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
734
			if ( $group = $this->type_to_group( $type ) ) {
735
				$this->items[ $group ] = null;
736
			}
737
		} else {
738
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id and items.order_id = %d", $this->get_id() ) );
739
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
740
			$this->items = array(
741
				'line_items'     => null,
742
				'coupon_lines'   => null,
743
				'shipping_lines' => null,
744
				'fee_lines'      => null,
745
				'tax_lines'      => null,
746
			);
747
		}
748
	}
749
750
	/**
751
	 * Convert a type to a types group.
752
	 * @param string $type
753
	 * @return string group
754
	 */
755
	protected function type_to_group( $type ) {
756
		$type_to_group = array(
757
			'line_item' => 'line_items',
758
			'tax'       => 'tax_lines',
759
			'shipping'  => 'shipping_lines',
760
			'fee'       => 'fee_lines',
761
			'coupon'    => 'coupon_lines',
762
		);
763
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
764
	}
765
766
	/**
767
	 * Return an array of items/products within this order.
768
	 * @param string|array $types Types of line items to get (array or string).
769
	 * @return Array of WC_Order_item
770
	 */
771
	public function get_items( $types = 'line_item' ) {
772
		$items = array();
773
		$types = array_filter( (array) $types );
774
775
		foreach ( $types as $type ) {
776
			if ( $group = $this->type_to_group( $type ) ) {
777
				if ( is_null( $this->items[ $group ] ) ) {
778
					$this->items[ $group ] = $this->get_items_from_db( $type );
779
				}
780
				// Don't use array_merge here because keys are numeric
781
				$items = $items + $this->items[ $group ];
782
			}
783
		}
784
785
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
786
	}
787
788
	/**
789
	 * Gets items from the database by type.
790
	 * @param  string $type
791
	 * @return array
792
	 */
793
	protected function get_items_from_db( $type ) {
794
		global $wpdb;
795
796
		$get_items_sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s ORDER BY order_item_id;", $this->get_id(), $type );
797
		$items         = $wpdb->get_results( $get_items_sql );
798
799
		if ( ! empty( $items ) ) {
800
			$items = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
801
		} else {
802
			$items = array();
803
		}
804
805
		return $items;
806
	}
807
808
	/**
809
	 * Return an array of fees within this order.
810
	 * @return array
811
	 */
812
	public function get_fees() {
813
		return $this->get_items( 'fee' );
814
	}
815
816
	/**
817
	 * Return an array of taxes within this order.
818
	 * @return array
819
	 */
820
	public function get_taxes() {
821
		return $this->get_items( 'tax' );
822
	}
823
824
	/**
825
	 * Return an array of shipping costs within this order.
826
	 * @return array
827
	 */
828
	public function get_shipping_methods() {
829
		return $this->get_items( 'shipping' );
830
	}
831
832
	/**
833
	 * Gets formatted shipping method title.
834
	 * @return string
835
	 */
836
	public function get_shipping_method() {
837
		$names = array();
838
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
839
			$names[] = $shipping_method->get_name();
840
		}
841
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
842
	}
843
844
	/**
845
	 * Get coupon codes only.
846
	 * @return array
847
	 */
848
	public function get_used_coupons() {
849
		$coupon_codes = array();
850
		if ( $coupons = $this->get_items( 'coupon' ) ) {
851
			foreach ( $coupons as $coupon ) {
852
				$coupon_codes[] = $coupon->get_code();
853
			}
854
		}
855
		return $coupon_codes;
856
	}
857
858
	/**
859
	 * Gets the count of order items of a certain type.
860
	 *
861
	 * @param string $item_type
862
	 * @return string
863
	 */
864
	public function get_item_count( $item_type = '' ) {
865
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
866
		$count = 0;
867
868
		foreach ( $items as $item ) {
869
			$count += $item->get_quantity();
870
		}
871
872
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
873
	}
874
875
	/**
876
	 * Get an order item object, based on it's type.
877
	 * @since  2.7.0
878
	 * @param  int $item_id
879
	 * @return WC_Order_Item
880
	 */
881
	public function get_item( $item_id ) {
882
		return WC_Order_Factory::get_order_item( $item_id );
883
	}
884
885
	/**
886
	 * Get key for where a certain item type is stored in _items.
887
	 * @since  2.7.0
888
	 * @param  $item object Order item (product, shipping, fee, coupon, tax)
889
	 * @return string
890
	 */
891
	protected function get_items_key( $item ) {
892
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
893
			return 'line_items';
894
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
895
			return 'fee_lines';
896
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
897
			return 'shipping_lines';
898
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
899
			return 'tax_lines';
900
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
901
			return 'coupon_lines';
902
		} else {
903
			return '';
904
		}
905
	}
906
907
	/**
908
	 * Remove item from the order.
909
	 * @param int $item_id
910
	 */
911
	public function remove_item( $item_id ) {
912
		$item = $this->get_item( $item_id );
913
914
		if ( ! $item || ! ( $items_key = $this->get_items_key( $item ) ) ) {
915
			return false;
916
		}
917
918
		// Unset and remove later
919
		$this->items_to_delete[] = $item;
920
		unset( $this->items[ $items_key ][ $item->get_id() ] );
921
	}
922
923
	/**
924
	 * Adds an order item to this order. The order item will not persist until save.
925
	 * @since 2.7.0
926
	 * @param WC_Order_Item Order item object (product, shipping, fee, coupon, tax)
927
	 */
928
	public function add_item( $item ) {
929
		if ( ! $items_key = $this->get_items_key( $item ) ) {
930
			return false;
931
		}
932
933
		// Make sure existing items are loaded so we can append this new one.
934
		if ( is_null( $this->items[ $items_key ] ) ) {
935
			$this->items[ $items_key ] = $this->get_items( $item->get_type() );
936
		}
937
938
		// Append new row with generated temporary ID
939
		if ( $item->get_id() ) {
940
			$this->items[ $items_key ][ $item->get_id() ] = $item;
941
		} else {
942
			$this->items[ $items_key ][ 'new:' . sizeof( $this->items[ $items_key ] ) ] = $item;
943
		}
944
	}
945
946
	/**
947
	 * Add a product line item to the order. This is the only line item type with
948
	 * it's own method because it saves looking up order amounts (costs are added up for you).
949
	 * @param  \WC_Product $product
950
	 * @param  int $qty
951
	 * @param  array $args
952
	 * @return int order item ID
953
	 * @throws WC_Data_Exception
954
	 */
955
	public function add_product( $product, $qty = 1, $args = array() ) {
956
		if ( $product ) {
957
			$default_args = array(
958
				'name'         => $product->get_title(),
959
				'tax_class'    => $product->get_tax_class(),
960
				'product_id'   => $product->get_id(),
961
				'variation_id' => isset( $product->variation_id ) ? $product->variation_id : 0,
962
				'variation'    => isset( $product->variation_id ) ? $product->get_variation_attributes() : array(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_attributes() does only exist in the following sub-classes of WC_Product: WC_Product_Variable, WC_Product_Variation. 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...
963
				'subtotal'     => $product->get_price_excluding_tax( $qty ),
964
				'total'        => $product->get_price_excluding_tax( $qty ),
965
				'quantity'     => $qty,
966
			);
967
		} else {
968
			$default_args = array(
969
				'quantity'     => $qty,
970
			);
971
		}
972
973
		$args = wp_parse_args( $args, $default_args );
974
975
		// BW compatibility with old args
976 View Code Duplication
		if ( isset( $args['totals'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
977
			foreach ( $args['totals'] as $key => $value ) {
978
				if ( 'tax' === $key ) {
979
					$args['total_tax'] = $value;
980
				} elseif ( 'tax_data' === $key ) {
981
					$args['taxes'] = $value;
982
				} else {
983
					$args[ $key ] = $value;
984
				}
985
			}
986
		}
987
988
		$item = new WC_Order_Item_Product( $args );
989
		$item->set_backorder_meta();
990
		$item->set_order_id( $this->get_id() );
991
		$item->save();
992
		$this->add_item( $item );
993
		wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '2.7', 'Use woocommerce_new_order_item action instead.' );
994
		return $item->get_id();
995
	}
996
997
	/*
998
	|--------------------------------------------------------------------------
999
	| Payment Token Handling
1000
	|--------------------------------------------------------------------------
1001
	|
1002
	| Payment tokens are hashes used to take payments by certain gateways.
1003
	|
1004
	*/
1005
1006
	/**
1007
	 * Add a payment token to an order
1008
	 *
1009
	 * @since 2.6
1010
	 * @param  WC_Payment_Token   $token     Payment token object
1011
	 * @return boolean|int The new token ID or false if it failed.
1012
	 */
1013
	public function add_payment_token( $token ) {
1014
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1015
			return false;
1016
		}
1017
1018
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1019
1020
		if ( empty( $token_ids ) ) {
1021
			$token_ids = array();
1022
		}
1023
1024
		$token_ids[] = $token->get_id();
1025
1026
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1027
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1028
		return $token->get_id();
1029
	}
1030
1031
	/**
1032
	 * Returns a list of all payment tokens associated with the current order
1033
	 *
1034
	 * @since 2.6
1035
	 * @return array An array of payment token objects
1036
	 */
1037
	public function get_payment_tokens() {
1038
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1039
	}
1040
1041
	/*
1042
	|--------------------------------------------------------------------------
1043
	| Calculations.
1044
	|--------------------------------------------------------------------------
1045
	|
1046
	| These methods calculate order totals and taxes based on the current data.
1047
	|
1048
	*/
1049
1050
	/**
1051
	 * Calculate shipping total.
1052
	 *
1053
	 * @since 2.2
1054
	 * @return float
1055
	 */
1056
	public function calculate_shipping() {
1057
		$shipping_total = 0;
1058
1059
		foreach ( $this->get_shipping_methods() as $shipping ) {
1060
			$shipping_total += $shipping->get_total();
1061
		}
1062
1063
		$this->set_shipping_total( $shipping_total );
1064
		$this->save();
1065
1066
		return $this->get_shipping_total();
1067
	}
1068
1069
	/**
1070
	 * Get all tax classes for items in the order.
1071
	 *
1072
	 * @since 2.6.3
1073
	 * @return array
1074
	 */
1075
	public function get_items_tax_classes() {
1076
		$found_tax_classes = array();
1077
1078
		foreach ( $this->get_items() as $item ) {
1079
			if ( $_product = $item->get_product() ) {
1080
				$found_tax_classes[] = $_product->get_tax_class();
1081
			}
1082
		}
1083
1084
		return array_unique( $found_tax_classes );
1085
	}
1086
1087
	/**
1088
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1089
	 *
1090
	 * Will use the base country unless customer addresses are set.
1091
	 * @param $args array Added in 2.7.0 to pass things like location.
1092
	 */
1093
	public function calculate_taxes( $args = array() ) {
1094
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1095
		$args              = wp_parse_args( $args, array(
1096
			'country'  => 'billing' === $tax_based_on ? $this->get_billing_country()  : $this->get_shipping_country(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_billing_country() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_shipping_country() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
1097
			'state'    => 'billing' === $tax_based_on ? $this->get_billing_state()    : $this->get_shipping_state(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_billing_state() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_shipping_state() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
1098
			'postcode' => 'billing' === $tax_based_on ? $this->get_billing_postcode() : $this->get_shipping_postcode(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_billing_postcode() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_shipping_postcode() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
1099
			'city'     => 'billing' === $tax_based_on ? $this->get_billing_city()     : $this->get_shipping_city(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_billing_city() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_shipping_city() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
1100
		) );
1101
1102
		// Default to base
1103
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1104
			$default          = wc_get_base_location();
1105
			$args['country']  = $default['country'];
1106
			$args['state']    = $default['state'];
1107
			$args['postcode'] = '';
1108
			$args['city']     = '';
1109
		}
1110
1111
		// Calc taxes for line items
1112
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1113
			$tax_class           = $item->get_tax_class();
1114
			$tax_status          = $item->get_tax_status();
1115
1116
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1117
				$tax_rates = WC_Tax::find_rates( array(
1118
					'country'   => $args['country'],
1119
					'state'     => $args['state'],
1120
					'postcode'  => $args['postcode'],
1121
					'city'      => $args['city'],
1122
					'tax_class' => $tax_class,
1123
				) );
1124
1125
				$total = $item->get_total();
1126
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1127
1128
				if ( $item->is_type( 'line_item' ) ) {
1129
					$subtotal       = $item->get_subtotal();
1130
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1131
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1132
				} else {
1133
					$item->set_taxes( array( 'total' => $taxes ) );
1134
				}
1135
				$item->save();
1136
			}
1137
		}
1138
1139
		// Calc taxes for shipping
1140
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1141
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1142
1143
			// Inherit tax class from items
1144
			if ( '' === $shipping_tax_class ) {
1145
				$tax_rates         = array();
1146
				$tax_classes       = array_merge( array( '' ), WC_Tax::get_tax_classes() );
1147
				$found_tax_classes = $this->get_items_tax_classes();
1148
1149
				foreach ( $tax_classes as $tax_class ) {
1150
					$tax_class = sanitize_title( $tax_class );
1151
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1152
						$tax_rates = WC_Tax::find_shipping_rates( array(
1153
							'country'   => $args['country'],
1154
							'state'     => $args['state'],
1155
							'postcode'  => $args['postcode'],
1156
							'city'      => $args['city'],
1157
							'tax_class' => $tax_class,
1158
						) );
1159
						break;
1160
					}
1161
				}
1162
			} else {
1163
				$tax_rates = WC_Tax::find_shipping_rates( array(
1164
					'country'   => $args['country'],
1165
					'state'     => $args['state'],
1166
					'postcode'  => $args['postcode'],
1167
					'city'      => $args['city'],
1168
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1169
				) );
1170
			}
1171
1172
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1173
			$item->save();
1174
		}
1175
		$this->update_taxes();
1176
	}
1177
1178
	/**
1179
	 * Update tax lines for the order based on the line item taxes themselves.
1180
	 */
1181
	public function update_taxes() {
1182
		$cart_taxes     = array();
1183
		$shipping_taxes = array();
1184
1185
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1186
			$taxes = $item->get_taxes();
1187 View Code Duplication
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1188
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1189
			}
1190
		}
1191
1192
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1193
			$taxes = $item->get_taxes();
1194 View Code Duplication
			foreach ( $taxes['total'] as $tax_rate_id => $tax ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1195
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1196
			}
1197
		}
1198
1199
		// Remove old existing tax rows.
1200
		$this->remove_order_items( 'tax' );
1201
1202
		// Now merge to keep tax rows.
1203
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1204
			$item = new WC_Order_Item_Tax();
1205
			$item->set_rate( $tax_rate_id );
1206
			$item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
1207
			$item->set_shipping_tax_total( isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
1208
			$this->add_item( $item );
1209
		}
1210
1211
		// Save tax totals
1212
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1213
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1214
		$this->save();
1215
	}
1216
1217
	/**
1218
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1219
	 *
1220
	 * @since 2.2
1221
	 * @param  bool $and_taxes Calc taxes if true.
1222
	 * @return float calculated grand total.
1223
	 */
1224
	public function calculate_totals( $and_taxes = true ) {
1225
		$cart_subtotal     = 0;
1226
		$cart_total        = 0;
1227
		$fee_total         = 0;
1228
		$cart_subtotal_tax = 0;
1229
		$cart_total_tax    = 0;
1230
1231
		if ( $and_taxes && wc_tax_enabled() ) {
1232
			$this->calculate_taxes();
1233
		}
1234
1235
		// line items
1236
		foreach ( $this->get_items() as $item ) {
1237
			$cart_subtotal     += $item->get_subtotal();
1238
			$cart_total        += $item->get_total();
1239
			$cart_subtotal_tax += $item->get_subtotal_tax();
1240
			$cart_total_tax    += $item->get_total_tax();
1241
		}
1242
1243
		$this->calculate_shipping();
1244
1245
		foreach ( $this->get_fees() as $item ) {
1246
			$fee_total += $item->get_total();
1247
		}
1248
1249
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1250
1251
		$this->set_discount_total( $cart_subtotal - $cart_total );
1252
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1253
		$this->set_total( $grand_total );
1254
		$this->save();
1255
1256
		return $grand_total;
1257
	}
1258
1259
	/**
1260
	 * Get item subtotal - this is the cost before discount.
1261
	 *
1262
	 * @param object $item
1263
	 * @param bool $inc_tax (default: false).
1264
	 * @param bool $round (default: true).
1265
	 * @return float
1266
	 */
1267 View Code Duplication
	public function get_item_subtotal( $item, $inc_tax = false, $round = true ) {
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...
1268
		$subtotal = 0;
1269
1270
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1271
			if ( $inc_tax ) {
1272
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_quantity() );
1273
			} else {
1274
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_quantity() ) );
1275
			}
1276
1277
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1278
		}
1279
1280
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1281
	}
1282
1283
	/**
1284
	 * Get line subtotal - this is the cost before discount.
1285
	 *
1286
	 * @param object $item
1287
	 * @param bool $inc_tax (default: false).
1288
	 * @param bool $round (default: true).
1289
	 * @return float
1290
	 */
1291 View Code Duplication
	public function get_line_subtotal( $item, $inc_tax = false, $round = true ) {
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...
1292
		$subtotal = 0;
1293
1294
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1295
			if ( $inc_tax ) {
1296
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1297
			} else {
1298
				$subtotal = $item->get_subtotal();
1299
			}
1300
1301
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1302
		}
1303
1304
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1305
	}
1306
1307
	/**
1308
	 * Calculate item cost - useful for gateways.
1309
	 *
1310
	 * @param object $item
1311
	 * @param bool $inc_tax (default: false).
1312
	 * @param bool $round (default: true).
1313
	 * @return float
1314
	 */
1315 View Code Duplication
	public function get_item_total( $item, $inc_tax = false, $round = true ) {
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...
1316
		$total = 0;
1317
1318
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1319
			if ( $inc_tax ) {
1320
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_quantity() );
1321
			} else {
1322
				$total = $item->get_total() / max( 1, $item->get_quantity() );
1323
			}
1324
1325
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1326
		}
1327
1328
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1329
	}
1330
1331
	/**
1332
	 * Calculate line total - useful for gateways.
1333
	 *
1334
	 * @param object $item
1335
	 * @param bool $inc_tax (default: false).
1336
	 * @param bool $round (default: true).
1337
	 * @return float
1338
	 */
1339 View Code Duplication
	public function get_line_total( $item, $inc_tax = false, $round = true ) {
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...
1340
		$total = 0;
1341
1342
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1343
			// Check if we need to add line tax to the line total.
1344
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1345
1346
			// Check if we need to round.
1347
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1348
		}
1349
1350
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1351
	}
1352
1353
	/**
1354
	 * Get item tax - useful for gateways.
1355
	 *
1356
	 * @param mixed $item
1357
	 * @param bool $round (default: true).
1358
	 * @return float
1359
	 */
1360
	public function get_item_tax( $item, $round = true ) {
1361
		$tax = 0;
1362
1363
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1364
			$tax = $item->get_total_tax() / max( 1, $item->get_quantity() );
1365
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1366
		}
1367
1368
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1369
	}
1370
1371
	/**
1372
	 * Get line tax - useful for gateways.
1373
	 *
1374
	 * @param mixed $item
1375
	 * @return float
1376
	 */
1377
	public function get_line_tax( $item ) {
1378
		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 );
1379
	}
1380
1381
	/**
1382
	 * Gets line subtotal - formatted for display.
1383
	 *
1384
	 * @param array  $item
1385
	 * @param string $tax_display
1386
	 * @return string
1387
	 */
1388
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1389
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1390
1391
		if ( 'excl' == $tax_display ) {
1392
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1393
1394
			$subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_currency() ) );
0 ignored issues
show
Documentation introduced by
$item is of type array, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1395
		} else {
1396
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array( 'currency' => $this->get_currency() ) );
0 ignored issues
show
Documentation introduced by
$item is of type array, but the function expects a object.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1397
		}
1398
1399
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1400
	}
1401
1402
	/**
1403
	 * Gets order total - formatted for display.
1404
	 * @return string
1405
	 */
1406
	public function get_formatted_order_total() {
1407
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1408
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1409
	}
1410
1411
	/**
1412
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1413
	 *
1414
	 * @param bool $compound (default: false).
1415
	 * @param string $tax_display (default: the tax_display_cart value).
1416
	 * @return string
1417
	 */
1418
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1419
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1420
		$subtotal    = 0;
1421
1422
		if ( ! $compound ) {
1423
			foreach ( $this->get_items() as $item ) {
1424
				$subtotal += $item->get_subtotal();
1425
1426
				if ( 'incl' === $tax_display ) {
1427
					$subtotal += $item->get_subtotal_tax();
1428
				}
1429
			}
1430
1431
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1432
1433
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1434
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1435
			}
1436
		} else {
1437
			if ( 'incl' === $tax_display ) {
1438
				return '';
1439
			}
1440
1441
			foreach ( $this->get_items() as $item ) {
1442
				$subtotal += $item->get_subtotal();
1443
			}
1444
1445
			// Add Shipping Costs.
1446
			$subtotal += $this->get_shipping_total();
1447
1448
			// Remove non-compound taxes.
1449
			foreach ( $this->get_taxes() as $tax ) {
1450
				if ( $this->is_compound() ) {
0 ignored issues
show
Bug introduced by
The method is_compound() does not seem to exist on object<WC_Abstract_Order>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1451
					continue;
1452
				}
1453
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1454
			}
1455
1456
			// Remove discounts.
1457
			$subtotal = $subtotal - $this->get_total_discount();
1458
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1459
		}
1460
1461
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1462
	}
1463
1464
	/**
1465
	 * Gets shipping (formatted).
1466
	 *
1467
	 * @return string
1468
	 */
1469
	public function get_shipping_to_display( $tax_display = '' ) {
1470
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1471
1472
		if ( $this->get_shipping_total() != 0 ) {
1473
1474
			if ( 'excl' === $tax_display ) {
1475
1476
				// Show shipping excluding tax.
1477
				$shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
1478
1479 View Code Duplication
				if ( $this->get_shipping_tax() != 0 && $this->get_prices_include_tax() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1480
					$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 );
1481
				}
1482
			} else {
1483
1484
				// Show shipping including tax.
1485
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
1486
1487 View Code Duplication
				if ( $this->get_shipping_tax() != 0 && ! $this->get_prices_include_tax() ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1488
					$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 );
1489
				}
1490
			}
1491
1492
			$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 );
1493
1494
		} elseif ( $this->get_shipping_method() ) {
1495
			$shipping = $this->get_shipping_method();
1496
		} else {
1497
			$shipping = __( 'Free!', 'woocommerce' );
1498
		}
1499
1500
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1501
	}
1502
1503
	/**
1504
	 * Get the discount amount (formatted).
1505
	 * @since  2.3.0
1506
	 * @return string
1507
	 */
1508
	public function get_discount_to_display( $tax_display = '' ) {
1509
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1510
		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 );
1511
	}
1512
1513
	/**
1514
	 * Get totals for display on pages and in emails.
1515
	 *
1516
	 * @param mixed $tax_display
1517
	 * @return array
1518
	 */
1519
	public function get_order_item_totals( $tax_display = '' ) {
1520
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1521
		$total_rows  = array();
1522
1523
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1524
			$total_rows['cart_subtotal'] = array(
1525
				'label' => __( 'Subtotal:', 'woocommerce' ),
1526
				'value'    => $subtotal,
1527
			);
1528
		}
1529
1530
		if ( $this->get_total_discount() > 0 ) {
1531
			$total_rows['discount'] = array(
1532
				'label' => __( 'Discount:', 'woocommerce' ),
1533
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1534
			);
1535
		}
1536
1537
		if ( $this->get_shipping_method() ) {
1538
			$total_rows['shipping'] = array(
1539
				'label' => __( 'Shipping:', 'woocommerce' ),
1540
				'value'    => $this->get_shipping_to_display( $tax_display ),
1541
			);
1542
		}
1543
1544
		if ( $fees = $this->get_fees() ) {
1545
			foreach ( $fees as $id => $fee ) {
1546
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1547
					continue;
1548
				}
1549
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1550
					'label' => $fee->get_name() . ':',
1551
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1552
				);
1553
			}
1554
		}
1555
1556
		// Tax for tax exclusive prices.
1557
		if ( 'excl' === $tax_display ) {
1558
1559
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1560
1561
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1562
1563
					$total_rows[ sanitize_title( $code ) ] = array(
1564
						'label' => $tax->label . ':',
1565
						'value'    => $tax->formatted_amount,
1566
					);
1567
				}
1568
			} else {
1569
1570
				$total_rows['tax'] = array(
1571
					'label' => WC()->countries->tax_or_vat() . ':',
1572
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1573
				);
1574
			}
1575
		}
1576
1577
		if ( $this->get_total() > 0 && $this->get_payment_method_title() ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_payment_method_title() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
1578
			$total_rows['payment_method'] = array(
1579
				'label' => __( 'Payment Method:', 'woocommerce' ),
1580
				'value' => $this->get_payment_method_title(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_payment_method_title() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
1581
			);
1582
		}
1583
1584
		if ( $refunds = $this->get_refunds() ) {
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Abstract_Order as the method get_refunds() does only exist in the following sub-classes of WC_Abstract_Order: WC_Order. 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...
1585
			foreach ( $refunds as $id => $refund ) {
1586
				$total_rows[ 'refund_' . $id ] = array(
1587
					'label' => $refund->get_reason() ? $refund->get_reason() : __( 'Refund', 'woocommerce' ) . ':',
1588
					'value'    => wc_price( '-' . $refund->get_amount(), array( 'currency' => $this->get_currency() ) ),
1589
				);
1590
			}
1591
		}
1592
1593
		$total_rows['order_total'] = array(
1594
			'label' => __( 'Total:', 'woocommerce' ),
1595
			'value'    => $this->get_formatted_order_total( $tax_display ),
0 ignored issues
show
Unused Code introduced by
The call to WC_Abstract_Order::get_formatted_order_total() has too many arguments starting with $tax_display.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1596
		);
1597
1598
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1599
	}
1600
1601
	/*
1602
	|--------------------------------------------------------------------------
1603
	| Conditionals
1604
	|--------------------------------------------------------------------------
1605
	|
1606
	| Checks if a condition is true or false.
1607
	|
1608
	*/
1609
1610
	/**
1611
	 * Checks the order status against a passed in status.
1612
	 *
1613
	 * @return bool
1614
	 */
1615
	public function has_status( $status ) {
1616
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1617
	}
1618
1619
	/**
1620
	 * Check whether this order has a specific shipping method or not.
1621
	 *
1622
	 * @param string $method_id
1623
	 * @return bool
1624
	 */
1625
	public function has_shipping_method( $method_id ) {
1626
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1627
			if ( strpos( $shipping_method->get_method_id(), $method_id ) === 0 ) {
1628
				return true;
1629
			}
1630
		}
1631
		return false;
1632
	}
1633
1634
	/**
1635
	 * Returns true if the order contains a free product.
1636
	 * @since 2.5.0
1637
	 * @return bool
1638
	 */
1639
	public function has_free_item() {
1640
		foreach ( $this->get_items() as $item ) {
1641
			if ( ! $item->get_total() ) {
1642
				return true;
1643
			}
1644
		}
1645
		return false;
1646
	}
1647
}
1648