Completed
Pull Request — master (#11762)
by Mike
13:05
created

WC_Order   D

Complexity

Total Complexity 204

Size/Duplication

Total Lines 1636
Duplicated Lines 6.6 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 9
Bugs 0 Features 1
Metric Value
c 9
b 0
f 1
dl 108
loc 1636
rs 4.4102
wmc 204
lcom 1
cbo 5

111 Methods

Rating   Name   Duplication   Size   Complexity  
D payment_complete() 0 44 13
C get_formatted_order_total() 0 32 12
B create() 0 41 2
A read() 0 47 2
A update() 0 63 2
C set_status() 0 22 7
A update_status() 0 12 3
A status_transition() 0 20 3
A get_data() 14 14 1
A get_order_number() 0 3 1
A get_order_key() 0 3 1
A get_customer_id() 0 3 1
A get_user_id() 0 3 1
A get_user() 0 3 2
A get_billing_first_name() 0 3 1
A get_billing_last_name() 0 3 1
A get_billing_company() 0 3 1
A get_billing_address_1() 0 3 1
A get_billing_address_2() 0 3 1
A get_billing_city() 0 3 1
A get_billing_state() 0 3 1
A get_billing_postcode() 0 3 1
A get_billing_country() 0 3 1
A get_billing_email() 0 3 1
A get_billing_phone() 0 3 1
A get_shipping_first_name() 0 3 1
A get_shipping_last_name() 0 3 1
A get_shipping_company() 0 3 1
A get_shipping_address_1() 0 3 1
A get_shipping_address_2() 0 3 1
A get_shipping_city() 0 3 1
A get_shipping_state() 0 3 1
A get_shipping_postcode() 0 3 1
A get_shipping_country() 0 3 1
A get_payment_method() 0 3 1
A get_payment_method_title() 0 3 1
A get_transaction_id() 0 3 1
A get_customer_ip_address() 0 3 1
A get_customer_user_agent() 0 3 1
A get_created_via() 0 3 1
A get_customer_note() 0 3 1
A get_date_completed() 0 3 1
A get_date_paid() 0 3 1
A get_address() 0 3 2
A get_shipping_address_map_url() 0 4 1
A get_formatted_billing_full_name() 0 3 1
A get_formatted_shipping_full_name() 0 3 1
A get_formatted_billing_address() 0 3 1
A get_formatted_shipping_address() 0 7 3
A get_cart_hash() 0 3 1
A set_order_key() 0 3 1
A set_customer_id() 0 3 1
A set_billing_first_name() 0 3 1
A set_billing_last_name() 0 3 1
A set_billing_company() 0 3 1
A set_billing_address_1() 0 3 1
A set_billing_address_2() 0 3 1
A set_billing_city() 0 3 1
A set_billing_state() 0 3 1
A set_billing_postcode() 0 3 1
A set_billing_country() 0 3 1
A maybe_set_user_billing_email() 0 9 4
A set_billing_email() 0 6 3
A set_billing_phone() 0 3 1
A set_shipping_first_name() 0 3 1
A set_shipping_last_name() 0 3 1
A set_shipping_company() 0 3 1
A set_shipping_address_1() 0 3 1
A set_shipping_address_2() 0 3 1
A set_shipping_city() 0 3 1
A set_shipping_state() 0 3 1
A set_shipping_postcode() 0 3 1
A set_shipping_country() 0 3 1
A set_payment_method() 0 11 3
A set_payment_method_title() 0 3 1
A set_transaction_id() 0 3 1
A set_customer_ip_address() 0 3 1
A set_customer_user_agent() 0 3 1
A set_created_via() 0 3 1
A set_customer_note() 0 3 1
A set_date_completed() 0 3 2
A set_date_paid() 0 3 2
A set_cart_hash() 0 3 1
A key_is_valid() 0 3 1
A has_cart_hash() 0 3 1
A is_editable() 0 3 1
A is_paid() 0 3 1
A is_download_permitted() 0 3 3
A needs_shipping_address() 0 20 4
B has_downloadable_item() 0 8 6
A needs_payment() 0 4 2
A get_checkout_payment_url() 3 15 4
A get_checkout_order_received_url() 0 11 3
A get_cancel_order_url() 8 8 1
A get_cancel_order_url_raw() 9 9 1
A get_cancel_endpoint() 0 12 3
A get_view_order_url() 0 3 1
C add_order_note() 0 37 7
B get_customer_order_notes() 0 24 3
A get_refunds() 0 8 1
A get_total_refunded() 13 13 1
A get_total_tax_refunded() 14 14 1
A get_total_shipping_refunded() 14 14 1
B get_item_count_refunded() 0 17 5
A get_total_qty_refunded() 0 9 3
A get_qty_refunded_for_item() 11 11 4
A get_total_refunded_for_item() 11 11 4
A get_tax_refunded_for_item() 11 11 4
A get_total_tax_refunded_by_rate_id() 0 12 4
A get_remaining_refund_amount() 0 3 1
A get_remaining_refund_items() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_Order often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_Order, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
if ( ! defined( 'ABSPATH' ) ) {
4
	exit;
5
}
6
7
/**
8
 * Order Class.
9
 *
10
 * These are regular WooCommerce orders, which extend the abstract order class.
11
 *
12
 * @class    WC_Order
13
 * @version  2.2.0
14
 * @package  WooCommerce/Classes
15
 * @category Class
16
 * @author   WooThemes
17
 */
18
class WC_Order extends WC_Abstract_Order {
19
20
	/**
21
	 * Data stored in meta keys, but not considered "meta" for an order.
22
	 * @since 2.7.0
23
	 * @var array
24
	 */
25
	protected $_internal_meta_keys = array(
26
		'_customer_user', '_order_key', '_order_currency', '_billing_first_name',
27
		'_billing_last_name', '_billing_company', '_billing_address_1', '_billing_address_2',
28
		'_billing_city', '_billing_state', '_billing_postcode', '_billing_country',
29
		'_billing_email', '_billing_phone', '_shipping_first_name', '_shipping_last_name',
30
		'_shipping_company', '_shipping_address_1', '_shipping_address_2', '_shipping_city',
31
		'_shipping_state', '_shipping_postcode', '_shipping_country', '_completed_date',
32
		'_paid_date', '_edit_lock', '_edit_last', '_cart_discount', '_cart_discount_tax',
33
		'_order_shipping', '_order_shipping_tax', '_order_tax', '_order_total', '_order_total',
34
		'_payment_method', '_payment_method_title', '_transaction_id', '_customer_ip_address',
35
		'_customer_user_agent', '_created_via', '_order_version', '_prices_include_tax',
36
		'_customer_note', '_date_completed', '_date_paid', '_payment_tokens',
37
	);
38
39
	/**
40
	 * Stores data about status changes so relevant hooks can be fired.
41
	 * @var bool|array
42
	 */
43
	protected $_status_transition = false;
44
45
	/**
46
	 * Default data values.
47
	 * @var array
48
	 */
49
	protected $_default_data = array(
50
		// Abstract order props
51
		'id'                   => 0,
52
		'parent_id'            => 0,
53
		'status'               => '',
54
		'currency'             => '',
55
		'version'              => '',
56
		'prices_include_tax'   => false,
57
		'date_created'         => '',
58
		'date_modified'        => '',
59
		'discount_total'       => 0,
60
		'discount_tax'         => 0,
61
		'shipping_total'       => 0,
62
		'shipping_tax'         => 0,
63
		'cart_tax'             => 0,
64
		'total'                => 0,
65
		'total_tax'            => 0,
66
67
		// Order props
68
		'customer_id'          => 0,
69
		'order_key'            => '',
70
		'billing'              => array(
71
			'first_name'       => '',
72
			'last_name'        => '',
73
			'company'          => '',
74
			'address_1'        => '',
75
			'address_2'        => '',
76
			'city'             => '',
77
			'state'            => '',
78
			'postcode'         => '',
79
			'country'          => '',
80
			'email'            => '',
81
			'phone'            => '',
82
		),
83
		'shipping'             => array(
84
			'first_name'       => '',
85
			'last_name'        => '',
86
			'company'          => '',
87
			'address_1'        => '',
88
			'address_2'        => '',
89
			'city'             => '',
90
			'state'            => '',
91
			'postcode'         => '',
92
			'country'          => '',
93
		),
94
		'payment_method'       => '',
95
		'payment_method_title' => '',
96
		'transaction_id'       => '',
97
		'customer_ip_address'  => '',
98
		'customer_user_agent'  => '',
99
		'created_via'          => '',
100
		'customer_note'        => '',
101
		'date_completed'       => '',
102
		'date_paid'            => '',
103
		'cart_hash'            => '',
104
	);
105
106
	/**
107
	 * When a payment is complete this function is called.
108
	 *
109
	 * Most of the time this should mark an order as 'processing' so that admin can process/post the items.
110
	 * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action.
111
	 * Stock levels are reduced at this point.
112
	 * Sales are also recorded for products.
113
	 * Finally, record the date of payment.
114
	 *
115
	 * Order must exist.
116
	 *
117
	 * @param string $transaction_id Optional transaction id to store in post meta.
118
	 * @return bool success
119
	 */
120
	public function payment_complete( $transaction_id = '' ) {
121
		try {
122
			if ( ! $this->get_id() ) {
123
				throw new Exception( 'Missing ID' );
124
			}
125
			do_action( 'woocommerce_pre_payment_complete', $this->get_id() );
126
127
			if ( ! empty( WC()->session ) ) {
128
				WC()->session->set( 'order_awaiting_payment', false );
129
			}
130
131
			if ( $this->has_status( apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this ) ) ) {
132
				$order_needs_processing = false;
133
134
				if ( sizeof( $this->get_items() ) > 0 ) {
135
					foreach ( $this->get_items() as $item ) {
136
						if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) ) {
137
							$virtual_downloadable_item = $product->is_downloadable() && $product->is_virtual();
138
139
							if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $product, $this->get_id() ) ) {
140
								$order_needs_processing = true;
141
								break;
142
							}
143
						}
144
					}
145
				}
