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

WC_Order   D

Complexity

Total Complexity 204

Size/Duplication

Total Lines 1637
Duplicated Lines 6.6 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 12
Bugs 0 Features 1
Metric Value
c 12
b 0
f 1
dl 108
loc 1637
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
	 * Order Data array. This is the core order data exposed in APIs since 2.7.0.
47
	 * @since 2.7.0
48
	 * @var array
49
	 */
50
	protected $_data = array(
51
		// Abstract order props
52
		'id'                   => 0,
53
		'parent_id'            => 0,
54
		'status'               => '',
55
		'currency'             => '',
56
		'version'              => '',
57
		'prices_include_tax'   => false,
58
		'date_created'         => '',
59
		'date_modified'        => '',
60
		'discount_total'       => 0,
61
		'discount_tax'         => 0,
62
		'shipping_total'       => 0,
63
		'shipping_tax'         => 0,
64
		'cart_tax'             => 0,
65
		'total'                => 0,
66
		'total_tax'            => 0,
67
68
		// Order props
69
		'customer_id'          => 0,
70
		'order_key'            => '',
71
		'billing'              => array(
72
			'first_name'       => '',
73
			'last_name'        => '',
74
			'company'          => '',
75
			'address_1'        => '',
76
			'address_2'        => '',
77
			'city'             => '',
78
			'state'            => '',
79
			'postcode'         => '',
80
			'country'          => '',
81
			'email'            => '',
82
			'phone'            => '',
83
		),
84
		'shipping'             => array(
85
			'first_name'       => '',
86
			'last_name'        => '',
87
			'company'          => '',
88
			'address_1'        => '',
89
			'address_2'        => '',
90
			'city'             => '',
91
			'state'            => '',
92
			'postcode'         => '',
93
			'country'          => '',
94
		),
95
		'payment_method'       => '',
96
		'payment_method_title' => '',
97
		'transaction_id'       => '',
98
		'customer_ip_address'  => '',
99
		'customer_user_agent'  => '',
100
		'created_via'          => '',
101
		'customer_note'        => '',
102
		'date_completed'       => '',
103
		'date_paid'            => '',
104
		'cart_hash'            => '',
105
	);
106
107
	/**
108
	 * When a payment is complete this function is called.
109
	 *
110
	 * Most of the time this should mark an order as 'processing' so that admin can process/post the items.
111
	 * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action.
112
	 * Stock levels are reduced at this point.
113
	 * Sales are also recorded for products.
114
	 * Finally, record the date of payment.
115
	 *
116
	 * Order must exist.
117
	 *
118
	 * @param string $transaction_id Optional transaction id to store in post meta.
119
	 * @return bool success
120
	 */
121
	public function payment_complete( $transaction_id = '' ) {
122
		try {
123
			if ( ! $this->get_id() ) {
124
				throw new Exception();
125
			}
126
			do_action( 'woocommerce_pre_payment_complete', $this->get_id() );
127
128
			if ( ! empty( WC()->session ) ) {
129
				WC()->session->set( 'order_awaiting_payment', false );
130
			}
131
132
			if ( $this->has_status( apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this ) ) ) {
133
				$order_needs_processing = false;
134
135
				if ( sizeof( $this->get_items() ) > 0 ) {
136
					foreach ( $this->get_items() as $item ) {
137
						if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) ) {
138
							$virtual_downloadable_item = $product->is_downloadable() && $product->is_virtual();
139
140
							if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $product, $this->get_id() ) ) {
141
								$order_needs_processing = true;
142
								break;
143
							}
144
						}
145
					}
146
				}
147
148
				if ( ! empty( $transaction_id ) ) {
149
					$this->set_transaction_id( $transaction_id );
150
				}
151
152
				$this->set_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->get_id() ) );
153
				$this->set_date_paid( current_time( 'timestamp' ) );
154
				$this->save();
155
156
				do_action( 'woocommerce_payment_complete', $this->get_id() );
157
			} else {
158
				do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->get_id() );
159
			}
160
		} catch ( Exception $e ) {
161
			return false;
162
		}
163
		return true;
164
	}
165
166
	/**
167
	 * Gets order total - formatted for display.
168
	 * @return string
169
	 */
170
	public function get_formatted_order_total( $tax_display = '', $display_refunded = true ) {
171
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_currency() ) );
172
		$order_total    = $this->get_total();
173
		$total_refunded = $this->get_total_refunded();
174
		$tax_string     = '';
175
176
		// Tax for inclusive prices
177
		if ( wc_tax_enabled() && 'incl' == $tax_display ) {
178
			$tax_string_array = array();
179
180
			if ( 'itemized' == get_option( 'woocommerce_tax_total_display' ) ) {
181
				foreach ( $this->get_tax_totals() as $code => $tax ) {
182
					$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;
183
					$tax_string_array[] = sprintf( '%s %s', $tax_amount, $tax->label );
184
				}
185
			} else {
186
				$tax_amount         = ( $total_refunded && $display_refunded ) ? $this->get_total_tax() - $this->get_total_tax_refunded() : $this->get_total_tax();
187
				$tax_string_array[] = sprintf( '%s %s', wc_price( $tax_amount, array( 'currency' => $this->get_currency() ) ), WC()->countries->tax_or_vat() );
188
			}
189
			if ( ! empty( $tax_string_array ) ) {
190
				$tax_string = ' ' . sprintf( __( '(includes %s)', 'woocommerce' ), implode( ', ', $tax_string_array ) );
191
			}
192
		}
193
194
		if ( $total_refunded && $display_refunded ) {
195
			$formatted_total = '<del>' . strip_tags( $formatted_total ) . '</del> <ins>' . wc_price( $order_total - $total_refunded, array( 'currency' => $this->get_currency() ) ) . $tax_string . '</ins>';
196
		} else {
197
			$formatted_total .= $tax_string;
198
		}
199
200
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
201
	}
202
203
	/*
204
	|--------------------------------------------------------------------------
205
	| CRUD methods
206
	|--------------------------------------------------------------------------
207
	|
208
	| Methods which create, read, update and delete orders from the database.
209
	| Written in abstract fashion so that the way orders are stored can be
210
	| changed more easily in the future.
211
	|
212
	| A save method is included for convenience (chooses update or create based
213
	| on if the order exists yet).
214
	|
215
	*/
216
217
	/**
218
	 * Insert data into the database.
219
	 * @since 2.7.0
220
	 */
