Completed
Push — master ( fb1e2f...9a3784 )
by Justin
25:57
created

WC_Abstract_Order::get_id()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

Loading history...
110
			$this->read( $read );
111
		} elseif ( $read instanceof self ) {
112
			$this->read( absint( $read->get_id() ) );
113
		} elseif ( ! empty( $read->ID ) ) {
114
			$this->read( absint( $read->ID ) );
115
		}
116
		// Set default status if none were read.
117
		if ( ! $this->get_status() ) {
118
			$this->set_status( apply_filters( 'woocommerce_default_order_status', 'pending' ) );
119
		}
120
	}
121
122
	/*
123
	|--------------------------------------------------------------------------
124
	| CRUD methods
125
	|--------------------------------------------------------------------------
126
	|
127
	| Methods which create, read, update and delete orders from the database.
128
	| Written in abstract fashion so that the way orders are stored can be
129
	| changed more easily in the future.
130
	|
131
	| A save method is included for convenience (chooses update or create based
132
	| on if the order exists yet).
133
	|
134
	*/
135
136
	/**
137
	 * Get internal type (post type.)
138
	 * @return string
139
	 */
140
	public function get_type() {
141
		return 'shop_order';
142
	}
143
144
	/**
145
	 * Get a title for the new post type.
146
	 */
147
	protected function get_post_title() {
148
		// @codingStandardsIgnoreStart
149
		return sprintf( __( 'Order &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
150
		// @codingStandardsIgnoreEnd
151
	}
152
153
	/**
154
	 * Insert data into the database.
155
	 * @since 2.7.0
156
	 */
157
	public function create() {
158
		$this->set_date_created( current_time( 'timestamp' ) );
159
		$this->set_currency( $this->get_currency() ? $this->get_currency() : get_woocommerce_currency() );
160
161
		$order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', array(
162
			'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
163
			'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
164
			'post_type'     => $this->get_type(),
165
			'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
166
			'ping_status'   => 'closed',
167
			'post_author'   => 1,
168
			'post_title'    => $this->get_post_title(),
169
			'post_password' => uniqid( 'order_' ),
170
			'post_parent'   => $this->get_parent_id(),
171
		) ), true );
172
173
		if ( $order_id ) {
174
			$this->set_id( $order_id );
175
176
			// Set meta data
177
			$this->update_post_meta( '_order_currency', $this->get_currency() );
178
			$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
179
			$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
180
			$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
181
			$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
182
			$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
183
			$this->update_post_meta( '_order_total', $this->get_total( true ) );
184
			$this->update_post_meta( '_order_version', $this->get_version() );
185
			$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
186
			$this->save_meta_data();
187
		}
188
	}
189
190
	/**
191
	 * Read from the database.
192
	 * @since 2.7.0
193
	 * @param int $id ID of object to read.
194
	 */