146
147
				if ( ! empty( $transaction_id ) ) {
148
					$this->set_transaction_id( $transaction_id );
149
				}
150
151
				$this->set_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->get_id() ) );
152
				$this->set_date_paid( current_time( 'timestamp' ) );
153
				$this->save();
154
155
				do_action( 'woocommerce_payment_complete', $this->get_id() );
156
			} else {
157
				do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->get_id() );
158
			}
159
		} catch ( Exception $e ) {
160
			return false;
161
		}
162
		return true;
163
	}
164
165
	/**
166
	 * Gets order total - formatted for display.
167
	 * @return string
168
	 */
169
	public function get_formatted_order_total( $tax_display = '', $display_refunded = true ) {
170
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
171
		$order_total    = $this->get_total();
172
		$total_refunded = $this->get_total_refunded();
173
		$tax_string     = '';
174
175
		// Tax for inclusive prices
176
		if ( wc_tax_enabled() && 'incl' == $tax_display ) {
177
			$tax_string_array = array();
178
179
			if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
180
				foreach ( $this->get_tax_totals() as $code => $tax ) {
181
					$tax_amount         = ( $total_refunded && $display_refunded ) ? wc_price( WC_Tax::round( $tax->amount - $this->get_total_tax_refunded_by_rate_id( $tax->rate_id ) ), array( 'currency' => $this->get_currency() ) ) : $tax->formatted_amount;
182
					$tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
183
				}
184
			} else {
185
				$tax_amount         = ( $total_refunded && $display_refunded ) ? $this->get_total_tax() - $this->get_total_tax_refunded() : $this->get_total_tax();
186
				$tax_string_array[] = sprintf( '%s %s', wc_price( $tax_amount, array( 'currency' => $this->get_currency() ) ), WC()->countries->tax_or_vat() );
187
			}
188
			if ( ! empty( $tax_string_array ) ) {
189
				$tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
190
			}
191
		}
192
193
		if ( $total_refunded && $display_refunded ) {
194
			$formatted_total = '<del>' . strip_tags( $formatted_total ) . '</del> <ins>' . wc_price( $order_total - $total_refunded, array( 'currency' => $this->get_currency() ) ) . $tax_string . '</ins>';
195
		} else {
196
			$formatted_total .= $tax_string;
197
		}
198
199
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
200
	}
201
202
	/*
203
	|--------------------------------------------------------------------------
204
	| CRUD methods
205
	|--------------------------------------------------------------------------
206
	|
207
	| Methods which create, read, update and delete orders from the database.
208
	| Written in abstract fashion so that the way orders are stored can be
209
	| changed more easily in the future.
210
	|
211
	| A save method is included for convenience (chooses update or create based
212
	| on if the order exists yet).
213
	|
214
	*/
215
216
	/**
217
	 * Insert data into the database.
218
	 * @since 2.7.0
219
	 */
220
	public function create() {
221
		parent::create();
222
223
		// Store additonal order data
224
		if ( $this->get_id() ) {
225
			$this->set_order_key( 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) );
226
			$this->update_post_meta( '_customer_user', $this->get_customer_id() );
227
			$this->update_post_meta( '_order_key', $this->get_order_key() );
228
			$this->update_post_meta( '_billing_first_name', $this->get_billing_first_name() );
229
			$this->update_post_meta( '_billing_last_name', $this->get_billing_last_name() );
230
			$this->update_post_meta( '_billing_company', $this->get_billing_company() );
231
			$this->update_post_meta( '_billing_address_1', $this->get_billing_address_1() );
232
			$this->update_post_meta( '_billing_address_2', $this->get_billing_address_2() );
233
			$this->update_post_meta( '_billing_city', $this->get_billing_city() );
234
			$this->update_post_meta( '_billing_state', $this->get_billing_state() );
235
			$this->update_post_meta( '_billing_postcode', $this->get_billing_postcode() );
236
			$this->update_post_meta( '_billing_country', $this->get_billing_country() );
237
			$this->update_post_meta( '_billing_email', $this->get_billing_email() );
238
			$this->update_post_meta( '_billing_phone', $this->get_billing_phone() );
239
			$this->update_post_meta( '_shipping_first_name', $this->get_shipping_first_name() );
240
			$this->update_post_meta( '_shipping_last_name', $this->get_shipping_last_name() );
241
			$this->update_post_meta( '_shipping_company', $this->get_shipping_company() );
242
			$this->update_post_meta( '_shipping_address_1', $this->get_shipping_address_1() );
243
			$this->update_post_meta( '_shipping_address_2', $this->get_shipping_address_2() );
244
			$this->update_post_meta( '_shipping_city', $this->get_shipping_city() );
245
			$this->update_post_meta( '_shipping_state', $this->get_shipping_state() );
246
			$this->update_post_meta( '_shipping_postcode', $this->get_shipping_postcode() );
247
			$this->update_post_meta( '_shipping_country', $this->get_shipping_country() );
248
			$this->update_post_meta( '_payment_method', $this->get_payment_method() );
249
			$this->update_post_meta( '_payment_method_title', $this->get_payment_method_title() );
250
			$this->update_post_meta( '_transaction_id', $this->get_transaction_id() );
251
			$this->update_post_meta( '_customer_ip_address', $this->get_customer_ip_address() );
252
			$this->update_post_meta( '_customer_user_agent', $this->get_customer_user_agent() );
253
			$this->update_post_meta( '_created_via', $this->get_created_via() );
254
			$this->update_post_meta( '_customer_note', $this->get_customer_note() );
255
			$this->update_post_meta( '_date_completed', $this->get_date_completed() );
256
			$this->update_post_meta( '_date_paid', $this->get_date_paid() );
257
			$this->update_post_meta( '_cart_hash', $this->get_cart_hash() );
258
			do_action( 'woocommerce_new_order', $this->get_id() );
259
		}
260
	}
261
262
	/**
263
	 * Read from the database.
264
	 * @since 2.7.0
265
	 * @param int $id ID of object to read.
266
	 */