221
	public function create() {
222
		parent::create();
223
224
		// Store additonal order data
225
		if ( $this->get_id() ) {
226
			$this->set_order_key( 'wc_' . apply_filters( 'woocommerce_generate_order_key', uniqid( 'order_' ) ) );
227
			$this->update_post_meta( '_customer_user', $this->get_customer_id() );
228
			$this->update_post_meta( '_order_key', $this->get_order_key() );
229
			$this->update_post_meta( '_billing_first_name', $this->get_billing_first_name() );
230
			$this->update_post_meta( '_billing_last_name', $this->get_billing_last_name() );
231
			$this->update_post_meta( '_billing_company', $this->get_billing_company() );
232
			$this->update_post_meta( '_billing_address_1', $this->get_billing_address_1() );
233
			$this->update_post_meta( '_billing_address_2', $this->get_billing_address_2() );
234
			$this->update_post_meta( '_billing_city', $this->get_billing_city() );
235
			$this->update_post_meta( '_billing_state', $this->get_billing_state() );
236
			$this->update_post_meta( '_billing_postcode', $this->get_billing_postcode() );
237
			$this->update_post_meta( '_billing_country', $this->get_billing_country() );
238
			$this->update_post_meta( '_billing_email', $this->get_billing_email() );
239
			$this->update_post_meta( '_billing_phone', $this->get_billing_phone() );
240
			$this->update_post_meta( '_shipping_first_name', $this->get_shipping_first_name() );
241
			$this->update_post_meta( '_shipping_last_name', $this->get_shipping_last_name() );
242
			$this->update_post_meta( '_shipping_company', $this->get_shipping_company() );
243
			$this->update_post_meta( '_shipping_address_1', $this->get_shipping_address_1() );
244
			$this->update_post_meta( '_shipping_address_2', $this->get_shipping_address_2() );
245
			$this->update_post_meta( '_shipping_city', $this->get_shipping_city() );
246
			$this->update_post_meta( '_shipping_state', $this->get_shipping_state() );
247
			$this->update_post_meta( '_shipping_postcode', $this->get_shipping_postcode() );
248
			$this->update_post_meta( '_shipping_country', $this->get_shipping_country() );
249
			$this->update_post_meta( '_payment_method', $this->get_payment_method() );
250
			$this->update_post_meta( '_payment_method_title', $this->get_payment_method_title() );
251
			$this->update_post_meta( '_transaction_id', $this->get_transaction_id() );
252
			$this->update_post_meta( '_customer_ip_address', $this->get_customer_ip_address() );
253
			$this->update_post_meta( '_customer_user_agent', $this->get_customer_user_agent() );
254
			$this->update_post_meta( '_created_via', $this->get_created_via() );
255
			$this->update_post_meta( '_customer_note', $this->get_customer_note() );
256
			$this->update_post_meta( '_date_completed', $this->get_date_completed() );
257
			$this->update_post_meta( '_date_paid', $this->get_date_paid() );
258
			$this->update_post_meta( '_cart_hash', $this->get_cart_hash() );
259
			do_action( 'woocommerce_new_order', $this->get_id() );
260
		}
261
	}
262
263
	/**
264
	 * Read from the database.
265
	 * @since 2.7.0
266
	 * @param int $id ID of object to read.
267
	 */
268
	public function read( $id ) {
269
		parent::read( $id );
270
271
		if ( ! $this->get_id() ) {
272
			return;
273
		}
274
275
		$post_object = get_post( $this->get_id() );
276
277
		$this->set_props( array(
278
			'order_key'            => get_post_meta( $this->get_id(), '_order_key', true ),
279
			'customer_id'          => get_post_meta( $this->get_id(), '_customer_user', true ),
280
			'billing_first_name'   => get_post_meta( $this->get_id(), '_billing_first_name', true ),
281
			'billing_last_name'    => get_post_meta( $this->get_id(), '_billing_last_name', true ),
282
			'billing_company'      => get_post_meta( $this->get_id(), '_billing_company', true ),
283
			'billing_address_1'    => get_post_meta( $this->get_id(), '_billing_address_1', true ),
284
			'billing_address_2'    => get_post_meta( $this->get_id(), '_billing_address_2', true ),
285
			'billing_city'         => get_post_meta( $this->get_id(), '_billing_city', true ),
286
			'billing_state'        => get_post_meta( $this->get_id(), '_billing_state', true ),
287
			'billing_postcode'     => get_post_meta( $this->get_id(), '_billing_postcode', true ),
288
			'billing_country'      => get_post_meta( $this->get_id(), '_billing_country', true ),
289
			'billing_email'        => get_post_meta( $this->get_id(), '_billing_email', true ),
290
			'billing_phone'        => get_post_meta( $this->get_id(), '_billing_phone', true ),
291
			'shipping_first_name'  => get_post_meta( $this->get_id(), '_shipping_first_name', true ),
292
			'shipping_last_name'   => get_post_meta( $this->get_id(), '_shipping_last_name', true ),
293
			'shipping_company'     => get_post_meta( $this->get_id(), '_shipping_company', true ),
294
			'shipping_address_1'   => get_post_meta( $this->get_id(), '_shipping_address_1', true ),
295
			'shipping_address_2'   => get_post_meta( $this->get_id(), '_shipping_address_2', true ),
296
			'shipping_city'        => get_post_meta( $this->get_id(), '_shipping_city', true ),
297
			'shipping_state'       => get_post_meta( $this->get_id(), '_shipping_state', true ),
298
			'shipping_postcode'    => get_post_meta( $this->get_id(), '_shipping_postcode', true ),
299
			'shipping_country'     => get_post_meta( $this->get_id(), '_shipping_country', true ),
300
			'payment_method'       => get_post_meta( $this->get_id(), '_payment_method', true ),
301
			'payment_method_title' => get_post_meta( $this->get_id(), '_payment_method_title', true ),
302
			'transaction_id'       => get_post_meta( $this->get_id(), '_transaction_id', true ),
303
			'customer_ip_address'  => get_post_meta( $this->get_id(), '_customer_ip_address', true ),
304
			'customer_user_agent'  => get_post_meta( $this->get_id(), '_customer_user_agent', true ),
305
			'created_via'          => get_post_meta( $this->get_id(), '_created_via', true ),
306
			'customer_note'        => get_post_meta( $this->get_id(), '_customer_note', true ),
307
			'date_completed'       => get_post_meta( $this->get_id(), '_completed_date', true ),
308
			'date_paid'            => get_post_meta( $this->get_id(), '_paid_date', true ),
309
			'cart_hash'            => get_post_meta( $this->get_id(), '_cart_hash', true ),
310
			'customer_note'        => $post_object->post_excerpt,
311
		) );
312
313
		$this->maybe_set_user_billing_email();
314
	}
315
316
	/**
317
	 * Update data in the database.
318
	 * @since 2.7.0
319
	 */
