Completed
Push — master ( a6f850...7796fe )
by Mike
07:58
created

WC_Abstract_Order::set_total()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 2
dl 0
loc 7
rs 9.4285
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
			if ( $group = $this->type_to_group( $type ) ) {
825
				$this->_items[ $group ] = null;
826
			}
827
		} else {
828
			$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() ) );
829
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
830
			$this->_items = array(
831
				'line_items'     => null,
832
				'coupon_lines'   => null,
833
				'shipping_lines' => null,
834
				'fee_lines'      => null,
835
				'tax_lines'      => null,
836
			);
837
		}
838
	}
839
840
	/**
841
	 * Convert a type to a types group.
842
	 * @param string $type
843
	 * @return string group
844
	 */
845
	protected function type_to_group( $type ) {
846
		$type_to_group = array(
847
			'line_item' => 'line_items',
848
			'tax'       => 'tax_lines',
849
			'shipping'  => 'shipping_lines',
850
			'fee'       => 'fee_lines',
851
			'coupon'    => 'coupon_lines',
852
		);
853
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
854
	}
855
856
	/**
857
	 * Return an array of items/products within this order.
858
	 * @param string|array $types Types of line items to get (array or string).
859
	 * @return Array of WC_Order_item
860
	 */
861
	public function get_items( $types = 'line_item' ) {
862
		$items = array();
863
		$types = array_filter( (array) $types );
864
865
		foreach ( $types as $type ) {
866
			if ( $group = $this->type_to_group( $type ) ) {
867
				if ( is_null( $this->_items[ $group ] ) ) {
868
					$this->_items[ $group ] = $this->get_items_from_db( $type );
869
				}
870
				$items = array_merge( $items, $this->_items[ $group ] );
871
			}
872
		}
873
874
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
875
	}
876
877
	/**
878
	 * Gets items from the database by type.
879
	 * @param  string $type
880
	 * @return array
881
	 */
882
	protected function get_items_from_db( $type ) {
883
		global $wpdb;
884
885
		$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 ) ;
886
		$items         = $wpdb->get_results( $get_items_sql );
887
888
		if ( ! empty( $items ) ) {
889
			$items = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
890
		} else {
891
			$items = array();
892
		}
893
894
		return $items;
895
	}
896
897
	/**
898
	 * Return an array of fees within this order.
899
	 * @return array
900
	 */
901
	public function get_fees() {
902
		return $this->get_items( 'fee' );
903
	}
904
905
	/**
906
	 * Return an array of taxes within this order.
907
	 * @return array
908
	 */
909
	public function get_taxes() {
910
		return $this->get_items( 'tax' );
911
	}
912
913
	/**
914
	 * Return an array of shipping costs within this order.
915
	 * @return array
916
	 */
917
	public function get_shipping_methods() {
918
		return $this->get_items( 'shipping' );
919
	}
920
921
	/**
922
	 * Gets formatted shipping method title.
923
	 * @return string
924
	 */
925
	public function get_shipping_method() {
926
		$names = array();
927
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
928
			$names[] = $shipping_method->get_name();
929
		}
930
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
931
	}
932
933
	/**
934
	 * Get coupon codes only.
935
	 * @return array
936
	 */
937
	public function get_used_coupons() {
938
		$coupon_codes = array();
939
		if ( $coupons = $this->get_items( 'coupon' ) ) {
940
			foreach ( $coupons as $coupon ) {
941
				$coupon_codes[] = $coupon->get_code();
942
			}
943
		}
944
		return $coupon_codes;
945
	}
946
947
	/**
948
	 * Gets the count of order items of a certain type.
949
	 *
950
	 * @param string $item_type
951
	 * @return string
952
	 */
953
	public function get_item_count( $item_type = '' ) {
954
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
955
		$count = 0;
956
957
		foreach ( $items as $item ) {
958
			$count += $item->get_qty();
959
		}
960
961
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
962
	}
963
964
	/**
965
	 * Get an order item object, based on it's type.
966
	 * @since  2.7.0
967
	 * @param  int $item_id
968
	 * @return WC_Order_Item
969
	 */
970
	public function get_item( $item_id ) {
971
		return WC_Order_Factory::get_order_item( $item_id );
972
	}
973
974
	/**
975
	 * Adds an order item to this order. The order item will not persist until save.
976
	 * @param object Order item (product, shipping, fee, coupon, tax)
977
	 */