267
	public function read( $id ) {
268
		parent::read( $id );
269
270
		if ( ! $this->get_id() ) {
271
			return;
272
		}
273
274
		$this->_reading = true;
275
		$post_object    = get_post( $this->get_id() );
276
277
		// Read additonal order data
278
		$this->set_order_key( get_post_meta( $this->get_id(), '_order_key', true ) );
279
		$this->set_customer_id( get_post_meta( $this->get_id(), '_customer_user', true ) );
280
		$this->set_billing_first_name( get_post_meta( $this->get_id(), '_billing_first_name', true ) );
281
		$this->set_billing_last_name( get_post_meta( $this->get_id(), '_billing_last_name', true ) );
282
		$this->set_billing_company( get_post_meta( $this->get_id(), '_billing_company', true ) );
283
		$this->set_billing_address_1( get_post_meta( $this->get_id(), '_billing_address_1', true ) );
284
		$this->set_billing_address_2( get_post_meta( $this->get_id(), '_billing_address_2', true ) );
285
		$this->set_billing_city( get_post_meta( $this->get_id(), '_billing_city', true ) );
286
		$this->set_billing_state( get_post_meta( $this->get_id(), '_billing_state', true ) );
287
		$this->set_billing_postcode( get_post_meta( $this->get_id(), '_billing_postcode', true ) );
288
		$this->set_billing_country( get_post_meta( $this->get_id(), '_billing_country', true ) );
289
		$this->set_billing_email( get_post_meta( $this->get_id(), '_billing_email', true ) );
290
		$this->set_billing_phone( get_post_meta( $this->get_id(), '_billing_phone', true ) );
291
		$this->set_shipping_first_name( get_post_meta( $this->get_id(), '_shipping_first_name', true ) );
292
		$this->set_shipping_last_name( get_post_meta( $this->get_id(), '_shipping_last_name', true ) );
293
		$this->set_shipping_company( get_post_meta( $this->get_id(), '_shipping_company', true ) );
294
		$this->set_shipping_address_1( get_post_meta( $this->get_id(), '_shipping_address_1', true ) );
295
		$this->set_shipping_address_2( get_post_meta( $this->get_id(), '_shipping_address_2', true ) );
296
		$this->set_shipping_city( get_post_meta( $this->get_id(), '_shipping_city', true ) );
297
		$this->set_shipping_state( get_post_meta( $this->get_id(), '_shipping_state', true ) );
298
		$this->set_shipping_postcode( get_post_meta( $this->get_id(), '_shipping_postcode', true ) );
299
		$this->set_shipping_country( get_post_meta( $this->get_id(), '_shipping_country', true ) );
300
		$this->set_payment_method( get_post_meta( $this->get_id(), '_payment_method', true ) );
301
		$this->set_payment_method_title( get_post_meta( $this->get_id(), '_payment_method_title', true ) );
302
		$this->set_transaction_id( get_post_meta( $this->get_id(), '_transaction_id', true ) );
303
		$this->set_customer_ip_address( get_post_meta( $this->get_id(), '_customer_ip_address', true ) );
304
		$this->set_customer_user_agent( get_post_meta( $this->get_id(), '_customer_user_agent', true ) );
305
		$this->set_created_via( get_post_meta( $this->get_id(), '_created_via', true ) );
306
		$this->set_customer_note( get_post_meta( $this->get_id(), '_customer_note', true ) );
307
		$this->set_date_completed( get_post_meta( $this->get_id(), '_completed_date', true ) );
308
		$this->set_date_paid( get_post_meta( $this->get_id(), '_paid_date', true ) );
309
		$this->set_cart_hash( get_post_meta( $this->get_id(), '_cart_hash', true ) );
310
		$this->set_customer_note( $post_object->post_excerpt );
311
		$this->maybe_set_user_billing_email();
312
		$this->_reading = false;
313
	}
314
315
	/**
316
	 * Update data in the database.
317
	 * @since 2.7.0
318
	 */
319
	public function update() {
320
		// Store additonal order data
321
		$this->update_post_meta( '_order_key', $this->get_order_key() );
322
		$this->update_post_meta( '_customer_user', $this->get_customer_id() );
323
		$this->update_post_meta( '_billing_first_name', $this->get_billing_first_name() );
324
		$this->update_post_meta( '_billing_last_name', $this->get_billing_last_name() );
325
		$this->update_post_meta( '_billing_company', $this->get_billing_company() );
326
		$this->update_post_meta( '_billing_address_1', $this->get_billing_address_1() );
327
		$this->update_post_meta( '_billing_address_2', $this->get_billing_address_2() );
328
		$this->update_post_meta( '_billing_city', $this->get_billing_city() );
329
		$this->update_post_meta( '_billing_state', $this->get_billing_state() );
330
		$this->update_post_meta( '_billing_postcode', $this->get_billing_postcode() );
331
		$this->update_post_meta( '_billing_country', $this->get_billing_country() );
332
		$this->update_post_meta( '_billing_email', $this->get_billing_email() );
333
		$this->update_post_meta( '_billing_phone', $this->get_billing_phone() );
334
		$this->update_post_meta( '_shipping_first_name', $this->get_shipping_first_name() );
335
		$this->update_post_meta( '_shipping_last_name', $this->get_shipping_last_name() );
336
		$this->update_post_meta( '_shipping_company', $this->get_shipping_company() );
337
		$this->update_post_meta( '_shipping_address_1', $this->get_shipping_address_1() );
338
		$this->update_post_meta( '_shipping_address_2', $this->get_shipping_address_2() );
339
		$this->update_post_meta( '_shipping_city', $this->get_shipping_city() );
340
		$this->update_post_meta( '_shipping_state', $this->get_shipping_state() );
341
		$this->update_post_meta( '_shipping_postcode', $this->get_shipping_postcode() );
342
		$this->update_post_meta( '_shipping_country', $this->get_shipping_country() );
343
		$this->update_post_meta( '_payment_method', $this->get_payment_method() );
344
		$this->update_post_meta( '_payment_method_title', $this->get_payment_method_title() );
345
		$this->update_post_meta( '_transaction_id', $this->get_transaction_id() );
346
		$this->update_post_meta( '_customer_ip_address', $this->get_customer_ip_address() );
347
		$this->update_post_meta( '_customer_user_agent', $this->get_customer_user_agent() );
348
		$this->update_post_meta( '_created_via', $this->get_created_via() );
349
		$this->update_post_meta( '_customer_note', $this->get_customer_note() );
350
		$this->update_post_meta( '_date_completed', $this->get_date_completed() );
351
		$this->update_post_meta( '_date_paid', $this->get_date_paid() );
352
		$this->update_post_meta( '_cart_hash', $this->get_cart_hash() );
353
354
		$customer_changed = $this->update_post_meta( '_customer_user', $this->get_customer_id() );
355
356
		// Update parent
357
		parent::update();
358
359
		// If customer changed, update any downloadable permissions
360
		if ( $customer_changed ) {
361
			$wpdb->update( $wpdb->prefix . "woocommerce_downloadable_product_permissions",
362
				array(
363
					'user_id'    => $this->get_customer_id(),
364
					'user_email' => $this->get_billing_email(),
365
				),
366
				array(
367
					'order_id'   => $this->get_id(),
368
				),
369
				array(
370
					'%d',
371
					'%s',
372
				),
373
				array(
374
					'%d',
375
				)
376
			);
377
		}
378
379
		// Handle status change
380
		$this->status_transition();
381
	}
382
383
	/**
384
	 * Set order status.
385
	 * @since 2.7.0
386
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
387
	 * @param string $note (default: '') Optional note to add.
388
	 * @param bool $manual_update is this a manual order status change?
389
	 * @param array details of change
390
	 */
391
	public function set_status( $new_status, $note = '', $manual_update = false ) {
392
		$result = parent::set_status( $new_status );
393
394
		if ( ! empty( $result['from'] ) && $result['from'] !== $result['to'] ) {
395
			$this->_status_transition = array(
396
				'from'   => ! empty( $this->_status_transition['from'] ) ? $this->_status_transition['from'] : $result['from'],
397
				'to'     => $result['to'],
398
				'note'   => $note,
399
				'manual' => (bool) $manual_update,
400
			);
401
402
			if ( 'pending' === $result['from'] && ! $manual_update ) {
403
				$this->set_date_paid( current_time( 'timestamp' ) );
404
			}
405
406
			if ( 'completed' === $result['to'] ) {
407
				$this->set_date_completed( current_time( 'timestamp' ) );
408
			}
409
		}
410
411
		return $result;
412
	}