320
	public function update() {
321
		// Store additonal order data
322
		$this->update_post_meta( '_order_key', $this->get_order_key() );
323
		$this->update_post_meta( '_customer_user', $this->get_customer_id() );
324
		$this->update_post_meta( '_billing_first_name', $this->get_billing_first_name() );
325
		$this->update_post_meta( '_billing_last_name', $this->get_billing_last_name() );
326
		$this->update_post_meta( '_billing_company', $this->get_billing_company() );
327
		$this->update_post_meta( '_billing_address_1', $this->get_billing_address_1() );
328
		$this->update_post_meta( '_billing_address_2', $this->get_billing_address_2() );
329
		$this->update_post_meta( '_billing_city', $this->get_billing_city() );
330
		$this->update_post_meta( '_billing_state', $this->get_billing_state() );
331
		$this->update_post_meta( '_billing_postcode', $this->get_billing_postcode() );
332
		$this->update_post_meta( '_billing_country', $this->get_billing_country() );
333
		$this->update_post_meta( '_billing_email', $this->get_billing_email() );
334
		$this->update_post_meta( '_billing_phone', $this->get_billing_phone() );
335
		$this->update_post_meta( '_shipping_first_name', $this->get_shipping_first_name() );
336
		$this->update_post_meta( '_shipping_last_name', $this->get_shipping_last_name() );
337
		$this->update_post_meta( '_shipping_company', $this->get_shipping_company() );
338
		$this->update_post_meta( '_shipping_address_1', $this->get_shipping_address_1() );
339
		$this->update_post_meta( '_shipping_address_2', $this->get_shipping_address_2() );
340
		$this->update_post_meta( '_shipping_city', $this->get_shipping_city() );
341
		$this->update_post_meta( '_shipping_state', $this->get_shipping_state() );
342
		$this->update_post_meta( '_shipping_postcode', $this->get_shipping_postcode() );
343
		$this->update_post_meta( '_shipping_country', $this->get_shipping_country() );
344
		$this->update_post_meta( '_payment_method', $this->get_payment_method() );
345
		$this->update_post_meta( '_payment_method_title', $this->get_payment_method_title() );
346
		$this->update_post_meta( '_transaction_id', $this->get_transaction_id() );
347
		$this->update_post_meta( '_customer_ip_address', $this->get_customer_ip_address() );
348
		$this->update_post_meta( '_customer_user_agent', $this->get_customer_user_agent() );
349
		$this->update_post_meta( '_created_via', $this->get_created_via() );
350
		$this->update_post_meta( '_customer_note', $this->get_customer_note() );
351
		$this->update_post_meta( '_date_completed', $this->get_date_completed() );
352
		$this->update_post_meta( '_date_paid', $this->get_date_paid() );
353
		$this->update_post_meta( '_cart_hash', $this->get_cart_hash() );
354
355
		$customer_changed = $this->update_post_meta( '_customer_user', $this->get_customer_id() );
356
357
		// Update parent
358
		parent::update();
359
360
		// If customer changed, update any downloadable permissions
361
		if ( $customer_changed ) {
362
			$wpdb->update( $wpdb->prefix . "woocommerce_downloadable_product_permissions",
363
				array(
364
					'user_id'    => $this->get_customer_id(),
365
					'user_email' => $this->get_billing_email(),
366
				),
367
				array(
368
					'order_id'   => $this->get_id(),
369
				),
370
				array(
371
					'%d',
372
					'%s',
373
				),
374
				array(
375
					'%d',
376
				)
377
			);
378
		}
379
380
		// Handle status change
381
		$this->status_transition();
382
	}
383
384
	/**
385
	 * Set order status.
386
	 * @since 2.7.0
387
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
388
	 * @param string $note (default: '') Optional note to add.
389
	 * @param bool $manual_update is this a manual order status change?
390
	 * @param array details of change
391
	 */
392
	public function set_status( $new_status, $note = '', $manual_update = false ) {
393
		$result = parent::set_status( $new_status );
394
395
		if ( ! empty( $result['from'] ) && $result['from'] !== $result['to'] ) {
396
			$this->_status_transition = array(
397
				'from'   => ! empty( $this->_status_transition['from'] ) ? $this->_status_transition['from'] : $result['from'],
398
				'to'     => $result['to'],
399
				'note'   => $note,
400
				'manual' => (bool) $manual_update,
401
			);
402
403
			if ( 'pending' === $result['from'] && ! $manual_update ) {
404
				$this->set_date_paid( current_time( 'timestamp' ) );
405
			}
406
407
			if ( 'completed' === $result['to'] ) {
408
				$this->set_date_completed( current_time( 'timestamp' ) );
409
			}
410
		}
411
412
		return $result;
413
	}
414
415
	/**
416
	 * Updates status of order immediately. Order must exist.
417
	 * @uses WC_Order::set_status()
418
	 * @return bool success
419
	 */
420
	public function update_status( $new_status, $note = '', $manual = false ) {
421
		try {
422
			if ( ! $this->get_id() ) {
423
				throw new Exception();
424
			}
425
			$this->set_status( $new_status, $note, $manual );
426
			$this->save();
427
		} catch ( Exception $e ) {
428
			return false;
429
		}
430
		return true;
431
	}
432
433
	/**
434
	 * Handle the status transition.
435
	 */
436
	protected function status_transition() {
437
		if ( $this->_status_transition ) {
438
			if ( ! empty( $this->_status_transition['from'] ) ) {
439
				$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'] ) );
440
441
				do_action( 'woocommerce_order_status_' . $this->_status_transition['from'] . '_to_' . $this->_status_transition['to'], $this->get_id() );
442
				do_action( 'woocommerce_order_status_changed', $this->get_id(), $this->_status_transition['from'], $this->_status_transition['to'] );
443
			} else {
444
				$transition_note = sprintf( __( 'Order status set to %s.', 'woocommerce' ), wc_get_order_status_name( $this->_status_transition['to'] ) );
445
			}
446
447
			do_action( 'woocommerce_order_status_' . $this->_status_transition['to'], $this->get_id() );
448
449
			// Note the transition occured
450
			$this->add_order_note( trim( $this->_status_transition['note'] . ' ' . $transition_note ), 0, $this->_status_transition['manual'] );
451
452
			// This has ran, so reset status transition variable
453
			$this->_status_transition = false;
454
		}
455
	}
456
457
	/*
458
	|--------------------------------------------------------------------------
459
	| Getters
460
	|--------------------------------------------------------------------------
461
	|
462
	| Methods for getting data from the order object.
463
	|
464
	*/
465
466
	/**
467
	 * Get all class data in array format.
468
	 * @since 2.7.0
469
	 * @return array
470
	 */
471 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...
472
		return array_merge(
473
			$this->_data,
474
			array(
475
				'number'         => $this->get_order_number(),
476
				'meta_data'      => $this->get_meta_data(),
477
				'line_items'     => $this->get_items( 'line_item' ),
478
				'tax_lines'      => $this->get_items( 'tax' ),
479
				'shipping_lines' => $this->get_items( 'shipping' ),
480
				'fee_lines'      => $this->get_items( 'fee' ),
481
				'coupon_lines'   => $this->get_items( 'coupon' ),
482
			)
483
		);
484
	}
485
486
	/**
487
	 * get_order_number function.
488
	 *
489
	 * Gets the order number for display (by default, order ID).
490
	 *
491
	 * @return string
492
	 */
493
	public function get_order_number() {
494
		return apply_filters( 'woocommerce_order_number', $this->get_id(), $this );
495
	}
496
497
	/**
498
	 * Get order key.
499
	 * @since 2.7.0
500
	 * @return string
501
	 */
502
	public function get_order_key() {
503
		return $this->get_prop( 'order_key' );
504
	}
505
506
	/**
507
	 * Get customer_id
508
	 * @return int
509
	 */
510
	public function get_customer_id() {
511
		return $this->get_prop( 'customer_id' );
512
	}
513
514
	/**
515
	 * Alias for get_customer_id().
516
	 * @return int
517
	 */
518
	public function get_user_id() {
519
		return $this->get_customer_id();
520
	}
521
522
	/**
523
	 * Get the user associated with the order. False for guests.
524
	 * @return WP_User|false
525
	 */
