Completed
Pull Request — master (#11645)
by Mike
10:05
created

WC_Abstract_Order::set_shipping_tax()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
if ( ! defined( 'ABSPATH' ) ) {
3
	exit;
4
}
5
6
include_once( '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, with defaults. This is the core order data exposed
24
	 * in APIs since 2.7.0.
25
	 *
26
	 * Notes:
27
	 * order_tax = Sum of all taxes.
28
	 * cart_tax = cart_tax is the new name for the legacy 'order_tax' which is the tax for items only, not shipping.
29
	 *
30
	 * @since 2.7.0
31
	 * @var array
32
	 */
33
	protected $_data = array(
34
		'id'                 => 0,
35
		'parent_id'          => 0,
36
		'status'             => '',
37
		'type'               => 'shop_order',
38
		'order_key'          => '',
39
		'currency'           => '',
40
		'version'            => '',
41
		'prices_include_tax' => false,
42
		'date_created'       => '',
43
		'date_modified'      => '',
44
		'customer_id'        => 0,
45
		'discount_total'     => 0,
46
		'discount_tax'       => 0,
47
		'shipping_total'     => 0,
48
		'shipping_tax'       => 0,
49
		'cart_tax'           => 0,
50
		'total'              => 0,
51
		'total_tax'          => 0,
52
	);
53
54
	/**
55
	 * Data stored in meta keys, but not considered "meta" for an order.
56
	 * @since 2.7.0
57
	 * @var array
58
	 */
59
	protected $_internal_meta_keys = array(
60
		'_customer_user', '_order_key', '_order_currency', '_cart_discount',
61
		'_cart_discount_tax', '_order_shipping', '_order_shipping_tax',
62
		'_order_tax', '_order_total', '_order_version', '_prices_include_tax',
63
		'_payment_tokens',
64
	);
65
66
	/**
67
	 * Order items will be stored here, sometimes before they persist in the DB.
68
	 * @since 2.7.0
69
	 * @var array
70
	 */
71
	protected $_items = array(
72
		'line_items'     => null,
73
		'coupon_lines'   => null,
74
		'shipping_lines' => null,
75
		'fee_lines'      => null,
76
		'tax_lines'      => null,
77
	);
78
79
	/**
80
	 *  Internal meta type used to store order data.
81
	 * @var string
82
	 */
83
	protected $_meta_type = 'post';
84
85
	/**
86
	 * Stores meta in cache for future reads.
87
	 * A group must be set to to enable caching.
88
	 * @var string
89
	 */
90
	protected $_cache_group = 'order';
91
92
	/**
93
	 * Get the order if ID is passed, otherwise the order is new and empty.
94
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
95
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
96
	 * methods that will be maintained going forward.
97
	 *
98
	 * @param  int|object|WC_Order $order Order to init.
99
	 */
100
	public function __construct( $order = 0 ) {
101
		if ( is_numeric( $order ) && $order > 0 ) {
102
			$this->read( $order );
103
		} elseif ( $order instanceof self ) {
104
			$this->read( absint( $order->get_id() ) );
105
		} elseif ( ! empty( $order->ID ) ) {
106
			$this->read( absint( $order->ID ) );
107
		}
108
		// Set default status if none were read.
109
		if ( ! $this->get_status() ) {
110
			$this->set_status( apply_filters( 'woocommerce_default_order_status', 'pending' ) );
111
		}
112
	}
113
114
	/*
115
	|--------------------------------------------------------------------------
116
	| CRUD methods
117
	|--------------------------------------------------------------------------
118
	|
119
	| Methods which create, read, update and delete orders from the database.
120
	| Written in abstract fashion so that the way orders are stored can be
121
	| changed more easily in the future.
122
	|
123
	| A save method is included for convenience (chooses update or create based
124
	| on if the order exists yet).
125
	|
126
	*/
127
128
	/**
129
	 * Get internal type (post type.)
130
	 * @return string
131
	 */
132
	public function get_type() {
133
		return 'shop_order';
134
	}
135
136
	/**
137
	 * Get a title for the new post type.
138
	 */
139
	protected function get_post_title() {
140
		return sprintf( __( 'Order &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
141
	}
142
143
	/**
144
	 * Insert data into the database.
145
	 * @since 2.7.0
146
	 */
147
	public function create() {
148
		$this->set_order_key( 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) );
149
		$this->set_date_created( current_time( 'timestamp' ) );
150
151
		$order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', array(
152
			'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
153
			'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
154
			'post_type'     => $this->get_type(),
155
			'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
156
			'ping_status'   => 'closed',
157
			'post_author'   => 1,
158
			'post_title'    => $this->get_post_title(),
159
			'post_password' => uniqid( 'order_' ),
160
			'post_parent'   => $this->get_parent_id(),
161
		) ), true );
162
163
		if ( $order_id ) {
164
			$this->set_id( $order_id );
165
166
			// Set meta data
167
			$this->update_post_meta( '_customer_user', $this->get_customer_id() );
168
			$this->update_post_meta( '_order_currency', $this->get_currency() );
169
			$this->update_post_meta( '_order_key', $this->get_order_key() );
170
			$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
171
			$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
172
			$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
173
			$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
174
			$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
175
			$this->update_post_meta( '_order_total', $this->get_total( true ) );
176
			$this->update_post_meta( '_order_version', $this->get_version() );
177
			$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
178
			$this->save_meta_data();
179
		}
180
	}
181
182
	/**
183
	 * Read from the database.
184
	 * @since 2.7.0
185
	 * @param int $id ID of object to read.
186
	 */