413
414
	/**
415
	 * Updates status of order immediately. Order must exist.
416
	 * @uses WC_Order::set_status()
417
	 * @return bool success
418
	 */
419
	public function update_status( $new_status, $note = '', $manual = false ) {
420
		try {
421
			if ( ! $this->get_id() ) {
422
				throw new Exception( 'Missing ID' );
423
			}
424
			$this->set_status( $new_status, $note, $manual );
425
			$this->save();
426
		} catch ( Exception $e ) {
427
			return false;
428
		}
429
		return true;
430
	}
431
432
	/**
433
	 * Handle the status transition.
434
	 */
435
	protected function status_transition() {
436
		if ( $this->_status_transition ) {
437
			if ( ! empty( $this->_status_transition['from'] ) ) {
438
				$transition_note = sprintf( __( 'Order status changed from %s to %s.', 'woocommerce' ), wc_get_order_status_name( $this->_status_transition['from'] ), wc_get_order_status_name( $this->_status_transition['to'] ) );
439
440
				do_action( 'woocommerce_order_status_' . $this->_status_transition['from'] . '_to_' . $this->_status_transition['to'], $this->get_id() );
441
				do_action( 'woocommerce_order_status_changed', $this->get_id(), $this->_status_transition['from'], $this->_status_transition['to'] );
442
			} else {
443
				$transition_note = sprintf( __( 'Order status set to %s.', 'woocommerce' ), wc_get_order_status_name( $this->_status_transition['to'] ) );
444
			}
445
446
			do_action( 'woocommerce_order_status_' . $this->_status_transition['to'], $this->get_id() );
447
448
			// Note the transition occured
449
			$this->add_order_note( trim( $this->_status_transition['note'] . ' ' . $transition_note ), 0, $this->_status_transition['manual'] );
450
451
			// This has ran, so reset status transition variable
452
			$this->_status_transition = false;
453
		}
454
	}
455
456
	/*
457
	|--------------------------------------------------------------------------
458
	| Getters
459
	|--------------------------------------------------------------------------
460
	|
461
	| Methods for getting data from the order object.
462
	|
463
	*/
464
465
	/**
466
	 * Get all class data in array format.
467
	 * @since 2.7.0
468
	 * @return array
469
	 */
470 View Code Duplication
	public function get_data() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
471
		return array_merge(
472
			$this->_data,
473
			array(
474
				'number'         => $this->get_order_number(),
475
				'meta_data'      => $this->get_meta_data(),
476
				'line_items'     => $this->get_items( 'line_item' ),
477
				'tax_lines'      => $this->get_items( 'tax' ),
478
				'shipping_lines' => $this->get_items( 'shipping' ),
479
				'fee_lines'      => $this->get_items( 'fee' ),
480
				'coupon_lines'   => $this->get_items( 'coupon' ),
481
			)
482
		);
483
	}
484
485
	/**
486
	 * get_order_number function.
487
	 *
488
	 * Gets the order number for display (by default, order ID).
489
	 *
490
	 * @return string
491
	 */
492
	public function get_order_number() {
493
		return apply_filters( 'woocommerce_order_number', $this->get_id(), $this );
494
	}
495
496
	/**
497
	 * Get order key.
498
	 * @since 2.7.0
499
	 * @return string
500
	 */
501
	public function get_order_key() {
502
		return $this->get_prop( 'order_key' );
503
	}
504
505
	/**
506
	 * Get customer_id
507
	 * @return int
508
	 */
509
	public function get_customer_id() {
510
		return $this->get_prop( 'customer_id' );
511
	}
512
513
	/**
514
	 * Alias for get_customer_id().
515
	 * @return int
516
	 */
517
	public function get_user_id() {
518
		return $this->get_customer_id();
519
	}
520
521
	/**
522
	 * Get the user associated with the order. False for guests.
523
	 * @return WP_User|false
524
	 */
525
	public function get_user() {
526
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
527
	}
528
529
	/**
530
	 * Get billing_first_name
531
	 * @return string
532
	 */
533
	public function get_billing_first_name() {
534
		return $this->get_prop( 'billing', 'first_name' );
535
	}
536
537
	/**
538
	 * Get billing_last_name
539
	 * @return string
540
	 */
541
	public function get_billing_last_name() {
542
		return $this->get_prop( 'billing', 'last_name' );
543
	}
544
545
	/**
546
	 * Get billing_company
547
	 * @return string
548
	 */
549
	public function get_billing_company() {
550
		return $this->get_prop( 'billing', 'company' );
551
	}
552
553
	/**
554
	 * Get billing_address_1
555
	 * @return string
556
	 */
557
	public function get_billing_address_1() {
558
		return $this->get_prop( 'billing', 'address_1' );
559
	}
560
561
	/**
562
	 * Get billing_address_2
563
	 * @return string $value
564
	 */
565
	public function get_billing_address_2() {
566
		return $this->get_prop( 'billing', 'address_2' );
567
	}
568
569
	/**
570
	 * Get billing_city
571
	 * @return string $value
572
	 */
573
	public function get_billing_city() {
574
		return $this->get_prop( 'billing', 'city' );
575
	}
576
577
	/**
578
	 * Get billing_state
579
	 * @return string
580
	 */
581
	public function get_billing_state() {
582
		return $this->get_prop( 'billing', 'state' );
583
	}
584
585
	/**
586
	 * Get billing_postcode
587
	 * @return string
588
	 */
589
	public function get_billing_postcode() {
590
		return $this->get_prop( 'billing', 'postcode' );
591
	}
592
593
	/**
594
	 * Get billing_country
595
	 * @return string
596
	 */
597
	public function get_billing_country() {
598
		return $this->get_prop( 'billing', 'country' );
599
	}
600
601
	/**
602
	 * Get billing_email
603
	 * @return string
604
	 */
605
	public function get_billing_email() {
606
		return $this->get_prop( 'billing', 'email' );
607
	}
608
609
	/**
610
	 * Get billing_phone
611
	 * @return string
612
	 */
613
	public function get_billing_phone() {
614
		return $this->get_prop( 'billing', 'phone' );
615
	}
616
617
	/**
618
	 * Get shipping_first_name
619
	 * @return string
620
	 */
621
	public function get_shipping_first_name() {
622
		return $this->get_prop( 'shipping', 'first_name' );
623
	}
624
625
	/**
626
	 * Get shipping_last_name
627
	 * @return string
628
	 */
629
	public function get_shipping_last_name() {
630
		 return $this->get_prop( 'shipping', 'last_name' );
631
	}
632
633
	/**
634
	 * Get shipping_company
635
	 * @return string
636
	 */
637
	public function get_shipping_company() {
638
		return $this->get_prop( 'shipping', 'company' );
639
	}
640
641
	/**
642
	 * Get shipping_address_1
643
	 * @return string
644
	 */
645
	public function get_shipping_address_1() {
646
		return $this->get_prop( 'shipping', 'address_1' );
647
	}
648
649
	/**
650
	 * Get shipping_address_2
651
	 * @return string
652
	 */
653
	public function get_shipping_address_2() {
654
		return $this->get_prop( 'shipping', 'address_2' );
655
	}
656
657
	/**
658
	 * Get shipping_city
659
	 * @return string
660
	 */
661
	public function get_shipping_city() {
662
		return $this->get_prop( 'shipping', 'city' );
663
	}
664
665
	/**
666
	 * Get shipping_state
667
	 * @return string
668
	 */
669
	public function get_shipping_state() {
670
		return $this->get_prop( 'shipping', 'state' );
671
	}
672
673
	/**
674
	 * Get shipping_postcode
675
	 * @return string
676
	 */
677
	public function get_shipping_postcode() {
678
		return $this->get_prop( 'shipping', 'postcode' );
679
	}
680
681
	/**
682
	 * Get shipping_country
683
	 * @return string
684
	 */
685
	public function get_shipping_country() {
686
		return $this->get_prop( 'shipping', 'country' );
687
	}
688
689
	/**
690
	 * Get the payment method.
691
	 * @return string
692
	 */
