Completed
Pull Request — master (#11208)
by Mike
09:50
created

WC_Abstract_Order::get_line_subtotal()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 15
Code Lines 9

Duplication

Lines 15
Ratio 100 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 1
b 0
f 0
nc 5
nop 3
dl 15
loc 15
rs 9.2
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'             => 'pending',
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
	 *  Internal meta type used to store order data.
68
	 * @var string
69
	 */
70
	protected $_meta_type = 'post';
71
72
	/**
73
	 * Stores meta in cache for future reads.
74
	 * A group must be set to to enable caching.
75
	 * @var string
76
	 */
77
	protected $_cache_group = 'order';
78
79
	/**
80
	 * Get the order if ID is passed, otherwise the order is new and empty.
81
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
82
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
83
	 * methods that will be maintained going forward.
84
	 *
85
	 * @param  int|object|WC_Order $order Order to init.
86
	 */
87 View Code Duplication
	public function __construct( $order = 0 ) {
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...
88
		if ( is_numeric( $order ) && $order > 0 ) {
89
			$this->read( $order );
90
		} elseif ( $order instanceof self ) {
91
			$this->read( absint( $order->get_id() ) );
92
		} elseif ( ! empty( $order->ID ) ) {
93
			$this->read( absint( $order->ID ) );
94
		}
95
	}
96
97
	/*
98
	|--------------------------------------------------------------------------
99
	| CRUD methods
100
	|--------------------------------------------------------------------------
101
	|
102
	| Methods which create, read, update and delete orders from the database.
103
	| Written in abstract fashion so that the way orders are stored can be
104
	| changed more easily in the future.
105
	|
106
	| A save method is included for convenience (chooses update or create based
107
	| on if the order exists yet).
108
	|
109
	*/
110
111
	/**
112
	 * Get internal type (post type.)
113
	 * @return string
114
	 */
115
	public function get_type() {
116
		return 'shop_order';
117
	}
118
119
	/**
120
	 * Get a title for the new post type.
121
	 */
122
	protected function get_post_title() {
123
		return sprintf( __( 'Order &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
124
	}
125
126
	/**
127
	 * Insert data into the database.
128
	 * @since 2.7.0
129
	 */
130
	public function create() {
131
		$this->set_order_key( 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) );
132
		$this->set_date_created( current_time( 'timestamp' ) );
133
134
		$order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', array(
135
			'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
136
			'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
137
			'post_type'     => $this->get_type(),
138
			'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
139
			'ping_status'   => 'closed',
140
			'post_author'   => 1,
141
			'post_title'    => $this->get_post_title(),
142
			'post_password' => uniqid( 'order_' ),
143
			'post_parent'   => $this->get_parent_id(),
144
		) ), true );
145
146
		if ( $order_id ) {
147
			$this->set_id( $order_id );
148
149
			// Set meta data
150
			$this->update_post_meta( '_customer_user', $this->get_customer_id() );
151
			$this->update_post_meta( '_order_currency', $this->get_currency() );
152
			$this->update_post_meta( '_order_key', $this->get_order_key() );
153
			$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
154
			$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
155
			$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
156
			$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
157
			$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
158
			$this->update_post_meta( '_order_total', $this->get_total( true ) );
159
			$this->update_post_meta( '_order_version', $this->get_version() );
160
			$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
161
			$this->save_meta_data();
162
		}
163
	}
164
165
	/**
166
	 * Read from the database.
167
	 * @since 2.7.0
168
	 * @param int $id ID of object to read.
169
	 */