978
	public function add_item( $item ) {
979
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
980
			$item_type = 'line_item';
981
			$items_key = 'line_items';
982
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
983
			$item_type = 'fee';
984
			$items_key = 'fee_lines';
985
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
986
			$item_type = 'shipping';
987
			$items_key = 'shipping_lines';
988
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
989
			$item_type = 'tax';
990
			$items_key = 'tax_lines';
991
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
992
			$item_type = 'coupon';
993
			$items_key = 'coupon_lines';
994
		} else {
995
			return false;
996
		}
997
998
		// Make sure existing items are loaded so we can append this new one.
999
		if ( is_null( $this->_items[ $items_key ] ) ) {
1000
			$this->_items[ $items_key ] = $this->get_items( $item_type );
1001
		}
1002
1003
		// Append new row with generated temporary ID
1004
		$this->_items[ $items_key ][ 'new:' . md5( json_encode( $item ) ) ] = $item;
1005
	}
1006
1007
	/**
1008
	 * Add a product line item to the order.
1009
	 * Order must be saved prior to adding items.
1010
	 * @param \WC_Product $product
1011
	 * @param array $args
1012
	 * @param array $deprecated qty was passed as arg 2 prior to 2.7.0
1013
	 * @return int order item ID
1014
	 */
1015
	public function add_product( $product, $args = array(), $deprecated = array() ) {
1016
		if ( ! is_array( $args ) ) {
1017
			_deprecated_argument( 'qty', '2.7', 'Pass only product and args' );
1018
			$qty         = $args;
1019
			$args        = $deprecated;
1020
			$args['qty'] = $qty;
1021
		}
1022
1023
		if ( empty( $args['qty'] ) ) {
1024
			$args['qty'] = 1;
1025
		}
1026
1027
		$args = wp_parse_args( $args, array(
1028
			'qty'          => 1,
1029
			'name'         => $product ? $product->get_title() : '',
1030
			'tax_class'    => $product ? $product->get_tax_class() : '',
1031
			'product_id'   => $product ? $product->get_id() : '',
1032
			'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0,
1033
			'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...
1034
			'subtotal'     => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
1035
			'total'        => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
1036
			'subtotal_tax' => 0,
1037
			'total_tax'    => 0,
1038
			'taxes'        => array(
1039
				'subtotal' => array(),
1040
				'total'    => array(),
1041
			),
1042
		) );
1043
1044
		// BW compatibility with old args
1045 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...
1046
			foreach ( $args['totals'] as $key => $value ) {
1047
				if ( 'tax' === $key ) {
1048
					$args['total_tax'] = $value;
1049
				} elseif ( 'tax_data' === $key ) {
1050
					$args['taxes'] = $value;
1051
				} else {
1052
					$args[ $key ] = $value;
1053
				}
1054
			}
1055
		}
1056
1057
		$item = new WC_Order_Item_Product( $args );
1058
1059
		// Handle backorders
1060 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...
1061
			$item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ), true );
1062
		}
1063
1064
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1065
		$item->save();
1066
1067 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...
1068
			_deprecated_function( 'Action: woocommerce_order_add_product', '2.7', 'Use woocommerce_new_order_item action instead.' );
1069
			do_action( 'woocommerce_order_add_product', $this->get_id(), $item->get_id(), $product, $qty, $args );
1070
		}
1071
1072
		return $item->get_id();
1073
	}
1074
1075
	/**
1076
	 * Add coupon code to the order.
1077
	 * Order must be saved prior to adding items.
1078
	 * @param array $args
1079
	 * @param int $deprecated1 2.7.0 code, discount, tax were passed.
1080
	 * @param int $deprecated2 2.7.0 code, discount, tax were passed.
1081
	 * @return int order item ID
1082
	 */
1083
	public function add_coupon( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
1084
		if ( ! is_array( $args ) ) {
1085
			_deprecated_argument( 'code', '2.7', 'Pass only an array of args' );
1086
			$args = array(
1087
				'code'         => $args,
1088
				'discount'     => $deprecated1,
1089
				'discount_tax' => $deprecated2,
1090
			);
1091
		}
1092
1093
		$args = wp_parse_args( $args, array(
1094
			'code'         => '',
1095
			'discount'     => 0,
1096
			'discount_tax' => 0,
1097
		) );
1098
1099
		$item = new WC_Order_Item_Coupon( $args );
1100
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1101
		$item->save();
1102
1103
		if ( has_action( 'woocommerce_order_add_coupon' ) ) {
1104
			_deprecated_function( 'Action: woocommerce_order_add_coupon', '2.7', 'Use woocommerce_new_order_item action instead.' );
1105
			do_action( 'woocommerce_order_add_coupon', $this->get_id(), $item->get_id(), $args['code'], $args['discount'], $args['discount_tax'] );
1106
		}
1107
1108
		return $item->get_id();
1109
	}