195
	public function read( $id ) {
196
		$this->set_defaults();
197
198
		if ( empty( $id ) || ! ( $post_object = get_post( $id ) ) ) {
199
			return;
200
		}
201
202
		$this->set_id( $id );
203
		$this->set_props( array(
204
			'parent_id'          => $post_object->post_parent,
205
			'date_created'       => $post_object->post_date,
206
			'date_modified'      => $post_object->post_modified,
207
			'status'             => $post_object->post_status,
208
			'currency'           => get_post_meta( $id, '_order_currency', true ),
209
			'discount_total'     => get_post_meta( $id, '_cart_discount', true ),
210
			'discount_tax'       => get_post_meta( $id, '_cart_discount_tax', true ),
211
			'shipping_total'     => get_post_meta( $id, '_order_shipping', true ),
212
			'shipping_tax'       => get_post_meta( $id, '_order_shipping_tax', true ),
213
			'cart_tax'           => get_post_meta( $id, '_order_tax', true ),
214
			'total'              => get_post_meta( $id, '_order_total', true ),
215
			'version'            => get_post_meta( $id, '_order_version', true ),
216
			'prices_include_tax' => metadata_exists( 'post', $id, '_prices_include_tax' ) ? 'yes' === get_post_meta( $id, '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ),
217
		) );
218
219
		$this->read_meta_data();
220
	}
221
222
	/**
223
	 * Post meta update wrapper. Sets or deletes based on value.
224
	 * @since 2.7.0
225
	 * @return bool Was it changed?
226
	 */
227
	protected function update_post_meta( $key, $value ) {
228
		if ( '' !== $value ) {
229
			return update_post_meta( $this->get_id(), $key, $value );
230
		} else {
231
			return delete_post_meta( $this->get_id(), $key );
232
		}
233
	}
234
235
	/**
236
	 * Update data in the database.
237
	 * @since 2.7.0
238
	 */
239
	public function update() {
240
		global $wpdb;
241
242
		$order_id = $this->get_id();
243
244
		$wpdb->update(
245
			$wpdb->posts,
246
			array(
247
				'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
248
				'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
249
				'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
250
				'post_parent'   => $this->get_parent_id(),
251
			),
252
			array(
253
				'ID' => $order_id,
254
			)
255
		);
256
257
		// Update meta data
258
		$this->update_post_meta( '_order_currency', $this->get_currency() );
259
		$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
260
		$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
261
		$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
262
		$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
263
		$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
264
		$this->update_post_meta( '_order_total', $this->get_total( true ) );
265
		$this->update_post_meta( '_order_version', $this->get_version() );
266
		$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
267
		$this->save_meta_data();
268
	}
269
270
	/**
271
	 * Delete data from the database.
272
	 * @since 2.7.0
273
	 */
274
	public function delete() {
275
		wp_delete_post( $this->get_id() );
276
	}
277
278
	/**
279
	 * Save data to the database.
280
	 * @since 2.7.0
281
	 * @return int order ID
282
	 */
283
	public function save() {
284
		$this->set_version( WC_VERSION );
285
286
		if ( ! $this->get_id() ) {
287
			$this->create();
288
		} else {
289
			$this->update();
290
		}
291
292
		$this->save_items();
293
		clean_post_cache( $this->get_id() );
294
		wc_delete_shop_order_transients( $this->get_id() );
295
296
		return $this->get_id();
297
	}
298
299
	/**
300
	 * Save all order items which are part of this order.
301
	 */
302
	protected function save_items() {
303
		// remove items
304
		foreach ( $this->items_to_delete as $item ) {
305
			$item->delete();
306
		}
307
308
		$this->items_to_delete = array();
309
310
		// Add/save items
311
		foreach ( $this->items as $item_group => $items ) {
312
			if ( is_array( $items ) ) {
313
				foreach ( $items as $item_key => $item ) {
314
					$item->set_order_id( $this->get_id() );
315
					$item_id = $item->save();
316
317
					// If ID changed (new item saved to DB)...
318
					if ( $item_id !== $item_key ) {
319
						$this->items[ $item_group ][ $item_id ] = $item;
320
						unset( $this->items[ $item_group ][ $item_key ] );
321
322
						// Legacy action handler
323
						switch ( $item_group ) {
324
							case 'fee_lines' :
325
								if ( isset( $item->legacy_fee, $item->legacy_fee_key ) ) {
326
									wc_do_deprecated_action( 'woocommerce_add_order_fee_meta', array( $this->get_id(), $item_id, $item->legacy_fee, $item->legacy_fee_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
327
								}
328
							break;
329
							case 'shipping_lines' :
330
								if ( isset( $item->legacy_package_key ) ) {
331
									wc_do_deprecated_action( 'woocommerce_add_shipping_order_item', array( $item_id, $item->legacy_package_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
332
								}
333
							break;
334
							case 'line_items' :
335
								if ( isset( $item->legacy_values, $item->legacy_cart_item_key ) ) {
336
									wc_do_deprecated_action( 'woocommerce_add_order_item_meta', array( $item_id, $item->legacy_values, $item->legacy_cart_item_key ), '2.7', 'Use woocommerce_new_order_item action instead.' );
337
								}
338
							break;
339
						}
340
					}
341
				}
342
			}
343
		}
344
	}
345
346
	/*
347
	|--------------------------------------------------------------------------
348
	| Getters
349
	|--------------------------------------------------------------------------
350
	|
351
	| Methods for getting data from the order object.
352
	|
353
	*/
354
355
	/**
356
	 * Get all class data in array format.
357
	 * @since 2.7.0
358
	 * @return array
359
	 */
360 View Code Duplication
	public function get_data() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
361
		return array_merge(
362
			$this->data,
363
			array(
364
				'meta_data'      => $this->get_meta_data(),
365
				'line_items'     => $this->get_items( 'line_item' ),
366
				'tax_lines'      => $this->get_items( 'tax' ),
367
				'shipping_lines' => $this->get_items( 'shipping' ),
368
				'fee_lines'      => $this->get_items( 'fee' ),
369
				'coupon_lines'   => $this->get_items( 'coupon' ),
370
			)
371
		);
372
	}
373
374
	/**
375
	 * Get parent order ID.
376
	 * @since 2.7.0
377
	 * @return integer
378
	 */
379
	public function get_parent_id() {
380
		return $this->data['parent_id'];
381
	}
382
383
	/**
384
	 * Gets order currency.
385
	 * @return string
386
	 */
387
	public function get_currency() {
388
		return apply_filters( 'woocommerce_get_currency', $this->data['currency'], $this );
389
	}
390
391
	/**
392
	 * Get order_version
393
	 * @return string
394
	 */
395
	public function get_version() {
396
		return $this->data['version'];
397
	}
398
399
	/**
400
	 * Get prices_include_tax
401
	 * @return bool
402
	 */
403
	public function get_prices_include_tax() {
404
		return $this->data['prices_include_tax'];
405
	}
406
407
	/**
408
	 * Get date_created
409
	 * @return int
410
	 */
411
	public function get_date_created() {
412
		return $this->data['date_created'];
413
	}
414
415
	/**
416
	 * Get date_modified
417
	 * @return int
418
	 */
419
	public function get_date_modified() {
420
		return $this->data['date_modified'];
421
	}
422
423
	/**
424
	 * Return the order statuses without wc- internal prefix.
425
	 * @return string
426
	 */
427
	public function get_status() {
428
		$status = $this->data['status'];
429
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $status, 0, 3 ) ? substr( $status, 3 ) : $status, $this );
430
	}
431
432
	/**
433
	 * Get discount_total
434
	 * @param bool $raw Gets raw unfiltered value.
435
	 * @return string
436
	 */
437
	public function get_discount_total( $raw = false ) {
438
		$value = wc_format_decimal( $this->data['discount_total'] );
439
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_total', $value, $this );
440
	}
441
442
	/**
443
	 * Get discount_tax
444
	 * @param bool $raw Gets raw unfiltered value.
445
	 * @return string
446
	 */
447
	public function get_discount_tax( $raw = false ) {
448
		$value = wc_format_decimal( $this->data['discount_tax'] );
449
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_tax', $value, $this );
450
	}
451
452
	/**
453
	 * Get shipping_total
454
	 * @param bool $raw Gets raw unfiltered value.
455
	 * @return string
456
	 */
457
	public function get_shipping_total( $raw = false ) {
458
		$value = wc_format_decimal( $this->data['shipping_total'] );
459
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_total', $value, $this );
460
	}
461
462
	/**
463
	 * Get shipping_tax.
464
	 * @param bool $raw Gets raw unfiltered value.
465
	 * @return string
466
	 */
467
	public function get_shipping_tax( $raw = false ) {
468
		$value = wc_format_decimal( $this->data['shipping_tax'] );
469
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_tax', $value, $this );
470
	}
471
472
	/**
473
	 * Gets cart tax amount.
474
	 * @param bool $raw Gets raw unfiltered value.
475
	 * @return float
476
	 */
477
	public function get_cart_tax( $raw = false ) {
478
		$value = wc_format_decimal( $this->data['cart_tax'] );
479
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_cart_tax', $value, $this );
480
	}
481
482
	/**
483
	 * Gets order grand total. incl. taxes. Used in gateways. Filtered.
484
	 * @param bool $raw Gets raw unfiltered value.
485
	 * @return float
486
	 */
487
	public function get_total( $raw = false ) {
488
		return $raw ? $this->data['total'] : apply_filters( 'woocommerce_order_amount_total', $this->data['total'], $this );
489
	}
490
491
	/**
492
	 * Get total tax amount. Alias for get_order_tax().
493
	 *
494
	 * @since 2.7.0 woocommerce_order_amount_total_tax filter has been removed to avoid
495
	 * these values being modified and then saved back to the DB. There are
496
	 * other, later hooks available to change totals on display. e.g.
497
	 * woocommerce_get_order_item_totals.
498
	 * @param bool $raw Gets raw unfiltered value.
499
	 * @return float
500
	 */
501
	public function get_total_tax( $raw = false ) {
502
		$value = wc_format_decimal( $this->data['total_tax'] );
503
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total_tax', $value, $this );
504
	}
505
506
	/**
507
	 * Gets the total discount amount.
508
	 * @param  bool $ex_tax Show discount excl any tax.
509
	 * @return float
510
	 */
511
	public function get_total_discount( $ex_tax = true ) {
512
		if ( $ex_tax ) {
513
			$total_discount = $this->get_discount_total();
514
		} else {
515
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
516
		}
517
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
518
	}
519
520
	/**
521
	 * Gets order subtotal.
522
	 * @return float
523
	 */
524
	public function get_subtotal() {
525
		$subtotal = 0;
526
527
		foreach ( $this->get_items() as $item ) {
528
			$subtotal += $item->get_subtotal();
529
		}
530
531
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
532
	}
533
534
	/**
535
	 * Get taxes, merged by code, formatted ready for output.
536
	 *
537
	 * @return array
538
	 */
539
	public function get_tax_totals() {
540
		$tax_totals = array();
541
542
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
543
			$code = $tax->get_rate_code();
544
545 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...
546
				$tax_totals[ $code ] = new stdClass();
547
				$tax_totals[ $code ]->amount = 0;
548
			}
549
550
			$tax_totals[ $code ]->id                = $key;
551
			$tax_totals[ $code ]->rate_id           = $tax->get_rate_id();
552
			$tax_totals[ $code ]->is_compound       = $tax->is_compound();
553
			$tax_totals[ $code ]->label             = $tax->get_label();
554
			$tax_totals[ $code ]->amount           += $tax->get_tax_total() + $tax->get_shipping_tax_total();
555
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
556
		}
557
558 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...
559
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
560
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
561
		}
