Completed
Pull Request — master (#10259)
by Mike
08:16
created

WC_Abstract_Order::add_fee()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 21
Code Lines 14

Duplication

Lines 4
Ratio 19.05 %

Importance

Changes 6
Bugs 0 Features 1
Metric Value
c 6
b 0
f 1
dl 4
loc 21
rs 9.0534
cc 4
eloc 14
nc 2
nop 1
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 20 and the first side effect is on line 3.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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.6.0
16
 * @package     WooCommerce/Classes
17
 * @category    Class
18
 * @author      WooThemes
19
 */
20
abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order implements WC_Data {
21
22
	/**
23
	 * Order Data array, with defaults. This is the core order data exposed
24
	 * in APIs since 2.6.0.
25
	 *
26
	 * Notes:
27
	 * order_tax = Sum of all taxes.
28
	 * cart_tax = cart_tax is the new name for the legacy 'order_tax' which is the tax for items only, not shipping.
29
	 *
30
	 * @since 2.6.0
31
	 * @var array
32
	 */
33
	protected $_data = array(
34
		'id'                 => 0,
35
		'parent_id'          => 0,
36
		'status'             => '',
37
		'type'               => 'shop_order',
38
		'order_key'          => '',
39
		'currency'           => '',
40
		'version'            => '',
41
		'prices_include_tax' => false,
42
		'date_created'       => '',
43
		'date_modified'      => '',
44
		'customer_id'        => 0,
45
		'discount_total'     => 0,
46
		'discount_tax'       => 0,
47
		'shipping_total'     => 0,
48
		'shipping_tax'       => 0,
49
		'cart_tax'           => 0,
50
		'total'              => 0,
51
		'total_tax'          => 0,
52
	);
53
54
	/**
55
	 * Stores additonal meta data.
56
	 * Order meta keys can be used once.
57
	 * @var array
58
	 */
59
	protected $_meta_data = array();
60
61
	/**
62
	 * Get the order if ID is passed, otherwise the order is new and empty.
63
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
64
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
65
	 * methods that will be maintained going forward.
66
	 *
67
	 * @param  int|object|WC_Order $order Order to init.
68
	 */
69 View Code Duplication
	public function __construct( $order = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
70
		if ( is_numeric( $order ) && $order > 0 ) {
71
			$this->read( $order );
72
		} elseif ( $order instanceof self ) {
73
			$this->read( absint( $order->get_id() ) );
74
		} elseif ( ! empty( $order->ID ) ) {
75
			$this->read( absint( $order->ID ) );
76
		}
77
	}
78
79
	/**
80
	 * Change data to JSON format.
81
	 * @return string Data in JSON format.
82
	 */
83
	public function __toString() {
84
		return json_encode( $this->get_data() );
85
	}
86
87
	/*
88
	|--------------------------------------------------------------------------
89
	| CRUD methods
90
	|--------------------------------------------------------------------------
91
	|
92
	| Methods which create, read, update and delete orders from the database.
93
	| Written in abstract fashion so that the way orders are stored can be
94
	| changed more easily in the future.
95
	|
96
	| A save method is included for convenience (chooses update or create based
97
	| on if the order exists yet).
98
	|
99
	*/
100
101
	/**
102
	 * Get internal type (post type.)
103
	 * @return string
104
	 */
105
	public function get_type() {
106
		return 'shop_order';
107
	}
108
109
	/**
110
	 * Get a title for the new post type.
111
	 */
112
	protected function get_post_title() {
113
		return sprintf( __( 'Order &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
114
	}
115
116
	/**
117
	 * Insert data into the database.
118
	 * @since 2.6.0
119
	 */
120
	public function create() {
121
		$this->set_order_key( 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) );
122
		$this->set_date_created( current_time( 'timestamp' ) );
123
124
		$order_id = wp_insert_post( apply_filters( 'woocommerce_new_order_data', array(
125
			'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
126
			'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
127
			'post_type'     => $this->get_type(),
128
			'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
129
			'ping_status'   => 'closed',
130
			'post_author'   => 1,
131
			'post_title'    => $this->get_post_title(),
132
			'post_password' => uniqid( 'order_' ),
133
			'post_parent'   => $this->get_parent_id(),
134
		) ), true );
135
136
		if ( $order_id ) {
137
			$this->set_id( $order_id );
138
139
			// Set meta data
140
			$this->update_post_meta( '_customer_user', $this->get_customer_id() );
141
			$this->update_post_meta( '_order_currency', $this->get_currency() );
142
			$this->update_post_meta( '_order_key', $this->get_order_key() );
143
			$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
144
			$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
145
			$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
146
			$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
147
			$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
148
			$this->update_post_meta( '_order_total', $this->get_total( true ) );
149
			$this->update_post_meta( '_order_version', $this->get_version() );
150
			$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
151
152
			foreach ( $this->get_meta_data() as $key => $value ) {
153
				$this->update_post_meta( '_' . $key, $value );
154
			}
155
		}
156
	}
157
158
	/**
159
	 * Read from the database.
160
	 * @since 2.6.0
161
	 * @param int $id ID of object to read.
162
	 */
163
	public function read( $id ) {
164
		if ( empty( $id ) || ! ( $post_object = get_post( $id ) ) ) {
165
			return;
166
		}
167
		$order_id = absint( $post_object->ID );
168
169
		// Map standard post data
170
		$this->set_id( $order_id );
171
		$this->set_date_created( $post_object->post_date );
172
		$this->set_date_modified( $post_object->post_modified );
173
		$this->set_status( $post_object->post_status );
174
		$this->set_order_type( $post_object->post_type );
175
		$this->set_customer_id( get_post_meta( $order_id, '_customer_user', true ) );
176
		$this->set_order_key( get_post_meta( $order_id, '_order_key', true ) );
177
		$this->set_currency( get_post_meta( $order_id, '_order_currency', true ) );
178
		$this->set_discount_total( get_post_meta( $order_id, '_cart_discount', true ) );
179
		$this->set_discount_tax( get_post_meta( $order_id, '_cart_discount_tax', true ) );
180
		$this->set_shipping_total( get_post_meta( $order_id, '_order_shipping', true ) );
181
		$this->set_shipping_tax( get_post_meta( $order_id, '_order_shipping_tax', true ) );
182
		$this->set_cart_tax( get_post_meta( $order_id, '_order_tax', true ) );
183
		$this->set_total( get_post_meta( $order_id, '_order_total', true ) );
184
185
		// Orders store the state of prices including tax when created.
186
		$this->set_prices_include_tax( metadata_exists( 'post', $order_id, '_prices_include_tax' ) ? 'yes' === get_post_meta( $order_id, '_prices_include_tax', true ) : 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
187
188
		// Load meta data
189
		$this->read_meta_data();
190
	}
191
192
	/**
193
	 * Post meta update wrapper. Sets or deletes based on value.
194
	 * @since 2.6.0
195
	 */
196
	protected function update_post_meta( $key, $value ) {
197
		if ( '' !== $value ) {
198
			update_post_meta( $this->get_id(), $key, $value );
199
		} else {
200
			delete_post_meta( $this->get_id(), $key );
201
		}
202
	}
203
204
	/**
205
	 * Update data in the database.
206
	 * @since 2.6.0
207
	 */
208
	public function update() {
209
		global $wpdb;
210
211
		$order_id = $this->get_id();
212
213
		$wpdb->update(
214
			$wpdb->posts,
215
			array(
216
				'post_date'     => date( 'Y-m-d H:i:s', $this->get_date_created() ),
217
				'post_date_gmt' => get_gmt_from_date( date( 'Y-m-d H:i:s', $this->get_date_created() ) ),
218
				'post_status'   => 'wc-' . ( $this->get_status() ? $this->get_status() : apply_filters( 'woocommerce_default_order_status', 'pending' ) ),
219
				'post_parent'   => $this->get_parent_id(),
220
			),
221
			array(
222
				'ID' => $order_id
223
			)
224
		);
225
226
		// Update meta data
227
		$this->update_post_meta( '_customer_user', $this->get_customer_id() );
228
		$this->update_post_meta( '_order_currency', $this->get_currency() );
229
		$this->update_post_meta( '_order_key', $this->get_order_key() );
230
		$this->update_post_meta( '_cart_discount', $this->get_discount_total( true ) );
231
		$this->update_post_meta( '_cart_discount_tax', $this->get_discount_tax( true ) );
232
		$this->update_post_meta( '_order_shipping', $this->get_shipping_total( true ) );
233
		$this->update_post_meta( '_order_shipping_tax', $this->get_shipping_tax( true ) );
234
		$this->update_post_meta( '_order_tax', $this->get_cart_tax( true ) );
235
		$this->update_post_meta( '_order_total', $this->get_total( true ) );
236
		$this->update_post_meta( '_prices_include_tax', $this->get_prices_include_tax() );
237
238
		foreach ( $this->get_meta_data() as $key => $value ) {
239
			$this->update_post_meta( '_' . $key, $value );
240
		}
241
	}
242
243
	/**
244
	 * Delete data from the database.
245
	 * @since 2.6.0
246
	 */
247
	public function delete() {
248
		wp_delete_post( $this->get_id() );
249
	}
250
251
	/**
252
	 * Save data to the database.
253
	 * @since 2.6.0
254
	 * @return int order ID
255
	 */
256
	public function save() {
257
		$this->set_version( WC_VERSION );
258
259
		if ( ! $this->get_id() ) {
260
			$this->create();
261
		} else {
262
			$this->update();
263
		}
264
265
		$this->save_meta_data();
266
		wc_delete_shop_order_transients( $this->get_id() );
267
268
		return $this->get_id();
269
	}
270
271
	/*
272
	|--------------------------------------------------------------------------
273
	| Meta Data Handling
274
	|--------------------------------------------------------------------------
275
	*/
276
277
	/**
278
	 * Get All Meta Data
279
	 * @return array
280
	 */
281
	public function get_meta_data() {
282
		return $this->_meta_data;
283
	}
284
285
	/**
286
	 * Internal meta keys we don't want exposed as part of meta_data. This is in
287
	 * addition to all data props with _ prefix.
288
	 * @return array()
0 ignored issues
show
Documentation introduced by
The doc-type array() could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
289
	 */
290
	protected function prefix_key( $key ) {
291
		return '_' === substr( $key, 0, 1 ) ? $key : '_' . $key;
292
	}
293
294
	/**
295
	 * Internal meta keys we don't want exposed as part of meta_data. This is in
296
	 * addition to all data props with _ prefix.
297
	 * @return array()
0 ignored issues
show
Documentation introduced by
The doc-type array() could not be parsed: Expected "|" or "end of type", but got "(" at position 5. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
298
	 */
299
	protected function get_internal_meta_keys() {
300
		return array_merge( array_map( array( $this, 'prefix_key' ), array_keys( $this->_data ) ), array( '_customer_user', '_order_key', '_order_currency', '_billing_first_name', '_billing_last_name', '_billing_company', '_billing_address_1', '_billing_address_2', '_billing_city', '_billing_state', '_billing_postcode', '_billing_country', '_billing_email', '_billing_phone', '_shipping_first_name', '_shipping_last_name', '_shipping_company', '_shipping_address_1', '_shipping_address_2', '_shipping_city', '_shipping_state', '_shipping_postcode', '_shipping_country', '_completed_date', '_paid_date', '_edit_lock', '_edit_last', '_cart_discount', '_cart_discount_tax', '_order_shipping', '_order_shipping_tax', '_order_tax', '_order_total', '_order_total', '_payment_method', '_payment_method_title', '_transaction_id', '_customer_ip_address', '_customer_user_agent', '_created_via', '_order_version', '_prices_include_tax', '_customer_note', '_date_completed', '_date_paid' ) );
301
	}
302
303
	/**
304
	 * Get Meta Data by Key
305
	 * @param string $key
306
	 * @param bool $single return first found meta with key, or all with $key
307
	 * @return mixed
308
	 */
309 View Code Duplication
	public function get_meta( $key = '', $single = 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...
310
		$meta_ids = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key );
311
		$value    = '';
312
313
		if ( $meta_ids ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $meta_ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
314
			if ( $single ) {
315
				$value = $this->_meta_data[ current( $meta_ids ) ]->value;
316
			} else {
317
				$value = array_intersect_key( $this->_meta_data, $meta_ids );
318
			}
319
		}
320
321
		return $value;
322
	}
323
324
	/**
325
	 * Set all meta data from array.
326
	 * @param array $data Key/Value pairs
327
	 */
328 View Code Duplication
	public function set_meta_data( $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...
329
		if ( ! empty( $data ) && is_array( $data ) ) {
330
			foreach ( $data as $meta_id => $meta ) {
331
				$meta = (array) $meta;
332
				if ( isset( $meta['key'], $meta['value'] ) ) {
333
					$this->_meta_data[ $meta_id ] = (object) array(
334
						'key'   => $meta['key'],
335
						'value' => $meta['value']
336
					);
337
				}
338
			}
339
		}
340
	}
341
342
	/**
343
	 * Add meta data.
344
	 * @param array $data Key/Value pairs
0 ignored issues
show
Bug introduced by
There is no parameter named $data. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
345
	 */
346 View Code Duplication
	public function add_meta_data( $key, $value, $unique = false ) {
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...
347
		if ( $unique ) {
348
			$meta_ids = array_keys( wp_list_pluck( $this->_meta_data, 'key' ), $key );
349
			$this->_meta_data = array_diff_key( $this->_meta_data, array_fill_keys( $meta_ids, '' ) );
350
		}
351
		$this->_meta_data[ 'new-' . sizeof( $this->_meta_data ) ] = (object) array(
352
			'key'   => $key,
353
			'value' => $value,
354
		);
355
	}
356
357
	/**
358
	 * Update meta data by key or ID, if provided.
359
	 * @param  string $key
360
	 * @param  string $value
361
	 * @param  int $meta_id
362
	 */
363 View Code Duplication
	public function update_meta_data( $key, $value, $meta_id = '' ) {
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...
364
		if ( $meta_id && isset( $this->_meta_data[ $meta_id ] ) ) {
365
			$this->_meta_data[ $meta_id ] = (object) array(
366
				'key'   => $key,
367
				'value' => $value,
368
			);
369
		} else {
370
			$this->add_meta_data( $key, $value, true );
371
		}
372
	}
373
374
	/**
375
	 * Read Meta Data from the database. Ignore any internal properties.
376
	 */
377 View Code Duplication
	protected function read_meta_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...
378
		$this->_meta_data = array();
379
380
		if ( ! $this->get_id() ) {
381
			return;
382
		}
383
384
		$cache_key   = WC_Cache_Helper::get_cache_prefix( 'order_meta' ) . $this->get_id();
385
		$cached_meta = wp_cache_get( $cache_key, 'order_meta' );
386
387
		if ( false !== $cached_meta ) {
388
			$this->_meta_data = $cached_meta;
389
		} else {
390
			global $wpdb;
391
392
			$raw_meta_data = $wpdb->get_results( $wpdb->prepare( "SELECT meta_id, meta_key, meta_value FROM {$wpdb->postmeta} WHERE post_id = %d ORDER BY meta_id", $this->get_id() ) );
393
394
			foreach ( $raw_meta_data as $meta ) {
395
				if ( in_array( $meta->meta_key, $this->get_internal_meta_keys() ) ) {
396
					continue;
397
				}
398
				$this->_meta_data[ $meta->meta_id ] = (object) array( 'key' => $meta->meta_key, 'value' => $meta->meta_value );
399
			}
400
401
			wp_cache_set( $cache_key, $this->_meta_data, 'order_meta' );
402
		}
403
	}
404
405
	/**
406
	 * Update Meta Data in the database.
407
	 */
408 View Code Duplication
	protected function save_meta_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...
409
		global $wpdb;
410
		$all_meta_ids = array_map( 'absint', $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM {$wpdb->postmeta} WHERE post_id = %d", $this->get_id() ) . " AND meta_key NOT IN ('" . implode( "','", array_map( 'esc_sql', $this->get_internal_meta_keys() ) ) . "');" ) );
411
		$set_meta_ids = array();
412
413
		foreach ( $this->_meta_data as $meta_id => $meta ) {
414
			if ( 'new' === substr( $meta_id, 0, 3 ) ) {
415
				$set_meta_ids[] = add_metadata( 'post', $this->get_id(), $meta->key, $meta->value, false );
416
			} else {
417
				update_metadata_by_mid( 'post', $meta_id, $meta->value, $meta->key );
418
				$set_meta_ids[] = absint( $meta_id );
419
			}
420
		}
421
422
		// Delete no longer set meta data
423
		$delete_meta_ids = array_diff( $all_meta_ids, $set_meta_ids );
424
425
		foreach ( $delete_meta_ids as $meta_id ) {
426
			delete_metadata_by_mid( 'post', $meta_id );
427
		}
428
429
		WC_Cache_Helper::incr_cache_prefix( 'order_meta' );
430
		$this->read_meta_data();
431
	}
432
433
	/*
434
	|--------------------------------------------------------------------------
435
	| Getters
436
	|--------------------------------------------------------------------------
437
	|
438
	| Methods for getting data from the order object.
439
	|
440
	*/
441
442
	/**
443
	 * Get all class data in array format.
444
	 * @since 2.6.0
445
	 * @return array
446
	 */
447
	public function get_data() {
448
		return array_merge(
449
			$this->_data,
450
			array(
451
				'meta_data'      => $this->get_meta_data(),
452
				'line_items'     => $this->get_items( 'line_item' ),
453
				'tax_lines'      => $this->get_items( 'tax' ),
454
				'shipping_lines' => $this->get_items( 'shipping' ),
455
				'fee_lines'      => $this->get_items( 'fee' ),
456
				'coupon_lines'   => $this->get_items( 'coupon' ),
457
			)
458
		);
459
	}
460
461
	/**
462
	 * Get order ID.
463
	 * @since 2.6.0
464
	 * @return integer
465
	 */
466
	public function get_id() {
467
		return absint( $this->_data['id'] );
468
	}
469
470
	/**
471
	 * Get parent order ID.
472
	 * @since 2.6.0
473
	 * @return integer
474
	 */
475
	public function get_parent_id() {
476
		return absint( $this->_data['parent_id'] );
477
	}
478
479
	/**
480
	 * get_order_number function.
481
	 *
482
	 * Gets the order number for display (by default, order ID).
483
	 *
484
	 * @return string
485
	 */
486
	public function get_order_number() {
487
		return apply_filters( 'woocommerce_order_number', $this->get_id(), $this );
488
	}
489
490
	/**
491
	 * Get order key.
492
	 * @since 2.6.0
493
	 * @return string
494
	 */
495
	public function get_order_key() {
496
		return $this->_data['order_key'];
497
	}
498
499
	/**
500
	 * Gets order currency.
501
	 * @return string
502
	 */
503
	public function get_currency() {
504
		return apply_filters( 'woocommerce_get_currency', $this->_data['currency'], $this );
505
	}
506
507
	/**
508
	 * Get order_version
509
	 * @return string
510
	 */
511
	public function get_version() {
512
		return $this->_data['version'];
513
	}
514
515
	/**
516
	 * Get prices_include_tax
517
	 * @return bool
518
	 */
519
	public function get_prices_include_tax() {
520
		return (bool) $this->_data['prices_include_tax'];
521
	}
522
523
	/**
524
	 * Get Order Type
525
	 * @return string
526
	 */
527
	public function get_order_type() {
528
		return $this->_data['type'];
529
	}
530
531
	/**
532
	 * Get date_created
533
	 * @return int
534
	 */
535
	public function get_date_created() {
536
		return absint( $this->_data['date_created'] );
537
	}
538
539
	/**
540
	 * Get date_modified
541
	 * @return int
542
	 */
543
	public function get_date_modified() {
544
		return absint( $this->_data['date_modified'] );
545
	}
546
547
	/**
548
	 * Get customer_id
549
	 * @return int
550
	 */
551
	public function get_customer_id() {
552
		return absint( $this->_data['customer_id'] );
553
	}
554
555
	/**
556
	 * Return the order statuses without wc- internal prefix.
557
	 * @return string
558
	 */
559
	public function get_status() {
560
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->_data['status'], 0, 3 ) ? substr( $this->_data['status'], 3 ) : $this->_data['status'], $this );
561
	}