1110
1111
	/**
1112
	 * Add a tax row to the order.
1113
	 * Order must be saved prior to adding items.
1114
	 * @since 2.2
1115
	 * @param array $args
1116
	 * @param int $deprecated1 2.7.0 tax_rate_id, amount, shipping amount.
1117
	 * @param int $deprecated2 2.7.0 tax_rate_id, amount, shipping amount.
1118
	 * @return int order item ID
1119
	 */
1120
	public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
1121
		if ( ! is_array( $args ) ) {
1122
			_deprecated_argument( 'tax_rate_id', '2.7', 'Pass only an array of args' );
1123
			$args = array(
1124
				'rate_id'            => $args,
1125
				'tax_total'          => $deprecated1,
1126
				'shipping_tax_total' => $deprecated2,
1127
			);
1128
		}
1129
1130
		$args = wp_parse_args( $args, array(
1131
			'rate_id'            => '',
1132
			'tax_total'          => 0,
1133
			'shipping_tax_total' => 0,
1134
			'rate_code'          => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '',
1135
			'label'              => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '',
1136
			'compound'           => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '',
1137
		) );
1138
1139
		$item = new WC_Order_Item_Tax( $args );
1140
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1141
		$item->save();
1142
1143
		if ( has_action( 'woocommerce_order_add_tax' ) ) {
1144
			_deprecated_function( 'Action: woocommerce_order_add_tax', '2.7', 'Use woocommerce_new_order_item action instead.' );
1145
			do_action( 'woocommerce_order_add_tax', $this->get_id(), $item->get_id(), $args['rate_id'], $args['tax_total'], $args['shipping_tax_total'] );
1146
		}
1147
1148
		return $item->get_id();
1149
	}
1150
1151
	/**
1152
	 * Add a shipping row to the order.
1153
	 * Order must be saved prior to adding items.
1154
	 * @param WC_Shipping_Rate shipping_rate
1155
	 * @return int order item ID
1156
	 */
1157
	public function add_shipping( $shipping_rate ) {
1158
		$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...
1159
			'method_title' => $shipping_rate->label,
1160
			'method_id'    => $shipping_rate->id,
1161
			'total'        => wc_format_decimal( $shipping_rate->cost ),
1162
			'taxes'        => $shipping_rate->taxes,
1163
			'meta_data'    => $shipping_rate->get_meta_data(),
1164
		) );
1165
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1166
		$item->save();
1167
1168 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...
1169
			_deprecated_function( 'Action: woocommerce_order_add_shipping', '2.7', 'Use woocommerce_new_order_item action instead.' );
1170
			do_action( 'woocommerce_order_add_shipping', $this->get_id(), $item->get_id(), $shipping_rate );
1171
		}
1172
1173
		return $item->get_id();
1174
	}
1175
1176
	/**
1177
	 * Add a fee to the order.
1178
	 * Order must be saved prior to adding items.
1179
	 * @param object $fee
1180
	 * @return int updated order item ID
1181
	 */
1182
	public function add_fee( $fee ) {
1183
		$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...
1184
			'name'      => $fee->name,
1185
			'tax_class' => $fee->taxable ? $fee->tax_class : 0,
1186
			'total'     => $fee->amount,
1187
			'total_tax' => $fee->tax,
1188
			'taxes'     => array(
1189
				'total' => $fee->tax_data,
1190
			),
1191
		) );
1192
1193
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1194
		$item->save();
1195
1196 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...
1197
			_deprecated_function( 'Action: woocommerce_order_add_fee', '2.7', 'Use woocommerce_new_order_item action instead.' );
1198
			do_action( 'woocommerce_order_add_fee', $this->get_id(), $item->get_id(), $fee );
1199
		}
1200
1201
		return $item->get_id();
1202
	}
1203
1204
	/**
1205
	 * Add a payment token to an order
1206
	 *
1207
	 * @since 2.6
1208
	 * @param  WC_Payment_Token   $token     Payment token object
1209
	 * @return boolean|int The new token ID or false if it failed.
1210
	 */