693
	public function get_payment_method() {
694
		return $this->get_prop( 'payment_method' );
695
	}
696
697
	/**
698
	 * Get payment_method_title
699
	 * @return string
700
	 */
701
	public function get_payment_method_title() {
702
		return $this->get_prop( 'payment_method_title' );
703
	}
704
705
	/**
706
	 * Get transaction_id
707
	 * @return string
708
	 */
709
	public function get_transaction_id() {
710
		return $this->get_prop( 'transaction_id' );
711
	}
712
713
	/**
714
	 * Get customer_ip_address
715
	 * @return string
716
	 */
717
	public function get_customer_ip_address() {
718
		return $this->get_prop( 'customer_ip_address' );
719
	}
720
721
	/**
722
	 * Get customer_user_agent
723
	 * @return string
724
	 */
725
	public function get_customer_user_agent() {
726
		return $this->get_prop( 'customer_user_agent' );
727
	}
728
729
	/**
730
	 * Get created_via
731
	 * @return string
732
	 */
733
	public function get_created_via() {
734
		return $this->get_prop( 'created_via' );
735
	}
736
737
	/**
738
	 * Get customer_note
739
	 * @return string
740
	 */
741
	public function get_customer_note() {
742
		return $this->get_prop( 'customer_note' );
743
	}
744
745
	/**
746
	 * Get date_completed
747
	 * @return int
748
	 */
749
	public function get_date_completed() {
750
		return absint( $this->get_prop( 'date_completed' ) );
751
	}
752
753
	/**
754
	 * Get date_paid
755
	 * @return int
756
	 */
757
	public function get_date_paid() {
758
		return absint( $this->get_prop( 'date_paid' ) );
759
	}
760
761
	/**
762
	 * Returns the requested address in raw, non-formatted way.
763
	 * @since  2.4.0
764
	 * @param  string $type Billing or shipping. Anything else besides 'billing' will return shipping address.
765
	 * @return array The stored address after filter.
766
	 */
767
	public function get_address( $type = 'billing' ) {
768
		return apply_filters( 'woocommerce_get_order_address', isset( $this->_data[ $type ] ) ? $this->_data[ $type ] : array(), $type, $this );
769
	}
770
771
	/**
772
	 * Get a formatted shipping address for the order.
773
	 *
774
	 * @return string
775
	 */
776
	public function get_shipping_address_map_url() {
777
		$address = apply_filters( 'woocommerce_shipping_address_map_url_parts', $this->get_address( 'shipping' ), $this );
778
		return apply_filters( 'woocommerce_shipping_address_map_url', 'http://maps.google.com/maps?&q=' . urlencode( implode( ', ', $address ) ) . '&z=16', $this );
779
	}
780
781
	/**
782
	 * Get a formatted billing full name.
783
	 * @return string
784
	 */
785
	public function get_formatted_billing_full_name() {
786
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->get_billing_first_name(), $this->get_billing_last_name() );
787
	}
788
789
	/**
790
	 * Get a formatted shipping full name.
791
	 * @return string
792
	 */
793
	public function get_formatted_shipping_full_name() {
794
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->get_shipping_first_name(), $this->get_shipping_last_name() );
795
	}
796
797
	/**
798
	 * Get a formatted billing address for the order.
799
	 * @return string
800
	 */
801
	public function get_formatted_billing_address() {
802
		return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_order_formatted_billing_address', $this->get_address( 'billing' ), $this ) );
803
	}
804
805
	/**
806
	 * Get a formatted shipping address for the order.
807
	 * @return string
808
	 */
809
	public function get_formatted_shipping_address() {
810
		if ( $this->get_shipping_address_1() || $this->get_shipping_address_2() ) {
811
			return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_order_formatted_shipping_address', $this->get_address( 'shipping' ), $this ) );
812
		} else {
813
			return '';
814
		}
815
	}
816
817
	/**
818
	 * Get cart hash
819
	 * @return string
820
	 */
821
	public function get_cart_hash() {
822
		return $this->get_prop( 'cart_hash' );
823
	}
824
825
	/*
826
	|--------------------------------------------------------------------------
827
	| Setters
828
	|--------------------------------------------------------------------------
829
	|
830
	| Functions for setting order data. These should not update anything in the
831
	| database itself and should only change what is stored in the class
832
	| object. However, for backwards compatibility pre 2.7.0 some of these
833
	| setters may handle both.
834
	|
835
	*/
836
837
	/**
838
	 * Set order_key.
839
	 * @param string $value Max length 20 chars.
840
	 * @throws WC_Data_Exception
841
	 */
842
	public function set_order_key( $value ) {
843
		$this->set_prop( 'order_key', substr( $value, 0, 20 ) );
844
	}
845
846
	/**
847
	 * Set customer_id
848
	 * @param int $value
849
	 * @throws WC_Data_Exception
850
	 */
851
	public function set_customer_id( $value ) {
852
		$this->set_prop( 'customer_id', absint( $value ) );
853
	}
854
855
	/**
856
	 * Set billing_first_name
857
	 * @param string $value
858
	 * @throws WC_Data_Exception
859
	 */
860
	public function set_billing_first_name( $value ) {
861
		$this->set_prop( 'billing', 'first_name', $value );
862
	}
863
864
	/**
865
	 * Set billing_last_name
866
	 * @param string $value
867
	 * @throws WC_Data_Exception
868
	 */
869
	public function set_billing_last_name( $value ) {
870
		$this->set_prop( 'billing', 'last_name', $value );
871
	}
872
873
	/**
874
	 * Set billing_company
875
	 * @param string $value
876
	 * @throws WC_Data_Exception
877
	 */
878
	public function set_billing_company( $value ) {
879
		$this->set_prop( 'billing', 'company', $value );
880
	}
881
882
	/**
883
	 * Set billing_address_1
884
	 * @param string $value
885
	 * @throws WC_Data_Exception
886
	 */
887
	public function set_billing_address_1( $value ) {
888
		$this->set_prop( 'billing', 'address_1', $value );
889
	}
890
891
	/**
892
	 * Set billing_address_2
893
	 * @param string $value
894
	 * @throws WC_Data_Exception
895
	 */
896
	public function set_billing_address_2( $value ) {
897
		$this->set_prop( 'billing', 'address_2', $value );
898
	}
899
900
	/**
901
	 * Set billing_city
902
	 * @param string $value
903
	 * @throws WC_Data_Exception
904
	 */
905
	public function set_billing_city( $value ) {
906
		$this->set_prop( 'billing', 'city', $value );
907
	}
908
909
	/**
910
	 * Set billing_state
911
	 * @param string $value
912
	 * @throws WC_Data_Exception
913
	 */
914
	public function set_billing_state( $value ) {
915
		$this->set_prop( 'billing', 'state', $value );
916
	}
917
918
	/**
919
	 * Set billing_postcode
920
	 * @param string $value
921
	 * @throws WC_Data_Exception
922
	 */
923
	public function set_billing_postcode( $value ) {
924
		$this->set_prop( 'billing', 'postcode', $value );
925
	}
926
927
	/**
928
	 * Set billing_country
929
	 * @param string $value
930
	 * @throws WC_Data_Exception
931
	 */
932
	public function set_billing_country( $value ) {
933
		$this->set_prop( 'billing', 'country', $value );
934
	}
935
936
	/**
937
	 * Maybe set empty billing email to that of the user who owns the order.
938
	 */
939
	protected function maybe_set_user_billing_email() {
940
		if ( ! $this->get_billing_email() && ( $user = $this->get_user() ) ) {
941
			try {
942
				$this->set_billing_email( $user->user_email );
943
			} catch( WC_Data_Exception $e ){
944
				unset( $e );
945
			}
946
		}
947
	}
948
949
	/**
950
	 * Set billing_email
951
	 * @param string $value
952
	 * @throws WC_Data_Exception
953
	 */
954
	public function set_billing_email( $value ) {
955
		if ( $value && ! is_email( $value ) ) {
956
			$this->invalid_data( 'order_invalid_billing_email', __( 'Invalid order billing email address', 'woocommerce' ) );
957
		}
958
		$this->set_prop( 'billing', 'email', sanitize_email( $value ) );
959
	}