187
	public function read( $id ) {
188
		if ( empty( $id ) || ! ( $post_object = get_post( $id ) ) ) {
189
			return;
190
		}
191
192
		// Map standard post data
193
		$this->set_id( $post_object->ID );
194
		$this->set_parent_id( $post_object->post_parent );
195
		$this->set_date_created( $post_object->post_date );
196
		$this->set_date_modified( $post_object->post_modified );
197
		$this->set_status( $post_object->post_status );
198
		$this->set_order_type( $post_object->post_type );
199
		$this->set_customer_id( get_post_meta( $this->get_id(), '_customer_user', true ) );
200
		$this->set_order_key( get_post_meta( $this->get_id(), '_order_key', true ) );
201
		$this->set_currency( get_post_meta( $this->get_id(), '_order_currency', true ) );
202
		$this->set_discount_total( get_post_meta( $this->get_id(), '_cart_discount', true ) );
203
		$this->set_discount_tax( get_post_meta( $this->get_id(), '_cart_discount_tax', true ) );
204
		$this->set_shipping_total( get_post_meta( $this->get_id(), '_order_shipping', true ) );
205
		$this->set_shipping_tax( get_post_meta( $this->get_id(), '_order_shipping_tax', true ) );
206
		$this->set_cart_tax( get_post_meta( $this->get_id(), '_order_tax', true ) );
207
		$this->set_total( get_post_meta( $this->get_id(), '_order_total', true ) );
208
209
		// Orders store the state of prices including tax when created.
210
		$this->set_prices_include_tax( metadata_exists( 'post', $this->get_id(), '_prices_include_tax' ) ? 'yes' === get_post_meta( $this->get_id(), '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
211
212
		// Load meta data
213
		$this->read_meta_data();
214
	}
215
216
	/**
217
	 * Post meta update wrapper. Sets or deletes based on value.
218
	 * @since 2.7.0
219
	 * @return bool Was it changed?
220
	 */
221
	protected function update_post_meta( $key, $value ) {
222
		if ( '' !== $value ) {
223
			return update_post_meta( $this->get_id(), $key, $value );
224
		} else {
225
			return delete_post_meta( $this->get_id(), $key );
226
		}
227
	}
228
229
	/**
230
	 * Update data in the database.
231
	 * @since 2.7.0
232
	 */
233
	public function update() {
234
		global $wpdb;
235
236
		$order_id = $this->get_id();
237
238
		$wpdb->update(
239
			$wpdb->posts,
240
			array(
241
				'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
242
				'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
243
				'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
244
				'post_parent'   => $this->get_parent_id(),
245
			),
246
			array(
247
				'ID' => $order_id
248
			)
249
		);
250
251
		// Update meta data
252
		$this->update_post_meta( '_customer_user', $this->get_customer_id() );
253
		$this->update_post_meta( '_order_currency', $this->get_currency() );
254
		$this->update_post_meta( '_order_key', $this->get_order_key() );
255
		$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
256
		$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
257
		$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
258
		$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
259
		$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
260
		$this->update_post_meta( '_order_total', $this->get_total( true ) );
261
		$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
262
		$this->save_meta_data();
263
	}
264
265
	/**
266
	 * Delete data from the database.
267
	 * @since 2.7.0
268
	 */
269
	public function delete() {
270
		wp_delete_post( $this->get_id() );
271
	}
272
273
	/**
274
	 * Save data to the database.
275
	 * @since 2.7.0
276
	 * @return int order ID
277
	 */
278
	public function save() {
279
		$this->set_version( WC_VERSION );
280
281
		if ( ! $this->get_id() ) {
282
			$this->create();
283
		} else {
284
			$this->update();
285
		}
286
287
		$this->save_items();
288
		clean_post_cache( $this->get_id() );
289
		wc_delete_shop_order_transients( $this->get_id() );
290
291
		return $this->get_id();
292
	}
293
294
	/**
295
	 * Save all order items which are part of this order.
296
	 */
297
	protected function save_items() {
298
		foreach ( $this->_items as $item_group => $items ) {
299
			if ( is_array( $items ) ) {
300
				foreach ( $items as $item_key => $item ) {
301
					$item->set_order_id( $this->get_id() );
302
					$item_id = $item->save();
303
304
					// If ID changed (new item saved to DB)...
305
					if ( $item_id !== $item_key ) {
306
						$this->_items[ $item_group ][ $item_id ] = $item;
307
						unset( $this->_items[ $item_group ][ $item_key ] );
308
309
						// Legacy action handler
310
						switch ( $item_group ) {
311
							case 'fee_lines' :
312
								if ( has_action( 'woocommerce_add_order_fee_meta' ) && isset( $item->legacy_fee, $item->legacy_fee_key ) ) {
313
									_deprecated_function( 'Action: woocommerce_add_order_fee_meta', '2.7', 'Use woocommerce_new_order_item action instead.' );
314
									do_action( 'woocommerce_add_order_fee_meta', $this->get_id(), $item_id, $item->legacy_fee, $item->legacy_fee_key );
315
								}
316
							break;
317 View Code Duplication
							case 'shipping_lines' :
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...
318
								if ( has_action( 'woocommerce_add_shipping_order_item' ) && isset( $item->legacy_package_key ) ) {
319
									_deprecated_function( 'Action: woocommerce_add_shipping_order_item', '2.7', 'Use woocommerce_new_order_item action instead.' );
320
									do_action( 'woocommerce_add_shipping_order_item', $item_id, $item->legacy_package_key );
321
								}
322
							break;
323 View Code Duplication
							case 'line_items' :
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...
324
								if ( has_action( 'woocommerce_add_order_item_meta' ) && isset( $item->legacy_values, $item->legacy_cart_item_key ) ) {
325
									_deprecated_function( 'Action: woocommerce_add_order_item_meta', '2.7', 'Use woocommerce_new_order_item action instead.' );
326
									do_action( 'woocommerce_add_order_item_meta', $item_id, $item->legacy_values, $item->legacy_cart_item_key );
327
								}
328
							break;
329
						}
330
					}
331
				}
332
			}
333
		}
334
	}
335
336
	/*
337
	|--------------------------------------------------------------------------
338
	| Getters
339
	|--------------------------------------------------------------------------
340
	|
341
	| Methods for getting data from the order object.
342
	|
343
	*/
344
345
	/**
346
	 * Get all class data in array format.
347
	 * @since 2.7.0
348
	 * @return array
349
	 */
350
	public function get_data() {
351
		return array_merge(
352
			$this->_data,
353
			array(
354
				'meta_data'      => $this->get_meta_data(),
355
				'line_items'     => $this->get_items( 'line_item' ),
356
				'tax_lines'      => $this->get_items( 'tax' ),
357
				'shipping_lines' => $this->get_items( 'shipping' ),
358
				'fee_lines'      => $this->get_items( 'fee' ),
359
				'coupon_lines'   => $this->get_items( 'coupon' ),
360
			)
361
		);
362
	}
363
364
	/**
365
	 * Get order ID.
366
	 * @since 2.7.0
367
	 * @return integer
368
	 */
369
	public function get_id() {
370
		return $this->_data['id'];
371
	}
372
373
	/**
374
	 * Get parent order ID.
375
	 * @since 2.7.0
376
	 * @return integer
377
	 */
378
	public function get_parent_id() {
379
		return $this->_data['parent_id'];
380
	}
381
382
	/**
383
	 * get_order_number function.
384
	 *
385
	 * Gets the order number for display (by default, order ID).
386
	 *
387
	 * @return string
388
	 */
389
	public function get_order_number() {
390
		return apply_filters( 'woocommerce_order_number', $this->get_id(), $this );
391
	}
392
393
	/**
394
	 * Get order key.
395
	 * @since 2.7.0
396
	 * @return string
397
	 */
398
	public function get_order_key() {
399
		return $this->_data['order_key'];
400
	}
401
402
	/**
403
	 * Gets order currency.
404
	 * @return string
405
	 */
406
	public function get_currency() {
407
		return apply_filters( 'woocommerce_get_currency', $this->_data['currency'], $this );
408
	}
409
410
	/**
411
	 * Get order_version
412
	 * @return string
413
	 */
414
	public function get_version() {
415
		return $this->_data['version'];
416
	}
417
418
	/**
419
	 * Get prices_include_tax
420
	 * @return bool
421
	 */
422
	public function get_prices_include_tax() {
423
		return $this->_data['prices_include_tax'];
424
	}
425
426
	/**
427
	 * Get Order Type
428
	 * @return string
429
	 */
430
	public function get_order_type() {
431
		return $this->_data['type'];
432
	}
433
434
	/**
435
	 * Get date_created
436
	 * @return int
437
	 */
438
	public function get_date_created() {
439
		return $this->_data['date_created'];
440
	}
441
442
	/**
443
	 * Get date_modified
444
	 * @return int
445
	 */
446
	public function get_date_modified() {
447
		return $this->_data['date_modified'];
448
	}
449
450
	/**
451
	 * Get customer_id
452
	 * @return int
453
	 */
454
	public function get_customer_id() {
455
		return $this->_data['customer_id'];
456
	}
457
458
	/**
459
	 * Return the order statuses without wc- internal prefix.
460
	 * @return string
461
	 */
462
	public function get_status() {
463
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->_data['status'], 0, 3 ) ? substr( $this->_data['status'], 3 ) : $this->_data['status'], $this );
464
	}