1211
	public function add_payment_token( $token ) {
1212
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1213
			return false;
1214
		}
1215
1216
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1217
1218
		if ( empty ( $token_ids ) ) {
1219
			$token_ids = array();
1220
		}
1221
1222
		$token_ids[] = $token->get_id();
1223
1224
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1225
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1226
		return $token->get_id();
1227
	}
1228
1229
	/**
1230
	 * Returns a list of all payment tokens associated with the current order
1231
	 *
1232
	 * @since 2.6
1233
	 * @return array An array of payment token objects
1234
	 */
1235
	public function get_payment_tokens() {
1236
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1237
	}
1238
1239
	/*
1240
	|--------------------------------------------------------------------------
1241
	| Calculations.
1242
	|--------------------------------------------------------------------------
1243
	|
1244
	| These methods calculate order totals and taxes based on the current data.
1245
	|
1246
	*/
1247
1248
	/**
1249
	 * Calculate shipping total.
1250
	 *
1251
	 * @since 2.2
1252
	 * @return float
1253
	 */
1254
	public function calculate_shipping() {
1255
		$shipping_total = 0;
1256
1257
		foreach ( $this->get_shipping_methods() as $shipping ) {
1258
			$shipping_total += $shipping->get_total();
1259
		}
1260
1261
		$this->set_shipping_total( $shipping_total );
1262
		$this->save();
1263
1264
		return $this->get_shipping_total();
1265
	}
1266
1267
	/**
1268
	 * Get all tax classes for items in the order.
1269
	 *
1270
	 * @since 2.6.3
1271
	 * @return array
1272
	 */
1273
	public function get_items_tax_classes() {
1274
		$found_tax_classes = array();
1275
1276
		foreach ( $this->get_items() as $item ) {
1277
			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...
1278
				$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...
1279
			}
1280
		}
1281
1282
		return array_unique( $found_tax_classes );
1283
	}
1284
1285
	/**
1286
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1287
	 *
1288
	 * Will use the base country unless customer addresses are set.
1289
	 * @param $args array Added in 2.7.0 to pass things like location.
1290
	 */
1291
	public function calculate_taxes( $args = array() ) {
1292
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1293
		$args              = wp_parse_args( $args, array(
1294
			'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...
1295
			'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...
1296
			'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...
1297
			'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...
1298
		) );
1299
1300
		// Default to base
1301
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1302
			$default          = wc_get_base_location();
1303
			$args['country']  = $default['country'];
1304
			$args['state']    = $default['state'];
1305
			$args['postcode'] = '';
1306
			$args['city']     = '';
1307
		}
1308
1309
		// Calc taxes for line items
1310
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1311
			$tax_class           = $item->get_tax_class();
1312
			$tax_status          = $item->get_tax_status();
1313
1314
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1315
				$tax_rates = WC_Tax::find_rates( array(
1316
					'country'   => $args['country'],
1317
					'state'     => $args['state'],
1318
					'postcode'  => $args['postcode'],
1319
					'city'      => $args['city'],
1320
					'tax_class' => $tax_class,
1321
				) );
1322
1323
				$total = $item->get_total();
1324
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1325
1326
				if ( $item->is_type( 'line_item' ) ) {
1327
					$subtotal       = $item->get_subtotal();
1328
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1329
					$subtotal_tax   = max( 0, array_sum( $subtotal_taxes ) );
1330
					$item->set_subtotal_tax( $subtotal_tax );
1331
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1332
				} else {
1333
					$item->set_taxes( array( 'total' => $taxes ) );
1334
				}
1335
				$item->save();
1336
			}
1337
		}
1338
1339
		// Calc taxes for shipping
1340
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1341
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1342
1343
			// Inherit tax class from items
1344
			if ( '' === $shipping_tax_class ) {
1345
				$tax_rates         = array();
1346
				$tax_classes       = array_merge( array( '' ), WC_Tax::get_tax_classes() );
1347
				$found_tax_classes = $this->get_items_tax_classes();
1348
1349
				foreach ( $tax_classes as $tax_class ) {
1350
					$tax_class = sanitize_title( $tax_class );
1351
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1352
						$tax_rates = WC_Tax::find_shipping_rates( array(
1353
							'country'   => $args['country'],
1354
							'state'     => $args['state'],
1355
							'postcode'  => $args['postcode'],
1356
							'city'      => $args['city'],
1357
							'tax_class' => $tax_class,
1358
						) );
1359
						break;
1360
					}
1361
				}