170
	public function read( $id ) {
171
		if ( empty( $id ) || ! ( $post_object = get_post( $id ) ) ) {
172
			return;
173
		}
174
		$order_id = absint( $post_object->ID );
175
176
		// Map standard post data
177
		$this->set_id( $order_id );
178
		$this->set_parent_id( $post_object->post_parent );
179
		$this->set_date_created( $post_object->post_date );
180
		$this->set_date_modified( $post_object->post_modified );
181
		$this->set_status( $post_object->post_status );
182
		$this->set_order_type( $post_object->post_type );
183
		$this->set_customer_id( get_post_meta( $order_id, '_customer_user', true ) );
184
		$this->set_order_key( get_post_meta( $order_id, '_order_key', true ) );
185
		$this->set_currency( get_post_meta( $order_id, '_order_currency', true ) );
186
		$this->set_discount_total( get_post_meta( $order_id, '_cart_discount', true ) );
187
		$this->set_discount_tax( get_post_meta( $order_id, '_cart_discount_tax', true ) );
188
		$this->set_shipping_total( get_post_meta( $order_id, '_order_shipping', true ) );
189
		$this->set_shipping_tax( get_post_meta( $order_id, '_order_shipping_tax', true ) );
190
		$this->set_cart_tax( get_post_meta( $order_id, '_order_tax', true ) );
191
		$this->set_total( get_post_meta( $order_id, '_order_total', true ) );
192
193
		// Orders store the state of prices including tax when created.
194
		$this->set_prices_include_tax( metadata_exists( 'post', $order_id, '_prices_include_tax' ) ? 'yes' === get_post_meta( $order_id, '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
195
196
		// Load meta data
197
		$this->read_meta_data();
198
	}
199
200
	/**
201
	 * Post meta update wrapper. Sets or deletes based on value.
202
	 * @since 2.7.0
203
	 * @return bool Was it changed?
204
	 */
205
	protected function update_post_meta( $key, $value ) {
206
		if ( '' !== $value ) {
207
			return update_post_meta( $this->get_id(), $key, $value );
208
		} else {
209
			return delete_post_meta( $this->get_id(), $key );
210
		}
211
	}
212
213
	/**
214
	 * Update data in the database.
215
	 * @since 2.7.0
216
	 */
217
	public function update() {
218
		global $wpdb;
219
220
		$order_id = $this->get_id();
221
222
		$wpdb->update(
223
			$wpdb->posts,
224
			array(
225
				'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
226
				'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
227
				'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
228
				'post_parent'   => $this->get_parent_id(),
229
			),
230
			array(
231
				'ID' => $order_id
232
			)
233
		);
234
235
		// Update meta data
236
		$this->update_post_meta( '_customer_user', $this->get_customer_id() );
237
		$this->update_post_meta( '_order_currency', $this->get_currency() );
238
		$this->update_post_meta( '_order_key', $this->get_order_key() );
239
		$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
240
		$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
241
		$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
242
		$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
243
		$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
244
		$this->update_post_meta( '_order_total', $this->get_total( true ) );
245
		$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
246
		$this->save_meta_data();
247
	}
248
249
	/**
250
	 * Delete data from the database.
251
	 * @since 2.7.0
252
	 */
253
	public function delete() {
254
		wp_delete_post( $this->get_id() );
255
	}
256
257
	/**
258
	 * Save data to the database.
259
	 * @since 2.7.0
260
	 * @return int order ID
261
	 */
262
	public function save() {
263
		$this->set_version( WC_VERSION );
264
265
		if ( ! $this->get_id() ) {
266
			$this->create();
267
		} else {
268
			$this->update();
269
		}
270
271
		clean_post_cache( $this->get_id() );
272
		wc_delete_shop_order_transients( $this->get_id() );
273
274
		return $this->get_id();
275
	}
276
277
	/*
278
	|--------------------------------------------------------------------------
279
	| Getters
280
	|--------------------------------------------------------------------------
281
	|
282
	| Methods for getting data from the order object.
283
	|
284
	*/
285
286
	/**
287
	 * Get all class data in array format.
288
	 * @since 2.7.0
289
	 * @return array
290
	 */
291
	public function get_data() {
292
		return array_merge(
293
			$this->_data,
294
			array(
295
				'meta_data'      => $this->get_meta_data(),
296
				'line_items'     => $this->get_items( 'line_item' ),
297
				'tax_lines'      => $this->get_items( 'tax' ),
298
				'shipping_lines' => $this->get_items( 'shipping' ),
299
				'fee_lines'      => $this->get_items( 'fee' ),
300
				'coupon_lines'   => $this->get_items( 'coupon' ),
301
			)
302
		);
303
	}
304
305
	/**
306
	 * Get order ID.
307
	 * @since 2.7.0
308
	 * @return integer
309
	 */
310
	public function get_id() {
311
		return absint( $this->_data['id'] );
312
	}
313
314
	/**
315
	 * Get parent order ID.
316
	 * @since 2.7.0
317
	 * @return integer
318
	 */
319
	public function get_parent_id() {
320
		return absint( $this->_data['parent_id'] );
321
	}
322
323
	/**
324
	 * get_order_number function.
325
	 *
326
	 * Gets the order number for display (by default, order ID).
327
	 *
328
	 * @return string
329
	 */
330
	public function get_order_number() {
331
		return apply_filters( 'woocommerce_order_number', $this->get_id(), $this );
332
	}
333
334
	/**
335
	 * Get order key.
336
	 * @since 2.7.0
337
	 * @return string
338
	 */
339
	public function get_order_key() {
340
		return $this->_data['order_key'];
341
	}
342
343
	/**
344
	 * Gets order currency.
345
	 * @return string
346
	 */
347
	public function get_currency() {
348
		return apply_filters( 'woocommerce_get_currency', $this->_data['currency'], $this );
349
	}
350
351
	/**
352
	 * Get order_version
353
	 * @return string
354
	 */
355
	public function get_version() {
356
		return $this->_data['version'];
357
	}
358
359
	/**
360
	 * Get prices_include_tax
361
	 * @return bool
362
	 */
363
	public function get_prices_include_tax() {
364
		return (bool) $this->_data['prices_include_tax'];
365
	}
366
367
	/**
368
	 * Get Order Type
369
	 * @return string
370
	 */
371
	public function get_order_type() {
372
		return $this->_data['type'];
373
	}
374
375
	/**
376
	 * Get date_created
377
	 * @return int
378
	 */
379
	public function get_date_created() {
380
		return absint( $this->_data['date_created'] );
381
	}
382
383
	/**
384
	 * Get date_modified
385
	 * @return int
386
	 */
387
	public function get_date_modified() {
388
		return absint( $this->_data['date_modified'] );
389
	}
390
391
	/**
392
	 * Get customer_id
393
	 * @return int
394
	 */
395
	public function get_customer_id() {
396
		return absint( $this->_data['customer_id'] );
397
	}
398
399
	/**
400
	 * Return the order statuses without wc- internal prefix.
401
	 * @return string
402
	 */
403
	public function get_status() {
404
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->_data['status'], 0, 3 ) ? substr( $this->_data['status'], 3 ) : $this->_data['status'], $this );
405
	}
406
407
	/**
408
	 * Alias for get_customer_id().
409
	 * @since  2.2
410
	 * @return int
411
	 */
412
	public function get_user_id() {
413
		return $this->get_customer_id();
414
	}
415
416
	/**
417
	 * Get the user associated with the order. False for guests.
418
	 *
419
	 * @since  2.2
420
	 * @return WP_User|false
421
	 */
422
	public function get_user() {
423
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
424
	}
425
426
	/**
427
	 * Get discount_total
428
	 * @param bool $raw Gets raw unfiltered value.
429
	 * @return string
430
	 */
431
	public function get_discount_total( $raw = false ) {
432
		$value = wc_format_decimal( $this->_data['discount_total'] );
433
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_total', $value, $this );
434
	}
435
436
	/**
437
	 * Get discount_tax
438
	 * @param bool $raw Gets raw unfiltered value.
439
	 * @return string
440
	 */
441
	public function get_discount_tax( $raw = false ) {
442
		$value = wc_format_decimal( $this->_data['discount_tax'] );
443
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_tax', $value, $this );
444
	}
445
446
	/**
447
	 * Get shipping_total
448
	 * @param bool $raw Gets raw unfiltered value.
449
	 * @return string
450
	 */
451
	public function get_shipping_total( $raw = false ) {
452
		$value = wc_format_decimal( $this->_data['shipping_total'] );
453
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_total', $value, $this );
454
	}