465
466
	/**
467
	 * Alias for get_customer_id().
468
	 * @since  2.2
469
	 * @return int
470
	 */
471
	public function get_user_id() {
472
		return $this->get_customer_id();
473
	}
474
475
	/**
476
	 * Get the user associated with the order. False for guests.
477
	 *
478
	 * @since  2.2
479
	 * @return WP_User|false
480
	 */
481
	public function get_user() {
482
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
483
	}
484
485
	/**
486
	 * Get discount_total
487
	 * @param bool $raw Gets raw unfiltered value.
488
	 * @return string
489
	 */
490
	public function get_discount_total( $raw = false ) {
491
		$value = wc_format_decimal( $this->_data['discount_total'] );
492
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_total', $value, $this );
493
	}
494
495
	/**
496
	 * Get discount_tax
497
	 * @param bool $raw Gets raw unfiltered value.
498
	 * @return string
499
	 */
500
	public function get_discount_tax( $raw = false ) {
501
		$value = wc_format_decimal( $this->_data['discount_tax'] );
502
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_tax', $value, $this );
503
	}
504
505
	/**
506
	 * Get shipping_total
507
	 * @param bool $raw Gets raw unfiltered value.
508
	 * @return string
509
	 */
510
	public function get_shipping_total( $raw = false ) {
511
		$value = wc_format_decimal( $this->_data['shipping_total'] );
512
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_total', $value, $this );
513
	}
514
515
	/**
516
	 * Get shipping_tax.
517
	 * @param bool $raw Gets raw unfiltered value.
518
	 * @return string
519
	 */
520
	public function get_shipping_tax( $raw = false ) {
521
		$value = wc_format_decimal( $this->_data['shipping_tax'] );
522
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_tax', $value, $this );
523
	}
524
525
	/**
526
	 * Gets cart tax amount.
527
	 * @param bool $raw Gets raw unfiltered value.
528
	 * @return float
529
	 */
530
	public function get_cart_tax( $raw = false ) {
531
		$value = wc_format_decimal( $this->_data['cart_tax'] );
532
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_cart_tax', $value, $this );
533
	}
534
535
	/**
536
	 * Gets order grand total. incl. taxes. Used in gateways. Filtered.
537
	 * @param bool $raw Gets raw unfiltered value.
538
	 * @return float
539
	 */
540
	public function get_total( $raw = false ) {
541
		$value = wc_format_decimal( $this->_data['total'], wc_get_price_decimals() );
542
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total', $value, $this );
543
	}
544
545
	/**
546
	 * Get total tax amount. Alias for get_order_tax().
547
	 *
548
	 * @since 2.7.0 woocommerce_order_amount_total_tax filter has been removed to avoid
549
	 * these values being modified and then saved back to the DB. There are
550
	 * other, later hooks available to change totals on display. e.g.
551
	 * woocommerce_get_order_item_totals.
552
	 * @param bool $raw Gets raw unfiltered value.
553
	 * @return float
554
	 */
555
	public function get_total_tax( $raw = false ) {
556
		$value = wc_format_decimal( $this->_data['total_tax'] );
557
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total_tax', $value, $this );
558
	}
559
560
	/**
561
	 * Gets the total discount amount.
562
	 * @param  bool $ex_tax Show discount excl any tax.
563
	 * @return float
564
	 */
565
	public function get_total_discount( $ex_tax = true ) {
566
		if ( $ex_tax ) {
567
			$total_discount = $this->get_discount_total();
568
		} else {
569
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
570
		}
571
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
572
	}
573
574
	/**
575
	 * Gets order subtotal.
576
	 * @return float
577
	 */
578
	public function get_subtotal() {
579
		$subtotal = 0;
580
581
		foreach ( $this->get_items() as $item ) {
582
			$subtotal += $item->get_subtotal();
583
		}
584
585
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
586
	}
587
588
	/**
589
	 * Get taxes, merged by code, formatted ready for output.
590
	 *
591
	 * @return array
592
	 */
593
	public function get_tax_totals() {
594
		$tax_totals = array();
595
596
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
597
			$code = $tax->get_rate_code();
598
599 View Code Duplication
			if ( ! isset( $tax_totals[ $code ] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
600
				$tax_totals[ $code ] = new stdClass();
601
				$tax_totals[ $code ]->amount = 0;
602
			}
603
604
			$tax_totals[ $code ]->id                = $key;
605
			$tax_totals[ $code ]->rate_id           = $tax->get_rate_id();
606
			$tax_totals[ $code ]->is_compound       = $tax->is_compound();
607
			$tax_totals[ $code ]->label             = $tax->get_label();
608
			$tax_totals[ $code ]->amount           += $tax->get_tax_total() + $tax->get_shipping_tax_total();
609
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
610
		}
611
612 View Code Duplication
		if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
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...
613
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
614
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
615
		}
616
617
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
618
	}
619
620
	/*
621
	|--------------------------------------------------------------------------
622
	| Setters
623
	|--------------------------------------------------------------------------
624
	|
625
	| Functions for setting order data. These should not update anything in the
626
	| database itself and should only change what is stored in the class
627
	| object. However, for backwards compatibility pre 2.7.0 some of these
628
	| setters may handle both.
629
	|
630
	*/
631
632
	/**
633
	 * Set order ID.
634
	 * @since 2.7.0
635
	 * @param int $value
636
	 */
637
	public function set_id( $value ) {
638
		$this->_data['id'] = absint( $value );
639
	}
640
641
	/**
642
	 * Set parent order ID.
643
	 * @since 2.7.0
644
	 * @param int $value
645
	 */
646
	public function set_parent_id( $value ) {
647
		$this->_data['parent_id'] = absint( $value );
648
	}
649
650
	/**
651
	 * Set order status.
652
	 * @since 2.7.0
653
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
654
	 * @param array details of change
655
	 */
656
	 public function set_status( $new_status ) {
657
		$old_status = $this->get_status();
658
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
659
660
		// Only allow valid new status
661
		if ( ! in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) ) {
662
			$new_status = 'pending';
663
		}
664
665
		$this->_data['status'] = 'wc-' . $new_status;
666
667
		// If the old status is set but unknown (e.g. draft) assume its pending for action usage.