562
563
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
564
	}
565
566
	/*
567
	|--------------------------------------------------------------------------
568
	| Setters
569
	|--------------------------------------------------------------------------
570
	|
571
	| Functions for setting order data. These should not update anything in the
572
	| database itself and should only change what is stored in the class
573
	| object. However, for backwards compatibility pre 2.7.0 some of these
574
	| setters may handle both.
575
	|
576
	*/
577
578
	/**
579
	 * Set parent order ID.
580
	 * @since 2.7.0
581
	 * @param int $value
582
	 * @throws WC_Data_Exception
583
	 */
584
	public function set_parent_id( $value ) {
585
		if ( $value && ! get_post( $value ) ) {
586
			$this->error( 'order_invalid_parent_id', __( 'Invalid parent ID', 'woocommerce' ) );
587
		}
588
		$this->data['parent_id'] = absint( $value );
589
	}
590
591
	/**
592
	 * Set order status.
593
	 * @since 2.7.0
594
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
595
	 * @return array details of change
596
	 */
597
	 public function set_status( $new_status ) {
598
		$old_status = $this->get_status();
599
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
600
601
		// Only allow valid new status
602
		if ( ! in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) ) {
603
			$new_status = 'pending';
604
		}
605
606
		$this->data['status'] = 'wc-' . $new_status;
607
608
		// If the old status is set but unknown (e.g. draft) assume its pending for action usage.
609
		if ( $old_status && ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
610
			$old_status = 'pending';
611
		}
612
613
		return array(
614
			'from' => $old_status,
615
			'to'   => $new_status,
616
		);
617
	 }
618
619
	/**
620
	 * Set order_version
621
	 * @param string $value
622
	 * @throws WC_Data_Exception
623
	 */
624
	public function set_version( $value ) {
625
		$this->data['version'] = $value;
626
	}
627
628
	/**
629
	 * Set order_currency
630
	 * @param string $value
631
	 * @throws WC_Data_Exception
632
	 */
633
	public function set_currency( $value ) {
634
		if ( $value && ! in_array( $value, array_keys( get_woocommerce_currencies() ) ) ) {
635
			$this->error( 'order_invalid_currency', __( 'Invalid currency code', 'woocommerce' ) );
636
		}
637
		$this->data['currency'] = $value ? $value : get_woocommerce_currency();
638
	}
639
640
	/**
641
	 * Set prices_include_tax
642
	 * @param bool $value
643
	 * @throws WC_Data_Exception
644
	 */
645
	public function set_prices_include_tax( $value ) {
646
		$this->data['prices_include_tax'] = (bool) $value;
647
	}
648
649
	/**
650
	 * Set date_created
651
	 * @param string $timestamp Timestamp
652
	 * @throws WC_Data_Exception
653
	 */
654
	public function set_date_created( $timestamp ) {
655
		$this->data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
656
	}
657
658
	/**
659
	 * Set date_modified
660
	 * @param string $timestamp
661
	 * @throws WC_Data_Exception
662
	 */
663
	public function set_date_modified( $timestamp ) {
664
		$this->data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
665
	}
666
667
	/**
668
	 * Set discount_total
669
	 * @param string $value
670
	 * @throws WC_Data_Exception
671
	 */
672
	public function set_discount_total( $value ) {
673
		$this->data['discount_total'] = wc_format_decimal( $value );
674
	}
675
676
	/**
677
	 * Set discount_tax
678
	 * @param string $value
679
	 * @throws WC_Data_Exception
680
	 */
681
	public function set_discount_tax( $value ) {
682
		$this->data['discount_tax'] = wc_format_decimal( $value );
683
	}
684
685
	/**
686
	 * Set shipping_total
687
	 * @param string $value
688
	 * @throws WC_Data_Exception
689
	 */
690
	public function set_shipping_total( $value ) {
691
		$this->data['shipping_total'] = wc_format_decimal( $value );
692
	}
693
694
	/**
695
	 * Set shipping_tax
696
	 * @param string $value
697
	 * @throws WC_Data_Exception
698
	 */
699
	public function set_shipping_tax( $value ) {
700
		$this->data['shipping_tax'] = wc_format_decimal( $value );
701
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
702
	}
703
704
	/**
705
	 * Set cart tax
706
	 * @param string $value
707
	 * @throws WC_Data_Exception
708
	 */
709
	public function set_cart_tax( $value ) {
710
		$this->data['cart_tax'] = wc_format_decimal( $value );
711
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
712
	}
713
714
	/**
715
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
716
	 * @param string $value
717
	 * @throws WC_Data_Exception
718
	 */
719
	protected function set_total_tax( $value ) {
720
		$this->data['total_tax'] = wc_format_decimal( $value );
721
	}