526
	public function get_user() {
527
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
528
	}
529
530
	/**
531
	 * Get billing_first_name
532
	 * @return string
533
	 */
534
	public function get_billing_first_name() {
535
		return $this->get_prop( 'billing', 'first_name' );
536
	}
537
538
	/**
539
	 * Get billing_last_name
540
	 * @return string
541
	 */
542
	public function get_billing_last_name() {
543
		return $this->get_prop( 'billing', 'last_name' );
544
	}
545
546
	/**
547
	 * Get billing_company
548
	 * @return string
549
	 */
550
	public function get_billing_company() {
551
		return $this->get_prop( 'billing', 'company' );
552
	}
553
554
	/**
555
	 * Get billing_address_1
556
	 * @return string
557
	 */
558
	public function get_billing_address_1() {
559
		return $this->get_prop( 'billing', 'address_1' );
560
	}
561
562
	/**
563
	 * Get billing_address_2
564
	 * @return string $value
565
	 */
566
	public function get_billing_address_2() {
567
		return $this->get_prop( 'billing', 'address_2' );
568
	}
569
570
	/**
571
	 * Get billing_city
572
	 * @return string $value
573
	 */
574
	public function get_billing_city() {
575
		return $this->get_prop( 'billing', 'city' );
576
	}
577
578
	/**
579
	 * Get billing_state
580
	 * @return string
581
	 */
582
	public function get_billing_state() {
583
		return $this->get_prop( 'billing', 'state' );
584
	}
585
586
	/**
587
	 * Get billing_postcode
588
	 * @return string
589
	 */
590
	public function get_billing_postcode() {
591
		return $this->get_prop( 'billing', 'postcode' );
592
	}
593
594
	/**
595
	 * Get billing_country
596
	 * @return string
597
	 */
598
	public function get_billing_country() {
599
		return $this->get_prop( 'billing', 'country' );
600
	}
601
602
	/**
603
	 * Get billing_email
604
	 * @return string
605
	 */
606
	public function get_billing_email() {
607
		return $this->get_prop( 'billing', 'email' );
608
	}
609
610
	/**
611
	 * Get billing_phone
612
	 * @return string
613
	 */
614
	public function get_billing_phone() {
615
		return $this->get_prop( 'billing', 'phone' );
616
	}
617
618
	/**
619
	 * Get shipping_first_name
620
	 * @return string
621
	 */
622
	public function get_shipping_first_name() {
623
		return $this->get_prop( 'shipping', 'first_name' );
624
	}
625
626
	/**
627
	 * Get shipping_last_name
628
	 * @return string
629
	 */
630
	public function get_shipping_last_name() {
631
		 return $this->get_prop( 'shipping', 'last_name' );
632
	}
633
634
	/**
635
	 * Get shipping_company
636
	 * @return string
637
	 */
638
	public function get_shipping_company() {
639
		return $this->get_prop( 'shipping', 'company' );
640
	}
641
642
	/**
643
	 * Get shipping_address_1
644
	 * @return string
645
	 */
646
	public function get_shipping_address_1() {
647
		return $this->get_prop( 'shipping', 'address_1' );
648
	}
649
650
	/**
651
	 * Get shipping_address_2
652
	 * @return string
653
	 */
654
	public function get_shipping_address_2() {
655
		return $this->get_prop( 'shipping', 'address_2' );
656
	}
657
658
	/**
659
	 * Get shipping_city
660
	 * @return string
661
	 */
662
	public function get_shipping_city() {
663
		return $this->get_prop( 'shipping', 'city' );
664
	}
665
666
	/**
667
	 * Get shipping_state
668
	 * @return string
669
	 */
670
	public function get_shipping_state() {
671
		return $this->get_prop( 'shipping', 'state' );
672
	}
673
674
	/**
675
	 * Get shipping_postcode
676
	 * @return string
677
	 */
678
	public function get_shipping_postcode() {
679
		return $this->get_prop( 'shipping', 'postcode' );
680
	}
681
682
	/**
683
	 * Get shipping_country
684
	 * @return string
685
	 */
686
	public function get_shipping_country() {
687
		return $this->get_prop( 'shipping', 'country' );
688
	}
689
690
	/**
691
	 * Get the payment method.
692
	 * @return string
693
	 */
694
	public function get_payment_method() {
695
		return $this->get_prop( 'payment_method' );
696
	}
697
698
	/**
699
	 * Get payment_method_title
700
	 * @return string
701
	 */
702
	public function get_payment_method_title() {
703
		return $this->get_prop( 'payment_method_title' );
704
	}
705
706
	/**
707
	 * Get transaction_id
708
	 * @return string
709
	 */
710
	public function get_transaction_id() {
711
		return $this->get_prop( 'transaction_id' );
712
	}
713
714
	/**
715
	 * Get customer_ip_address
716
	 * @return string
717
	 */
718
	public function get_customer_ip_address() {
719
		return $this->get_prop( 'customer_ip_address' );
720
	}
721
722
	/**
723
	 * Get customer_user_agent
724
	 * @return string
725
	 */
726
	public function get_customer_user_agent() {
727
		return $this->get_prop( 'customer_user_agent' );
728
	}
729
730
	/**
731
	 * Get created_via
732
	 * @return string
733
	 */
734
	public function get_created_via() {
735
		return $this->get_prop( 'created_via' );
736
	}
737
738
	/**
739
	 * Get customer_note
740
	 * @return string
741
	 */
742
	public function get_customer_note() {
743
		return $this->get_prop( 'customer_note' );
744
	}
745
746
	/**
747
	 * Get date_completed
748
	 * @return int
749
	 */
750
	public function get_date_completed() {
751
		return absint( $this->get_prop( 'date_completed' ) );
752
	}
753
754
	/**
755
	 * Get date_paid
756
	 * @return int
757
	 */
758
	public function get_date_paid() {
759
		return absint( $this->get_prop( 'date_paid' ) );
760
	}
761
762
	/**
763
	 * Returns the requested address in raw, non-formatted way.
764
	 * @since  2.4.0
765
	 * @param  string $type Billing or shipping. Anything else besides 'billing' will return shipping address.
766
	 * @return array The stored address after filter.
767
	 */
768
	public function get_address( $type = 'billing' ) {
769
		return apply_filters( 'woocommerce_get_order_address', isset( $this->_data[ $type ] ) ? $this->_data[ $type ] : array(), $type, $this );
770
	}
771
772
	/**
773
	 * Get a formatted shipping address for the order.
774
	 *
775
	 * @return string
776
	 */
777
	public function get_shipping_address_map_url() {
778
		$address = apply_filters( 'woocommerce_shipping_address_map_url_parts', $this->get_address( 'shipping' ), $this );
779
		return apply_filters( 'woocommerce_shipping_address_map_url', 'http://maps.google.com/maps?&q=' . urlencode( implode( ', ', $address ) ) . '&z=16', $this );
780
	}
781
782
	/**
783
	 * Get a formatted billing full name.
784
	 * @return string
785
	 */
786
	public function get_formatted_billing_full_name() {
787
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->get_billing_first_name(), $this->get_billing_last_name() );
788
	}