668
		if ( $old_status && ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
669
			$old_status = 'pending';
670
		}
671
672
		return array(
673
			'from' => $old_status,
674
			'to'   => $new_status
675
		);
676
	 }
677
678
	/**
679
	 * Set Order Type
680
	 * @param string $value
681
	 */
682
	public function set_order_type( $value ) {
683
		$this->_data['type'] = $value;
684
	}
685
686
	/**
687
	 * Set order_key.
688
	 * @param string $value Max length 20 chars.
689
	 */
690
	public function set_order_key( $value ) {
691
		$this->_data['order_key'] = substr( $value, 0, 20 );
692
	}
693
694
	/**
695
	 * Set order_version
696
	 * @param string $value
697
	 */
698
	public function set_version( $value ) {
699
		$this->_data['version'] = $value;
700
	}
701
702
	/**
703
	 * Set order_currency
704
	 * @param string $value
705
	 */
706
	public function set_currency( $value ) {
707
		$this->_data['currency'] = $value;
708
	}
709
710
	/**
711
	 * Set prices_include_tax
712
	 * @param bool $value
713
	 */
714
	public function set_prices_include_tax( $value ) {
715
		$this->_data['prices_include_tax'] = (bool) $value;
716
	}
717
718
	/**
719
	 * Set date_created
720
	 * @param string $timestamp Timestamp
721
	 */
722
	public function set_date_created( $timestamp ) {
723
		$this->_data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
724
	}
725
726
	/**
727
	 * Set date_modified
728
	 * @param string $timestamp
729
	 */
730
	public function set_date_modified( $timestamp ) {
731
		$this->_data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
732
	}
733
734
	/**
735
	 * Set customer_id
736
	 * @param int $value
737
	 */
738
	public function set_customer_id( $value ) {
739
		$this->_data['customer_id'] = absint( $value );
740
	}
741
742
	/**
743
	 * Set discount_total
744
	 * @param string $value
745
	 */
746
	public function set_discount_total( $value ) {
747
		$this->_data['discount_total'] = wc_format_decimal( $value );
748
	}
749
750
	/**
751
	 * Set discount_tax
752
	 * @param string $value
753
	 */
754
	public function set_discount_tax( $value ) {
755
		$this->_data['discount_tax'] = wc_format_decimal( $value );
756
	}
757
758
	/**
759
	 * Set shipping_total
760
	 * @param string $value
761
	 */
762
	public function set_shipping_total( $value ) {
763
		$this->_data['shipping_total'] = wc_format_decimal( $value );
764
	}
765
766
	/**
767
	 * Set shipping_tax
768
	 * @param string $value
769
	 */
770
	public function set_shipping_tax( $value ) {
771
		$this->_data['shipping_tax'] = wc_format_decimal( $value );
772
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
773
	}
774
775
	/**
776
	 * Set cart tax
777
	 * @param string $value
778
	 */
779
	public function set_cart_tax( $value ) {
780
		$this->_data['cart_tax'] = wc_format_decimal( $value );
781
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
782
	}
783
784
	/**
785
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
786
	 * @param string $value
787
	 */
788
	protected function set_total_tax( $value ) {
789
		$this->_data['total_tax'] = wc_format_decimal( $value );
790
	}
791
792
	/**
793
	 * Set total
794
	 * @param string $value
795
	 * @param string $deprecated Function used to set different totals based on this.
796
	 */
797
	public function set_total( $value, $deprecated = '' ) {
798
		if ( $deprecated ) {
799
			_deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' );
800
			return $this->legacy_set_total( $value, $deprecated );
801
		}
802
		$this->_data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
803
	}
804
805
	/*
806
	|--------------------------------------------------------------------------
807
	| Order Item Handling
808
	|--------------------------------------------------------------------------
809
	|
810
	| Order items are used for products, taxes, shipping, and fees within
811
	| each order.
812
	|
813
	*/
814
815
	/**
816
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
817
	 * @param string $type Order item type. Default null.
818
	 */
819
	public function remove_order_items( $type = null ) {
820
		global $wpdb;
821
		if ( ! empty( $type ) ) {
822
			$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 ) );
823
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
824
		} else {
825
			$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() ) );
826
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
827
		}
828
	}
829
830
	/**
831
	 * Return an array of items/products within this order.
832
	 * @param string|array $types Types of line items to get (array or string).
833
	 * @return Array of WC_Order_item
834
	 */
835
	public function get_items( $types = 'line_item' ) {
836
		$type_to_group = array(
837
			'line_item' => 'line_items',
838
			'tax'       => 'tax_lines',
839
			'shipping'  => 'shipping_lines',
840
			'fee'       => 'fee_lines',
841
			'coupon'    => 'coupon_lines',
842
		);
843
844
		$items = array();
845
		$types = array_filter( (array) $types );
846
847
		foreach ( $types as $type ) {
848
			if ( isset( $type_to_group[ $type ] ) ) {
849
				if ( is_null( $this->_items[ $type_to_group[ $type ] ] ) ) {
850
					$this->_items[ $type_to_group[ $type ] ] = $this->get_items_from_db( $type );
851
				}
852
853
				$items = array_merge( $items, $this->_items[ $type_to_group[ $type ] ] );
854
			}
855
		}
856
857
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
858
	}
859
860
	/**
861
	 * Gets items from the database by type.
862
	 * @param  string $type
863
	 * @return array
864
	 */
865
	protected function get_items_from_db( $type ) {
866
		global $wpdb;
867
868
		$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 ) ;
869
		$items         = $wpdb->get_results( $get_items_sql );
870
871
		if ( ! empty( $items ) ) {
872
			$items = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
873
		} else {
874
			$items = array();
875
		}
876
877
		return $items;
878
	}
879
880
	/**
881
	 * Return an array of fees within this order.
882
	 * @return array
883
	 */
884
	public function get_fees() {
885
		return $this->get_items( 'fee' );
886
	}
887
888
	/**
889
	 * Return an array of taxes within this order.
890
	 * @return array
891
	 */
892
	public function get_taxes() {
893
		return $this->get_items( 'tax' );
894
	}
895
896
	/**
897
	 * Return an array of shipping costs within this order.
898
	 * @return array
899
	 */
900
	public function get_shipping_methods() {
901
		return $this->get_items( 'shipping' );
902
	}
903
904
	/**
905
	 * Gets formatted shipping method title.
906
	 * @return string
907
	 */
908
	public function get_shipping_method() {
909
		$names = array();
910
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
911
			$names[] = $shipping_method->get_name();
912
		}
913
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
914
	}
915
916
	/**
917
	 * Get coupon codes only.
918
	 * @return array
919
	 */
920
	public function get_used_coupons() {
921
		$coupon_codes = array();
922
		if ( $coupons = $this->get_items( 'coupon' ) ) {
923
			foreach ( $coupons as $coupon ) {
924
				$coupon_codes[] = $coupon->get_code();
925
			}
926
		}
927
		return $coupon_codes;
928
	}