722
723
	/**
724
	 * Set total
725
	 * @param string $value
726
	 * @param string $deprecated Function used to set different totals based on this.
727
	 * @throws WC_Data_Exception
728
	 */
729
	public function set_total( $value, $deprecated = '' ) {
730
		if ( $deprecated ) {
731
			_deprecated_argument( 'total_type', '2.7', 'Use dedicated total setter methods instead.' );
732
			return $this->legacy_set_total( $value, $deprecated );
733
		}
734
		$this->data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
735
	}
736
737
	/*
738
	|--------------------------------------------------------------------------
739
	| Order Item Handling
740
	|--------------------------------------------------------------------------
741
	|
742
	| Order items are used for products, taxes, shipping, and fees within
743
	| each order.
744
	|
745
	*/
746
747
	/**
748
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
749
	 * @param string $type Order item type. Default null.
750
	 */
751
	public function remove_order_items( $type = null ) {
752
		global $wpdb;
753
		if ( ! empty( $type ) ) {
754
			$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 ) );
755
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
756
			if ( $group = $this->type_to_group( $type ) ) {
757
				$this->items[ $group ] = null;
758
			}
759
		} else {
760
			$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() ) );
761
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
762
			$this->items = array(
763
				'line_items'     => null,
764
				'coupon_lines'   => null,
765
				'shipping_lines' => null,
766
				'fee_lines'      => null,
767
				'tax_lines'      => null,
768
			);
769
		}
770
	}
771
772
	/**
773
	 * Convert a type to a types group.
774
	 * @param string $type
775
	 * @return string group
776
	 */
777
	protected function type_to_group( $type ) {
778
		$type_to_group = array(
779
			'line_item' => 'line_items',
780
			'tax'       => 'tax_lines',
781
			'shipping'  => 'shipping_lines',
782
			'fee'       => 'fee_lines',
783
			'coupon'    => 'coupon_lines',
784
		);
785
		return isset( $type_to_group[ $type ] ) ? $type_to_group[ $type ] : '';
786
	}
787
788
	/**
789
	 * Return an array of items/products within this order.
790
	 * @param string|array $types Types of line items to get (array or string).
791
	 * @return Array of WC_Order_item
792
	 */
793
	public function get_items( $types = 'line_item' ) {
794
		$items = array();
795
		$types = array_filter( (array) $types );
796
797
		foreach ( $types as $type ) {
798
			if ( $group = $this->type_to_group( $type ) ) {
799
				if ( is_null( $this->items[ $group ] ) ) {
800
					$this->items[ $group ] = $this->get_items_from_db( $type );
801
				}
802
				// Don't use array_merge here because keys are numeric
803
				$items = $items + $this->items[ $group ];
804
			}
805
		}
806
807
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
808
	}
809
810
	/**
811
	 * Gets items from the database by type.
812
	 * @param  string $type
813
	 * @return array
814
	 */
815
	protected function get_items_from_db( $type ) {
816
		global $wpdb;
817
818
		$get_items_sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s ORDER BY order_item_id;", $this->get_id(), $type );
819
		$items         = $wpdb->get_results( $get_items_sql );
820
821
		if ( ! empty( $items ) ) {
822
			$items = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
823
		} else {
824
			$items = array();
825
		}
826
827
		return $items;
828
	}
829
830
	/**
831
	 * Return an array of fees within this order.
832
	 * @return array
833
	 */
834
	public function get_fees() {
835
		return $this->get_items( 'fee' );
836
	}
837
838
	/**
839
	 * Return an array of taxes within this order.
840
	 * @return array
841
	 */
842
	public function get_taxes() {
843
		return $this->get_items( 'tax' );
844
	}
845
846
	/**
847
	 * Return an array of shipping costs within this order.
848
	 * @return array
849
	 */
850
	public function get_shipping_methods() {
851
		return $this->get_items( 'shipping' );
852
	}
853
854
	/**
855
	 * Gets formatted shipping method title.
856
	 * @return string
857
	 */
858
	public function get_shipping_method() {
859
		$names = array();
860
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
861
			$names[] = $shipping_method->get_name();
862
		}
863
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
864
	}
865
866
	/**
867
	 * Get coupon codes only.
868
	 * @return array
869
	 */
870
	public function get_used_coupons() {
871
		$coupon_codes = array();
872
		if ( $coupons = $this->get_items( 'coupon' ) ) {
873
			foreach ( $coupons as $coupon ) {
874
				$coupon_codes[] = $coupon->get_code();
875
			}
876
		}
877
		return $coupon_codes;
878
	}
879
880
	/**
881
	 * Gets the count of order items of a certain type.
882
	 *
883
	 * @param string $item_type
884
	 * @return string
885
	 */
886
	public function get_item_count( $item_type = '' ) {
887
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
888
		$count = 0;
889
890
		foreach ( $items as $item ) {
891
			$count += $item->get_quantity();
892
		}
893
894
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
895
	}
896
897
	/**
898
	 * Get an order item object, based on it's type.
899
	 * @since  2.7.0
900
	 * @param  int $item_id
901
	 * @return WC_Order_Item
902
	 */
903
	public function get_item( $item_id ) {
904
		return WC_Order_Factory::get_order_item( $item_id );
905
	}
906
907
	/**
908
	 * Get key for where a certain item type is stored in _items.
909
	 * @since  2.7.0
910
	 * @param  $item object Order item (product, shipping, fee, coupon, tax)
911
	 * @return string
912
	 */
913
	protected function get_items_key( $item ) {
914
		if ( is_a( $item, 'WC_Order_Item_Product' ) ) {
915
			return 'line_items';
916
		} elseif ( is_a( $item, 'WC_Order_Item_Fee' ) ) {
917
			return 'fee_lines';
918
		} elseif ( is_a( $item, 'WC_Order_Item_Shipping' ) ) {
919
			return 'shipping_lines';
920
		} elseif ( is_a( $item, 'WC_Order_Item_Tax' ) ) {
921
			return 'tax_lines';
922
		} elseif ( is_a( $item, 'WC_Order_Item_Coupon' ) ) {
923
			return 'coupon_lines';
924
		} else {
925
			return '';
926
		}
927
	}
928
929
	/**
930
	 * Remove item from the order.
931
	 * @param int $item_id
932
	 */
933
	public function remove_item( $item_id ) {
934
		$item = $this->get_item( $item_id );
935
936
		if ( ! $item || ! ( $items_key = $this->get_items_key( $item ) ) ) {
937
			return false;
938
		}
939
940
		// Unset and remove later
941
		$this->items_to_delete[] = $item;
942
		unset( $this->items[ $items_key ][ $item->get_id() ] );
943
	}
