Completed
Pull Request — master (#11208)
by Mike
11:30
created

WC_Order   D

Complexity

Total Complexity 191

Size/Duplication

Total Lines 1461
Duplicated Lines 6.43 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 94
loc 1461
rs 4
wmc 191
lcom 1
cbo 5

102 Methods

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