929
930
	/**
931
	 * Gets the count of order items of a certain type.
932
	 *
933
	 * @param string $item_type
934
	 * @return string
935
	 */
936
	public function get_item_count( $item_type = '' ) {
937
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
938
		$count = 0;
939
940
		foreach ( $items as $item ) {
941
			$count += $item->get_qty();
942
		}
943
944
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
945
	}
946
947
	/**
948
	 * Get an order item object, based on it's type.
949
	 * @since  2.7.0
950
	 * @param  int $item_id
951
	 * @return WC_Order_Item
952
	 */
953
	public function get_item( $item_id ) {
954
		return WC_Order_Factory::get_order_item( $item_id );
955
	}
956
957
	/**
958
	 * Adds an order item to this order. The order item will not persist until save.
959
	 * @param object Order item (product, shipping, fee, coupon, tax)
960
	 */
961
	public function add_item( $item ) {
962
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
963
			$item_type = 'line_item';
964
			$items_key = 'line_items';
965
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
966
			$item_type = 'fee';
967
			$items_key = 'fee_lines';
968
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
969
			$item_type = 'shipping';
970
			$items_key = 'shipping_lines';
971
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
972
			$item_type = 'tax';
973
			$items_key = 'tax_lines';
974
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
975
			$item_type = 'coupon';
976
			$items_key = 'coupon_lines';
977
		} else {
978
			return false;
979
		}
980
981
		// Make sure existing items are loaded so we can append this new one.
982
		if ( is_null( $this->_items[ $items_key ] ) ) {
983
			$this->_items[ $items_key ] = $this->get_items( $item_type );
984
		}
985
986
		// Append new row with generated temporary ID
987
		$this->_items[ $items_key ][ 'new:' . md5( json_encode( $item ) ) ] = $item;
988
	}
989
990
	/**
991
	 * Add a product line item to the order.
992
	 * Order must be saved prior to adding items.
993
	 * @param \WC_Product $product
994
	 * @param array $args
995
	 * @param array $deprecated qty was passed as arg 2 prior to 2.7.0
996
	 * @return int order item ID
997
	 */
998
	public function add_product( $product, $args = array(), $deprecated = array() ) {
999
		if ( ! is_array( $args ) ) {
1000
			_deprecated_argument( 'qty', '2.7', 'Pass only product and args' );
1001
			$qty         = $args;
1002
			$args        = $deprecated;
1003
			$args['qty'] = $qty;
1004
		}
1005
1006
		if ( empty( $args['qty'] ) ) {
1007
			$args['qty'] = 1;
1008
		}
1009
1010
		$args = wp_parse_args( $args, array(
1011
			'qty'          => 1,
1012
			'name'         => $product ? $product->get_title() : '',
1013
			'tax_class'    => $product ? $product->get_tax_class() : '',
1014
			'product_id'   => $product ? $product->get_id() : '',
1015
			'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0,
1016
			'variation'    => $product && 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...
1017
			'subtotal'     => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
1018
			'total'        => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
1019
			'subtotal_tax' => 0,
1020
			'total_tax'    => 0,
1021
			'taxes'        => array(
1022
				'subtotal' => array(),
1023
				'total'    => array(),
1024
			),
1025
		) );
1026
1027
		// BW compatibility with old args
1028 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...
1029
			foreach ( $args['totals'] as $key => $value ) {
1030
				if ( 'tax' === $key ) {
1031
					$args['total_tax'] = $value;
1032
				} elseif ( 'tax_data' === $key ) {
1033
					$args['taxes'] = $value;
1034
				} else {
1035
					$args[ $key ] = $value;
1036
				}
1037
			}
1038
		}
1039
1040
		$item = new WC_Order_Item_Product( $args );
1041
1042
		// Handle backorders
1043 View Code Duplication
		if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) {
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...
1044
			$item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ), true );
1045
		}
1046
1047
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1048
		$item->save();
1049
1050 View Code Duplication
		if ( has_action( 'woocommerce_order_add_product' ) ) {
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...
1051
			_deprecated_function( 'Action: woocommerce_order_add_product', '2.7', 'Use woocommerce_new_order_item action instead.' );
1052
			do_action( 'woocommerce_order_add_product', $this->get_id(), $item->get_id(), $product, $qty, $args );
1053
		}
1054
1055
		return $item->get_id();
1056
	}
1057
1058
	/**
1059
	 * Add coupon code to the order.
1060
	 * Order must be saved prior to adding items.
1061
	 * @param array $args
1062
	 * @param int $deprecated1 2.7.0 code, discount, tax were passed.
1063
	 * @param int $deprecated2 2.7.0 code, discount, tax were passed.
1064
	 * @return int order item ID
1065
	 */
1066
	public function add_coupon( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
1067
		if ( ! is_array( $args ) ) {
1068
			_deprecated_argument( 'code', '2.7', 'Pass only an array of args' );
1069
			$args = array(
1070
				'code'         => $args,
1071
				'discount'     => $deprecated1,
1072
				'discount_tax' => $deprecated2,
1073
			);
1074
		}
1075
1076
		$args = wp_parse_args( $args, array(
1077
			'code'         => '',
1078
			'discount'     => 0,
1079
			'discount_tax' => 0,
1080
		) );
1081
1082
		$item = new WC_Order_Item_Coupon( $args );
1083
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1084
		$item->save();
1085
1086
		if ( has_action( 'woocommerce_order_add_coupon' ) ) {
1087
			_deprecated_function( 'Action: woocommerce_order_add_coupon', '2.7', 'Use woocommerce_new_order_item action instead.' );
1088
			do_action( 'woocommerce_order_add_coupon', $this->get_id(), $item->get_id(), $args['code'], $args['discount'], $args['discount_tax'] );
1089
		}
1090
1091
		return $item->get_id();
1092
	}
1093
1094
	/**
1095
	 * Add a tax row to the order.
1096
	 * Order must be saved prior to adding items.
1097
	 * @since 2.2
1098
	 * @param array $args
1099
	 * @param int $deprecated1 2.7.0 tax_rate_id, amount, shipping amount.
1100
	 * @param int $deprecated2 2.7.0 tax_rate_id, amount, shipping amount.
1101
	 * @return int order item ID
1102
	 */
1103
	public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
1104
		if ( ! is_array( $args ) ) {
1105
			_deprecated_argument( 'tax_rate_id', '2.7', 'Pass only an array of args' );
1106
			$args = array(
1107
				'rate_id'            => $args,
1108
				'tax_total'          => $deprecated1,
1109
				'shipping_tax_total' => $deprecated2,
1110
			);
1111
		}
1112
1113
		$args = wp_parse_args( $args, array(
1114
			'rate_id'            => '',
1115
			'tax_total'          => 0,
1116
			'shipping_tax_total' => 0,
1117
			'rate_code'          => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '',
1118
			'label'              => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '',
1119
			'compound'           => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '',
1120
		) );
1121
1122
		$item = new WC_Order_Item_Tax( $args );
1123
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1124
		$item->save();