944
945
	/**
946
	 * Adds an order item to this order. The order item will not persist until save.
947
	 * @since 2.7.0
948
	 * @param WC_Order_Item Order item object (product, shipping, fee, coupon, tax)
949
	 */
950
	public function add_item( $item ) {
951
		if ( ! $items_key = $this->get_items_key( $item ) ) {
952
			return false;
953
		}
954
955
		// Make sure existing items are loaded so we can append this new one.
956
		if ( is_null( $this->items[ $items_key ] ) ) {
957
			$this->items[ $items_key ] = $this->get_items( $item->get_type() );
958
		}
959
960
		// Append new row with generated temporary ID
961
		if ( $item->get_id() ) {
962
			$this->items[ $items_key ][ $item->get_id() ] = $item;
963
		} else {
964
			$this->items[ $items_key ][ 'new:' . sizeof( $this->items[ $items_key ] ) ] = $item;
965
		}
966
	}
967
968
	/**
969
	 * Add a product line item to the order. This is the only line item type with
970
	 * it's own method because it saves looking up order amounts (costs are added up for you).
971
	 * @param  \WC_Product $product
972
	 * @param  int $qty
973
	 * @param  array $args
974
	 * @return int order item ID
975
	 * @throws WC_Data_Exception
976
	 */
977
	public function add_product( $product, $qty = 1, $args = array() ) {
978
		if ( $product ) {
979
			$default_args = array(
980
				'name'         => $product->get_title(),
981
				'tax_class'    => $product->get_tax_class(),
982
				'product_id'   => $product->get_id(),
983
				'variation_id' => isset( $product->variation_id ) ? $product->variation_id : 0,
984
				'variation'    => isset( $product->variation_id ) ? $product->get_variation_attributes() : array(),
0 ignored issues
show
Bug introduced by
It seems like you code against a specific sub-type and not the parent class WC_Product as the method get_variation_attributes() does only exist in the following sub-classes of WC_Product: WC_Product_Variable, WC_Product_Variation. Maybe you want to instanceof check for one of these explicitly?

Let’s take a look at an example:

abstract class User
{
    /** @return string */
    abstract public function getPassword();
}

class MyUser extends User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different sub-classes of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the parent class:

    abstract class User
    {
        /** @return string */
        abstract public function getPassword();
    
        /** @return string */
        abstract public function getDisplayName();
    }
    
Loading history...
985
				'subtotal'     => $product->get_price_excluding_tax( $qty ),
986
				'total'        => $product->get_price_excluding_tax( $qty ),
987
				'quantity'     => $qty,
988
			);
989
		} else {
990
			$default_args = array(
991
				'quantity'     => $qty,
992
			);
993
		}
994
995
		$args = wp_parse_args( $args, $default_args );
996
997
		// BW compatibility with old args
998 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...
999
			foreach ( $args['totals'] as $key => $value ) {
1000
				if ( 'tax' === $key ) {
1001
					$args['total_tax'] = $value;
1002
				} elseif ( 'tax_data' === $key ) {
1003
					$args['taxes'] = $value;
1004
				} else {
1005
					$args[ $key ] = $value;
1006
				}
1007
			}
1008
		}
1009
1010
		$item = new WC_Order_Item_Product( $args );
1011
		$item->set_backorder_meta();
1012
		$item->set_order_id( $this->get_id() );
1013
		$item->save();
1014
		$this->add_item( $item );
1015
		wc_do_deprecated_action( 'woocommerce_order_add_product', array( $this->get_id(), $item->get_id(), $product, $qty, $args ), '2.7', 'Use woocommerce_new_order_item action instead.' );
1016
		return $item->get_id();
1017
	}
1018
1019
	/*
1020
	|--------------------------------------------------------------------------
1021
	| Payment Token Handling
1022
	|--------------------------------------------------------------------------
1023
	|
1024
	| Payment tokens are hashes used to take payments by certain gateways.
1025
	|
1026
	*/
1027
1028
	/**
1029
	 * Add a payment token to an order
1030
	 *
1031
	 * @since 2.6
1032
	 * @param  WC_Payment_Token   $token     Payment token object
1033
	 * @return boolean|int The new token ID or false if it failed.
1034
	 */
1035
	public function add_payment_token( $token ) {
1036
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
1037
			return false;
1038
		}
1039
1040
		$token_ids = get_post_meta( $this->get_id(), '_payment_tokens', true );
1041
1042
		if ( empty( $token_ids ) ) {
1043
			$token_ids = array();
1044
		}
1045
1046
		$token_ids[] = $token->get_id();
1047
1048
		update_post_meta( $this->get_id(), '_payment_tokens', $token_ids );
1049
		do_action( 'woocommerce_payment_token_added_to_order', $this->get_id(), $token->get_id(), $token, $token_ids );
1050
		return $token->get_id();
1051
	}
1052
1053
	/**
1054
	 * Returns a list of all payment tokens associated with the current order
1055
	 *
1056
	 * @since 2.6
1057
	 * @return array An array of payment token objects
1058
	 */
1059
	public function get_payment_tokens() {
1060
		return WC_Payment_Tokens::get_order_tokens( $this->get_id() );
1061
	}
1062
1063
	/*
1064
	|--------------------------------------------------------------------------
1065
	| Calculations.
1066
	|--------------------------------------------------------------------------
1067
	|
1068
	| These methods calculate order totals and taxes based on the current data.
1069
	|
1070
	*/
1071
1072
	/**
1073
	 * Calculate shipping total.
1074
	 *
1075
	 * @since 2.2
1076
	 * @return float
1077
	 */
1078
	public function calculate_shipping() {
1079
		$shipping_total = 0;
1080
1081
		foreach ( $this->get_shipping_methods() as $shipping ) {
1082
			$shipping_total += $shipping->get_total();
1083
		}
1084
1085
		$this->set_shipping_total( $shipping_total );
1086
		$this->save();
1087
1088
		return $this->get_shipping_total();
1089
	}
1090
1091
	/**
1092
	 * Get all tax classes for items in the order.
1093
	 *
1094
	 * @since 2.6.3
1095
	 * @return array
1096
	 */