455
456
	/**
457
	 * Get shipping_tax.
458
	 * @param bool $raw Gets raw unfiltered value.
459
	 * @return string
460
	 */
461
	public function get_shipping_tax( $raw = false ) {
462
		$value = wc_format_decimal( $this->_data['shipping_tax'] );
463
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_tax', $value, $this );
464
	}
465
466
	/**
467
	 * Gets cart tax amount.
468
	 * @param bool $raw Gets raw unfiltered value.
469
	 * @return float
470
	 */
471
	public function get_cart_tax( $raw = false ) {
472
		$value = wc_format_decimal( $this->_data['cart_tax'] );
473
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_cart_tax', $value, $this );
474
	}
475
476
	/**
477
	 * Gets order grand total. incl. taxes. Used in gateways. Filtered.
478
	 * @param bool $raw Gets raw unfiltered value.
479
	 * @return float
480
	 */
481
	public function get_total( $raw = false ) {
482
		$value = wc_format_decimal( $this->_data['total'], wc_get_price_decimals() );
483
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total', $value, $this );
484
	}
485
486
	/**
487
	 * Get total tax amount. Alias for get_order_tax().
488
	 *
489
	 * @since 2.7.0 woocommerce_order_amount_total_tax filter has been removed to avoid
490
	 * these values being modified and then saved back to the DB. There are
491
	 * other, later hooks available to change totals on display. e.g.
492
	 * woocommerce_get_order_item_totals.
493
	 * @param bool $raw Gets raw unfiltered value.
494
	 * @return float
495
	 */
496
	public function get_total_tax( $raw = false ) {
497
		$value = wc_format_decimal( $this->_data['total_tax'] );
498
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total_tax', $value, $this );
499
	}
500
501
	/**
502
	 * Gets the total discount amount.
503
	 * @param  bool $ex_tax Show discount excl any tax.
504
	 * @return float
505
	 */
506
	public function get_total_discount( $ex_tax = true ) {
507
		if ( $ex_tax ) {
508
			$total_discount = $this->get_discount_total();
509
		} else {
510
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
511
		}
512
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
513
	}
514
515
	/**
516
	 * Gets order subtotal.
517
	 * @return float
518
	 */
519
	public function get_subtotal() {
520
		$subtotal = 0;
521
522
		foreach ( $this->get_items() as $item ) {
523
			$subtotal += $item->get_subtotal();
524
		}
525
526
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
527
	}
528
529
	/**
530
	 * Get taxes, merged by code, formatted ready for output.
531
	 *
532
	 * @return array
533
	 */
534
	public function get_tax_totals() {
535
		$tax_totals = array();
536
537
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
538
			$code = $tax->get_rate_code();
539
540 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...
541
				$tax_totals[ $code ] = new stdClass();
542
				$tax_totals[ $code ]->amount = 0;
543
			}
544
545
			$tax_totals[ $code ]->id                = $key;
546
			$tax_totals[ $code ]->rate_id           = $tax->get_rate_id();
547
			$tax_totals[ $code ]->is_compound       = $tax->is_compound();
548
			$tax_totals[ $code ]->label             = $tax->get_label();
549
			$tax_totals[ $code ]->amount           += $tax->get_tax_total() + $tax->get_shipping_tax_total();
550
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
551
		}
552
553 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...
554
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
555
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
556
		}
557
558
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
559
	}
560
561
	/*
562
	|--------------------------------------------------------------------------
563
	| Setters
564
	|--------------------------------------------------------------------------
565
	|
566
	| Functions for setting order data. These should not update anything in the
567
	| database itself and should only change what is stored in the class
568
	| object. However, for backwards compatibility pre 2.7.0 some of these
569
	| setters may handle both.
570
	|
571
	*/
572
573
	/**
574
	 * Set order ID.
575
	 * @since 2.7.0
576
	 * @param int $value
577
	 */
578
	public function set_id( $value ) {
579
		$this->_data['id'] = absint( $value );
580
	}
581
582
	/**
583
	 * Set parent order ID.
584
	 * @since 2.7.0
585
	 * @param int $value
586
	 */
587
	public function set_parent_id( $value ) {
588
		$this->_data['parent_id'] = absint( $value );
589
	}
590
591
	/**
592
	 * Set order status.
593
	 * @since 2.7.0
594
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
595
	 * @param array details of change
596
	 */
597
	 public function set_status( $new_status ) {
598
 		$old_status = $this->get_status();
599
 		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
600
601
		// If the old status is unknown (e.g. draft) assume its pending for action usage.
602
		if ( ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
603
			$old_status = 'pending';
604
		}
605
606
 		if ( in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) && $new_status !== $old_status ) {
607
 			$this->_data['status'] = 'wc-' . $new_status;
608
 		} else {
609
			$new_status = $old_status;
610
		}
611
612
		return array(
613
			'from' => $old_status,
614
			'to'   => $new_status
615
		);
616
 	}
617
618
	/**
619
	 * Set Order Type
620
	 * @param string $value
621
	 */
622
	public function set_order_type( $value ) {
623
		$this->_data['type'] = $value;
624
	}
625
626
	/**
627
	 * Set order_key.
628
	 * @param string $value Max length 20 chars.
629
	 */
630
	public function set_order_key( $value ) {
631
		$this->_data['order_key'] = substr( $value, 0, 20 );
632
	}
633
634
	/**
635
	 * Set order_version
636
	 * @param string $value
637
	 */
638
	public function set_version( $value ) {
639
		$this->_data['version'] = $value;
640
	}
641
642
	/**
643
	 * Set order_currency
644
	 * @param string $value
645
	 */
646
	public function set_currency( $value ) {
647
		$this->_data['currency'] = $value;
648
	}
649
650
	/**
651
	 * Set prices_include_tax
652
	 * @param bool $value
653
	 */
654
	public function set_prices_include_tax( $value ) {
655
		$this->_data['prices_include_tax'] = (bool) $value;
656
	}