1125
1126
		if ( has_action( 'woocommerce_order_add_tax' ) ) {
1127
			_deprecated_function( 'Action: woocommerce_order_add_tax', '2.7', 'Use woocommerce_new_order_item action instead.' );
1128
			do_action( 'woocommerce_order_add_tax', $this->get_id(), $item->get_id(), $args['rate_id'], $args['tax_total'], $args['shipping_tax_total'] );
1129
		}
1130
1131
		return $item->get_id();
1132
	}
1133
1134
	/**
1135
	 * Add a shipping row to the order.
1136
	 * Order must be saved prior to adding items.
1137
	 * @param WC_Shipping_Rate shipping_rate
1138
	 * @return int order item ID
1139
	 */
1140
	public function add_shipping( $shipping_rate ) {
1141
		$item = new WC_Order_Item_Shipping( array(
0 ignored issues
show
Documentation introduced by
array('method_title' => ..._rate->get_meta_data()) is of type array<string,?,{"method_...":"?","meta_data":"?"}>, but the function expects a integer.

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...
1142
			'method_title' => $shipping_rate->label,
1143
			'method_id'    => $shipping_rate->id,
1144
			'total'        => wc_format_decimal( $shipping_rate->cost ),
1145
			'taxes'        => $shipping_rate->taxes,
1146
			'meta_data'    => $shipping_rate->get_meta_data(),
1147
		) );
1148
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1149
		$item->save();
1150
1151 View Code Duplication
		if ( has_action( 'woocommerce_order_add_shipping' ) ) {
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...
1152
			_deprecated_function( 'Action: woocommerce_order_add_shipping', '2.7', 'Use woocommerce_new_order_item action instead.' );
1153
			do_action( 'woocommerce_order_add_shipping', $this->get_id(), $item->get_id(), $shipping_rate );
1154
		}
1155
1156
		return $item->get_id();
1157
	}
1158
1159
	/**
1160
	 * Add a fee to the order.
1161
	 * Order must be saved prior to adding items.
1162
	 * @param object $fee
1163
	 * @return int updated order item ID
1164
	 */
1165
	public function add_fee( $fee ) {
1166
		$item = new WC_Order_Item_Fee( array(
0 ignored issues
show
Documentation introduced by
array('name' => $fee->na...al' => $fee->tax_data)) is of type array<string,?,{"name":"...?,{\"total\":\"?\"}>"}>, but the function expects a integer.

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...
1167
			'name'      => $fee->name,
1168
			'tax_class' => $fee->taxable ? $fee->tax_class : 0,
1169
			'total'     => $fee->amount,
1170
			'total_tax' => $fee->tax,
1171
			'taxes'     => array(
1172
				'total' => $fee->tax_data,
1173
			),
1174
		) );
1175
1176
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1177
		$item->save();
1178
1179 View Code Duplication
		if ( has_action( 'woocommerce_order_add_fee' ) ) {
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...
1180
			_deprecated_function( 'Action: woocommerce_order_add_fee', '2.7', 'Use woocommerce_new_order_item action instead.' );
1181
			do_action( 'woocommerce_order_add_fee', $this->get_id(), $item->get_id(), $fee );
1182
		}
1183
1184
		return $item->get_id();
1185
	}
1186
1187
	/**
1188
	 * Add a payment token to an order
1189
	 *
1190
	 * @since 2.6
1191
	 * @param  WC_Payment_Token   $token     Payment token object
1192
	 * @return boolean|int The new token ID or false if it failed.
1193
	 */
1194
	public function add_payment_token( $token ) {
1195
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1196
			return false;
1197
		}
1198
1199
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1200
1201
		if ( empty ( $token_ids ) ) {
1202
			$token_ids = array();
1203
		}
1204
1205
		$token_ids[] = $token->get_id();
1206
1207
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1208
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1209
		return $token->get_id();
1210
	}
1211
1212
	/**
1213
	 * Returns a list of all payment tokens associated with the current order
1214
	 *
1215
	 * @since 2.6
1216
	 * @return array An array of payment token objects
1217
	 */
1218
	public function get_payment_tokens() {
1219
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1220
	}
1221
1222
	/*
1223
	|--------------------------------------------------------------------------
1224
	| Calculations.
1225
	|--------------------------------------------------------------------------
1226
	|
1227
	| These methods calculate order totals and taxes based on the current data.
1228
	|
1229
	*/
1230
1231
	/**
1232
	 * Calculate shipping total.
1233
	 *
1234
	 * @since 2.2
1235
	 * @return float
1236
	 */
1237
	public function calculate_shipping() {
1238
		$shipping_total = 0;
1239
1240
		foreach ( $this->get_shipping_methods() as $shipping ) {
1241
			$shipping_total += $shipping->get_total();
1242
		}
1243
1244
		$this->set_shipping_total( $shipping_total );
1245
		$this->save();
1246
1247
		return $this->get_shipping_total();
1248
	}
1249
1250
	/**
1251
	 * Get all tax classes for items in the order.
1252
	 *
1253
	 * @since 2.6.3
1254
	 * @return array
1255
	 */
1256
	public function get_items_tax_classes() {
1257
		$found_tax_classes = array();
1258
1259
		foreach ( $this->get_items() as $item ) {
1260
			if ( $_product = $this->get_product_from_item( $item ) ) {
0 ignored issues
show
Deprecated Code introduced by
The method WC_Abstract_Legacy_Order::get_product_from_item() has been deprecated with message: Add deprecation notices in future release. Replaced with $item->get_product()

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

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

Loading history...
1261
				$found_tax_classes[] = $_product->get_tax_class();
0 ignored issues
show
Bug introduced by
The method get_tax_class cannot be called on $_product (of type boolean).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
1262
			}
1263
		}
1264
1265
		return array_unique( $found_tax_classes );
1266
	}
1267
1268
	/**
1269
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1270
	 *
1271
	 * Will use the base country unless customer addresses are set.
1272
	 * @param $args array Added in 2.7.0 to pass things like location.
1273
	 */
1274
	public function calculate_taxes( $args = array() ) {
1275
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1276
		$args              = wp_parse_args( $args, array(
1277
			'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...
1278
			'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...
1279
			'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...
1280
			'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...
1281
		) );
1282
1283
		// Default to base
1284
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1285
			$default          = wc_get_base_location();
1286
			$args['country']  = $default['country'];
1287
			$args['state']    = $default['state'];
1288
			$args['postcode'] = '';
1289
			$args['city']     = '';
1290
		}
1291
1292
		// Calc taxes for line items
1293
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1294
			$tax_class           = $item->get_tax_class();
1295
			$tax_status          = $item->get_tax_status();
1296
1297
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1298
				$tax_rates = WC_Tax::find_rates( array(
1299
					'country'   => $args['country'],
1300
					'state'     => $args['state'],
1301
					'postcode'  => $args['postcode'],
1302
					'city'      => $args['city'],
1303
					'tax_class' => $tax_class,
1304
				) );
1305
1306
				$total = $item->get_total();