562
563
	/**
564
	 * Alias for get_customer_id().
565
	 * @since  2.2
566
	 * @return int
567
	 */
568
	public function get_user_id() {
569
		return $this->get_customer_id();
570
	}
571
572
	/**
573
	 * Get the user associated with the order. False for guests.
574
	 *
575
	 * @since  2.2
576
	 * @return WP_User|false
577
	 */
578
	public function get_user() {
579
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
580
	}
581
582
	/**
583
	 * Get discount_total
584
	 * @param bool $raw Gets raw unfiltered value.
585
	 * @return string
586
	 */
587
	public function get_discount_total( $raw = false ) {
588
		$value = wc_format_decimal( $this->_data['discount_total'] );
589
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_total', $value, $this );
590
	}
591
592
	/**
593
	 * Get discount_tax
594
	 * @param bool $raw Gets raw unfiltered value.
595
	 * @return string
596
	 */
597
	public function get_discount_tax( $raw = false ) {
598
		$value = wc_format_decimal( $this->_data['discount_tax'] );
599
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_discount_tax', $value, $this );
600
	}
601
602
	/**
603
	 * Get shipping_total
604
	 * @param bool $raw Gets raw unfiltered value.
605
	 * @return string
606
	 */
607
	public function get_shipping_total( $raw = false ) {
608
		$value = wc_format_decimal( $this->_data['shipping_total'] );
609
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_total', $value, $this );
610
	}