657
658
	/**
659
	 * Set date_created
660
	 * @param string $timestamp Timestamp
661
	 */
662
	public function set_date_created( $timestamp ) {
663
		$this->_data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
664
	}
665
666
	/**
667
	 * Set date_modified
668
	 * @param string $timestamp
669
	 */
670
	public function set_date_modified( $timestamp ) {
671
		$this->_data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
672
	}
673
674
	/**
675
	 * Set customer_id
676
	 * @param int $value
677
	 */
678
	public function set_customer_id( $value ) {
679
		$this->_data['customer_id'] = absint( $value );
680
	}
681
682
	/**
683
	 * Set discount_total
684
	 * @param string $value
685
	 */
686
	public function set_discount_total( $value ) {
687
		$this->_data['discount_total'] = wc_format_decimal( $value );
688
	}
689
690
	/**
691
	 * Set discount_tax
692
	 * @param string $value
693
	 */
694
	public function set_discount_tax( $value ) {
695
		$this->_data['discount_tax'] = wc_format_decimal( $value );
696
	}
697
698
	/**
699
	 * Set shipping_total
700
	 * @param string $value
701
	 */
702
	public function set_shipping_total( $value ) {
703
		$this->_data['shipping_total'] = wc_format_decimal( $value );
704
	}
705
706
	/**
707
	 * Set shipping_tax
708
	 * @param string $value
709
	 */
710
	public function set_shipping_tax( $value ) {
711
		$this->_data['shipping_tax'] = wc_format_decimal( $value );
712
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
713
	}
714
715
	/**
716
	 * Set cart tax
717
	 * @param string $value
718
	 */
719
	public function set_cart_tax( $value ) {
720
		$this->_data['cart_tax'] = wc_format_decimal( $value );
721
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
722
	}
723
724
	/**
725
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
726
	 * @param string $value
727
	 */
728
	protected function set_total_tax( $value ) {
729
		$this->_data['total_tax'] = wc_format_decimal( $value );
730
	}
731
732
	/**
733
	 * Set total
734
	 * @param string $value
735
	 * @param string $deprecated Function used to set different totals based on this.
736
	 */
737
	public function set_total( $value, $deprecated = '' ) {
738
		if ( $deprecated ) {
739
			_deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' );
740
			return $this->legacy_set_total( $value, $deprecated );
741
		}
742
		$this->_data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
743
	}
744
745
	/*
746
	|--------------------------------------------------------------------------
747
	| Order Item Handling
748
	|--------------------------------------------------------------------------
749
	|
750
	| Order items are used for products, taxes, shipping, and fees within
751
	| each order.
752
	|
753
	*/
754
755
	/**
756
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
757
	 * @param string $type Order item type. Default null.
758
	 */
759
	public function remove_order_items( $type = null ) {
760
		global $wpdb;
761
		if ( ! empty( $type ) ) {
762
			$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 ) );
763
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
764
		} else {
765
			$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() ) );
766
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
767
		}
768
	}
769
770
	/**
771
	 * Return an array of items/products within this order.
772
	 * @param string|array $type Types of line items to get (array or string).
773
	 * @return Array of WC_Order_item
774
	 */
775
	public function get_items( $type = 'line_item' ) {
776
		global $wpdb;
777
		$get_items_sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d ", $this->get_id() ) . "AND order_item_type IN ( '" . implode( "','", array_map( 'esc_sql', (array) $type ) ) . "' ) ORDER BY order_item_id;";
778
		$items         = $wpdb->get_results( $get_items_sql );
779
		$items         = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
780
781
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
782
	}
783
784
	/**
785
	 * Return an array of fees within this order.
786
	 * @return array
787
	 */
788
	public function get_fees() {
789
		return $this->get_items( 'fee' );
790
	}
791
792
	/**
793
	 * Return an array of taxes within this order.
794
	 * @return array
795
	 */
796
	public function get_taxes() {
797
		return $this->get_items( 'tax' );
798
	}
799
800
	/**
801
	 * Return an array of shipping costs within this order.
802
	 * @return array
803
	 */
804
	public function get_shipping_methods() {
805
		return $this->get_items( 'shipping' );
806
	}
807
808
	/**
809
	 * Gets formatted shipping method title.
810
	 * @return string
811
	 */
812
	public function get_shipping_method() {
813
		$names = array();
814
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
815
			$names[] = $shipping_method->get_name();
816
		}
817
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
818
	}
819
820
	/**
821
	 * Get coupon codes only.
822
	 * @return array
823
	 */
824
	public function get_used_coupons() {
825
		$coupon_codes = array();
826
		if ( $coupons = $this->get_items( 'coupon' ) ) {
827
			foreach ( $coupons as $coupon ) {
828
				$coupon_codes[] = $coupon->get_code();
829
			}
830
		}
831
		return $coupon_codes;
832
	}
833
834
	/**
835
	 * Gets the count of order items of a certain type.
836
	 *
837
	 * @param string $item_type
838
	 * @return string
839
	 */
840
	public function get_item_count( $item_type = '' ) {
841
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
842
		$count = 0;
843
844
		foreach ( $items as $item ) {
845
			$count += $item->get_qty();
846
		}
847
848
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
849
	}
850
851
	/**
852
	 * Get an order item object, based on it's type.
853
	 * @since  2.7.0
854
	 * @param  int $item_id
855
	 * @return WC_Order_Item
856
	 */
857
	public function get_item( $item_id ) {
858
		return WC_Order_Factory::get_order_item( $item_id );
859
	}
860
861
	/**
862
	 * Add a product line item to the order.
863
	 * Order must be saved prior to adding items.
864
	 * @param \WC_Product $product
865
	 * @param array $args
866
	 * @param array $deprecated qty was passed as arg 2 prior to 2.7.0
867
	 * @return int order item ID
868
	 */