1097
	public function get_items_tax_classes() {
1098
		$found_tax_classes = array();
1099
1100
		foreach ( $this->get_items() as $item ) {
1101
			if ( $_product = $item->get_product() ) {
1102
				$found_tax_classes[] = $_product->get_tax_class();
1103
			}
1104
		}
1105
1106
		return array_unique( $found_tax_classes );
1107
	}
1108
1109
	/**
1110
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
1111
	 *
1112
	 * Will use the base country unless customer addresses are set.
1113
	 * @param $args array Added in 2.7.0 to pass things like location.
1114
	 */
1115
	public function calculate_taxes( $args = array() ) {
1116
		$tax_based_on      = get_option( 'woocommerce_tax_based_on' );
1117
		$args              = wp_parse_args( $args, array(
1118
			'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...
1119
			'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...
1120
			'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...
1121
			'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...
1122
		) );
1123
1124
		// Default to base
1125
		if ( 'base' === $tax_based_on || empty( $args['country'] ) ) {
1126
			$default          = wc_get_base_location();
1127
			$args['country']  = $default['country'];
1128
			$args['state']    = $default['state'];
1129
			$args['postcode'] = '';
1130
			$args['city']     = '';
1131
		}
1132
1133
		// Calc taxes for line items
1134
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1135
			$tax_class           = $item->get_tax_class();
1136
			$tax_status          = $item->get_tax_status();
1137
1138
			if ( '0' !== $tax_class && 'taxable' === $tax_status ) {
1139
				$tax_rates = WC_Tax::find_rates( array(
1140
					'country'   => $args['country'],
1141
					'state'     => $args['state'],
1142
					'postcode'  => $args['postcode'],
1143
					'city'      => $args['city'],
1144
					'tax_class' => $tax_class,
1145
				) );
1146
1147
				$total = $item->get_total();
1148
				$taxes = WC_Tax::calc_tax( $total, $tax_rates, false );
1149
1150
				if ( $item->is_type( 'line_item' ) ) {
1151
					$subtotal       = $item->get_subtotal();
1152
					$subtotal_taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, false );
1153
					$item->set_taxes( array( 'total' => $taxes, 'subtotal' => $subtotal_taxes ) );
1154
				} else {
1155
					$item->set_taxes( array( 'total' => $taxes ) );
1156
				}
1157
				$item->save();
1158
			}
1159
		}
1160
1161
		// Calc taxes for shipping
1162
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1163
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
1164
1165
			// Inherit tax class from items
1166
			if ( '' === $shipping_tax_class ) {
1167
				$tax_rates         = array();
1168
				$tax_classes       = array_merge( array( '' ), WC_Tax::get_tax_classes() );
1169
				$found_tax_classes = $this->get_items_tax_classes();
1170
1171
				foreach ( $tax_classes as $tax_class ) {
1172
					$tax_class = sanitize_title( $tax_class );
1173
					if ( in_array( $tax_class, $found_tax_classes ) ) {
1174
						$tax_rates = WC_Tax::find_shipping_rates( array(
1175
							'country'   => $args['country'],
1176
							'state'     => $args['state'],
1177
							'postcode'  => $args['postcode'],
1178
							'city'      => $args['city'],
1179
							'tax_class' => $tax_class,
1180
						) );
1181
						break;
1182
					}
1183
				}
1184
			} else {
1185
				$tax_rates = WC_Tax::find_shipping_rates( array(
1186
					'country'   => $args['country'],
1187
					'state'     => $args['state'],
1188
					'postcode'  => $args['postcode'],
1189
					'city'      => $args['city'],
1190
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
1191
				) );
1192
			}
1193
1194
			$item->set_taxes( array( 'total' => WC_Tax::calc_tax( $item->get_total(), $tax_rates, false ) ) );
1195
			$item->save();
1196
		}
1197
		$this->update_taxes();
1198
	}
1199
1200
	/**
1201
	 * Update tax lines for the order based on the line item taxes themselves.
1202
	 */
1203
	public function update_taxes() {
1204
		$cart_taxes     = array();
1205
		$shipping_taxes = array();
1206
1207
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
1208
			$taxes = $item->get_taxes();
1209 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...
1210
				$cart_taxes[ $tax_rate_id ] = isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] + $tax : $tax;
1211
			}
1212
		}
1213
1214
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
1215
			$taxes = $item->get_taxes();
1216 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...
1217
				$shipping_taxes[ $tax_rate_id ] = isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] + $tax : $tax;
1218
			}
1219
		}
1220
1221
		// Remove old existing tax rows.
1222
		$this->remove_order_items( 'tax' );
1223
1224
		// Now merge to keep tax rows.
1225
		foreach ( array_keys( $cart_taxes + $shipping_taxes ) as $tax_rate_id ) {
1226
			$item = new WC_Order_Item_Tax();
1227
			$item->set_rate( $tax_rate_id );
1228
			$item->set_tax_total( isset( $cart_taxes[ $tax_rate_id ] ) ? $cart_taxes[ $tax_rate_id ] : 0 );
1229
			$item->set_shipping_tax_total( isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
1230
			$this->add_item( $item );
1231
		}
1232
1233
		// Save tax totals
1234
		$this->set_shipping_tax( WC_Tax::round( array_sum( $shipping_taxes ) ) );
1235
		$this->set_cart_tax( WC_Tax::round( array_sum( $cart_taxes ) ) );
1236
		$this->save();
1237
	}
1238
1239
	/**
1240
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
1241
	 *
1242
	 * @since 2.2
1243
	 * @param  bool $and_taxes Calc taxes if true.
1244
	 * @return float calculated grand total.
1245
	 */