611
612
	/**
613
	 * Get shipping_tax.
614
	 * @param bool $raw Gets raw unfiltered value.
615
	 * @return string
616
	 */
617
	public function get_shipping_tax( $raw = false ) {
618
		$value = wc_format_decimal( $this->_data['shipping_tax'] );
619
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_shipping_tax', $value, $this );
620
	}
621
622
	/**
623
	 * Gets cart tax amount.
624
	 * @param bool $raw Gets raw unfiltered value.
625
	 * @return float
626
	 */
627
	public function get_cart_tax( $raw = false ) {
628
		$value = wc_format_decimal( $this->_data['cart_tax'] );
629
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_cart_tax', $value, $this );
630
	}
631
632
	/**
633
	 * Gets order grand total. incl. taxes. Used in gateways. Filtered.
634
	 * @param bool $raw Gets raw unfiltered value.
635
	 * @return float
636
	 */
637
	public function get_total( $raw = false ) {
638
		$value = wc_format_decimal( $this->_data['total'], wc_get_price_decimals() );
639
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total', $value, $this );
640
	}
641
642
	/**
643
	 * Get total tax amount. Alias for get_order_tax().
644
	 *
645
	 * @since 2.6.0 woocommerce_order_amount_total_tax filter has been removed to avoid
646
	 * these values being modified and then saved back to the DB. There are
647
	 * other, later hooks available to change totals on display. e.g.
648
	 * woocommerce_get_order_item_totals.
649
	 * @param bool $raw Gets raw unfiltered value.
650
	 * @return float
651
	 */