1362
			} else {
1363
				$tax_rates = WC_Tax::find_shipping_rates( array(
1364
					'country'   => $args['country'],
1365
					'state'     => $args['state'],
1366
					'postcode'  => $args['postcode'],
1367
					'city'      => $args['city'],
1368
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1369
				) );
1370
			}
1371
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1372
			$item->save();
1373
		}
1374
		$this->update_taxes();
1375
	}
1376
1377
	/**
1378
	 * Update tax lines for the order based on the line item taxes themselves.
1379
	 */
1380
	public function update_taxes() {
1381
		$cart_taxes     = array();
1382
		$shipping_taxes = array();
1383
1384
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1385
			$taxes = $item->get_taxes();
1386 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...
1387
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1388
			}
1389
		}
1390
1391
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1392
			$taxes = $item->get_taxes();
1393 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...
1394
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1395
			}
1396
		}
1397
1398
		// Remove old existing tax rows.
1399
		$this->remove_order_items( 'tax' );
1400
1401
		// Now merge to keep tax rows.
1402
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1403
			$this->add_tax( array(
1404
				'rate_id'            => $tax_rate_id,
1405
				'tax_total'          => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0,
1406
				'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0,
1407
			) );
1408
		}
1409
1410
		// Save tax totals
1411
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1412
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1413
		$this->save();
1414
	}
1415
1416
	/**
1417
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1418
	 *
1419
	 * @since 2.2
1420
	 * @param  bool $and_taxes Calc taxes if true.
1421
	 * @return float calculated grand total.
1422
	 */
1423
	public function calculate_totals( $and_taxes = true ) {
1424
		$cart_subtotal     = 0;
1425
		$cart_total        = 0;
1426
		$fee_total         = 0;
1427
		$cart_subtotal_tax = 0;
1428
		$cart_total_tax    = 0;
1429
1430
		if ( $and_taxes && wc_tax_enabled() ) {
1431
			$this->calculate_taxes();
1432
		}
1433
1434
		// line items
1435
		foreach ( $this->get_items() as $item ) {
1436
			$cart_subtotal     += wc_format_decimal( $item->get_subtotal() );
1437
			$cart_total        += wc_format_decimal( $item->get_total() );
1438
			$cart_subtotal_tax += wc_format_decimal( $item->get_subtotal_tax() );
1439
			$cart_total_tax    += wc_format_decimal( $item->get_total_tax() );
1440
		}
1441
1442
		$this->calculate_shipping();
1443
1444
		foreach ( $this->get_fees() as $item ) {
1445
			$fee_total += $item->get_total();
1446
		}
1447
1448
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1449
1450
		$this->set_discount_total( $cart_subtotal - $cart_total );
1451
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1452
		$this->set_total( $grand_total );
1453
		$this->save();
1454
1455
		return $grand_total;
1456
	}
1457
1458
	/**
1459
	 * Get item subtotal - this is the cost before discount.
1460
	 *
1461
	 * @param object $item
1462
	 * @param bool $inc_tax (default: false).
1463
	 * @param bool $round (default: true).
1464
	 * @return float
1465
	 */
1466 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...
1467
		$subtotal = 0;
1468
1469
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1470
			if ( $inc_tax ) {
1471
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_qty() );
1472
			} else {
1473
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_qty() ) );
1474
			}
1475
1476
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1477
		}
1478
1479
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1480
	}
1481
1482
	/**
1483
	 * Get line subtotal - this is the cost before discount.
1484
	 *
1485
	 * @param object $item
1486
	 * @param bool $inc_tax (default: false).
1487
	 * @param bool $round (default: true).
1488
	 * @return float
1489
	 */
1490 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...
1491
		$subtotal = 0;
1492
1493
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1494
			if ( $inc_tax ) {
1495
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1496
			} else {
1497
				$subtotal = $item->get_subtotal();
1498
			}
1499
1500
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1501
		}
1502
1503
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1504
	}
1505
1506
	/**
1507
	 * Calculate item cost - useful for gateways.
1508
	 *
1509
	 * @param object $item
1510
	 * @param bool $inc_tax (default: false).
1511
	 * @param bool $round (default: true).
1512
	 * @return float
1513
	 */