1307
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1308
1309
				if ( $item->is_type( 'line_item' ) ) {
1310
					$subtotal       = $item->get_subtotal();
1311
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1312
					$subtotal_tax   = max( 0, array_sum( $subtotal_taxes ) );
1313
					$item->set_subtotal_tax( $subtotal_tax );
1314
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1315
				} else {
1316
					$item->set_taxes( array( 'total' => $taxes ) );
1317
				}
1318
				$item->save();
1319
			}
1320
		}
1321
1322
		// Calc taxes for shipping
1323
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1324
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1325
1326
			// Inherit tax class from items
1327
			if ( '' === $shipping_tax_class ) {
1328
				$tax_rates         = array();
1329
				$tax_classes       = array_merge( array( '' ), WC_Tax::get_tax_classes() );
1330
				$found_tax_classes = $this->get_items_tax_classes();
1331
1332
				foreach ( $tax_classes as $tax_class ) {
1333
					$tax_class = sanitize_title( $tax_class );
1334
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1335
						$tax_rates = WC_Tax::find_shipping_rates( array(
1336
							'country'   => $args['country'],
1337
							'state'     => $args['state'],
1338
							'postcode'  => $args['postcode'],
1339
							'city'      => $args['city'],
1340
							'tax_class' => $tax_class,
1341
						) );
1342
						break;
1343
					}
1344
				}
1345
			} else {
1346
				$tax_rates = WC_Tax::find_shipping_rates( array(
1347
					'country'   => $args['country'],
1348
					'state'     => $args['state'],
1349
					'postcode'  => $args['postcode'],
1350
					'city'      => $args['city'],
1351
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1352
				) );
1353
			}
1354
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1355
			$item->save();
1356
		}
1357
		$this->update_taxes();
1358
	}
1359
1360
	/**
1361
	 * Update tax lines for the order based on the line item taxes themselves.
1362
	 */
1363
	public function update_taxes() {
1364
		$cart_taxes     = array();
1365
		$shipping_taxes = array();
1366
1367
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1368
			$taxes = $item->get_taxes();
1369 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...
1370
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1371
			}
1372
		}
1373
1374
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1375
			$taxes = $item->get_taxes();
1376 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...
1377
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1378
			}
1379
		}
1380
1381
		// Remove old existing tax rows.
1382
		$this->remove_order_items( 'tax' );
1383
1384
		// Now merge to keep tax rows.
1385
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1386
			$this->add_tax( array(
1387
				'rate_id'            => $tax_rate_id,
1388
				'tax_total'          => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0,
1389
				'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0,
1390
			) );
1391
		}
1392
1393
		// Save tax totals
1394
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1395
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1396
		$this->save();
1397
	}
1398
1399
	/**
1400
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1401
	 *
1402
	 * @since 2.2
1403
	 * @param  bool $and_taxes Calc taxes if true.
1404
	 * @return float calculated grand total.
1405
	 */
1406
	public function calculate_totals( $and_taxes = true ) {
1407
		$cart_subtotal     = 0;
1408
		$cart_total        = 0;
1409
		$fee_total         = 0;
1410
		$cart_subtotal_tax = 0;
1411
		$cart_total_tax    = 0;
1412
1413
		if ( $and_taxes && wc_tax_enabled() ) {
1414
			$this->calculate_taxes();
1415
		}
1416
1417
		// line items
1418
		foreach ( $this->get_items() as $item ) {
1419
			$cart_subtotal     += wc_format_decimal( $item->get_subtotal() );
1420
			$cart_total        += wc_format_decimal( $item->get_total() );
1421
			$cart_subtotal_tax += wc_format_decimal( $item->get_subtotal_tax() );
1422
			$cart_total_tax    += wc_format_decimal( $item->get_total_tax() );
1423
		}
1424
1425
		$this->calculate_shipping();
1426
1427
		foreach ( $this->get_fees() as $item ) {
1428
			$fee_total += $item->get_total();
1429
		}
1430
1431
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1432
1433
		$this->set_discount_total( $cart_subtotal - $cart_total );
1434
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1435
		$this->set_total( $grand_total );
1436
		$this->save();
1437
1438
		return $grand_total;
1439
	}
1440
1441
	/**
1442
	 * Get item subtotal - this is the cost before discount.
1443
	 *
1444
	 * @param object $item
1445
	 * @param bool $inc_tax (default: false).
1446
	 * @param bool $round (default: true).
1447
	 * @return float
1448
	 */
1449 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...
1450
		$subtotal = 0;
1451
1452
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1453
			if ( $inc_tax ) {
1454
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_qty() );
1455
			} else {
1456
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_qty() ) );
1457
			}
1458
1459
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1460
		}
1461
1462
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1463
	}
1464
1465
	/**
1466
	 * Get line subtotal - this is the cost before discount.
1467
	 *
1468
	 * @param object $item
1469
	 * @param bool $inc_tax (default: false).
1470
	 * @param bool $round (default: true).
1471
	 * @return float
1472
	 */
1473 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...
1474
		$subtotal = 0;
1475
1476
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1477
			if ( $inc_tax ) {
1478
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1479
			} else {
1480
				$subtotal = $item->get_subtotal();
1481
			}
1482
1483
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1484
		}
1485
1486
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1487
	}
1488
1489
	/**
1490
	 * Calculate item cost - useful for gateways.
1491
	 *
1492
	 * @param object $item
1493
	 * @param bool $inc_tax (default: false).
1494
	 * @param bool $round (default: true).
1495
	 * @return float
1496
	 */
1497 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...
1498
		$total = 0;
1499
1500
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1501
			if ( $inc_tax ) {
1502
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_qty() );
1503
			} else {
1504
				$total = $item->get_total() / max( 1, $item->get_qty() );
1505
			}
1506
1507
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1508
		}
1509
1510
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1511
	}
1512
1513
	/**
1514
	 * Calculate line total - useful for gateways.
1515
	 *
1516
	 * @param object $item
1517
	 * @param bool $inc_tax (default: false).
1518
	 * @param bool $round (default: true).
1519
	 * @return float
1520
	 */
1521 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...
1522
		$total = 0;
1523
1524
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1525
			// Check if we need to add line tax to the line total.
1526
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1527
1528
			// Check if we need to round.
1529
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1530
		}
1531
1532
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1533
	}
1534
1535
	/**
1536
	 * Get item tax - useful for gateways.
1537
	 *
1538
	 * @param mixed $item
1539
	 * @param bool $round (default: true).
1540
	 * @return float
1541
	 */
1542
	public function get_item_tax( $item, $round = true ) {
1543
		$tax = 0;
1544
1545
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1546
			$tax = $item->get_total_tax() / max( 1, $item->get_qty() );
1547
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1548
		}
1549
1550
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1551
	}
1552
1553
	/**
1554
	 * Get line tax - useful for gateways.
1555
	 *
1556
	 * @param mixed $item
1557
	 * @return float
1558
	 */
1559
	public function get_line_tax( $item ) {
1560
		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 );
1561
	}
1562
1563
	/**
1564
	 * Gets line subtotal - formatted for display.
1565
	 *
1566
	 * @param array  $item
1567
	 * @param string $tax_display
1568
	 * @return string
1569
	 */