789
790
	/**
791
	 * Get a formatted shipping full name.
792
	 * @return string
793
	 */
794
	public function get_formatted_shipping_full_name() {
795
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ), $this->get_shipping_first_name(), $this->get_shipping_last_name() );
796
	}
797
798
	/**
799
	 * Get a formatted billing address for the order.
800
	 * @return string
801
	 */
802
	public function get_formatted_billing_address() {
803
		return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_order_formatted_billing_address', $this->get_address( 'billing' ), $this ) );
804
	}
805
806
	/**
807
	 * Get a formatted shipping address for the order.
808
	 * @return string
809
	 */
810
	public function get_formatted_shipping_address() {
811
		if ( $this->get_shipping_address_1() || $this->get_shipping_address_2() ) {
812
			return WC()->countries->get_formatted_address( apply_filters( 'woocommerce_order_formatted_shipping_address', $this->get_address( 'shipping' ), $this ) );
813
		} else {
814
			return '';
815
		}
816
	}
817
818
	/**
819
	 * Get cart hash
820
	 * @return string
821
	 */
822
	public function get_cart_hash() {
823
		return $this->get_prop( 'cart_hash' );
824
	}
825
826
	/*
827
	|--------------------------------------------------------------------------
828
	| Setters
829
	|--------------------------------------------------------------------------
830
	|
831
	| Functions for setting order data. These should not update anything in the
832
	| database itself and should only change what is stored in the class
833
	| object. However, for backwards compatibility pre 2.7.0 some of these
834
	| setters may handle both.
835
	|
836
	*/
837
838
	/**
839
	 * Set order_key.
840
	 * @param string $value Max length 20 chars.
841
	 * @throws WC_Data_Exception
842
	 */
843
	public function set_order_key( $value ) {
844
		$this->set_prop( 'order_key', substr( $value, 0, 20 ) );
845
	}
846
847
	/**
848
	 * Set customer_id
849
	 * @param int $value
850
	 * @throws WC_Data_Exception
851
	 */
852
	public function set_customer_id( $value ) {
853
		$this->set_prop( 'customer_id', absint( $value ) );
854
	}
855
856
	/**
857
	 * Set billing_first_name
858
	 * @param string $value
859
	 * @throws WC_Data_Exception
860
	 */
861
	public function set_billing_first_name( $value ) {
862
		$this->set_prop( 'billing', 'first_name', $value );
863
	}
864
865
	/**
866
	 * Set billing_last_name
867
	 * @param string $value
868
	 * @throws WC_Data_Exception
869
	 */
870
	public function set_billing_last_name( $value ) {
871
		$this->set_prop( 'billing', 'last_name', $value );
872
	}
873
874
	/**
875
	 * Set billing_company
876
	 * @param string $value
877
	 * @throws WC_Data_Exception
878
	 */
879
	public function set_billing_company( $value ) {
880
		$this->set_prop( 'billing', 'company', $value );
881
	}
882
883
	/**
884
	 * Set billing_address_1
885
	 * @param string $value
886
	 * @throws WC_Data_Exception
887
	 */
888
	public function set_billing_address_1( $value ) {
889
		$this->set_prop( 'billing', 'address_1', $value );
890
	}
891
892
	/**
893
	 * Set billing_address_2
894
	 * @param string $value
895
	 * @throws WC_Data_Exception
896
	 */
897
	public function set_billing_address_2( $value ) {
898
		$this->set_prop( 'billing', 'address_2', $value );
899
	}
900
901
	/**
902
	 * Set billing_city
903
	 * @param string $value
904
	 * @throws WC_Data_Exception
905
	 */
906
	public function set_billing_city( $value ) {
907
		$this->set_prop( 'billing', 'city', $value );
908
	}
909
910
	/**
911
	 * Set billing_state
912
	 * @param string $value
913
	 * @throws WC_Data_Exception
914
	 */
915
	public function set_billing_state( $value ) {
916
		$this->set_prop( 'billing', 'state', $value );
917
	}
918
919
	/**
920
	 * Set billing_postcode
921
	 * @param string $value
922
	 * @throws WC_Data_Exception
923
	 */
924
	public function set_billing_postcode( $value ) {
925
		$this->set_prop( 'billing', 'postcode', $value );
926
	}
927
928
	/**
929
	 * Set billing_country
930
	 * @param string $value
931
	 * @throws WC_Data_Exception
932
	 */
933
	public function set_billing_country( $value ) {
934
		$this->set_prop( 'billing', 'country', $value );
935
	}
936
937
	/**
938
	 * Maybe set empty billing email to that of the user who owns the order.
939
	 */
940
	protected function maybe_set_user_billing_email() {
941
		if ( ! $this->get_billing_email() && ( $user = $this->get_user() ) ) {
942
			try {
943
				$this->set_billing_email( $user->user_email );
944
			} catch( WC_Data_Exception $e ){
945
				unset( $e );
946
			}
947
		}
948
	}
949
950
	/**
951
	 * Set billing_email
952
	 * @param string $value
953
	 * @throws WC_Data_Exception
954
	 */
955
	public function set_billing_email( $value ) {
956
		if ( $value && ! is_email( $value ) ) {
957
			$this->error( 'order_invalid_billing_email', __( 'Invalid order billing email address', 'woocommerce' ) );
958
		}
959
		$this->set_prop( 'billing', 'email', sanitize_email( $value ) );
960
	}
961
962
	/**
963
	 * Set billing_phone
964
	 * @param string $value
965
	 * @throws WC_Data_Exception
966
	 */
967
	public function set_billing_phone( $value ) {
968
		$this->set_prop( 'billing', 'phone', $value );
969
	}
970
971
	/**
972
	 * Set shipping_first_name
973
	 * @param string $value
974
	 * @throws WC_Data_Exception
975
	 */
976
	public function set_shipping_first_name( $value ) {
977
		$this->set_prop( 'shipping', 'first_name', $value );
978
	}
979
980
	/**
981
	 * Set shipping_last_name
982
	 * @param string $value
983
	 * @throws WC_Data_Exception
984
	 */
985
	public function set_shipping_last_name( $value ) {
986
		$this->set_prop( 'shipping', 'last_name', $value );
987
	}
988
989
	/**
990
	 * Set shipping_company
991
	 * @param string $value
992
	 * @throws WC_Data_Exception
993
	 */
994
	public function set_shipping_company( $value ) {
995
		$this->set_prop( 'shipping', 'company', $value );
996
	}
997
998
	/**
999
	 * Set shipping_address_1
1000
	 * @param string $value
1001
	 * @throws WC_Data_Exception
1002
	 */
1003
	public function set_shipping_address_1( $value ) {
1004
		$this->set_prop( 'shipping', 'address_1', $value );
1005
	}
1006
1007
	/**
1008
	 * Set shipping_address_2
1009
	 * @param string $value
1010
	 * @throws WC_Data_Exception
1011
	 */
1012
	public function set_shipping_address_2( $value ) {
1013
		$this->set_prop( 'shipping', 'address_2', $value );
1014
	}
1015
1016
	/**
1017
	 * Set shipping_city
1018
	 * @param string $value
1019
	 * @throws WC_Data_Exception
1020
	 */