1514 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...
1515
		$total = 0;
1516
1517
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1518
			if ( $inc_tax ) {
1519
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_qty() );
1520
			} else {
1521
				$total = $item->get_total() / max( 1, $item->get_qty() );
1522
			}
1523
1524
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1525
		}
1526
1527
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1528
	}
1529
1530
	/**
1531
	 * Calculate line total - useful for gateways.
1532
	 *
1533
	 * @param object $item
1534
	 * @param bool $inc_tax (default: false).
1535
	 * @param bool $round (default: true).
1536
	 * @return float
1537
	 */
1538 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...
1539
		$total = 0;
1540
1541
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1542
			// Check if we need to add line tax to the line total.
1543
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1544
1545
			// Check if we need to round.
1546
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1547
		}
1548
1549
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1550
	}
1551
1552
	/**
1553
	 * Get item tax - useful for gateways.
1554
	 *
1555
	 * @param mixed $item
1556
	 * @param bool $round (default: true).
1557
	 * @return float
1558
	 */
1559
	public function get_item_tax( $item, $round = true ) {
1560
		$tax = 0;
1561
1562
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1563
			$tax = $item->get_total_tax() / max( 1, $item->get_qty() );
1564
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1565
		}
1566
1567
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1568
	}
1569
1570
	/**
1571
	 * Get line tax - useful for gateways.
1572
	 *
1573
	 * @param mixed $item
1574
	 * @return float
1575
	 */
1576
	public function get_line_tax( $item ) {
1577
		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 );
1578
	}
1579
1580
	/**
1581
	 * Gets line subtotal - formatted for display.
1582
	 *
1583
	 * @param array  $item
1584
	 * @param string $tax_display
1585
	 * @return string
1586
	 */
1587
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1588
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1589
1590
		if ( 'excl' == $tax_display ) {
1591
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1592
1593
			$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...
1594
		} else {
1595
			$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...
1596
		}
1597
1598
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1599
	}
1600
1601
	/**
1602
	 * Gets order total - formatted for display.
1603
	 * @return string
1604
	 */
1605
	public function get_formatted_order_total() {
1606
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1607
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1608
	}
1609
1610
	/**
1611
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1612
	 *
1613
	 * @param bool $compound (default: false).
1614
	 * @param string $tax_display (default: the tax_display_cart value).
1615
	 * @return string
1616
	 */
1617
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1618
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1619
		$subtotal    = 0;
1620
1621
		if ( ! $compound ) {
1622
			foreach ( $this->get_items() as $item ) {
1623
				$subtotal += $item->get_subtotal();
1624
1625
				if ( 'incl' === $tax_display ) {
1626
					$subtotal += $item->get_subtotal_tax();
1627
				}
1628
			}
1629
1630
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1631
1632
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1633
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1634
			}
1635
1636
		} else {
1637
			if ( 'incl' === $tax_display ) {
1638
				return '';
1639
			}
1640
1641
			foreach ( $this->get_items() as $item ) {
1642
				$subtotal += $item->get_subtotal();
1643
			}
1644
1645
			// Add Shipping Costs.
1646
			$subtotal += $this->get_shipping_total();
1647
1648
			// Remove non-compound taxes.
1649
			foreach ( $this->get_taxes() as $tax ) {
1650
				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...
1651
					continue;
1652
				}
1653
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1654
			}
1655
1656
			// Remove discounts.
1657
			$subtotal = $subtotal - $this->get_total_discount();
1658
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1659
		}
1660
1661
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1662
	}
1663
1664
	/**
1665
	 * Gets shipping (formatted).
1666
	 *
1667
	 * @return string
1668
	 */
1669
	public function get_shipping_to_display( $tax_display = '' ) {
1670
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1671
1672
		if ( $this->get_shipping_total() != 0 ) {
1673
1674
			if ( $tax_display == 'excl' ) {
1675
1676
				// Show shipping excluding tax.
1677
				$shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) );
1678
1679 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...
1680
					$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 );
1681
				}
1682
1683
			} else {
1684
1685
				// Show shipping including tax.
1686
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) );
1687
1688 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...
1689
					$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 );
1690
				}
1691
1692
			}
1693
1694
			$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 );
1695
1696
		} elseif ( $this->get_shipping_method() ) {
1697
			$shipping = $this->get_shipping_method();
1698
		} else {
1699
			$shipping = __( 'Free!', 'woocommerce' );
1700
		}