652
	public function get_total_tax( $raw = false ) {
653
		$value = wc_format_decimal( $this->_data['total_tax'] );
654
		return $raw ? $value : apply_filters( 'woocommerce_order_amount_total_tax', $value, $this );
655
	}
656
657
	/**
658
	 * Gets the total discount amount.
659
	 * @param  bool $ex_tax Show discount excl any tax.
660
	 * @return float
661
	 */
662
	public function get_total_discount( $ex_tax = true ) {
663
		if ( $ex_tax ) {
664
			$total_discount = $this->get_discount_total();
665
		} else {
666
			$total_discount = $this->get_discount_total() + $this->get_discount_tax();
667
		}
668
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
669
	}
670
671
	/**
672
	 * Gets order subtotal.
673
	 * @return float
674
	 */
675
	public function get_subtotal() {
676
		$subtotal = 0;
677
678
		foreach ( $this->get_items() as $item ) {
679
			$subtotal += $item->get_subtotal();
680
		}
681
682
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
683
	}
684
685
	/**
686
	 * Get taxes, merged by code, formatted ready for output.
687
	 *
688
	 * @return array
689
	 */
690
	public function get_tax_totals() {
691
		$tax_totals = array();
692
693
		foreach ( $this->get_items( 'tax' ) as $key => $tax ) {
694
			$code = $tax->get_rate_code();
695
696 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...
697
				$tax_totals[ $code ] = new stdClass();
698
				$tax_totals[ $code ]->amount = 0;
699
			}
700
701
			$tax_totals[ $code ]->id                = $key;
702
			$tax_totals[ $code ]->rate_id           = $tax->get_rate_id();
703
			$tax_totals[ $code ]->is_compound       = $tax->is_compound();
704
			$tax_totals[ $code ]->label             = $tax->get_label();
705
			$tax_totals[ $code ]->amount           += $tax->get_tax_total() + $tax->get_shipping_tax_total();
706
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array( 'currency' => $this->get_currency() ) );
707
		}