1021
	public function set_shipping_city( $value ) {
1022
		$this->set_prop( 'shipping', 'city', $value );
1023
	}
1024
1025
	/**
1026
	 * Set shipping_state
1027
	 * @param string $value
1028
	 * @throws WC_Data_Exception
1029
	 */
1030
	public function set_shipping_state( $value ) {
1031
		$this->set_prop( 'shipping', 'state', $value );
1032
	}
1033
1034
	/**
1035
	 * Set shipping_postcode
1036
	 * @param string $value
1037
	 * @throws WC_Data_Exception
1038
	 */
1039
	public function set_shipping_postcode( $value ) {
1040
		$this->set_prop( 'shipping', 'postcode', $value );
1041
	}
1042
1043
	/**
1044
	 * Set shipping_country
1045
	 * @param string $value
1046
	 * @throws WC_Data_Exception
1047
	 */
1048
	public function set_shipping_country( $value ) {
1049
		$this->set_prop( 'shipping', 'country', $value );
1050
	}
1051
1052
	/**
1053
	 * Set the payment method.
1054
	 * @param string $payment_method Supports WC_Payment_Gateway for bw compatibility with < 2.7
1055
	 * @throws WC_Data_Exception
1056
	 */
1057
	public function set_payment_method( $payment_method = '' ) {
1058
		if ( is_object( $payment_method ) ) {
1059
			$this->set_payment_method( $payment_method->id );
1060
			$this->set_payment_method_title( $payment_method->get_title() );
1061
		} elseif ( '' === $payment_method ) {
1062
			$this->set_prop( 'payment_method', '' );
1063
			$this->set_prop( 'payment_method_title', '' );
1064
		} else {
1065
			$this->set_prop( 'payment_method', $payment_method );
1066
		}
1067
	}
1068
1069
	/**
1070
	 * Set payment_method_title
1071
	 * @param string $value
1072
	 * @throws WC_Data_Exception
1073
	 */
1074
	public function set_payment_method_title( $value ) {
1075
		$this->set_prop( 'payment_method_title', $value );
1076
	}
1077
1078
	/**
1079
	 * Set transaction_id
1080
	 * @param string $value
1081
	 * @throws WC_Data_Exception
1082
	 */
1083
	public function set_transaction_id( $value ) {
1084
		$this->set_prop( 'transaction_id', $value );
1085
	}
1086
1087
	/**
1088
	 * Set customer_ip_address
1089
	 * @param string $value
1090
	 * @throws WC_Data_Exception
1091
	 */
1092
	public function set_customer_ip_address( $value ) {
1093
		$this->set_prop( 'customer_ip_address', $value );
1094
	}
1095
1096
	/**
1097
	 * Set customer_user_agent
1098
	 * @param string $value
1099
	 * @throws WC_Data_Exception
1100
	 */
1101
	public function set_customer_user_agent( $value ) {
1102
		$this->set_prop( 'customer_user_agent', $value );
1103
	}
1104
1105
	/**
1106
	 * Set created_via
1107
	 * @param string $value
1108
	 * @throws WC_Data_Exception
1109
	 */
1110
	public function set_created_via( $value ) {
1111
		$this->set_prop( 'created_via', $value );
1112
	}
1113
1114
	/**
1115
	 * Set customer_note
1116
	 * @param string $value
1117
	 * @throws WC_Data_Exception
1118
	 */
1119
	public function set_customer_note( $value ) {
1120
		$this->set_prop( 'customer_note', $value );
1121
	}
1122
1123
	/**
1124
	 * Set date_completed
1125
	 * @param string $timestamp
1126
	 * @throws WC_Data_Exception
1127
	 */
1128
	public function set_date_completed( $timestamp ) {
1129
		$this->set_prop( 'date_completed', is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp ) );
1130
	}
1131
1132
	/**
1133
	 * Set date_paid
1134
	 * @param string $timestamp
1135
	 * @throws WC_Data_Exception
1136
	 */
1137
	public function set_date_paid( $timestamp ) {
1138
		$this->set_prop( 'date_paid', is_numeric( $timestamp ) ? $timestamp : strtotime( $timestamp ) );
1139
	}
1140
1141
	/**
1142
	 * Set cart hash
1143
	 * @param string $value
1144
	 * @throws WC_Data_Exception
1145
	 */
1146
	public function set_cart_hash( $value ) {
1147
		$this->set_prop( 'cart_hash', $value );
1148
	}
1149
1150
	/*
1151
	|--------------------------------------------------------------------------
1152
	| Conditionals
1153
	|--------------------------------------------------------------------------
1154
	|
1155
	| Checks if a condition is true or false.
1156
	|
1157
	*/
1158
1159
	/**
1160
	 * Check if an order key is valid.
1161
	 *
1162
	 * @param mixed $key
1163
	 * @return bool
1164
	 */
1165
	public function key_is_valid( $key ) {
1166
		return $key === $this->get_order_key();
1167
	}
1168
1169
	/**
1170
	 * See if order matches cart_hash.
1171
	 * @return bool
1172
	 */
1173
	public function has_cart_hash( $cart_hash = '' ) {
1174
		return hash_equals( $this->get_cart_hash(), $cart_hash );
1175
	}
1176
1177
	/**
1178
	 * Checks if an order can be edited, specifically for use on the Edit Order screen.
1179
	 * @return bool
1180
	 */
1181
	public function is_editable() {
1182
		return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft' ) ), $this );
1183
	}
1184
1185
	/**
1186
	 * Returns if an order has been paid for based on the order status.
1187
	 * @since 2.5.0
1188
	 * @return bool
1189
	 */
1190
	public function is_paid() {
1191
		return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this );
1192
	}
1193
1194
	/**
1195
	 * Checks if product download is permitted.
1196
	 *
1197
	 * @return bool
1198
	 */
1199
	public function is_download_permitted() {
1200
		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 );
1201
	}
1202
1203
	/**
1204
	 * Checks if an order needs display the shipping address, based on shipping method.
1205
	 * @return bool
1206
	 */
1207
	public function needs_shipping_address() {
1208
		if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
1209
			return false;
1210
		}
1211
1212
		$hide  = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this );
1213
		$needs_address = false;
1214
1215
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
1216
			// Remove any instance IDs after :
1217
			$shipping_method_id = current( explode( ':', $shipping_method['method_id'] ) );
1218
1219
			if ( ! in_array( $shipping_method_id, $hide ) ) {
1220
				$needs_address = true;
1221
				break;
1222
			}
1223
		}
1224
1225
		return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this );
1226
	}
1227
1228
	/**
1229
	 * Returns true if the order contains a downloadable product.
1230
	 * @return bool
1231
	 */
1232
	public function has_downloadable_item() {
1233
		foreach ( $this->get_items() as $item ) {
1234
			if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->is_downloadable() && $product->has_file() ) {
1235
				return true;
1236
			}
1237
		}
1238
		return false;
1239
	}
1240
1241
	/**
1242
	 * Checks if an order needs payment, based on status and order total.
1243
	 *
1244
	 * @return bool
1245
	 */
1246
	public function needs_payment() {
1247
		$valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this );
1248
		return apply_filters( 'woocommerce_order_needs_payment', ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ), $this, $valid_order_statuses );