960
961
	/**
962
	 * Set billing_phone
963
	 * @param string $value
964
	 * @throws WC_Data_Exception
965
	 */
966
	public function set_billing_phone( $value ) {
967
		$this->set_prop( 'billing', 'phone', $value );
968
	}
969
970
	/**
971
	 * Set shipping_first_name
972
	 * @param string $value
973
	 * @throws WC_Data_Exception
974
	 */
975
	public function set_shipping_first_name( $value ) {
976
		$this->set_prop( 'shipping', 'first_name', $value );
977
	}
978
979
	/**
980
	 * Set shipping_last_name
981
	 * @param string $value
982
	 * @throws WC_Data_Exception
983
	 */
984
	public function set_shipping_last_name( $value ) {
985
		$this->set_prop( 'shipping', 'last_name', $value );
986
	}
987
988
	/**
989
	 * Set shipping_company
990
	 * @param string $value
991
	 * @throws WC_Data_Exception
992
	 */
993
	public function set_shipping_company( $value ) {
994
		$this->set_prop( 'shipping', 'company', $value );
995
	}
996
997
	/**
998
	 * Set shipping_address_1
999
	 * @param string $value
1000
	 * @throws WC_Data_Exception
1001
	 */
1002
	public function set_shipping_address_1( $value ) {
1003
		$this->set_prop( 'shipping', 'address_1', $value );
1004
	}
1005
1006
	/**
1007
	 * Set shipping_address_2
1008
	 * @param string $value
1009
	 * @throws WC_Data_Exception
1010
	 */
1011
	public function set_shipping_address_2( $value ) {
1012
		$this->set_prop( 'shipping', 'address_2', $value );
1013
	}
1014
1015
	/**
1016
	 * Set shipping_city
1017
	 * @param string $value
1018
	 * @throws WC_Data_Exception
1019
	 */
1020
	public function set_shipping_city( $value ) {
1021
		$this->set_prop( 'shipping', 'city', $value );
1022
	}
1023
1024
	/**
1025
	 * Set shipping_state
1026
	 * @param string $value
1027
	 * @throws WC_Data_Exception
1028
	 */
1029
	public function set_shipping_state( $value ) {
1030
		$this->set_prop( 'shipping', 'state', $value );
1031
	}
1032
1033
	/**
1034
	 * Set shipping_postcode
1035
	 * @param string $value
1036
	 * @throws WC_Data_Exception
1037
	 */
1038
	public function set_shipping_postcode( $value ) {
1039
		$this->set_prop( 'shipping', 'postcode', $value );
1040
	}
1041
1042
	/**
1043
	 * Set shipping_country
1044
	 * @param string $value
1045
	 * @throws WC_Data_Exception
1046
	 */
1047
	public function set_shipping_country( $value ) {
1048
		$this->set_prop( 'shipping', 'country', $value );
1049
	}
1050
1051
	/**
1052
	 * Set the payment method.
1053
	 * @param string $payment_method Supports WC_Payment_Gateway for bw compatibility with < 2.7
1054
	 * @throws WC_Data_Exception
1055
	 */
1056
	public function set_payment_method( $payment_method = '' ) {
1057
		if ( is_object( $payment_method ) ) {
1058
			$this->set_payment_method( $payment_method->id );
1059
			$this->set_payment_method_title( $payment_method->get_title() );
1060
		} elseif ( '' === $payment_method ) {
1061
			$this->set_prop( 'payment_method', '' );
1062
			$this->set_prop( 'payment_method_title', '' );
1063
		} else {
1064
			$this->set_prop( 'payment_method', $payment_method );
1065
		}
1066
	}
1067
1068
	/**
1069
	 * Set payment_method_title
1070
	 * @param string $value
1071
	 * @throws WC_Data_Exception
1072
	 */
1073
	public function set_payment_method_title( $value ) {
1074
		$this->set_prop( 'payment_method_title', $value );
1075
	}
1076
1077
	/**
1078
	 * Set transaction_id
1079
	 * @param string $value
1080
	 * @throws WC_Data_Exception
1081
	 */
1082
	public function set_transaction_id( $value ) {
1083
		$this->set_prop( 'transaction_id', $value );
1084
	}
1085
1086
	/**
1087
	 * Set customer_ip_address
1088
	 * @param string $value
1089
	 * @throws WC_Data_Exception
1090
	 */
1091
	public function set_customer_ip_address( $value ) {
1092
		$this->set_prop( 'customer_ip_address', $value );
1093
	}
1094
1095
	/**
1096
	 * Set customer_user_agent
1097
	 * @param string $value
1098
	 * @throws WC_Data_Exception
1099
	 */
1100
	public function set_customer_user_agent( $value ) {
1101
		$this->set_prop( 'customer_user_agent', $value );
1102
	}
1103
1104
	/**
1105
	 * Set created_via
1106
	 * @param string $value
1107
	 * @throws WC_Data_Exception
1108
	 */
1109
	public function set_created_via( $value ) {
1110
		$this->set_prop( 'created_via', $value );
1111
	}
1112
1113
	/**
1114
	 * Set customer_note
1115
	 * @param string $value
1116
	 * @throws WC_Data_Exception
1117
	 */
1118
	public function set_customer_note( $value ) {
1119
		$this->set_prop( 'customer_note', $value );
1120
	}
1121
1122
	/**
1123
	 * Set date_completed
1124
	 * @param string $timestamp
1125
	 * @throws WC_Data_Exception
1126
	 */
1127
	public function set_date_completed( $timestamp ) {
1128
		$this->set_prop( 'date_completed', is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp ) );
1129
	}
1130
1131
	/**
1132
	 * Set date_paid
1133
	 * @param string $timestamp
1134
	 * @throws WC_Data_Exception
1135
	 */
1136
	public function set_date_paid( $timestamp ) {
1137
		$this->set_prop( 'date_paid', is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp ) );
1138
	}
1139
1140
	/**
1141
	 * Set cart hash
1142
	 * @param string $value
1143
	 * @throws WC_Data_Exception
1144
	 */
1145
	public function set_cart_hash( $value ) {
1146
		$this->set_prop( 'cart_hash', $value );
1147
	}
1148
1149
	/*
1150
	|--------------------------------------------------------------------------
1151
	| Conditionals
1152
	|--------------------------------------------------------------------------
1153
	|
1154
	| Checks if a condition is true or false.
1155
	|
1156
	*/
1157
1158
	/**
1159
	 * Check if an order key is valid.
1160
	 *
1161
	 * @param mixed $key
1162
	 * @return bool
1163
	 */
1164
	public function key_is_valid( $key ) {
1165
		return $key === $this->get_order_key();
1166
	}
1167
1168
	/**
1169
	 * See if order matches cart_hash.
1170
	 * @return bool
1171
	 */
1172
	public function has_cart_hash( $cart_hash = '' ) {
1173
		return hash_equals( $this->get_cart_hash(), $cart_hash );
1174
	}
1175
1176
	/**
1177
	 * Checks if an order can be edited, specifically for use on the Edit Order screen.
1178
	 * @return bool
1179
	 */
1180
	public function is_editable() {
1181
		return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft' ) ), $this );
1182
	}
1183
1184
	/**
1185
	 * Returns if an order has been paid for based on the order status.
1186
	 * @since 2.5.0
1187
	 * @return bool
1188
	 */
1189
	public function is_paid() {
1190
		return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this );
1191
	}
1192
1193
	/**
1194
	 * Checks if product download is permitted.
1195
	 *
1196
	 * @return bool
1197
	 */
1198
	public function is_download_permitted() {
1199
		return apply_filters( 'woocommerce_order_is_download_permitted', $this->has_status( 'completed' ) || ( 'yes' === get_option( 'woocommerce_downloads_grant_access_after_payment' ) && $this->has_status( 'processing' ) ), $this );
1200
	}
1201
1202
	/**
1203
	 * Checks if an order needs display the shipping address, based on shipping method.
1204
	 * @return bool
1205
	 */
1206
	public function needs_shipping_address() {
1207
		if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
1208
			return false;
1209
		}
1210
1211
		$hide  = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this );
1212
		$needs_address = false;
1213
1214
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1215
			// Remove any instance IDs after :