708
709
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
710
	}
711
712
	/*
713
	|--------------------------------------------------------------------------
714
	| Setters
715
	|--------------------------------------------------------------------------
716
	|
717
	| Functions for setting order data. These should not update anything in the
718
	| database itself and should only change what is stored in the class
719
	| object. However, for backwards compatibility pre 2.6.0 some of these
720
	| setters may handle both.
721
	|
722
	*/
723
724
	/**
725
	 * Set order ID.
726
	 * @since 2.6.0
727
	 * @param int $value
728
	 */
729
	public function set_id( $value ) {
730
		$this->_data['id'] = absint( $value );
731
	}
732
733
	/**
734
	 * Set parent order ID.
735
	 * @since 2.6.0
736
	 * @param int $value
737
	 */
738
	public function set_parent_id( $value ) {
739
		$this->_data['parent_id'] = absint( $value );
740
	}
741
742
	/**
743
	 * Set order status.
744
	 * @since 2.6.0
745
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
746
	 * @param array details of change
747
	 */
748
	 public function set_status( $new_status ) {
749
 		$old_status = $this->get_status();
750
 		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
751
752
		// If the old status is unknown (e.g. draft) assume its pending for action usage.
753
		if ( ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
754
			$old_status = 'pending';
755
		}
756
757
 		if ( in_array( 'wc-' . $new_status, array_keys( wc_get_order_statuses() ) ) && $new_status !== $old_status ) {
758
 			$this->_data['status'] = 'wc-' . $new_status;
759
 		} else {
760
			$new_status = $old_status;
761
		}
762
763
		return array(
764
			'from' => $old_status,
765
			'to'   => $new_status
766
		);
767
 	}