1246
	public function calculate_totals( $and_taxes = true ) {
1247
		$cart_subtotal     = 0;
1248
		$cart_total        = 0;
1249
		$fee_total         = 0;
1250
		$cart_subtotal_tax = 0;
1251
		$cart_total_tax    = 0;
1252
1253
		if ( $and_taxes && wc_tax_enabled() ) {
1254
			$this->calculate_taxes();
1255
		}
1256
1257
		// line items
1258
		foreach ( $this->get_items() as $item ) {
1259
			$cart_subtotal     += $item->get_subtotal();
1260
			$cart_total        += $item->get_total();
1261
			$cart_subtotal_tax += $item->get_subtotal_tax();
1262
			$cart_total_tax    += $item->get_total_tax();
1263
		}
1264
1265
		$this->calculate_shipping();
1266
1267
		foreach ( $this->get_fees() as $item ) {
1268
			$fee_total += $item->get_total();
1269
		}
1270
1271
		$grand_total = round( $cart_total + $fee_total + $this->get_shipping_total() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
1272
1273
		$this->set_discount_total( $cart_subtotal - $cart_total );
1274
		$this->set_discount_tax( $cart_subtotal_tax - $cart_total_tax );
1275
		$this->set_total( $grand_total );
1276
		$this->save();
1277
1278
		return $grand_total;
1279
	}
1280
1281
	/**
1282
	 * Get item subtotal - this is the cost before discount.
1283
	 *
1284
	 * @param object $item
1285
	 * @param bool $inc_tax (default: false).
1286
	 * @param bool $round (default: true).
1287
	 * @return float
1288
	 */
1289 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...
1290
		$subtotal = 0;
1291
1292
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1293
			if ( $inc_tax ) {
1294
				$subtotal = ( $item->get_subtotal() + $item->get_subtotal_tax() ) / max( 1, $item->get_quantity() );
1295
			} else {
1296
				$subtotal = ( $item->get_subtotal() / max( 1, $item->get_quantity() ) );
1297
			}
1298
1299
			$subtotal = $round ? number_format( (float) $subtotal, wc_get_price_decimals(), '.', '' ) : $subtotal;
1300
		}
1301
1302
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1303
	}
1304
1305
	/**
1306
	 * Get line subtotal - this is the cost before discount.
1307
	 *
1308
	 * @param object $item
1309
	 * @param bool $inc_tax (default: false).
1310
	 * @param bool $round (default: true).
1311
	 * @return float
1312
	 */
1313 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...
1314
		$subtotal = 0;
1315
1316
		if ( is_callable( array( $item, 'get_subtotal' ) ) ) {
1317
			if ( $inc_tax ) {
1318
				$subtotal = $item->get_subtotal() + $item->get_subtotal_tax();
1319
			} else {
1320
				$subtotal = $item->get_subtotal();
1321
			}
1322
1323
			$subtotal = $round ? round( $subtotal, wc_get_price_decimals() ) : $subtotal;
1324
		}
1325
1326
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $subtotal, $this, $item, $inc_tax, $round );
1327
	}
1328
1329
	/**
1330
	 * Calculate item cost - useful for gateways.
1331
	 *
1332
	 * @param object $item
1333
	 * @param bool $inc_tax (default: false).
1334
	 * @param bool $round (default: true).
1335
	 * @return float
1336
	 */
1337 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...
1338
		$total = 0;
1339
1340
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1341
			if ( $inc_tax ) {
1342
				$total = ( $item->get_total() + $item->get_total_tax() ) / max( 1, $item->get_quantity() );
1343
			} else {
1344
				$total = $item->get_total() / max( 1, $item->get_quantity() );
1345
			}
1346
1347
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1348
		}
1349
1350
		return apply_filters( 'woocommerce_order_amount_item_total', $total, $this, $item, $inc_tax, $round );
1351
	}
1352
1353
	/**
1354
	 * Calculate line total - useful for gateways.
1355
	 *
1356
	 * @param object $item
1357
	 * @param bool $inc_tax (default: false).
1358
	 * @param bool $round (default: true).
1359
	 * @return float
1360
	 */
1361 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...
1362
		$total = 0;
1363
1364
		if ( is_callable( array( $item, 'get_total' ) ) ) {
1365
			// Check if we need to add line tax to the line total.
1366
			$total = $inc_tax ? $item->get_total() + $item->get_total_tax() : $item->get_total();
1367
1368
			// Check if we need to round.
1369
			$total = $round ? round( $total, wc_get_price_decimals() ) : $total;
1370
		}
1371
1372
		return apply_filters( 'woocommerce_order_amount_line_total', $total, $this, $item, $inc_tax, $round );
1373
	}
1374
1375
	/**
1376
	 * Get item tax - useful for gateways.
1377
	 *
1378
	 * @param mixed $item
1379
	 * @param bool $round (default: true).
1380
	 * @return float
1381
	 */
1382
	public function get_item_tax( $item, $round = true ) {
1383
		$tax = 0;
1384
1385
		if ( is_callable( array( $item, 'get_total_tax' ) ) ) {
1386
			$tax = $item->get_total_tax() / max( 1, $item->get_quantity() );
1387
			$tax = $round ? wc_round_tax_total( $tax ) : $tax;
1388
		}
1389
1390
		return apply_filters( 'woocommerce_order_amount_item_tax', $tax, $item, $round, $this );
1391
	}
1392
1393
	/**
1394
	 * Get line tax - useful for gateways.
1395
	 *
1396
	 * @param mixed $item
1397
	 * @return float
1398
	 */
1399
	public function get_line_tax( $item ) {
1400
		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 );
1401
	}
1402
1403
	/**
1404
	 * Gets line subtotal - formatted for display.
1405
	 *
1406
	 * @param array  $item
1407
	 * @param string $tax_display
1408
	 * @return string
1409
	 */
1410
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1411
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1412
1413
		if ( 'excl' == $tax_display ) {
1414
			$ex_tax_label = $this->get_prices_include_tax() ? 1 : 0;
1415
1416
			$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...
1417
		} else {
1418
			$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...
1419
		}
1420
1421
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1422
	}
1423
1424
	/**
1425
	 * Gets order total - formatted for display.
1426
	 * @return string
1427
	 */
1428
	public function get_formatted_order_total() {
1429
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
1430
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1431
	}
1432
1433
	/**
1434
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1435
	 *
1436
	 * @param bool $compound (default: false).
1437
	 * @param string $tax_display (default: the tax_display_cart value).
1438
	 * @return string
1439
	 */
1440
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1441
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1442
		$subtotal    = 0;
1443
1444
		if ( ! $compound ) {
1445
			foreach ( $this->get_items() as $item ) {
1446
				$subtotal += $item->get_subtotal();
1447
1448
				if ( 'incl' === $tax_display ) {
1449
					$subtotal += $item->get_subtotal_tax();
1450
				}
1451
			}
1452
1453
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1454
1455
			if ( 'excl' === $tax_display && $this->get_prices_include_tax() ) {
1456
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1457
			}
1458
		} else {
1459
			if ( 'incl' === $tax_display ) {
1460
				return '';
1461
			}
1462
1463
			foreach ( $this->get_items() as $item ) {
1464
				$subtotal += $item->get_subtotal();
1465
			}
1466
1467
			// Add Shipping Costs.
1468
			$subtotal += $this->get_shipping_total();
1469
1470
			// Remove non-compound taxes.
1471
			foreach ( $this->get_taxes() as $tax ) {
1472
				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...
1473
					continue;
1474
				}
1475
				$subtotal = $subtotal + $tax->get_tax_total() + $tax->get_shipping_tax_total();
1476
			}
