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

WC_Abstract_Order::add_tax()   C

Complexity

Conditions 7
Paths 4

Size

Total Lines 30
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

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