768
769
	/**
770
	 * Set Order Type
771
	 * @param string $value
772
	 */
773
	public function set_order_type( $value ) {
774
		$this->_data['type'] = $value;
775
	}
776
777
	/**
778
	 * Set order_key.
779
	 * @param string $value Max length 20 chars.
780
	 */
781
	public function set_order_key( $value ) {
782
		$this->_data['order_key'] = substr( $value, 0, 20 );
783
	}
784
785
	/**
786
	 * Set order_version
787
	 * @param string $value
788
	 */
789
	public function set_version( $value ) {
790
		$this->_data['version'] = $value;
791
	}
792
793
	/**
794
	 * Set order_currency
795
	 * @param string $value
796
	 */
797
	public function set_currency( $value ) {
798
		$this->_data['currency'] = $value;
799
	}
800
801
	/**
802
	 * Set prices_include_tax
803
	 * @param bool $value
804
	 */
805
	public function set_prices_include_tax( $value ) {
806
		$this->_data['prices_include_tax'] = (bool) $value;
807
	}
808
809
	/**
810
	 * Set date_created
811
	 * @param string $timestamp Timestamp
812
	 */
813
	public function set_date_created( $timestamp ) {
814
		$this->_data['date_created'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
815
	}
816
817
	/**
818
	 * Set date_modified
819
	 * @param string $timestamp
820
	 */
821
	public function set_date_modified( $timestamp ) {
822
		$this->_data['date_modified'] = is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp );
823
	}
824
825
	/**
826
	 * Set customer_id
827
	 * @param int $value
828
	 */
829
	public function set_customer_id( $value ) {
830
		$this->_data['customer_id'] = absint( $value );
831
	}
832
833
	/**
834
	 * Set discount_total
835
	 * @param string $value
836
	 */
837
	public function set_discount_total( $value ) {
838
		$this->_data['discount_total'] = wc_format_decimal( $value );
839
	}
840
841
	/**
842
	 * Set discount_tax
843
	 * @param string $value
844
	 */
845
	public function set_discount_tax( $value ) {
846
		$this->_data['discount_tax'] = wc_format_decimal( $value );
847
	}
848
849
	/**
850
	 * Set shipping_total
851
	 * @param string $value
852
	 */
853
	public function set_shipping_total( $value ) {
854
		$this->_data['shipping_total'] = wc_format_decimal( $value );
855
	}
856
857
	/**
858
	 * Set shipping_tax
859
	 * @param string $value
860
	 */
861
	public function set_shipping_tax( $value ) {
862
		$this->_data['shipping_tax'] = wc_format_decimal( $value );
863
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
864
	}
865
866
	/**
867
	 * Set cart tax
868
	 * @param string $value
869
	 */
870
	public function set_cart_tax( $value ) {
871
		$this->_data['cart_tax'] = wc_format_decimal( $value );
872
		$this->set_total_tax( $this->get_cart_tax() + $this->get_shipping_tax() );
873
	}
874
875
	/**
876
	 * Sets order tax (sum of cart and shipping tax). Used internaly only.
877
	 * @param string $value
878
	 */
879
	protected function set_total_tax( $value ) {
880
		$this->_data['total_tax'] = wc_format_decimal( $value );
881
	}
882
883
	/**
884
	 * Set total
885
	 * @param string $value
886
	 * @param string $deprecated Function used to set different totals based on this.
887
	 */
888
	public function set_total( $value, $deprecated = '' ) {
889
		if ( $deprecated ) {
890
			_deprecated_argument( 'total_type', '2.6', 'Use dedicated total setter methods instead.' );
891
			return $this->legacy_set_total( $value, $deprecated );
892
		}
893
		$this->_data['total'] = wc_format_decimal( $value, wc_get_price_decimals() );
894
	}
895
896
	/*
897
	|--------------------------------------------------------------------------
898
	| Order Item Handling
899
	|--------------------------------------------------------------------------
900
	|
901
	| Order items are used for products, taxes, shipping, and fees within
902
	| each order.
903
	|
904
	*/
905
906
	/**
907
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
908
	 * @param string $type Order item type. Default null.
909
	 */
910
	public function remove_order_items( $type = null ) {
911
		global $wpdb;
912
		if ( ! empty( $type ) ) {
913
			$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 ) );
914
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->get_id(), $type ) );
915
		} else {
916
			$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() ) );
917
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->get_id() ) );
918
		}
919
	}
920
921
	/**
922
	 * Return an array of items/products within this order.
923
	 * @param string|array $type Types of line items to get (array or string).
924
	 * @return Array of WC_Order_item
925
	 */
926
	public function get_items( $type = 'line_item' ) {
927
		global $wpdb;
928
		$get_items_sql = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d ", $this->get_id() ) . "AND order_item_type IN ( '" . implode( "','", array_map( 'esc_sql', (array) $type ) ) . "' ) ORDER BY order_item_id;";
929
		$items         = $wpdb->get_results( $get_items_sql );
930
		$items         = array_map( array( $this, 'get_item' ), array_combine( wp_list_pluck( $items, 'order_item_id' ), $items ) );
931
932
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
933
	}
