Completed
Pull Request — master (#11208)
by Mike
09:06
created

WC_Order   D

Complexity

Total Complexity 190

Size/Duplication

Total Lines 1472
Duplicated Lines 6.39 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 94
loc 1472
rs 4
c 3
b 0
f 0
wmc 190
lcom 1
cbo 5

102 Methods

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