869
	public function add_product( $product, $args = array(), $deprecated = array() ) {
870
		if ( ! is_array( $args ) ) {
871
			_deprecated_argument( 'qty', '2.7', 'Pass only product and args' );
872
			$qty         = $args;
873
			$args        = $deprecated;
874
			$args['qty'] = $qty;
875
		}
876
877
		$args = wp_parse_args( $args, array(
878
			'qty'          => 1,
879
			'name'         => $product ? $product->get_title() : '',
880
			'tax_class'    => $product ? $product->get_tax_class() : '',
881
			'product_id'   => $product ? $product->get_id() : '',
882
			'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0,
883
			'subtotal'     => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
884
			'total'        => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
885
			'subtotal_tax' => 0,
886
			'total_tax'    => 0,
887
			'variation'    => array(),
888
			'taxes'        => array(
889
				'subtotal' => array(),
890
				'total'    => array(),
891
			),
892
		) );
893
894
		// BW compatibility with old args
895 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...
896
			foreach ( $args['totals'] as $key => $value ) {
897
				if ( 'tax' === $key ) {
898
					$args['total_tax'] = $value;
899
				} elseif ( 'tax_data' === $key ) {
900
					$args['taxes'] = $value;
901
				} else {
902
					$args[ $key ] = $value;
903
				}
904
			}
905
		}
906
907
		$item = new WC_Order_Item_Product( $args );
908
909
		// Handle backorders
910 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...
911
			$item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ), true );
912
		}
913
914
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
915
		$item->save();
916
917 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...
918
			_deprecated_function( 'Action: woocommerce_order_add_product', '2.7', 'Use woocommerce_new_order_item action instead.' );
919
			do_action( 'woocommerce_order_add_product', $this->get_id(), $item->get_id(), $product, $qty, $args );
920
		}
921
922
		return $item->get_id();
923
	}
924
925
	/**
926
	 * Add coupon code to the order.
927
	 * Order must be saved prior to adding items.
928
	 * @param array $args
929
	 * @param int $deprecated1 2.7.0 code, discount, tax were passed.
930
	 * @param int $deprecated2 2.7.0 code, discount, tax were passed.
931
	 * @return int order item ID
932
	 */
933
	public function add_coupon( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
934
		if ( ! is_array( $args ) ) {
935
			_deprecated_argument( 'code', '2.7', 'Pass only an array of args' );
936
			$args = array(
937
				'code'         => $args,
938
				'discount'     => $deprecated1,
939
				'discount_tax' => $deprecated2,
940
			);
941
		}
942
943
		$args = wp_parse_args( $args, array(
944
			'code'         => '',
945
			'discount'     => 0,
946
			'discount_tax' => 0,
947
		) );
948
949
		$item = new WC_Order_Item_Coupon( $args );
950
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
951
		$item->save();
952
953
		if ( has_action( 'woocommerce_order_add_coupon' ) ) {
954
			_deprecated_function( 'Action: woocommerce_order_add_coupon', '2.7', 'Use woocommerce_new_order_item action instead.' );
955
			do_action( 'woocommerce_order_add_coupon', $this->get_id(), $item->get_id(), $args['code'], $args['discount'], $args['discount_tax'] );
956
		}
957
958
		return $item->get_id();
959
	}
960
961
	/**
962
	 * Add a tax row to the order.
963
	 * Order must be saved prior to adding items.
964
	 * @since 2.2
965
	 * @param array $args
966
	 * @param int $deprecated1 2.7.0 tax_rate_id, amount, shipping amount.
967
	 * @param int $deprecated2 2.7.0 tax_rate_id, amount, shipping amount.
968
	 * @return int order item ID
969
	 */
970
	public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
971
		if ( ! is_array( $args ) ) {
972
			_deprecated_argument( 'tax_rate_id', '2.7', 'Pass only an array of args' );
973
			$args = array(
974
				'rate_id'            => $args,
975
				'tax_total'          => $deprecated1,
976
				'shipping_tax_total' => $deprecated2,
977
			);
978
		}
979
980
		$args = wp_parse_args( $args, array(
981
			'rate_id'            => '',
982
			'tax_total'          => 0,
983
			'shipping_tax_total' => 0,
984
			'rate_code'          => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '',
985
			'label'              => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '',
986
			'compound'           => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '',
987
		) );
988
989
		$item = new WC_Order_Item_Tax( $args );
990
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
991
		$item->save();
992
993
		if ( has_action( 'woocommerce_order_add_tax' ) ) {
994
			_deprecated_function( 'Action: woocommerce_order_add_tax', '2.7', 'Use woocommerce_new_order_item action instead.' );
995
			do_action( 'woocommerce_order_add_tax', $this->get_id(), $item->get_id(), $args['rate_id'], $args['tax_total'], $args['shipping_tax_total'] );
996
		}
997
998
		return $item->get_id();
999
	}
1000
1001
	/**
1002
	 * Add a shipping row to the order.
1003
	 * Order must be saved prior to adding items.
1004
	 * @param WC_Shipping_Rate shipping_rate
1005
	 * @return int order item ID
1006
	 */
1007
	public function add_shipping( $shipping_rate ) {
1008
		$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...
1009
			'method_title' => $shipping_rate->label,
1010
			'method_id'    => $shipping_rate->id,
1011
			'total'        => wc_format_decimal( $shipping_rate->cost ),
1012
			'taxes'        => $shipping_rate->taxes,
1013
			'meta_data'    => $shipping_rate->get_meta_data(),
1014
		) );
1015
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1016
		$item->save();
1017
1018 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...
1019
			_deprecated_function( 'Action: woocommerce_order_add_shipping', '2.7', 'Use woocommerce_new_order_item action instead.' );
1020
			do_action( 'woocommerce_order_add_shipping', $this->get_id(), $item->get_id(), $shipping_rate );
1021
		}
1022
1023
		return $item->get_id();
1024
	}
1025
1026
	/**
1027
	 * Add a fee to the order.
1028
	 * Order must be saved prior to adding items.
1029
	 * @param object $fee
1030
	 * @return int updated order item ID
1031
	 */