1701
1702
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1703
	}
1704
1705
	/**
1706
	 * Get the discount amount (formatted).
1707
	 * @since  2.3.0
1708
	 * @return string
1709
	 */
1710
	public function get_discount_to_display( $tax_display = '' ) {
1711
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1712
		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 );
1713
	}
1714
1715
	/**
1716
	 * Get totals for display on pages and in emails.
1717
	 *
1718
	 * @param mixed $tax_display
1719
	 * @return array
1720
	 */
1721
	public function get_order_item_totals( $tax_display = '' ) {
1722
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1723
		$total_rows  = array();
1724
1725
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1726
			$total_rows['cart_subtotal'] = array(
1727
				'label' => __( 'Subtotal:', 'woocommerce' ),
1728
				'value'    => $subtotal,
1729
			);
1730
		}
1731
1732
		if ( $this->get_total_discount() > 0 ) {
1733
			$total_rows['discount'] = array(
1734
				'label' => __( 'Discount:', 'woocommerce' ),
1735
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1736
			);
1737
		}
1738
1739
		if ( $this->get_shipping_method() ) {
1740
			$total_rows['shipping'] = array(
1741
				'label' => __( 'Shipping:', 'woocommerce' ),
1742
				'value'    => $this->get_shipping_to_display( $tax_display ),
1743
			);
1744
		}
1745
1746
		if ( $fees = $this->get_fees() ) {
1747
			foreach ( $fees as $id => $fee ) {
1748
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1749
					continue;
1750
				}
1751
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1752
					'label' => $fee->get_name() . ':',
1753
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) )
1754
				);
1755
			}
1756
		}
1757
1758
		// Tax for tax exclusive prices.
1759
		if ( 'excl' === $tax_display ) {
1760
1761
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1762
1763
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1764
1765
					$total_rows[ sanitize_title( $code ) ] = array(
1766
						'label' => $tax->label . ':',
1767
						'value'    => $tax->formatted_amount,
1768
					);
1769
				}
1770
1771
			} else {
1772
1773
				$total_rows['tax'] = array(
1774
					'label' => WC()->countries->tax_or_vat() . ':',
1775
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1776
				);
1777
			}
1778
		}
1779
1780
		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...
1781
			$total_rows['payment_method'] = array(
1782
				'label' => __( 'Payment Method:', 'woocommerce' ),
1783
				'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...
1784
			);
1785
		}
1786
1787
		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...
1788
			foreach ( $refunds as $id => $refund ) {
1789
				$total_rows[ 'refund_' . $id ] = array(
1790
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1791
					'value'    => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ),
1792
				);
1793
			}
1794
		}
1795
1796
		$total_rows['order_total'] = array(
1797
			'label' => __( 'Total:', 'woocommerce' ),
1798
			'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...
1799
		);
1800
1801
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1802
	}
1803
1804
	/*
1805
	|--------------------------------------------------------------------------
1806
	| Conditionals
1807
	|--------------------------------------------------------------------------
1808
	|
1809
	| Checks if a condition is true or false.
1810
	|
1811
	*/
1812
1813
	/**
1814
	 * Checks the order status against a passed in status.
1815
	 *
1816
	 * @return bool
1817
	 */
1818
	public function has_status( $status ) {
1819
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1820
	}
1821
1822
	/**
1823
	 * Check whether this order has a specific shipping method or not.
1824
	 *
1825
	 * @param string $method_id
1826
	 * @return bool
1827
	 */
1828
	public function has_shipping_method( $method_id ) {
1829
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1830
			if ( $shipping_method->get_method_id() === $method_id ) {
1831
				return true;
1832
			}
1833
		}
1834
		return false;
1835
	}
1836
1837
	/**
1838
	 * Check if an order key is valid.
1839
	 *
1840
	 * @param mixed $key
1841
	 * @return bool
1842
	 */
1843
	public function key_is_valid( $key ) {
1844
		return $key === $this->get_order_key();
1845
	}
1846
1847
	/**
1848
	 * Returns true if the order contains a free product.
1849
	 * @since 2.5.0
1850
	 * @return bool
1851
	 */
1852
	public function has_free_item() {
1853
		foreach ( $this->get_items() as $item ) {
1854
			if ( ! $item->get_total() ) {
1855
				return true;
1856
			}
1857
		}
1858
		return false;
1859
	}
1860
}
1861