1216
			$shipping_method_id = current( explode( ':', $shipping_method['method_id'] ) );
1217
1218
			if ( ! in_array( $shipping_method_id, $hide ) ) {
1219
				$needs_address = true;
1220
				break;
1221
			}
1222
		}
1223
1224
		return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this );
1225
	}
1226
1227
	/**
1228
	 * Returns true if the order contains a downloadable product.
1229
	 * @return bool
1230
	 */
1231
	public function has_downloadable_item() {
1232
		foreach ( $this->get_items() as $item ) {
1233
			if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->is_downloadable() && $product->has_file() ) {
1234
				return true;
1235
			}
1236
		}
1237
		return false;
1238
	}
1239
1240
	/**
1241
	 * Checks if an order needs payment, based on status and order total.
1242
	 *
1243
	 * @return bool
1244
	 */
1245
	public function needs_payment() {
1246
		$valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this );
1247
		return apply_filters( 'woocommerce_order_needs_payment', ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ), $this, $valid_order_statuses );
1248
	}
1249
1250
	/*
1251
	|--------------------------------------------------------------------------
1252
	| URLs and Endpoints
1253
	|--------------------------------------------------------------------------
1254
	*/
1255
1256
	/**
1257
	 * Generates a URL so that a customer can pay for their (unpaid - pending) order. Pass 'true' for the checkout version which doesn't offer gateway choices.
1258
	 *
1259
	 * @param  bool $on_checkout
1260
	 * @return string
1261
	 */
1262
	public function get_checkout_payment_url( $on_checkout = false ) {
1263
		$pay_url = wc_get_endpoint_url( 'order-pay', $this->get_id(), wc_get_page_permalink( 'checkout' ) );
1264
1265 View Code Duplication
		if ( 'yes' == get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) {
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...
1266
			$pay_url = str_replace( 'http:', 'https:', $pay_url );
1267
		}
1268
1269
		if ( $on_checkout ) {
1270
			$pay_url = add_query_arg( 'key', $this->get_order_key(), $pay_url );
1271
		} else {
1272
			$pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->get_order_key() ), $pay_url );
1273
		}
1274
1275
		return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this );
1276
	}
1277
1278
	/**
1279
	 * Generates a URL for the thanks page (order received).
1280
	 *
1281
	 * @return string
1282
	 */
1283
	public function get_checkout_order_received_url() {
1284
		$order_received_url = wc_get_endpoint_url( 'order-received', $this->get_id(), wc_get_page_permalink( 'checkout' ) );
1285
1286
		if ( 'yes' === get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) {
1287
			$order_received_url = str_replace( 'http:', 'https:', $order_received_url );
1288
		}
1289
1290
		$order_received_url = add_query_arg( 'key', $this->get_order_key(), $order_received_url );
1291
1292
		return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this );
1293
	}
1294
1295
	/**
1296
	 * Generates a URL so that a customer can cancel their (unpaid - pending) order.
1297
	 *
1298
	 * @param string $redirect
1299
	 *
1300
	 * @return string
1301
	 */
1302 View Code Duplication
	public function get_cancel_order_url( $redirect = '' ) {
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...
1303
		return apply_filters( 'woocommerce_get_cancel_order_url', wp_nonce_url( add_query_arg( array(
1304
			'cancel_order' => 'true',
1305
			'order'        => $this->get_order_key(),
1306
			'order_id'     => $this->get_id(),
1307
			'redirect'     => $redirect
1308
		), $this->get_cancel_endpoint() ), 'woocommerce-cancel_order' ) );
1309
	}
1310
1311
	/**
1312
	 * Generates a raw (unescaped) cancel-order URL for use by payment gateways.
1313
	 *
1314
	 * @param string $redirect
1315
	 *
1316
	 * @return string The unescaped cancel-order URL.
1317
	 */
1318 View Code Duplication
	public function get_cancel_order_url_raw( $redirect = '' ) {
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...
1319
		return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array(
1320
			'cancel_order' => 'true',
1321
			'order'        => $this->get_order_key(),
1322
			'order_id'     => $this->get_id(),
1323
			'redirect'     => $redirect,
1324
			'_wpnonce'     => wp_create_nonce( 'woocommerce-cancel_order' )
1325
		), $this->get_cancel_endpoint() ) );
1326
	}
1327
1328
	/**
1329
	 * Helper method to return the cancel endpoint.
1330
	 *
1331
	 * @return string the cancel endpoint; either the cart page or the home page.
1332
	 */
1333
	public function get_cancel_endpoint() {
1334
		$cancel_endpoint = wc_get_page_permalink( 'cart' );
1335
		if ( ! $cancel_endpoint ) {
1336
			$cancel_endpoint = home_url();
1337
		}
1338
1339
		if ( false === strpos( $cancel_endpoint, '?' ) ) {
1340
			$cancel_endpoint = trailingslashit( $cancel_endpoint );
1341
		}
1342
1343
		return $cancel_endpoint;
1344
	}
1345
1346
	/**
1347
	 * Generates a URL to view an order from the my account page.
1348
	 *
1349
	 * @return string
1350
	 */
1351
	public function get_view_order_url() {
1352
		return apply_filters( 'woocommerce_get_view_order_url', wc_get_endpoint_url( 'view-order', $this->get_id(), wc_get_page_permalink( 'myaccount' ) ), $this );
1353
	}
1354
1355
	/*
1356
	|--------------------------------------------------------------------------
1357
	| Order notes.
1358
	|--------------------------------------------------------------------------
1359
	*/
1360
1361
	/**
1362
	 * Adds a note (comment) to the order. Order must exist.
1363
	 *
1364
	 * @param string $note Note to add.
1365
	 * @param int $is_customer_note (default: 0) Is this a note for the customer?
1366
	 * @param  bool added_by_user Was the note added by a user?
1367
	 * @return int Comment ID.
1368
	 */
1369
	public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) {
1370
		if ( ! $this->get_id() ) {
1371
			return 0;
1372
		}
1373
1374
		if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->get_id() ) && $added_by_user ) {
1375
			$user                 = get_user_by( 'id', get_current_user_id() );
1376
			$comment_author       = $user->display_name;
1377
			$comment_author_email = $user->user_email;
1378
		} else {
1379
			$comment_author       = __( 'WooCommerce', 'woocommerce' );
1380
			$comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@';
1381
			$comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
1382
			$comment_author_email = sanitize_email( $comment_author_email );
1383
		}
1384
		$commentdata = apply_filters( 'woocommerce_new_order_note_data', array(
1385
			'comment_post_ID'      => $this->get_id(),
1386
			'comment_author'       => $comment_author,
1387
			'comment_author_email' => $comment_author_email,
1388
			'comment_author_url'   => '',
1389
			'comment_content'      => $note,
1390
			'comment_agent'        => 'WooCommerce',
1391
			'comment_type'         => 'order_note',
1392
			'comment_parent'       => 0,
1393
			'comment_approved'     => 1,
1394
		), array( 'order_id' => $this->get_id(), 'is_customer_note' => $is_customer_note ) );
1395
1396
		$comment_id = wp_insert_comment( $commentdata );
1397
1398
		if ( $is_customer_note ) {
1399
			add_comment_meta( $comment_id, 'is_customer_note', 1 );
1400
1401
			do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->get_id(), 'customer_note' => $commentdata['comment_content'] ) );
1402
		}
1403
1404
		return $comment_id;
1405
	}
1406
1407
	/**
1408
	 * List order notes (public) for the customer.
1409
	 *
1410
	 * @return array
1411
	 */
1412
	public function get_customer_order_notes() {
1413
		$notes = array();
1414
		$args  = array(
1415
			'post_id' => $this->get_id(),
1416
			'approve' => 'approve',
1417
			'type'    => ''
1418
		);
1419
1420
		remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
1421
1422
		$comments = get_comments( $args );
1423
1424
		foreach ( $comments as $comment ) {
1425
			if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) {
1426
				continue;
1427
			}
1428
			$comment->comment_content = make_clickable( $comment->comment_content );
1429
			$notes[] = $comment;
1430
		}
1431
1432
		add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
1433
1434
		return $notes;
1435
	}