1032
	public function add_fee( $fee ) {
1033
		$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...
1034
			'name'      => $fee->name,
1035
			'tax_class' => $fee->taxable ? $fee->tax_class : 0,
1036
			'total'     => $fee->amount,
1037
			'total_tax' => $fee->tax,
1038
			'taxes'     => array(
1039
				'total' => $fee->tax_data,
1040
			),
1041
		) );
1042
1043
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1044
		$item->save();
1045
1046 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...
1047
			_deprecated_function( 'Action: woocommerce_order_add_fee', '2.7', 'Use woocommerce_new_order_item action instead.' );
1048
			do_action( 'woocommerce_order_add_fee', $this->get_id(), $item->get_id(), $fee );
1049
		}
1050
1051
		return $item->get_id();
1052
	}
1053
1054
	/**
1055
	 * Add a payment token to an order
1056
	 *
1057
	 * @since 2.6
1058
	 * @param  WC_Payment_Token   $token     Payment token object
1059
	 * @return boolean|int The new token ID or false if it failed.
1060
	 */
1061
	public function add_payment_token( $token ) {
1062
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1063
			return false;
1064
		}
1065
1066
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1067
1068
		if ( empty ( $token_ids ) ) {
1069
			$token_ids = array();
1070
		}
1071
1072
		$token_ids[] = $token->get_id();
1073
1074
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1075
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1076
		return $token->get_id();
1077
	}
1078
1079
	/**
1080
	 * Returns a list of all payment tokens associated with the current order
1081
	 *
1082
	 * @since 2.6
1083
	 * @return array An array of payment token objects
1084
	 */
1085
	public function get_payment_tokens() {
1086
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1087
	}
1088
1089
	/*
1090
	|--------------------------------------------------------------------------
1091
	| Calculations.
1092
	|--------------------------------------------------------------------------
1093
	|
1094
	| These methods calculate order totals and taxes based on the current data.
1095
	|
1096
	*/
1097
1098
	/**
1099
	 * Calculate shipping total.
1100
	 *
1101
	 * @since 2.2
1102
	 * @return float
1103
	 */
1104
	public function calculate_shipping() {
1105
		$shipping_total = 0;
1106
1107
		foreach ( $this->get_shipping_methods() as $shipping ) {
1108
			$shipping_total += $shipping->get_total();
1109
		}
1110
1111
		$this->set_shipping_total( $shipping_total );
1112
		$this->save();
1113
1114
		return $this->get_shipping_total();
1115
	}
1116
1117
	/**
1118
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1119
	 *
1120
	 * Will use the base country unless customer addresses are set.
1121
	 * @param $args array Added in 2.7.0 to pass things like location.
1122
	 */
1123
	public function calculate_taxes( $args = array() ) {
1124
		$found_tax_classes = array();
1125
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1126
		$args              = wp_parse_args( $args, array(
1127
			'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...
1128
			'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...
1129
			'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...
1130
			'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...
1131
		) );
1132
1133
		// Default to base
1134
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1135
			$default          = wc_get_base_location();
1136
			$args['country']  = $default['country'];
1137
			$args['state']    = $default['state'];
1138
			$args['postcode'] = '';
1139
			$args['city']     = '';
1140
		}
1141
1142
		// Calc taxes for line items
1143
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1144
			$tax_class           = $item->get_tax_class();
1145
			$tax_status          = $item->get_tax_status();
1146
			$found_tax_classes[] = $tax_class;
1147
1148
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1149
				$tax_rates = WC_Tax::find_rates( array(
1150
					'country'   => $args['country'],
1151
					'state'     => $args['state'],
1152
					'postcode'  => $args['postcode'],
1153
					'city'      => $args['city'],
1154
					'tax_class' => $tax_class,
1155
				) );
1156
1157
				$total = $item->get_total();
1158
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1159
1160
				if ( $item->is_type( 'line_item' ) ) {
1161
					$subtotal       = $item->get_subtotal();
1162
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1163
					$subtotal_tax   = max( 0, array_sum( $subtotal_taxes ) );
1164
					$item->set_subtotal_tax( $subtotal_tax );
1165
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1166
				} else {
1167
					$item->set_taxes( array( 'total' => $taxes ) );
1168
				}
1169
				$item->save();
1170
			}
1171
		}
1172
1173
		// Calc taxes for shipping
1174
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1175
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1176
1177
			// Inherit tax class from items
1178
			if ( '' === $shipping_tax_class ) {
1179
				$tax_classes = WC_Tax::get_tax_classes();
1180
1181
				foreach ( $tax_classes as $tax_class ) {
1182
					$tax_class = sanitize_title( $tax_class );
1183
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1184
						$tax_rates = WC_Tax::find_shipping_rates( array(
1185
							'country'   => $args['country'],
1186
							'state'     => $args['state'],
1187
							'postcode'  => $args['postcode'],
1188
							'city'      => $args['city'],
1189
							'tax_class' => $tax_class,
1190
						) );
1191
						break;
1192
					}
1193
				}
1194
			} else {
1195
				$tax_rates = WC_Tax::find_shipping_rates( array(
1196
					'country'   => $args['country'],
1197
					'state'     => $args['state'],
1198
					'postcode'  => $args['postcode'],
1199
					'city'      => $args['city'],
1200
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1201
				) );
1202
			}
1203
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1204
			$item->save();
1205
		}
1206
		$this->update_taxes();
1207
	}
1208
1209
	/**
1210
	 * Update tax lines for the order based on the line item taxes themselves.
1211
	 */
1212
	public function update_taxes() {
1213
		$cart_taxes     = array();
1214
		$shipping_taxes = array();
1215
1216
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1217
			$taxes = $item->get_taxes();
1218 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...
1219
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1220
			}
1221
		}
1222
1223
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1224
			$taxes = $item->get_taxes();
1225 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...
1226
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1227
			}
1228
		}
1229
1230
		// Remove old existing tax rows.
1231
		$this->remove_order_items( 'tax' );
1232
1233
		// Now merge to keep tax rows.