934
935
	/**
936
	 * Return an array of fees within this order.
937
	 * @return array
938
	 */
939
	public function get_fees() {
940
		return $this->get_items( 'fee' );
941
	}
942
943
	/**
944
	 * Return an array of taxes within this order.
945
	 * @return array
946
	 */
947
	public function get_taxes() {
948
		return $this->get_items( 'tax' );
949
	}
950
951
	/**
952
	 * Return an array of shipping costs within this order.
953
	 * @return array
954
	 */
955
	public function get_shipping_methods() {
956
		return $this->get_items( 'shipping' );
957
	}
958
959
	/**
960
	 * Gets formatted shipping method title.
961
	 * @return string
962
	 */
963
	public function get_shipping_method() {
964
		$names = array();
965
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
966
			$names[] = $shipping_method->get_name();
967
		}
968
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $names ), $this );
969
	}
970
971
	/**
972
	 * Get coupon codes only.
973
	 * @return array
974
	 */
975
	public function get_used_coupons() {
976
		return array_map( 'trim', wp_list_pluck( $this->get_items( 'coupon' ), 'name' ) );
977
	}
978
979
	/**
980
	 * Gets the count of order items of a certain type.
981
	 *
982
	 * @param string $item_type
983
	 * @return string
984
	 */
985
	public function get_item_count( $item_type = '' ) {
986
		$items = $this->get_items( empty( $item_type ) ? 'line_item' : $item_type );
987
		$count = 0;
988
989
		foreach ( $items as $item ) {
990
			$count += $item->get_qty();
991
		}
992
993
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
994
	}
995
996
	/**
997
	 * Get an order item object, based on it's type.
998
	 * @since  2.6.0
999
	 * @param  int $item_id
1000
	 * @return WC_Order_Item
1001
	 */
1002
	public function get_item( $item_id ) {
1003
		return WC_Order_Factory::get_order_item( $item_id );
1004
	}
1005
1006
	/**
1007
	 * Add a product line item to the order.
1008
	 * Order must be saved prior to adding items.
1009
	 * @param \WC_Product $product
1010
	 * @param array $args
1011
	 * @param array $deprecated qty was passed as arg 2 prior to 2.6.0
1012
	 * @return int order item ID
1013
	 */
1014
	public function add_product( $product, $args = array(), $deprecated = array() ) {
1015
		if ( ! is_array( $args ) ) {
1016
			_deprecated_argument( 'qty', '2.6', 'Pass only product and args' );
1017
			$qty         = $args;
1018
			$args        = $deprecated;
1019
			$args['qty'] = $qty;
1020
		}
1021
1022
		$args = wp_parse_args( $args, array(
1023
			'qty'          => 1,
1024
			'name'         => $product ? $product->get_title() : '',
1025
			'tax_class'    => $product ? $product->get_tax_class() : '',
1026
			'product_id'   => $product ? $product->get_id() : '',
1027
			'variation_id' => $product && isset( $product->variation_id ) ? $product->variation_id : 0,
1028
			'subtotal'     => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
1029
			'total'        => $product ? $product->get_price_excluding_tax( $args['qty'] ) : '',
1030
			'subtotal_tax' => 0,
1031
			'total_tax'    => 0,
1032
			'variation'    => array(),
1033
			'taxes'        => array(
1034
				'subtotal' => array(),
1035
				'total'    => array(),
1036
			),
1037
		) );
1038
1039
		// BW compatibility with old args
1040 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...
1041
			foreach ( $args['totals'] as $key => $value ) {
1042
				if ( 'tax' === $key ) {
1043
					$args['total_tax'] = $value;
1044
				} elseif ( 'tax_data' === $key ) {
1045
					$args['taxes'] = $value;
1046
				} else {
1047
					$args[ $key ] = $value;
1048
				}
1049
			}
1050
		}
1051
1052
		$item = new WC_Order_Item_Product( $args );
1053
1054
		// Handle backorders
1055 View Code Duplication
		if ( $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1056
			$item->add_meta_data( apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ), true );
1057
		}
1058
1059
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1060
		$item->save();
1061
1062 View Code Duplication
		if ( has_action( 'woocommerce_order_add_product' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1063
			_deprecated_function( 'Action: woocommerce_order_add_product', '2.6', 'Use woocommerce_new_order_item action instead.' );
1064
			do_action( 'woocommerce_order_add_product', $this->get_id(), $item->get_id(), $product, $qty, $args );
1065
		}
1066
1067
		return $item->get_id();
1068
	}
1069
1070
	/**
1071
	 * Add coupon code to the order.
1072
	 * Order must be saved prior to adding items.
1073
	 * @param array $args
1074
	 * @param int $deprecated1 2.6.0 code, discount, tax were passed.
1075
	 * @param int $deprecated2 2.6.0 code, discount, tax were passed.
1076
	 * @return int order item ID
1077
	 */