1249
	}
1250
1251
	/*
1252
	|--------------------------------------------------------------------------
1253
	| URLs and Endpoints
1254
	|--------------------------------------------------------------------------
1255
	*/
1256
1257
	/**
1258
	 * 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.
1259
	 *
1260
	 * @param  bool $on_checkout
1261
	 * @return string
1262
	 */
1263
	public function get_checkout_payment_url( $on_checkout = false ) {
1264
		$pay_url = wc_get_endpoint_url( 'order-pay', $this->get_id(), wc_get_page_permalink( 'checkout' ) );
1265
1266 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...
1267
			$pay_url = str_replace( 'http:', 'https:', $pay_url );
1268
		}
1269
1270
		if ( $on_checkout ) {
1271
			$pay_url = add_query_arg( 'key', $this->get_order_key(), $pay_url );
1272
		} else {
1273
			$pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->get_order_key() ), $pay_url );
1274
		}
1275
1276
		return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this );
1277
	}
1278
1279
	/**
1280
	 * Generates a URL for the thanks page (order received).
1281
	 *
1282
	 * @return string
1283
	 */
1284
	public function get_checkout_order_received_url() {
1285
		$order_received_url = wc_get_endpoint_url( 'order-received', $this->get_id(), wc_get_page_permalink( 'checkout' ) );
1286
1287
		if ( 'yes' === get_option( 'woocommerce_force_ssl_checkout' ) || is_ssl() ) {
1288
			$order_received_url = str_replace( 'http:', 'https:', $order_received_url );
1289
		}
1290
1291
		$order_received_url = add_query_arg( 'key', $this->get_order_key(), $order_received_url );
1292
1293
		return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this );
1294
	}
1295
1296
	/**
1297
	 * Generates a URL so that a customer can cancel their (unpaid - pending) order.
1298
	 *
1299
	 * @param string $redirect
1300
	 *
1301
	 * @return string
1302
	 */
1303 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...
1304
		return apply_filters( 'woocommerce_get_cancel_order_url', wp_nonce_url( add_query_arg( array(
1305
			'cancel_order' => 'true',
1306
			'order'        => $this->get_order_key(),
1307
			'order_id'     => $this->get_id(),
1308
			'redirect'     => $redirect
1309
		), $this->get_cancel_endpoint() ), 'woocommerce-cancel_order' ) );
1310
	}
1311
1312
	/**
1313
	 * Generates a raw (unescaped) cancel-order URL for use by payment gateways.
1314
	 *
1315
	 * @param string $redirect
1316
	 *
1317
	 * @return string The unescaped cancel-order URL.
1318
	 */
1319 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...
1320
		return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array(
1321
			'cancel_order' => 'true',
1322
			'order'        => $this->get_order_key(),
1323
			'order_id'     => $this->get_id(),
1324
			'redirect'     => $redirect,
1325
			'_wpnonce'     => wp_create_nonce( 'woocommerce-cancel_order' )
1326
		), $this->get_cancel_endpoint() ) );
1327
	}
1328
1329
	/**
1330
	 * Helper method to return the cancel endpoint.
1331
	 *
1332
	 * @return string the cancel endpoint; either the cart page or the home page.
1333
	 */
1334
	public function get_cancel_endpoint() {
1335
		$cancel_endpoint = wc_get_page_permalink( 'cart' );
1336
		if ( ! $cancel_endpoint ) {
1337
			$cancel_endpoint = home_url();
1338
		}
1339
1340
		if ( false === strpos( $cancel_endpoint, '?' ) ) {
1341
			$cancel_endpoint = trailingslashit( $cancel_endpoint );
1342
		}
1343
1344
		return $cancel_endpoint;
1345
	}
1346
1347
	/**
1348
	 * Generates a URL to view an order from the my account page.
1349
	 *
1350
	 * @return string
1351
	 */
1352
	public function get_view_order_url() {
1353
		return apply_filters( 'woocommerce_get_view_order_url', wc_get_endpoint_url( 'view-order', $this->get_id(), wc_get_page_permalink( 'myaccount' ) ), $this );
1354
	}
1355
1356
	/*
1357
	|--------------------------------------------------------------------------
1358
	| Order notes.
1359
	|--------------------------------------------------------------------------
1360
	*/
1361
1362
	/**
1363
	 * Adds a note (comment) to the order. Order must exist.
1364
	 *
1365
	 * @param string $note Note to add.
1366
	 * @param int $is_customer_note (default: 0) Is this a note for the customer?
1367
	 * @param  bool added_by_user Was the note added by a user?
1368
	 * @return int Comment ID.
1369
	 */
1370
	public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) {
1371
		if ( ! $this->get_id() ) {
1372
			return 0;
1373
		}
1374
1375
		if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->get_id() ) && $added_by_user ) {
1376
			$user                 = get_user_by( 'id', get_current_user_id() );
1377
			$comment_author       = $user->display_name;
1378
			$comment_author_email = $user->user_email;
1379
		} else {
1380
			$comment_author       = __( 'WooCommerce', 'woocommerce' );
1381
			$comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@';
1382
			$comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
1383
			$comment_author_email = sanitize_email( $comment_author_email );
1384
		}
1385
		$commentdata = apply_filters( 'woocommerce_new_order_note_data', array(
1386
			'comment_post_ID'      => $this->get_id(),
1387
			'comment_author'       => $comment_author,
1388
			'comment_author_email' => $comment_author_email,
1389
			'comment_author_url'   => '',
1390
			'comment_content'      => $note,
1391
			'comment_agent'        => 'WooCommerce',
1392
			'comment_type'         => 'order_note',
1393
			'comment_parent'       => 0,
1394
			'comment_approved'     => 1,
1395
		), array( 'order_id' => $this->get_id(), 'is_customer_note' => $is_customer_note ) );
1396
1397
		$comment_id = wp_insert_comment( $commentdata );
1398
1399
		if ( $is_customer_note ) {
1400
			add_comment_meta( $comment_id, 'is_customer_note', 1 );
1401
1402
			do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->get_id(), 'customer_note' => $commentdata['comment_content'] ) );
1403
		}
1404
1405
		return $comment_id;
1406
	}
1407
1408
	/**
1409
	 * List order notes (public) for the customer.
1410
	 *
1411
	 * @return array
1412
	 */
1413
	public function get_customer_order_notes() {
1414
		$notes = array();
1415
		$args  = array(
1416
			'post_id' => $this->get_id(),
1417
			'approve' => 'approve',
1418
			'type'    => ''
1419
		);
1420
1421
		remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
1422
1423
		$comments = get_comments( $args );
1424
1425
		foreach ( $comments as $comment ) {
1426
			if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) {
1427
				continue;
1428
			}
1429
			$comment->comment_content = make_clickable( $comment->comment_content );
1430
			$notes[] = $comment;
1431
		}
1432
1433
		add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
1434
1435
		return $notes;
1436
	}
1437
1438
	/*
1439
	|--------------------------------------------------------------------------
1440
	| Refunds
1441
	|--------------------------------------------------------------------------
1442
	*/
1443
1444
	/**
1445
	 * Get order refunds.
1446
	 * @since 2.2
1447
	 * @return array of WC_Order_Refund objects
1448
	 */