1234
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1235
			$this->add_tax( array(
1236
				'rate_id'            => $tax_rate_id,
1237
				'tax_total'          => isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0,
1238
				'shipping_tax_total' => isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0,
1239
			) );
1240
		}
1241
1242
		// Save tax totals
1243
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1244
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1245
		$this->save();
1246
	}
1247
1248
	/**
1249
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1250
	 *
1251
	 * @since 2.2
1252
	 * @param  bool $and_taxes Calc taxes if true.
1253
	 * @return float calculated grand total.
1254
	 */
1255
	public function calculate_totals( $and_taxes = true ) {
1256
		$cart_subtotal     = 0;
1257
		$cart_total        = 0;
1258
		$fee_total         = 0;
1259
		$cart_subtotal_tax = 0;
1260
		$cart_total_tax    = 0;
1261
1262
		if ( $and_taxes && wc_tax_enabled() ) {
1263
			$this->calculate_taxes();
1264
		}
1265
1266
		// line items
1267
		foreach ( $this->get_items() as $item ) {
1268
			$cart_subtotal     += wc_format_decimal( $item->get_subtotal() );
1269
			$cart_total        += wc_format_decimal( $item->get_total() );
1270
			$cart_subtotal_tax += wc_format_decimal( $item->get_subtotal_tax() );
1271
			$cart_total_tax    += wc_format_decimal( $item->get_total_tax() );
1272
		}
1273
1274
		$this->calculate_shipping();
1275
1276
		foreach ( $this->get_fees() as $item ) {
1277
			$fee_total += $item->get_total();
1278
		}
1279
1280
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1281
1282
		$this->set_discount_total( $cart_subtotal - $cart_total );
1283
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1284
		$this->set_total( $grand_total );
1285
		$this->save();
1286
1287
		return $grand_total;
1288
	}
1289
1290
	/**
1291
	 * Get item subtotal - this is the cost before discount.
1292
	 *
1293
	 * @param object $item
1294
	 * @param bool $inc_tax (default: false).
1295
	 * @param bool $round (default: true).
1296
	 * @return float
1297
	 */
1298 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...
1299
		$subtotal = 0;
1300
1301
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1302
			if ( $inc_tax ) {
1303
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_qty() );
1304
			} else {
1305
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_qty() ) );
1306
			}
1307
1308
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1309
		}
1310
1311
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1312
	}
1313
1314
	/**
1315
	 * Get line subtotal - this is the cost before discount.
1316
	 *
1317
	 * @param object $item
1318
	 * @param bool $inc_tax (default: false).
1319
	 * @param bool $round (default: true).
1320
	 * @return float
1321
	 */
1322 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...
1323
		$subtotal = 0;
1324
1325
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1326
			if ( $inc_tax ) {
1327
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1328
			} else {
1329
				$subtotal = $item->get_subtotal();
1330
			}
1331
1332
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1333
		}
1334
1335
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1336
	}
1337
1338
	/**
1339
	 * Calculate item cost - useful for gateways.
1340
	 *
1341
	 * @param object $item
1342
	 * @param bool $inc_tax (default: false).
1343
	 * @param bool $round (default: true).
1344
	 * @return float
1345
	 */
1346 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...
1347
		$total = 0;
1348
1349
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1350
			if ( $inc_tax ) {
1351
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_qty() );
1352
			} else {
1353
				$total = $item->get_total() / max( 1, $item->get_qty() );
1354
			}
1355
1356
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1357
		}
1358
1359
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1360
	}
1361
1362
	/**
1363
	 * Calculate line total - useful for gateways.
1364
	 *
1365
	 * @param object $item
1366
	 * @param bool $inc_tax (default: false).
1367
	 * @param bool $round (default: true).
1368
	 * @return float
1369
	 */
1370 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...
1371
		$total = 0;
1372
1373
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1374
			// Check if we need to add line tax to the line total.
1375
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1376
1377
			// Check if we need to round.
1378
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1379
		}
1380
1381
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1382
	}
1383
1384
	/**
1385
	 * Get item tax - useful for gateways.
1386
	 *
1387
	 * @param mixed $item
1388
	 * @param bool $round (default: true).
1389
	 * @return float
1390
	 */
1391
	public function get_item_tax( $item, $round = true ) {
1392
		$tax = 0;
1393
1394
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1395
			$tax = $item->get_total_tax() / max( 1, $item->get_qty() );
1396
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1397
		}
1398
1399
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1400
	}
1401
1402
	/**
1403
	 * Get line tax - useful for gateways.
1404
	 *
1405
	 * @param mixed $item
1406
	 * @return float
1407
	 */
1408
	public function get_line_tax( $item ) {
1409
		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 );
1410
	}
1411
1412
	/**
1413
	 * Gets line subtotal - formatted for display.
1414
	 *
1415
	 * @param array  $item
1416
	 * @param string $tax_display
1417
	 * @return string
1418
	 */
1419
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1420
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1421
1422
		if ( 'excl' == $tax_display ) {
1423
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1424
1425
			$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...
1426
		} else {
1427
			$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...
1428
		}
1429
1430
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1431
	}
1432
1433
	/**
1434
	 * Gets order total - formatted for display.
1435
	 * @return string
1436
	 */
1437
	public function get_formatted_order_total() {
1438
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1439
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1440
	}
1441
1442
	/**
1443
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1444
	 *
1445
	 * @param bool $compound (default: false).
1446
	 * @param string $tax_display (default: the tax_display_cart value).
1447
	 * @return string
1448
	 */
1449
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1450
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1451
		$subtotal    = 0;
1452
1453
		if ( ! $compound ) {
1454
			foreach ( $this->get_items() as $item ) {
1455
				$subtotal += $item->get_subtotal();
1456
1457
				if ( 'incl' === $tax_display ) {
1458
					$subtotal += $item->get_subtotal_tax();
1459
				}
1460
			}
1461
1462
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1463
1464
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1465
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1466
			}