1078
	public function add_coupon( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
1079
		if ( ! is_array( $args ) ) {
1080
			_deprecated_argument( 'code', '2.6', 'Pass only an array of args' );
1081
			$args = array(
1082
				'code'         => $args,
1083
				'discount'     => $deprecated1,
1084
				'discount_tax' => $deprecated2,
1085
			);
1086
		}
1087
1088
		$args = wp_parse_args( $args, array(
1089
			'code'         => '',
1090
			'discount'     => 0,
1091
			'discount_tax' => 0,
1092
		) );
1093
1094
		$item = new WC_Order_Item_Coupon( $args );
1095
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1096
		$item->save();
1097
1098
		if ( has_action( 'woocommerce_order_add_coupon' ) ) {
1099
			_deprecated_function( 'Action: woocommerce_order_add_coupon', '2.6', 'Use woocommerce_new_order_item action instead.' );
1100
			do_action( 'woocommerce_order_add_coupon', $this->get_id(), $item->get_id(), $args['code'], $args['discount'], $args['discount_tax'] );
1101
		}
1102
1103
		return $item->get_id();
1104
	}
1105
1106
	/**
1107
	 * Add a tax row to the order.
1108
	 * Order must be saved prior to adding items.
1109
	 * @since 2.2
1110
	 * @param array $args
1111
	 * @param int $deprecated1 2.6.0 tax_rate_id, amount, shipping amount.
1112
	 * @param int $deprecated2 2.6.0 tax_rate_id, amount, shipping amount.
1113
	 * @return int order item ID
1114
	 */
1115
	public function add_tax( $args = array(), $deprecated1 = 0, $deprecated2 = 0 ) {
1116
		if ( ! is_array( $args ) ) {
1117
			_deprecated_argument( 'tax_rate_id', '2.6', 'Pass only an array of args' );
1118
			$args = array(
1119
				'rate_id'            => $args,
1120
				'tax_total'          => $deprecated1,
1121
				'shipping_tax_total' => $deprecated2,
1122
			);
1123
		}
1124
1125
		$args = wp_parse_args( $args, array(
1126
			'rate_id'            => '',
1127
			'tax_total'          => 0,
1128
			'shipping_tax_total' => 0,
1129
			'rate_code'          => isset( $args['rate_id'] ) ? WC_Tax::get_rate_code( $args['rate_id'] ) : '',
1130
			'label'              => isset( $args['rate_id'] ) ? WC_Tax::get_rate_label( $args['rate_id'] ) : '',
1131
			'compound'           => isset( $args['rate_id'] ) ? WC_Tax::is_compound( $args['rate_id'] ) : '',
1132
		) );
1133
1134
		$item = new WC_Order_Item_Tax( $args );
1135
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1136
		$item->save();
1137
1138
		if ( has_action( 'woocommerce_order_add_tax' ) ) {
1139
			_deprecated_function( 'Action: woocommerce_order_add_tax', '2.6', 'Use woocommerce_new_order_item action instead.' );
1140
			do_action( 'woocommerce_order_add_tax', $this->get_id(), $item->get_id(), $args['rate_id'], $args['tax_total'], $args['shipping_tax_total'] );
1141
		}
1142
1143
		return $item->get_id();
1144
	}
1145
1146
	/**
1147
	 * Add a shipping row to the order.
1148
	 * Order must be saved prior to adding items.
1149
	 * @param WC_Shipping_Rate shipping_rate
1150
	 * @return int order item ID
1151
	 */
1152
	public function add_shipping( $shipping_rate ) {
1153
		$item = new WC_Order_Item_Shipping( array(
0 ignored issues
show
Documentation introduced by
array('method_title' => ..._rate->get_meta_data()) is of type array<string,?,{"method_...":"?","meta_data":"?"}>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1154
			'method_title' => $shipping_rate->label,
1155
			'method_id'    => $shipping_rate->id,
1156
			'total'        => wc_format_decimal( $shipping_rate->cost ),
1157
			'taxes'        => $shipping_rate->taxes,
1158
			'meta_data'    => $shipping_rate->get_meta_data(),
1159
		) );
1160
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1161
		$item->save();
1162
1163 View Code Duplication
		if ( has_action( 'woocommerce_order_add_shipping' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1164
			_deprecated_function( 'Action: woocommerce_order_add_shipping', '2.6', 'Use woocommerce_new_order_item action instead.' );
1165
			do_action( 'woocommerce_order_add_shipping', $this->get_id(), $item->get_id(), $shipping_rate );
1166
		}
1167
1168
		return $item->get_id();
1169
	}
1170
1171
	/**
1172
	 * Add a fee to the order.
1173
	 * Order must be saved prior to adding items.
1174
	 * @param object $fee
1175
	 * @return int updated order item ID
1176
	 */
1177
	public function add_fee( $fee ) {
1178
		$item = new WC_Order_Item_Fee( array(
0 ignored issues
show
Documentation introduced by
array('name' => $fee->na...al' => $fee->tax_data)) is of type array<string,?,{"name":"...?,{\"total\":\"?\"}>"}>, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1179
			'name'      => $fee->name,
1180
			'tax_class' => $fee->taxable ? $fee->tax_class : 0,
1181
			'total'     => $fee->amount,
1182
			'total_tax' => $fee->tax,
1183
			'taxes'     => array(
1184
				'total' => $fee->tax_data,
1185
			),
1186
		) );
1187
1188
		$item->set_order_id( $this->get_id() ? $this->get_id() : $this->save() );
1189
		$item->save();
1190
1191 View Code Duplication
		if ( has_action( 'woocommerce_order_add_fee' ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

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