1570
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1571
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1572
1573
		if ( 'excl' == $tax_display ) {
1574
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1575
1576
			$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...
1577
		} else {
1578
			$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...
1579
		}
1580
1581
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1582
	}
1583
1584
	/**
1585
	 * Gets order total - formatted for display.
1586
	 * @return string
1587
	 */
1588
	public function get_formatted_order_total() {
1589
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1590
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1591
	}
1592
1593
	/**
1594
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1595
	 *
1596
	 * @param bool $compound (default: false).
1597
	 * @param string $tax_display (default: the tax_display_cart value).
1598
	 * @return string
1599
	 */
1600
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1601
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1602
		$subtotal    = 0;
1603
1604
		if ( ! $compound ) {
1605
			foreach ( $this->get_items() as $item ) {
1606
				$subtotal += $item->get_subtotal();
1607
1608
				if ( 'incl' === $tax_display ) {
1609
					$subtotal += $item->get_subtotal_tax();
1610
				}
1611
			}
1612
1613
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1614
1615
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1616
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1617
			}
1618
1619
		} else {
1620
			if ( 'incl' === $tax_display ) {
1621
				return '';
1622
			}
1623
1624
			foreach ( $this->get_items() as $item ) {
1625
				$subtotal += $item->get_subtotal();
1626
			}
1627
1628
			// Add Shipping Costs.
1629
			$subtotal += $this->get_shipping_total();
1630
1631
			// Remove non-compound taxes.
1632
			foreach ( $this->get_taxes() as $tax ) {
1633
				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...
1634
					continue;
1635
				}
1636
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1637
			}
1638
1639
			// Remove discounts.
1640
			$subtotal = $subtotal - $this->get_total_discount();
1641
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1642
		}
1643
1644
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1645
	}
1646
1647
	/**
1648
	 * Gets shipping (formatted).
1649
	 *
1650
	 * @return string
1651
	 */
1652
	public function get_shipping_to_display( $tax_display = '' ) {
1653
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1654
1655
		if ( $this->get_shipping_total() != 0 ) {
1656
1657
			if ( $tax_display == 'excl' ) {
1658
1659
				// Show shipping excluding tax.
1660
				$shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) );
1661
1662 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...
1663
					$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 );
1664
				}
1665
1666
			} else {
1667
1668
				// Show shipping including tax.
1669
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) );
1670
1671 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...
1672
					$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 );
1673
				}
1674
1675
			}
1676
1677
			$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 );
1678
1679
		} elseif ( $this->get_shipping_method() ) {
1680
			$shipping = $this->get_shipping_method();
1681
		} else {
1682
			$shipping = __( 'Free!', 'woocommerce' );
1683
		}
1684
1685
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1686
	}
1687
1688
	/**
1689
	 * Get the discount amount (formatted).
1690
	 * @since  2.3.0
1691
	 * @return string
1692
	 */
1693
	public function get_discount_to_display( $tax_display = '' ) {
1694
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1695
		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 );
1696
	}
1697
1698
	/**
1699
	 * Get totals for display on pages and in emails.
1700
	 *
1701
	 * @param mixed $tax_display
1702
	 * @return array
1703
	 */
1704
	public function get_order_item_totals( $tax_display = '' ) {
1705
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1706
		$total_rows  = array();
1707
1708
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1709
			$total_rows['cart_subtotal'] = array(
1710
				'label' => __( 'Subtotal:', 'woocommerce' ),
1711
				'value'    => $subtotal,
1712
			);
1713
		}
1714
1715
		if ( $this->get_total_discount() > 0 ) {
1716
			$total_rows['discount'] = array(
1717
				'label' => __( 'Discount:', 'woocommerce' ),
1718
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1719
			);
1720
		}
1721
1722
		if ( $this->get_shipping_method() ) {
1723
			$total_rows['shipping'] = array(
1724
				'label' => __( 'Shipping:', 'woocommerce' ),
1725
				'value'    => $this->get_shipping_to_display( $tax_display ),
1726
			);
1727
		}
1728
1729
		if ( $fees = $this->get_fees() ) {
1730
			foreach ( $fees as $id => $fee ) {
1731
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1732
					continue;
1733
				}
1734
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1735
					'label' => $fee->get_name() . ':',
1736
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) )
1737
				);
1738
			}
1739
		}
1740
1741
		// Tax for tax exclusive prices.
1742
		if ( 'excl' === $tax_display ) {
1743
1744
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1745
1746
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1747
1748
					$total_rows[ sanitize_title( $code ) ] = array(
1749
						'label' => $tax->label . ':',
1750
						'value'    => $tax->formatted_amount,
1751
					);
1752
				}
1753
1754
			} else {
1755
1756
				$total_rows['tax'] = array(
1757
					'label' => WC()->countries->tax_or_vat() . ':',
1758
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1759
				);
1760
			}
1761
		}
1762
1763
		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...
1764
			$total_rows['payment_method'] = array(
1765
				'label' => __( 'Payment Method:', 'woocommerce' ),
1766
				'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...
1767
			);
1768
		}
1769
1770
		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...
1771
			foreach ( $refunds as $id => $refund ) {
1772
				$total_rows[ 'refund_' . $id ] = array(
1773
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1774
					'value'    => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ),
1775
				);
1776
			}
1777
		}
1778
1779
		$total_rows['order_total'] = array(
1780
			'label' => __( 'Total:', 'woocommerce' ),
1781
			'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...
1782
		);
1783
1784
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1785
	}
1786
1787
	/*
1788
	|--------------------------------------------------------------------------
1789
	| Conditionals
1790
	|--------------------------------------------------------------------------
1791
	|
1792
	| Checks if a condition is true or false.
1793
	|
1794
	*/
1795
1796
	/**
1797
	 * Checks the order status against a passed in status.
1798
	 *
1799
	 * @return bool
1800
	 */
1801
	public function has_status( $status ) {
1802
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1803
	}
1804
1805
	/**
1806
	 * Check whether this order has a specific shipping method or not.
1807
	 *
1808
	 * @param string $method_id
1809
	 * @return bool
1810
	 */
1811
	public function has_shipping_method( $method_id ) {
1812
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1813
			if ( $shipping_method->get_method_id() === $method_id ) {
1814
				return true;
1815
			}
1816
		}
1817
		return false;
1818
	}
1819
1820
	/**
1821
	 * Check if an order key is valid.
1822
	 *
1823
	 * @param mixed $key
1824
	 * @return bool
1825
	 */
1826
	public function key_is_valid( $key ) {
1827
		return $key === $this->get_order_key();
1828
	}
1829
1830
	/**
1831
	 * Returns true if the order contains a free product.
1832
	 * @since 2.5.0
1833
	 * @return bool
1834
	 */
1835
	public function has_free_item() {
1836
		foreach ( $this->get_items() as $item ) {
1837
			if ( ! $item->get_total() ) {
1838
				return true;
1839
			}
1840
		}
1841
		return false;
1842
	}
1843
}
1844