1477
1478
			// Remove discounts.
1479
			$subtotal = $subtotal - $this->get_total_discount();
1480
			$subtotal = wc_price( $subtotal, array( 'currency' => $this->get_currency() ) );
1481
		}
1482
1483
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1484
	}
1485
1486
	/**
1487
	 * Gets shipping (formatted).
1488
	 *
1489
	 * @return string
1490
	 */
1491
	public function get_shipping_to_display( $tax_display = '' ) {
1492
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1493
1494
		if ( $this->get_shipping_total() != 0 ) {
1495
1496
			if ( $tax_display == 'excl' ) {
1497
1498
				// Show shipping excluding tax.
1499
				$shipping = wc_price( $this->get_shipping_total(), array( 'currency' => $this->get_currency() ) );
1500
1501 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...
1502
					$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 );
1503
				}
1504
			} else {
1505
1506
				// Show shipping including tax.
1507
				$shipping = wc_price( $this->get_shipping_total() + $this->get_shipping_tax(), array( 'currency' => $this->get_currency() ) );
1508
1509 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...
1510
					$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 );
1511
				}
1512
			}
1513
1514
			$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 );
1515
1516
		} elseif ( $this->get_shipping_method() ) {
1517
			$shipping = $this->get_shipping_method();
1518
		} else {
1519
			$shipping = __( 'Free!', 'woocommerce' );
1520
		}
1521
1522
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1523
	}
1524
1525
	/**
1526
	 * Get the discount amount (formatted).
1527
	 * @since  2.3.0
1528
	 * @return string
1529
	 */
1530
	public function get_discount_to_display( $tax_display = '' ) {
1531
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1532
		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 );
1533
	}
1534
1535
	/**
1536
	 * Get totals for display on pages and in emails.
1537
	 *
1538
	 * @param mixed $tax_display
1539
	 * @return array
1540
	 */
1541
	public function get_order_item_totals( $tax_display = '' ) {
1542
		$tax_display = $tax_display ? $tax_display : get_option( 'woocommerce_tax_display_cart' );
1543
		$total_rows  = array();
1544
1545
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1546
			$total_rows['cart_subtotal'] = array(
1547
				'label' => __( 'Subtotal:', 'woocommerce' ),
1548
				'value'    => $subtotal,
1549
			);
1550
		}
1551
1552
		if ( $this->get_total_discount() > 0 ) {
1553
			$total_rows['discount'] = array(
1554
				'label' => __( 'Discount:', 'woocommerce' ),
1555
				'value'    => '-' . $this->get_discount_to_display( $tax_display ),
1556
			);
1557
		}
1558
1559
		if ( $this->get_shipping_method() ) {
1560
			$total_rows['shipping'] = array(
1561
				'label' => __( 'Shipping:', 'woocommerce' ),
1562
				'value'    => $this->get_shipping_to_display( $tax_display ),
1563
			);
1564
		}
1565
1566
		if ( $fees = $this->get_fees() ) {
1567
			foreach ( $fees as $id => $fee ) {
1568
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', empty( $fee['line_total'] ) && empty( $fee['line_tax'] ), $id ) ) {
1569
					continue;
1570
				}
1571
				$total_rows[ 'fee_' . $fee->get_id() ] = array(
1572
					'label' => $fee->get_name() . ':',
1573
					'value' => wc_price( 'excl' === $tax_display ? $fee->get_total() : $fee->get_total() + $fee->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1574
				);
1575
			}
1576
		}
1577
1578
		// Tax for tax exclusive prices.
1579
		if ( 'excl' === $tax_display ) {
1580
1581
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1582
1583
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1584
1585
					$total_rows[ sanitize_title( $code ) ] = array(
1586
						'label' => $tax->label . ':',
1587
						'value'    => $tax->formatted_amount,
1588
					);
1589
				}
1590
			} else {
1591
1592
				$total_rows['tax'] = array(
1593
					'label' => WC()->countries->tax_or_vat() . ':',
1594
					'value'    => wc_price( $this->get_total_tax(), array( 'currency' => $this->get_currency() ) ),
1595
				);
1596
			}
1597
		}
1598
1599
		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...
1600
			$total_rows['payment_method'] = array(
1601
				'label' => __( 'Payment Method:', 'woocommerce' ),
1602
				'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...
1603
			);
1604
		}
1605
1606
		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...
1607
			foreach ( $refunds as $id => $refund ) {
1608
				$total_rows[ 'refund_' . $id ] = array(
1609
					'label' => $refund->get_reason() ? $refund->get_reason() : __( 'Refund', 'woocommerce' ) . ':',
1610
					'value'    => wc_price( '-' . $refund->get_amount(), array( 'currency' => $this->get_currency() ) ),
1611
				);
1612
			}
1613
		}
1614
1615
		$total_rows['order_total'] = array(
1616
			'label' => __( 'Total:', 'woocommerce' ),
1617
			'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...
1618
		);
1619
1620
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1621
	}
1622
1623
	/*
1624
	|--------------------------------------------------------------------------
1625
	| Conditionals
1626
	|--------------------------------------------------------------------------
1627
	|
1628
	| Checks if a condition is true or false.
1629
	|
1630
	*/
1631
1632
	/**
1633
	 * Checks the order status against a passed in status.
1634
	 *
1635
	 * @return bool
1636
	 */
1637
	public function has_status( $status ) {
1638
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1639
	}
1640
1641
	/**
1642
	 * Check whether this order has a specific shipping method or not.
1643
	 *
1644
	 * @param string $method_id
1645
	 * @return bool
1646
	 */
1647
	public function has_shipping_method( $method_id ) {
1648
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1649
			if ( strpos( $shipping_method->get_method_id(), $method_id ) === 0 ) {
1650
				return true;
1651
			}
1652
		}
1653
		return false;
1654
	}
1655
1656
	/**
1657
	 * Returns true if the order contains a free product.
1658
	 * @since 2.5.0
1659
	 * @return bool
1660
	 */
1661
	public function has_free_item() {
1662
		foreach ( $this->get_items() as $item ) {
1663
			if ( ! $item->get_total() ) {
1664
				return true;
1665
			}
1666
		}
1667
		return false;
1668
	}
1669
}
1670