1467
1468
		} else {
1469
			if ( 'incl' === $tax_display ) {
1470
				return '';
1471
			}
1472
1473
			foreach ( $this->get_items() as $item ) {
1474
				$subtotal += $item->get_subtotal();
1475
			}
1476
1477
			// Add Shipping Costs.
1478
			$subtotal += $this->get_shipping_total();
1479
1480
			// Remove non-compound taxes.
1481
			foreach ( $this->get_taxes() as $tax ) {
1482
				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...
1483
					continue;
1484
				}
1485
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1486
			}
1487
1488
			// Remove discounts.
1489
			$subtotal = $subtotal - $this->get_total_discount();
1490
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1491
		}
1492
1493
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1494
	}
1495
1496
	/**
1497
	 * Gets shipping (formatted).
1498
	 *
1499
	 * @return string
1500
	 */
1501
	public function get_shipping_to_display( $tax_display = '' ) {
1502
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1503
1504
		if ( $this->get_shipping_total() != 0 ) {
1505
1506
			if ( $tax_display == 'excl' ) {
1507
1508
				// Show shipping excluding tax.
1509
				$shipping = wc_price( $this->get_shipping_total(), array('currency' => $this->get_currency()) );
1510
1511 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...
1512
					$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 );
1513
				}
1514
1515
			} else {
1516
1517
				// Show shipping including tax.
1518
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array('currency' => $this->get_currency()) );
1519
1520 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...
1521
					$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 );
1522
				}
1523
1524
			}
1525
1526
			$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 );
1527
1528
		} elseif ( $this->get_shipping_method() ) {
1529
			$shipping = $this->get_shipping_method();
1530
		} else {
1531
			$shipping = __( 'Free!', 'woocommerce' );
1532
		}
1533
1534
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1535
	}
1536
1537
	/**
1538
	 * Get the discount amount (formatted).
1539
	 * @since  2.3.0
1540
	 * @return string
1541
	 */
1542
	public function get_discount_to_display( $tax_display = '' ) {
1543
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1544
		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 );
1545
	}
1546
1547
	/**
1548
	 * Get totals for display on pages and in emails.
1549
	 *
1550
	 * @param mixed $tax_display
1551
	 * @return array
1552
	 */
1553
	public function get_order_item_totals( $tax_display = '' ) {
1554
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1555
		$total_rows  = array();
1556
1557
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1558
			$total_rows['cart_subtotal'] = array(
1559
				'label' => __( 'Subtotal:', 'woocommerce' ),
1560
				'value'    => $subtotal,
1561
			);
1562
		}
1563
1564
		if ( $this->get_total_discount() > 0 ) {
1565
			$total_rows['discount'] = array(
1566
				'label' => __( 'Discount:', 'woocommerce' ),
1567
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1568
			);
1569
		}
1570
1571
		if ( $this->get_shipping_method() ) {
1572
			$total_rows['shipping'] = array(
1573
				'label' => __( 'Shipping:', 'woocommerce' ),
1574
				'value'    => $this->get_shipping_to_display( $tax_display ),
1575
			);
1576
		}
1577
1578
		if ( $fees = $this->get_fees() ) {
1579
			foreach ( $fees as $id => $fee ) {
1580
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1581
					continue;
1582
				}
1583
				$total_rows[ 'fee_' . $fee->get_order_item_id() ] = array(
1584
					'label' => $fee->get_name() . ':',
1585
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array('currency' => $this->get_currency()) )
1586
				);
1587
			}
1588
		}
1589
1590
		// Tax for tax exclusive prices.
1591
		if ( 'excl' === $tax_display ) {
1592
1593
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1594
1595
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1596
1597
					$total_rows[ sanitize_title( $code ) ] = array(
1598
						'label' => $tax->label . ':',
1599
						'value'    => $tax->formatted_amount,
1600
					);
1601
				}
1602
1603
			} else {
1604
1605
				$total_rows['tax'] = array(
1606
					'label' => WC()->countries->tax_or_vat() . ':',
1607
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1608
				);
1609
			}
1610
		}
1611
1612
		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...
1613
			$total_rows['payment_method'] = array(
1614
				'label' => __( 'Payment Method:', 'woocommerce' ),
1615
				'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...
1616
			);
1617
		}
1618
1619
		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...
1620
			foreach ( $refunds as $id => $refund ) {
1621
				$total_rows[ 'refund_' . $id ] = array(
1622
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1623
					'value'    => wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_currency() ) ),
1624
				);
1625
			}
1626
		}
1627
1628
		$total_rows['order_total'] = array(
1629
			'label' => __( 'Total:', 'woocommerce' ),
1630
			'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...
1631
		);
1632
1633
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1634
	}
1635
1636
	/*
1637
	|--------------------------------------------------------------------------
1638
	| Conditionals
1639
	|--------------------------------------------------------------------------
1640
	|
1641
	| Checks if a condition is true or false.
1642
	|
1643
	*/
1644
1645
	/**
1646
	 * Checks the order status against a passed in status.
1647
	 *
1648
	 * @return bool
1649
	 */
1650
	public function has_status( $status ) {
1651
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1652
	}
1653
1654
	/**
1655
	 * Check whether this order has a specific shipping method or not.
1656
	 *
1657
	 * @param string $method_id
1658
	 * @return bool
1659
	 */
1660
	public function has_shipping_method( $method_id ) {
1661
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1662
			if ( $shipping_method->get_method_id() === $method_id ) {
1663
				return true;
1664
			}
1665
		}
1666
		return false;
1667
	}
1668
1669
	/**
1670
	 * Check if an order key is valid.
1671
	 *
1672
	 * @param mixed $key
1673
	 * @return bool
1674
	 */
1675
	public function key_is_valid( $key ) {
1676
		return $key === $this->get_order_key();
1677
	}
1678
1679
	/**
1680
	 * Returns true if the order contains a free product.
1681
	 * @since 2.5.0
1682
	 * @return bool
1683
	 */
1684
	public function has_free_item() {
1685
		foreach ( $this->get_items() as $item ) {
1686
			if ( ! $item->get_total() ) {
1687
				return true;
1688
			}
1689
		}
1690
		return false;
1691
	}
1692
}
1693