1436
1437
	/*
1438
	|--------------------------------------------------------------------------
1439
	| Refunds
1440
	|--------------------------------------------------------------------------
1441
	*/
1442
1443
	/**
1444
	 * Get order refunds.
1445
	 * @since 2.2
1446
	 * @return array of WC_Order_Refund objects
1447
	 */
1448
	public function get_refunds() {
1449
		$this->refunds = wc_get_orders( array(
1450
			'type'   => 'shop_order_refund',
1451
			'parent' => $this->get_id(),
1452
			'limit'  => -1,
1453
		) );
1454
		return $this->refunds;
1455
	}
1456
1457
	/**
1458
	 * Get amount already refunded.
1459
	 *
1460
	 * @since 2.2
1461
	 * @return string
1462
	 */
1463 View Code Duplication
	public function get_total_refunded() {
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...
1464
		global $wpdb;
1465
1466
		$total = $wpdb->get_var( $wpdb->prepare( "
1467
			SELECT SUM( postmeta.meta_value )
1468
			FROM $wpdb->postmeta AS postmeta
1469
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
1470
			WHERE postmeta.meta_key = '_refund_amount'
1471
			AND postmeta.post_id = posts.ID
1472
		", $this->get_id() ) );
1473
1474
		return $total;
1475
	}
1476
1477
	/**
1478
	 * Get the total tax refunded.
1479
	 *
1480
	 * @since  2.3
1481
	 * @return float
1482
	 */
1483 View Code Duplication
	public function get_total_tax_refunded() {
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...
1484
		global $wpdb;
1485
1486
		$total = $wpdb->get_var( $wpdb->prepare( "
1487
			SELECT SUM( order_itemmeta.meta_value )
1488
			FROM {$wpdb->prefix}woocommerce_order_itemmeta AS order_itemmeta
1489
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
1490
			INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON ( order_items.order_id = posts.ID AND order_items.order_item_type = 'tax' )
1491
			WHERE order_itemmeta.order_item_id = order_items.order_item_id
1492
			AND order_itemmeta.meta_key IN ('tax_amount', 'shipping_tax_amount')
1493
		", $this->get_id() ) );
1494
1495
		return abs( $total );
1496
	}
1497
1498
	/**
1499
	 * Get the total shipping refunded.
1500
	 *
1501
	 * @since  2.4
1502
	 * @return float
1503
	 */
1504 View Code Duplication
	public function get_total_shipping_refunded() {
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...
1505
		global $wpdb;
1506
1507
		$total = $wpdb->get_var( $wpdb->prepare( "
1508
			SELECT SUM( order_itemmeta.meta_value )
1509
			FROM {$wpdb->prefix}woocommerce_order_itemmeta AS order_itemmeta
1510
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
1511
			INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON ( order_items.order_id = posts.ID AND order_items.order_item_type = 'shipping' )
1512
			WHERE order_itemmeta.order_item_id = order_items.order_item_id
1513
			AND order_itemmeta.meta_key IN ('cost')
1514
		", $this->get_id() ) );
1515
1516
		return abs( $total );
1517
	}
1518
1519
	/**
1520
	 * Gets the count of order items of a certain type that have been refunded.
1521
	 * @since  2.4.0
1522
	 * @param string $item_type
1523
	 * @return string
1524
	 */
1525
	public function get_item_count_refunded( $item_type = '' ) {
1526
		if ( empty( $item_type ) ) {
1527
			$item_type = array( 'line_item' );
1528
		}
1529
		if ( ! is_array( $item_type ) ) {
1530
			$item_type = array( $item_type );
1531
		}
1532
		$count = 0;
1533
1534
		foreach ( $this->get_refunds() as $refund ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_refunds() of type array|object<stdClass> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1535
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1536
				$count += $refunded_item->get_quantity();
1537
			}
1538
		}
1539
1540
		return apply_filters( 'woocommerce_get_item_count_refunded', $count, $item_type, $this );
1541
	}
1542
1543
	/**
1544
	 * Get the total number of items refunded.
1545
	 *
1546
	 * @since  2.4.0
1547
	 * @param  string $item_type type of the item we're checking, if not a line_item
1548
	 * @return integer
1549
	 */
1550
	public function get_total_qty_refunded( $item_type = 'line_item' ) {
1551
		$qty = 0;
1552
		foreach ( $this->get_refunds() as $refund ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_refunds() of type array|object<stdClass> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1553
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1554
				$qty += $refunded_item->get_quantity();
1555
			}
1556
		}
1557
		return $qty;
1558
	}
1559
1560
	/**
1561
	 * Get the refunded amount for a line item.
1562
	 *
1563
	 * @param  int $item_id ID of the item we're checking
1564
	 * @param  string $item_type type of the item we're checking, if not a line_item
1565
	 * @return integer
1566
	 */
1567 View Code Duplication
	public function get_qty_refunded_for_item( $item_id, $item_type = 'line_item' ) {
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...
1568
		$qty = 0;
1569
		foreach ( $this->get_refunds() as $refund ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_refunds() of type array|object<stdClass> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1570
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1571
				if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) {
1572
					$qty += $refunded_item->get_quantity();
1573
				}
1574
			}
1575
		}
1576
		return $qty;
1577
	}
1578
1579
	/**
1580
	 * Get the refunded amount for a line item.
1581
	 *
1582
	 * @param  int $item_id ID of the item we're checking
1583
	 * @param  string $item_type type of the item we're checking, if not a line_item
1584
	 * @return integer
1585
	 */
1586 View Code Duplication
	public function get_total_refunded_for_item( $item_id, $item_type = 'line_item' ) {
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...
1587
		$total = 0;
1588
		foreach ( $this->get_refunds() as $refund ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_refunds() of type array|object<stdClass> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1589
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1590
				if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) {
1591
					$total += $refunded_item->get_total();
1592
				}
1593
			}
1594
		}
1595
		return $total * -1;
1596
	}
1597
1598
	/**
1599
	 * Get the refunded amount for a line item.
1600
	 *
1601
	 * @param  int $item_id ID of the item we're checking
1602
	 * @param  int $tax_id ID of the tax we're checking
1603
	 * @param  string $item_type type of the item we're checking, if not a line_item
1604
	 * @return double
1605
	 */
1606 View Code Duplication
	public function get_tax_refunded_for_item( $item_id, $tax_id, $item_type = 'line_item' ) {
0 ignored issues
show
Unused Code introduced by
The parameter $tax_id is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
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...
1607
		$total = 0;
1608
		foreach ( $this->get_refunds() as $refund ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_refunds() of type array|object<stdClass> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1609
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1610
				if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) {
1611
					$total += $refunded_item->get_total_tax();
1612
				}
1613
			}
1614
		}
1615
		return wc_round_tax_total( $total ) * -1;
1616
	}
1617
1618
	/**
1619
	 * Get total tax refunded by rate ID.
1620
	 *
1621
	 * @param  int $rate_id
1622
	 *
1623
	 * @return float
1624
	 */
1625
	public function get_total_tax_refunded_by_rate_id( $rate_id ) {
1626
		$total = 0;
1627
		foreach ( $this->get_refunds() as $refund ) {
0 ignored issues
show
Bug introduced by
The expression $this->get_refunds() of type array|object<stdClass> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1628
			foreach ( $refund->get_items( 'tax' ) as $refunded_item ) {
1629
				if ( absint( $refunded_item->get_rate_id() ) === $rate_id ) {
1630
					$total += abs( $refunded_item->get_tax_total() ) + abs( $refunded_item->get_shipping_tax_total() );
1631
				}
1632
			}
1633
		}
1634
1635
		return $total;
1636
	}
1637
1638
	/**
1639
	 * How much money is left to refund?
1640
	 * @return string
1641
	 */
1642
	public function get_remaining_refund_amount() {
1643
		return wc_format_decimal( $this->get_total() - $this->get_total_refunded(), wc_get_price_decimals() );
1644
	}
1645
1646
	/**
1647
	 * How many items are left to refund?
1648
	 * @return int
1649
	 */
1650
	public function get_remaining_refund_items() {
1651
		return absint( $this->get_item_count() - $this->get_item_count_refunded() );
1652
	}
1653
}
1654