1449
	public function get_refunds() {
1450
		$this->refunds = wc_get_orders( array(
1451
			'type'   => 'shop_order_refund',
1452
			'parent' => $this->get_id(),
1453
			'limit'  => -1,
1454
		) );
1455
		return $this->refunds;
1456
	}
1457
1458
	/**
1459
	 * Get amount already refunded.
1460
	 *
1461
	 * @since 2.2
1462
	 * @return string
1463
	 */
1464 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...
1465
		global $wpdb;
1466
1467
		$total = $wpdb->get_var( $wpdb->prepare( "
1468
			SELECT SUM( postmeta.meta_value )
1469
			FROM $wpdb->postmeta AS postmeta
1470
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
1471
			WHERE postmeta.meta_key = '_refund_amount'
1472
			AND postmeta.post_id = posts.ID
1473
		", $this->get_id() ) );
1474
1475
		return $total;
1476
	}
1477
1478
	/**
1479
	 * Get the total tax refunded.
1480
	 *
1481
	 * @since  2.3
1482
	 * @return float
1483
	 */
1484 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...
1485
		global $wpdb;
1486
1487
		$total = $wpdb->get_var( $wpdb->prepare( "
1488
			SELECT SUM( order_itemmeta.meta_value )
1489
			FROM {$wpdb->prefix}woocommerce_order_itemmeta AS order_itemmeta
1490
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
1491
			INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON ( order_items.order_id = posts.ID AND order_items.order_item_type = 'tax' )
1492
			WHERE order_itemmeta.order_item_id = order_items.order_item_id
1493
			AND order_itemmeta.meta_key IN ('tax_amount', 'shipping_tax_amount')
1494
		", $this->get_id() ) );
1495
1496
		return abs( $total );
1497
	}
1498
1499
	/**
1500
	 * Get the total shipping refunded.
1501
	 *
1502
	 * @since  2.4
1503
	 * @return float
1504
	 */
1505 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...
1506
		global $wpdb;
1507
1508
		$total = $wpdb->get_var( $wpdb->prepare( "
1509
			SELECT SUM( order_itemmeta.meta_value )
1510
			FROM {$wpdb->prefix}woocommerce_order_itemmeta AS order_itemmeta
1511
			INNER JOIN $wpdb->posts AS posts ON ( posts.post_type = 'shop_order_refund' AND posts.post_parent = %d )
1512
			INNER JOIN {$wpdb->prefix}woocommerce_order_items AS order_items ON ( order_items.order_id = posts.ID AND order_items.order_item_type = 'shipping' )
1513
			WHERE order_itemmeta.order_item_id = order_items.order_item_id
1514
			AND order_itemmeta.meta_key IN ('cost')
1515
		", $this->get_id() ) );
1516
1517
		return abs( $total );
1518
	}
1519
1520
	/**
1521
	 * Gets the count of order items of a certain type that have been refunded.
1522
	 * @since  2.4.0
1523
	 * @param string $item_type
1524
	 * @return string
1525
	 */
1526
	public function get_item_count_refunded( $item_type = '' ) {
1527
		if ( empty( $item_type ) ) {
1528
			$item_type = array( 'line_item' );
1529
		}
1530
		if ( ! is_array( $item_type ) ) {
1531
			$item_type = array( $item_type );
1532
		}
1533
		$count = 0;
1534
1535
		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...
1536
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1537
				$count += $refunded_item->get_quantity();
1538
			}
1539
		}
1540
1541
		return apply_filters( 'woocommerce_get_item_count_refunded', $count, $item_type, $this );
1542
	}
1543
1544
	/**
1545
	 * Get the total number of items refunded.
1546
	 *
1547
	 * @since  2.4.0
1548
	 * @param  string $item_type type of the item we're checking, if not a line_item
1549
	 * @return integer
1550
	 */
1551
	public function get_total_qty_refunded( $item_type = 'line_item' ) {
1552
		$qty = 0;
1553
		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...
1554
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1555
				$qty += $refunded_item->get_quantity();
1556
			}
1557
		}
1558
		return $qty;
1559
	}
1560
1561
	/**
1562
	 * Get the refunded amount for a line item.
1563
	 *
1564
	 * @param  int $item_id ID of the item we're checking
1565
	 * @param  string $item_type type of the item we're checking, if not a line_item
1566
	 * @return integer
1567
	 */
1568 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...
1569
		$qty = 0;
1570
		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...
1571
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1572
				if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) {
1573
					$qty += $refunded_item->get_quantity();
1574
				}
1575
			}
1576
		}
1577
		return $qty;
1578
	}
1579
1580
	/**
1581
	 * Get the refunded amount for a line item.
1582
	 *
1583
	 * @param  int $item_id ID of the item we're checking
1584
	 * @param  string $item_type type of the item we're checking, if not a line_item
1585
	 * @return integer
1586
	 */
1587 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...
1588
		$total = 0;
1589
		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...
1590
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1591
				if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) {
1592
					$total += $refunded_item->get_total();
1593
				}
1594
			}
1595
		}
1596
		return $total * -1;
1597
	}
1598
1599
	/**
1600
	 * Get the refunded amount for a line item.
1601
	 *
1602
	 * @param  int $item_id ID of the item we're checking
1603
	 * @param  int $tax_id ID of the tax we're checking
1604
	 * @param  string $item_type type of the item we're checking, if not a line_item
1605
	 * @return double
1606
	 */
1607 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...
1608
		$total = 0;
1609
		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...
1610
			foreach ( $refund->get_items( $item_type ) as $refunded_item ) {
1611
				if ( absint( $refunded_item->get_meta( '_refunded_item_id' ) ) === $item_id ) {
1612
					$total += $refunded_item->get_total_tax();
1613
				}
1614
			}
1615
		}
1616
		return wc_round_tax_total( $total ) * -1;
1617
	}
1618
1619
	/**
1620
	 * Get total tax refunded by rate ID.
1621
	 *
1622
	 * @param  int $rate_id
1623
	 *
1624
	 * @return float
1625
	 */
1626
	public function get_total_tax_refunded_by_rate_id( $rate_id ) {
1627
		$total = 0;
1628
		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...
1629
			foreach ( $refund->get_items( 'tax' ) as $refunded_item ) {
1630
				if ( absint( $refunded_item->get_rate_id() ) === $rate_id ) {
1631
					$total += abs( $refunded_item->get_tax_total() ) + abs( $refunded_item->get_shipping_tax_total() );
1632
				}
1633
			}
1634
		}
1635
1636
		return $total;
1637
	}
1638
1639
	/**
1640
	 * How much money is left to refund?
1641
	 * @return string
1642
	 */
1643
	public function get_remaining_refund_amount() {
1644
		return wc_format_decimal( $this->get_total() - $this->get_total_refunded(), wc_get_price_decimals() );
1645
	}
1646
1647
	/**
1648
	 * How many items are left to refund?
1649
	 * @return int
1650
	 */
1651
	public function get_remaining_refund_items() {
1652
		return absint( $this->get_item_count() - $this->get_item_count_refunded() );
1653
	}
1654
}
1655