Completed
Push — master ( e81112...ff637a )
by Claudio
10:03
created

WC_Abstract_Order   D

Complexity

Total Complexity 411

Size/Duplication

Total Lines 2598
Duplicated Lines 8.35 %

Coupling/Cohesion

Components 2
Dependencies 10
Metric Value
wmc 411
lcom 2
cbo 10
dl 217
loc 2598
rs 4.4103

105 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 4
A init() 15 15 4
A remove_order_items() 0 11 2
A set_payment_method() 0 7 2
A set_address() 0 6 2
B get_address() 0 32 2
C add_product() 5 56 12
C update_product() 0 43 16
A add_coupon() 0 17 2
B update_coupon() 0 22 5
B add_tax() 0 27 4
B add_shipping() 0 25 2
B update_shipping() 0 32 5
B add_fee() 0 28 3
B update_fee() 0 30 6
B set_total() 0 26 5
F calculate_taxes() 17 97 21
A calculate_shipping() 0 12 2
C update_taxes() 23 50 12
D calculate_totals() 0 34 9
A get_order() 13 13 3
B populate() 0 18 5
A __isset() 0 8 2
B __get() 0 14 6
A get_status() 0 4 2
A has_status() 0 3 4
A get_user_id() 0 3 2
A get_user() 0 3 2
A get_transaction_id() 0 3 1
A key_is_valid() 0 8 2
A get_order_number() 0 3 1
A get_formatted_billing_address() 0 21 2
B get_formatted_shipping_address() 0 24 4
A get_shipping_address_map_url() 0 12 1
A get_billing_address() 0 4 1
A get_shipping_address() 0 4 1
A get_formatted_billing_full_name() 0 3 1
A get_formatted_shipping_full_name() 0 3 1
B get_items() 0 27 4
B expand_item_meta() 0 32 6
B get_item_count() 17 17 5
A get_refunds() 0 1 1
A get_fees() 0 3 1
A get_taxes() 0 3 1
A get_shipping_methods() 0 3 1
A has_shipping_method() 0 17 4
B get_tax_totals() 4 24 4
A has_meta() 0 7 1
A get_item_meta_array() 0 18 3
A display_item_meta() 0 5 1
A get_item_meta() 0 3 1
C get_total_discount() 12 26 7
A get_cart_discount() 0 4 1
A get_order_discount_to_display() 0 3 1
A get_order_discount() 0 4 1
A get_cart_tax() 0 3 1
A get_shipping_tax() 0 3 1
A get_total_tax() 0 3 1
A get_total_shipping() 0 3 1
A get_total() 0 3 1
A get_subtotal() 0 9 3
A get_item_subtotal() 0 11 3
A get_line_subtotal() 11 11 3
A get_item_total() 0 14 4
A get_line_total() 10 10 3
A get_item_tax() 0 7 2
A get_line_tax() 0 3 1
A get_shipping_method() 0 19 3
B get_formatted_line_subtotal() 0 20 6
A get_order_currency() 0 3 1
A get_formatted_order_total() 0 4 1
C get_subtotal_to_display() 0 62 13
D get_shipping_to_display() 6 37 9
A get_discount_to_display() 0 6 3
A get_cart_discount_to_display() 0 4 1
A get_product_from_item() 0 12 4
F get_order_item_totals() 0 98 18
C email_order_items_table() 0 29 7
A is_paid() 0 3 1
A is_download_permitted() 0 3 3
B has_downloadable_item() 0 10 6
A has_free_item() 0 8 3
A get_checkout_payment_url() 3 16 4
A get_checkout_order_received_url() 3 12 3
A get_cancel_order_url() 12 12 1
A get_cancel_order_url_raw() 13 13 1
A get_cancel_endpoint() 0 13 3
A get_view_order_url() 0 6 1
B get_item_downloads() 0 33 5
B display_item_downloads() 0 17 7
A get_download_url() 0 8 1
B add_order_note() 0 31 6
C update_status() 0 64 9
A cancel_order() 0 4 1
C payment_complete() 0 49 12
B record_product_sales() 0 29 6
A get_used_coupons() 0 11 2
B increase_coupon_usage_counts() 26 26 6
B decrease_coupon_usage_counts() 27 27 6
C reduce_order_stock() 0 26 12
B send_stock_notifications() 0 21 7
B get_customer_order_notes() 0 24 3
A needs_payment() 0 12 3
A needs_shipping_address() 0 17 4
A is_editable() 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_Abstract_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_Abstract_Order, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Abstract Order
4
 *
5
 * The WooCommerce order class handles order data.
6
 *
7
 * @class       WC_Order
8
 * @version     2.2.0
9
 * @package     WooCommerce/Classes
10
 * @category    Class
11
 * @author      WooThemes
12
 *
13
 * @property    string $billing_first_name The billing address first name.
14
 * @property    string $billing_last_name The billing address last name.
15
 * @property    string $billing_company The billing address company.
16
 * @property    string $billing_address_1 The first line of the billing address.
17
 * @property    string $billing_address_2 The second line of the billing address.
18
 * @property    string $billing_city The city of the billing address.
19
 * @property    string $billing_state The state of the billing address.
20
 * @property    string $billing_postcode The postcode of the billing address.
21
 * @property    string $billing_country The country of the billing address.
22
 * @property    string $billing_phone The billing phone number.
23
 * @property    string $billing_email The billing email.
24
 * @property    string $shipping_first_name The shipping address first name.
25
 * @property    string $shipping_last_name The shipping address last name.
26
 * @property    string $shipping_company The shipping address company.
27
 * @property    string $shipping_address_1 The first line of the shipping address.
28
 * @property    string $shipping_address_2 The second line of the shipping address.
29
 * @property    string $shipping_city The city of the shipping address.
30
 * @property    string $shipping_state The state of the shipping address.
31
 * @property    string $shipping_postcode The postcode of the shipping address.
32
 * @property    string $shipping_country The country of the shipping address.
33
 * @property    string $cart_discount Total amount of discount.
34
 * @property    string $cart_discount_tax Total amount of discount applied to taxes.
35
 * @property    string $shipping_method_title < 2.1 was used for shipping method title. Now @deprecated.
36
 * @property    int $customer_user User ID who the order belongs to. 0 for guests.
37
 * @property    string $order_key Random key/password unqique to each order.
38
 * @property    string $order_discount Stored after tax discounts pre-2.3. Now @deprecated.
39
 * @property    string $order_tax Stores order tax total.
40
 * @property    string $order_shipping_tax Stores shipping tax total.
41
 * @property    string $order_shipping Stores shipping total.
42
 * @property    string $order_total Stores order total.
43
 * @property    string $order_currency Stores currency code used for the order.
44
 * @property    string $payment_method method ID.
45
 * @property    string $payment_method_title Name of the payment method used.
46
 * @property    string $customer_ip_address Customer IP Address.
47
 * @property    string $customer_user_agent Customer User agent.
48
 */
49
abstract class WC_Abstract_Order {
50
51
	/** @public int Order (post) ID. */
52
	public $id                          = 0;
53
54
	/** @var $post WP_Post. */
55
	public $post                        = null;
56
57
	/** @public string Order type. */
58
	public $order_type                  = false;
59
60
	/** @public string Order Date. */
61
	public $order_date                  = '';
62
63
	/** @public string Order Modified Date. */
64
	public $modified_date               = '';
65
66
	/** @public string Customer Message (excerpt). */
67
	public $customer_message            = '';
68
69
	/** @public string Customer Note */
70
	public $customer_note               = '';
71
72
	/** @public string Order Status. */
73
	public $post_status                 = '';
74
75
	/** @public bool Do prices include tax? */
76
	public $prices_include_tax          = false;
77
78
	/** @public string Display mode for taxes in cart. */
79
	public $tax_display_cart            = '';
80
81
	/** @public bool Do totals display ex tax? */
82
	public $display_totals_ex_tax       = false;
83
84
	/** @public bool Do cart prices display ex tax? */
85
	public $display_cart_ex_tax         = false;
86
87
	/** @protected string Formatted address. Accessed via get_formatted_billing_address(). */
88
	protected $formatted_billing_address  = '';
89
90
	/** @protected string Formatted address. Accessed via get_formatted_shipping_address(). */
91
	protected $formatted_shipping_address = '';
92
93
	/**
94
	 * Get the order if ID is passed, otherwise the order is new and empty.
95
	 * This class should NOT be instantiated, but the get_order function or new WC_Order_Factory.
96
	 * should be used. It is possible, but the aforementioned are preferred and are the only.
97
	 * methods that will be maintained going forward.
98
	 *
99
	 * @param int $order
100
	 */
101
	public function __construct( $order = 0 ) {
102
		$this->prices_include_tax    = get_option('woocommerce_prices_include_tax') == 'yes' ? true : false;
103
		$this->tax_display_cart      = get_option( 'woocommerce_tax_display_cart' );
104
		$this->display_totals_ex_tax = $this->tax_display_cart == 'excl' ? true : false;
105
		$this->display_cart_ex_tax   = $this->tax_display_cart == 'excl' ? true : false;
106
		$this->init( $order );
107
	}
108
109
	/**
110
	 * Init/load the order object. Called from the constructor.
111
	 *
112
	 * @param  int|object|WC_Order $order Order to init.
113
	 */
114 View Code Duplication
	protected function init( $order ) {
1 ignored issue
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...
115
		if ( is_numeric( $order ) ) {
116
			$this->id   = absint( $order );
117
			$this->post = get_post( $order );
118
			$this->get_order( $this->id );
119
		} elseif ( $order instanceof WC_Order ) {
120
			$this->id   = absint( $order->id );
121
			$this->post = $order->post;
122
			$this->get_order( $this->id );
123
		} elseif ( isset( $order->ID ) ) {
124
			$this->id   = absint( $order->ID );
125
			$this->post = $order;
126
			$this->get_order( $this->id );
127
		}
128
	}
129
130
	/**
131
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
132
	 *
133
	 * @param string $type Order item type. Default null.
134
	 */
135
	public function remove_order_items( $type = null ) {
136
		global $wpdb;
137
138
		if ( ! empty( $type ) ) {
139
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id AND items.order_id = %d AND items.order_item_type = %s", $this->id, $type ) );
140
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->id, $type ) );
141
		} else {
142
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id and items.order_id = %d", $this->id ) );
143
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->id ) );
144
		}
145
	}
146
147
	/**
148
	 * Set the payment method for the order.
149
	 *
150
	 * @param WC_Payment_Gateway
151
	 * @param WC_Payment_Gateway $payment_method
152
	 */
153
	public function set_payment_method( $payment_method ) {
154
155
		if ( is_object( $payment_method ) ) {
156
			update_post_meta( $this->id, '_payment_method', $payment_method->id );
157
			update_post_meta( $this->id, '_payment_method_title', $payment_method->get_title() );
158
		}
159
	}
160
161
	/**
162
	 * Set the customer address.
163
	 *
164
	 * @param array $address Address data.
165
	 * @param string $type billing or shipping.
166
	 */
167
	public function set_address( $address, $type = 'billing' ) {
168
169
		foreach ( $address as $key => $value ) {
170
			update_post_meta( $this->id, "_{$type}_" . $key, $value );
171
		}
172
	}
173
174
	/**
175
	 * Returns the requested address in raw, non-formatted way.
176
	 * @since  2.4.0
177
	 * @param  string $type Billing or shipping. Anything else besides 'billing' will return shipping address.
178
	 * @return array The stored address after filter.
179
	 */
180
	public function get_address( $type = 'billing' ) {
181
182
		if ( 'billing' === $type ) {
183
			$address = array(
184
				'first_name' => $this->billing_first_name,
185
				'last_name'  => $this->billing_last_name,
186
				'company'    => $this->billing_company,
187
				'address_1'  => $this->billing_address_1,
188
				'address_2'  => $this->billing_address_2,
189
				'city'       => $this->billing_city,
190
				'state'      => $this->billing_state,
191
				'postcode'   => $this->billing_postcode,
192
				'country'    => $this->billing_country,
193
				'email'      => $this->billing_email,
194
				'phone'      => $this->billing_phone,
195
			);
196
		} else {
197
			$address = array(
198
				'first_name' => $this->shipping_first_name,
199
				'last_name'  => $this->shipping_last_name,
200
				'company'    => $this->shipping_company,
201
				'address_1'  => $this->shipping_address_1,
202
				'address_2'  => $this->shipping_address_2,
203
				'city'       => $this->shipping_city,
204
				'state'      => $this->shipping_state,
205
				'postcode'   => $this->shipping_postcode,
206
				'country'    => $this->shipping_country,
207
			);
208
		}
209
210
		return apply_filters( 'woocommerce_get_order_address', $address, $type, $this );
211
	}
212
213
	/**
214
	 * Add a product line item to the order.
215
	 *
216
	 * @since 2.2
217
	 * @param \WC_Product $product
218
	 * @param int $qty Line item quantity.
219
	 * @param array $args
220
	 * @return int|bool Item ID or false.
221
	 */
222
	public function add_product( $product, $qty = 1, $args = array() ) {
223
224
		$default_args = array(
225
			'variation' => array(),
226
			'totals'    => array()
227
		);
228
229
		$args    = wp_parse_args( $args, $default_args );
230
		$item_id = wc_add_order_item( $this->id, array(
231
			'order_item_name' => $product->get_title(),
232
			'order_item_type' => 'line_item'
233
		) );
234
235
		if ( ! $item_id ) {
236
			return false;
237
		}
238
239
		wc_add_order_item_meta( $item_id, '_qty',          wc_stock_amount( $qty ) );
240
		wc_add_order_item_meta( $item_id, '_tax_class',    $product->get_tax_class() );
241
		wc_add_order_item_meta( $item_id, '_product_id',   $product->id );
242
		wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 );
243
244
		// Set line item totals, either passed in or from the product
245
		wc_add_order_item_meta( $item_id, '_line_subtotal',     wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $qty ) ) );
246
		wc_add_order_item_meta( $item_id, '_line_total',        wc_format_decimal( isset( $args['totals']['total'] ) ? $args['totals']['total'] : $product->get_price_excluding_tax( $qty ) ) );
247
		wc_add_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) );
248
		wc_add_order_item_meta( $item_id, '_line_tax',          wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) );
249
250
		// Save tax data - Since 2.2
251
		if ( isset( $args['totals']['tax_data'] ) ) {
252
253
			$tax_data             = array();
254
			$tax_data['total']    = array_map( 'wc_format_decimal', $args['totals']['tax_data']['total'] );
255
			$tax_data['subtotal'] = array_map( 'wc_format_decimal', $args['totals']['tax_data']['subtotal'] );
256
257
			wc_add_order_item_meta( $item_id, '_line_tax_data', $tax_data );
258
		} else {
259
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
260
		}
261
262
		// Add variation meta
263 View Code Duplication
		if ( ! empty( $args['variation'] ) ) {
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...
264
			foreach ( $args['variation'] as $key => $value ) {
265
				wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
266
			}
267
		}
268
269
		// Backorders
270
		if ( $product->backorders_require_notification() && $product->is_on_backorder( $qty ) ) {
271
			wc_add_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $qty - max( 0, $product->get_total_stock() ) );
272
		}
273
274
		do_action( 'woocommerce_order_add_product', $this->id, $item_id, $product, $qty, $args );
275
276
		return $item_id;
277
	}
278
279
280
	/**
281
	 * Update a line item for the order.
282
	 *
283
	 * Note this does not update order totals.
284
	 *
285
	 * @since 2.2
286
	 * @param int $item_id order item ID.
287
	 * @param array $args data to update.
288
	 * @param WC_Product $product
289
	 * @return bool
290
	 */
291
	public function update_product( $item_id, $product, $args ) {
292
293
		if ( ! $item_id || ! is_object( $product ) ) {
294
			return false;
295
		}
296
297
		// quantity
298
		if ( isset( $args['qty'] ) ) {
299
			wc_update_order_item_meta( $item_id, '_qty', wc_stock_amount( $args['qty'] ) );
300
		}
301
302
		// tax class
303
		if ( isset( $args['tax_class'] ) ) {
304
			wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] );
305
		}
306
307
		// set item totals, either provided or from product
308
		if ( isset( $args['qty'] ) ) {
309
			wc_update_order_item_meta( $item_id, '_line_subtotal', wc_format_decimal( isset( $args['totals']['subtotal'] ) ? $args['totals']['subtotal'] : $product->get_price_excluding_tax( $args['qty'] ) ) );
310
			wc_update_order_item_meta( $item_id, '_line_total', wc_format_decimal( isset( $args['totals']['total'] ) ? $args['totals']['total'] : $product->get_price_excluding_tax( $args['qty'] ) ) );
311
		}
312
313
		// set item tax totals
314
		wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) );
315
		wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) );
316
317
		// variation meta
318
		if ( isset( $args['variation'] ) && is_array( $args['variation'] ) ) {
319
320
			foreach ( $args['variation'] as $key => $value ) {
321
				wc_update_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
322
			}
323
		}
324
325
		// backorders
326
		if ( isset( $args['qty'] ) && $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) {
327
			wc_update_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ) );
328
		}
329
330
		do_action( 'woocommerce_order_edit_product', $this->id, $item_id, $args, $product );
331
332
		return true;
333
	}
334
335
336
	/**
337
	 * Add coupon code to the order.
338
	 *
339
	 * @param string $code
340
	 * @param integer $discount_amount
341
	 * @param integer $discount_amount_tax "Discounted" tax - used for tax inclusive prices.
342
	 * @return int|bool Item ID or false.
343
	 */
344
	public function add_coupon( $code, $discount_amount = 0, $discount_amount_tax = 0 ) {
345
		$item_id = wc_add_order_item( $this->id, array(
346
			'order_item_name' => $code,
347
			'order_item_type' => 'coupon'
348
		) );
349
350
		if ( ! $item_id ) {
351
			return false;
352
		}
353
354
		wc_add_order_item_meta( $item_id, 'discount_amount', $discount_amount );
355
		wc_add_order_item_meta( $item_id, 'discount_amount_tax', $discount_amount_tax );
356
357
		do_action( 'woocommerce_order_add_coupon', $this->id, $item_id, $code, $discount_amount, $discount_amount_tax );
358
359
		return $item_id;
360
	}
361
362
	/**
363
	 * Update coupon for order.
364
	 *
365
	 * Note this does not update order totals.
366
	 *
367
	 * @since 2.2
368
	 * @param int $item_id
369
	 * @param array $args
370
	 * @return bool
371
	 */
372
	public function update_coupon( $item_id, $args ) {
373
		if ( ! $item_id ) {
374
			return false;
375
		}
376
377
		// code
378
		if ( isset( $args['code'] ) ) {
379
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['code'] ) );
380
		}
381
382
		// amount
383
		if ( isset( $args['discount_amount'] ) ) {
384
			wc_update_order_item_meta( $item_id, 'discount_amount', wc_format_decimal( $args['discount_amount'] ) );
385
		}
386
		if ( isset( $args['discount_amount_tax'] ) ) {
387
			wc_add_order_item_meta( $item_id, 'discount_amount_tax', wc_format_decimal( $args['discount_amount_tax'] ) );
388
		}
389
390
		do_action( 'woocommerce_order_update_coupon', $this->id, $item_id, $args );
391
392
		return true;
393
	}
394
395
	/**
396
	 * Add a tax row to the order.
397
	 *
398
	 * @since 2.2
399
	 * @param int tax_rate_id
400
	 * @return int|bool Item ID or false.
401
	 */
402
	public function add_tax( $tax_rate_id, $tax_amount = 0, $shipping_tax_amount = 0 ) {
403
404
		$code = WC_Tax::get_rate_code( $tax_rate_id );
405
406
		if ( ! $code ) {
407
			return false;
408
		}
409
410
		$item_id = wc_add_order_item( $this->id, array(
411
			'order_item_name' => $code,
412
			'order_item_type' => 'tax'
413
		) );
414
415
		if ( ! $item_id ) {
416
			return false;
417
		}
418
419
		wc_add_order_item_meta( $item_id, 'rate_id', $tax_rate_id );
420
		wc_add_order_item_meta( $item_id, 'label', WC_Tax::get_rate_label( $tax_rate_id ) );
421
		wc_add_order_item_meta( $item_id, 'compound', WC_Tax::is_compound( $tax_rate_id ) ? 1 : 0 );
422
		wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( $tax_amount ) );
423
		wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( $shipping_tax_amount ) );
424
425
		do_action( 'woocommerce_order_add_tax', $this->id, $item_id, $tax_rate_id, $tax_amount, $shipping_tax_amount );
426
427
		return $item_id;
428
	}
429
430
	/**
431
	 * Add a shipping row to the order.
432
	 *
433
	 * @param WC_Shipping_Rate shipping_rate
434
	 * @return int|bool Item ID or false.
435
	 */
436
	public function add_shipping( $shipping_rate ) {
437
438
		$item_id = wc_add_order_item( $this->id, array(
439
			'order_item_name' 		=> $shipping_rate->label,
440
			'order_item_type' 		=> 'shipping'
441
		) );
442
443
		if ( ! $item_id ) {
444
			return false;
445
		}
446
447
		wc_add_order_item_meta( $item_id, 'method_id', $shipping_rate->id );
448
		wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_rate->cost ) );
449
450
		// Save shipping taxes - Since 2.2
451
		$taxes = array_map( 'wc_format_decimal', $shipping_rate->taxes );
452
		wc_add_order_item_meta( $item_id, 'taxes', $taxes );
453
454
		do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $shipping_rate );
455
456
		// Update total
457
		$this->set_total( $this->order_shipping + wc_format_decimal( $shipping_rate->cost ), 'shipping' );
458
459
		return $item_id;
460
	}
461
462
	/**
463
	 * Update shipping method for order.
464
	 *
465
	 * Note this does not update the order total.
466
	 *
467
	 * @since 2.2
468
	 * @param int $item_id
469
	 * @param array $args
470
	 * @return bool
471
	 */
472
	public function update_shipping( $item_id, $args ) {
473
474
		if ( ! $item_id ) {
475
			return false;
476
		}
477
478
		// method title
479
		if ( isset( $args['method_title'] ) ) {
480
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['method_title'] ) );
481
		}
482
483
		// method ID
484
		if ( isset( $args['method_id'] ) ) {
485
			wc_update_order_item_meta( $item_id, 'method_id', $args['method_id'] );
486
		}
487
488
		// method cost
489
		if ( isset( $args['cost'] ) ) {
490
			// Get old cost before updating
491
			$old_cost = wc_get_order_item_meta( $item_id, 'cost' );
492
493
			// Update
494
			wc_update_order_item_meta( $item_id, 'cost', wc_format_decimal( $args['cost'] ) );
495
496
			// Update total
497
			$this->set_total( $this->order_shipping - wc_format_decimal( $old_cost ) + wc_format_decimal( $args['cost'] ), 'shipping' );
498
		}
499
500
		do_action( 'woocommerce_order_update_shipping', $this->id, $item_id, $args );
501
502
		return true;
503
	}
504
505
	/**
506
	 * Add a fee to the order.
507
	 *
508
	 * @param object $fee
509
	 * @return int|bool Item ID or false.
510
	 */
511
	public function add_fee( $fee ) {
512
513
		$item_id = wc_add_order_item( $this->id, array(
514
			'order_item_name' => $fee->name,
515
			'order_item_type' => 'fee'
516
		) );
517
518
		if ( ! $item_id ) {
519
			return false;
520
		}
521
522
		if ( $fee->taxable ) {
523
			wc_add_order_item_meta( $item_id, '_tax_class', $fee->tax_class );
524
		} else {
525
			wc_add_order_item_meta( $item_id, '_tax_class', '0' );
526
		}
527
528
		wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $fee->amount ) );
529
		wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $fee->tax ) );
530
531
		// Save tax data - Since 2.2
532
		$tax_data = array_map( 'wc_format_decimal', $fee->tax_data );
533
		wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $tax_data ) );
534
535
		do_action( 'woocommerce_order_add_fee', $this->id, $item_id, $fee );
536
537
		return $item_id;
538
	}
539
540
	/**
541
	 * Update fee for order.
542
	 *
543
	 * Note this does not update order totals.
544
	 *
545
	 * @since 2.2
546
	 * @param int $item_id
547
	 * @param array $args
548
	 * @return bool
549
	 */
550
	public function update_fee( $item_id, $args ) {
551
552
		if ( ! $item_id ) {
553
			return false;
554
		}
555
556
		// name
557
		if ( isset( $args['name'] ) ) {
558
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['name'] ) );
559
		}
560
561
		// tax class
562
		if ( isset( $args['tax_class'] ) ) {
563
			wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] );
564
		}
565
566
		// total
567
		if ( isset( $args['line_total'] ) ) {
568
			wc_update_order_item_meta( $item_id, '_line_total', wc_format_decimal( $args['line_total'] ) );
569
		}
570
571
		// total tax
572
		if ( isset( $args['line_tax'] ) ) {
573
			wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $args['line_tax'] ) );
574
		}
575
576
		do_action( 'woocommerce_order_update_fee', $this->id, $item_id, $args );
577
578
		return true;
579
	}
580
581
	/**
582
	 * Set an order total.
583
	 *
584
	 * @param float $amount
585
	 * @param string $total_type
586
	 *
587
	 * @return bool
588
	 */
589
	public function set_total( $amount, $total_type = 'total' ) {
590
591
		if ( ! in_array( $total_type, array( 'shipping', 'tax', 'shipping_tax', 'total', 'cart_discount', 'cart_discount_tax' ) ) ) {
592
			return false;
593
		}
594
595
		switch ( $total_type ) {
596
			case 'total' :
597
				$key    = '_order_total';
598
				$amount = wc_format_decimal( $amount, wc_get_price_decimals() );
599
			break;
600
			case 'cart_discount' :
601
			case 'cart_discount_tax' :
602
				$key    = '_' . $total_type;
603
				$amount = wc_format_decimal( $amount );
604
			break;
605
			default :
606
				$key    = '_order_' . $total_type;
607
				$amount = wc_format_decimal( $amount );
608
			break;
609
		}
610
611
		update_post_meta( $this->id, $key, $amount );
612
613
		return true;
614
	}
615
616
	/**
617
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
618
	 *
619
	 * Will use the base country unless customer addresses are set.
620
	 *
621
	 * @return bool success or fail.
622
	 */
623
	public function calculate_taxes() {
624
		$tax_total    = 0;
625
		$taxes        = array();
626
		$tax_based_on = get_option( 'woocommerce_tax_based_on' );
627
628
		if ( 'billing' === $tax_based_on ) {
629
			$country  = $this->billing_country;
630
			$state    = $this->billing_state;
631
			$postcode = $this->billing_postcode;
632
			$city     = $this->billing_city;
633
		} elseif ( 'shipping' === $tax_based_on ) {
634
			$country  = $this->shipping_country;
635
			$state    = $this->shipping_state;
636
			$postcode = $this->shipping_postcode;
637
			$city     = $this->shipping_city;
638
		}
639
640
		// Default to base
641 View Code Duplication
		if ( 'base' === $tax_based_on || empty( $country ) ) {
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...
642
			$default  = wc_get_base_location();
643
			$country  = $default['country'];
644
			$state    = $default['state'];
645
			$postcode = '';
646
			$city     = '';
647
		}
648
649
		// Get items
650
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
651
652
			$product           = $this->get_product_from_item( $item );
653
			$line_total        = isset( $item['line_total'] ) ? $item['line_total'] : 0;
654
			$line_subtotal     = isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0;
655
			$tax_class         = $item['tax_class'];
656
			$item_tax_status   = $product ? $product->get_tax_status() : 'taxable';
657
658
			if ( '0' !== $tax_class && 'taxable' === $item_tax_status ) {
659
660
				$tax_rates = WC_Tax::find_rates( array(
661
					'country'   => $country,
662
					'state'     => $state,
663
					'postcode'  => $postcode,
664
					'city'      => $city,
665
					'tax_class' => $tax_class
666
				) );
667
668
				$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal, $tax_rates, false );
669
				$line_taxes          = WC_Tax::calc_tax( $line_total, $tax_rates, false );
670
				$line_subtotal_tax   = max( 0, array_sum( $line_subtotal_taxes ) );
671
				$line_tax            = max( 0, array_sum( $line_taxes ) );
672
				$tax_total           += $line_tax;
673
674
				wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( $line_subtotal_tax ) );
675
				wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) );
676
				wc_update_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $line_taxes, 'subtotal' => $line_subtotal_taxes ) );
677
678
				// Sum the item taxes
679
				foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
680
					$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
681
				}
682
			}
683
		}
684
685
		// Now calculate shipping tax
686
		$matched_tax_rates = array();
687
		$tax_rates         = WC_Tax::find_rates( array(
688
			'country'   => $country,
689
			'state'     => $state,
690
			'postcode'  => $postcode,
691
			'city'      => $city,
692
			'tax_class' => ''
693
		) );
694
695 View Code Duplication
		if ( ! empty( $tax_rates ) ) {
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...
696
			foreach ( $tax_rates as $key => $rate ) {
697
				if ( isset( $rate['shipping'] ) && 'yes' === $rate['shipping'] ) {
698
					$matched_tax_rates[ $key ] = $rate;
699
				}
700
			}
701
		}
702
703
		$shipping_taxes     = WC_Tax::calc_shipping_tax( $this->order_shipping, $matched_tax_rates );
704
		$shipping_tax_total = WC_Tax::round( array_sum( $shipping_taxes ) );
705
706
		// Save tax totals
707
		$this->set_total( $shipping_tax_total, 'shipping_tax' );
708
		$this->set_total( $tax_total, 'tax' );
709
710
		// Tax rows
711
		$this->remove_order_items( 'tax' );
712
713
		// Now merge to keep tax rows
714 View Code Duplication
		foreach ( array_keys( $taxes + $shipping_taxes ) as $tax_rate_id ) {
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...
715
			$this->add_tax( $tax_rate_id, isset( $taxes[ $tax_rate_id ] ) ? $taxes[ $tax_rate_id ] : 0, isset( $shipping_taxes[ $tax_rate_id ] ) ? $shipping_taxes[ $tax_rate_id ] : 0 );
716
		}
717
718
		return true;
719
	}
720
721
722
	/**
723
	 * Calculate shipping total.
724
	 *
725
	 * @since 2.2
726
	 * @return float
727
	 */
728
	public function calculate_shipping() {
729
730
		$shipping_total = 0;
731
732
		foreach ( $this->get_shipping_methods() as $shipping ) {
733
			$shipping_total += $shipping['cost'];
734
		}
735
736
		$this->set_total( $shipping_total, 'shipping' );
737
738
		return $this->get_total_shipping();
739
	}
740
741
	/**
742
	 * Update tax lines at order level by looking at the line item taxes themselves.
743
	 *
744
	 * @return bool success or fail.
745
	 */
746
	public function update_taxes() {
747
		$order_taxes          = array();
748
		$order_shipping_taxes = array();
749
750
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
751
752
			$line_tax_data = maybe_unserialize( $item['line_tax_data'] );
753
754 View Code Duplication
			if ( isset( $line_tax_data['total'] ) ) {
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...
755
756
				foreach ( $line_tax_data['total'] as $tax_rate_id => $tax ) {
757
758
					if ( ! isset( $order_taxes[ $tax_rate_id ] ) ) {
759
						$order_taxes[ $tax_rate_id ] = 0;
760
					}
761
762
					$order_taxes[ $tax_rate_id ] += $tax;
763
				}
764
			}
765
		}
766
767
		foreach ( $this->get_items( array( 'shipping' ) ) as $item_id => $item ) {
768
769
			$line_tax_data = maybe_unserialize( $item['taxes'] );
770
771 View Code Duplication
			if ( isset( $line_tax_data ) ) {
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...
772
				foreach ( $line_tax_data as $tax_rate_id => $tax ) {
773
					if ( ! isset( $order_shipping_taxes[ $tax_rate_id ] ) ) {
774
						$order_shipping_taxes[ $tax_rate_id ] = 0;
775
					}
776
777
					$order_shipping_taxes[ $tax_rate_id ] += $tax;
778
				}
779
			}
780
		}
781
782
		// Remove old existing tax rows.
783
		$this->remove_order_items( 'tax' );
784
785
		// Now merge to keep tax rows.
786 View Code Duplication
		foreach ( array_keys( $order_taxes + $order_shipping_taxes ) as $tax_rate_id ) {
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...
787
			$this->add_tax( $tax_rate_id, isset( $order_taxes[ $tax_rate_id ] ) ? $order_taxes[ $tax_rate_id ] : 0, isset( $order_shipping_taxes[ $tax_rate_id ] ) ? $order_shipping_taxes[ $tax_rate_id ] : 0 );
788
		}
789
790
		// Save tax totals
791
		$this->set_total( WC_Tax::round( array_sum( $order_shipping_taxes ) ), 'shipping_tax' );
792
		$this->set_total( WC_Tax::round( array_sum( $order_taxes ) ), 'tax' );
793
794
		return true;
795
	}
796
797
	/**
798
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
799
	 *
800
	 * @since 2.2
801
	 * @param  $and_taxes bool Calc taxes if true.
802
	 * @return float calculated grand total.
803
	 */
804
	public function calculate_totals( $and_taxes = true ) {
805
		$cart_subtotal     = 0;
806
		$cart_total        = 0;
807
		$fee_total         = 0;
808
		$cart_subtotal_tax = 0;
809
		$cart_total_tax    = 0;
810
811
		if ( $and_taxes && wc_tax_enabled() ) {
812
			$this->calculate_taxes();
813
		}
814
815
		// line items
816
		foreach ( $this->get_items() as $item ) {
817
			$cart_subtotal     += wc_format_decimal( isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0 );
818
			$cart_total        += wc_format_decimal( isset( $item['line_total'] ) ? $item['line_total'] : 0 );
819
			$cart_subtotal_tax += wc_format_decimal( isset( $item['line_subtotal_tax'] ) ? $item['line_subtotal_tax'] : 0 );
820
			$cart_total_tax    += wc_format_decimal( isset( $item['line_tax'] ) ? $item['line_tax'] : 0 );
821
		}
822
823
		$this->calculate_shipping();
824
825
		foreach ( $this->get_fees() as $item ) {
826
			$fee_total += $item['line_total'];
827
		}
828
829
		$this->set_total( $cart_subtotal - $cart_total, 'cart_discount' );
830
		$this->set_total( $cart_subtotal_tax - $cart_total_tax, 'cart_discount_tax' );
831
832
		$grand_total = round( $cart_total + $fee_total + $this->get_total_shipping() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
833
834
		$this->set_total( $grand_total, 'total' );
835
836
		return $grand_total;
837
	}
838
839
	/**
840
	 * Gets an order from the database.
841
	 *
842
	 * @param int $id (default: 0).
843
	 * @return bool
844
	 */
845 View Code Duplication
	public function get_order( $id = 0 ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
846
847
		if ( ! $id ) {
848
			return false;
849
		}
850
851
		if ( $result = get_post( $id ) ) {
852
			$this->populate( $result );
853
			return true;
854
		}
855
856
		return false;
857
	}
858
859
	/**
860
	 * Populates an order from the loaded post data.
861
	 *
862
	 * @param mixed $result
863
	 */
864
	public function populate( $result ) {
865
866
		// Standard post data
867
		$this->id                  = $result->ID;
868
		$this->order_date          = $result->post_date;
869
		$this->modified_date       = $result->post_modified;
870
		$this->customer_message    = $result->post_excerpt;
871
		$this->customer_note       = $result->post_excerpt;
872
		$this->post_status         = $result->post_status;
873
874
		// Billing email can default to user if set.
875
		if ( empty( $this->billing_email ) && ! empty( $this->customer_user ) && ( $user = get_user_by( 'id', $this->customer_user ) ) ) {
876
			$this->billing_email = $user->user_email;
877
		}
878
879
		// Orders store the state of prices including tax when created.
880
		$this->prices_include_tax = metadata_exists( 'post', $this->id, '_prices_include_tax' ) ? get_post_meta( $this->id, '_prices_include_tax', true ) === 'yes' : $this->prices_include_tax;
881
	}
882
883
	/**
884
	 * __isset function.
885
	 *
886
	 * @param mixed $key
887
	 * @return bool
888
	 */
889
	public function __isset( $key ) {
890
891
		if ( ! $this->id ) {
892
			return false;
893
		}
894
895
		return metadata_exists( 'post', $this->id, '_' . $key );
896
	}
897
898
	/**
899
	 * __get function.
900
	 *
901
	 * @param mixed $key
902
	 * @return mixed
903
	 */
904
	public function __get( $key ) {
905
		// Get values or default if not set.
906
		if ( 'completed_date' === $key ) {
907
			$value = ( $value = get_post_meta( $this->id, '_completed_date', true ) ) ? $value : $this->modified_date;
908
		} elseif ( 'user_id' === $key ) {
909
			$value = ( $value = get_post_meta( $this->id, '_customer_user', true ) ) ? absint( $value ) : '';
910
		} elseif ( 'status' === $key ) {
911
			$value = $this->get_status();
912
		} else {
913
			$value = get_post_meta( $this->id, '_' . $key, true );
914
		}
915
916
		return $value;
917
	}
918
919
	/**
920
	 * Return the order statuses without wc- internal prefix.
921
	 *
922
	 * Queries get_post_status() directly to avoid having out of date statuses, if updated elsewhere.
923
	 *
924
	 * @return string
925
	 */
926
	public function get_status() {
927
		$this->post_status = get_post_status( $this->id );
928
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->post_status, 0, 3 ) ? substr( $this->post_status, 3 ) : $this->post_status, $this );
929
	}
930
931
	/**
932
	 * Checks the order status against a passed in status.
933
	 *
934
	 * @return bool
935
	 */
936
	public function has_status( $status ) {
937
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
938
	}
939
940
	/**
941
	 * Gets the user ID associated with the order. Guests are 0.
942
	 *
943
	 * @since  2.2
944
	 * @return int
945
	 */
946
	public function get_user_id() {
947
		return $this->customer_user ? intval( $this->customer_user ) : 0;
948
	}
949
950
	/**
951
	 * Get the user associated with the order. False for guests.
952
	 *
953
	 * @since  2.2
954
	 * @return WP_User|false
955
	 */
956
	public function get_user() {
957
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
958
	}
959
960
	/**
961
	 * Get transaction id for the order.
962
	 *
963
	 * @return string
964
	 */
965
	public function get_transaction_id() {
966
		return get_post_meta( $this->id, '_transaction_id', true );
967
	}
968
969
	/**
970
	 * Check if an order key is valid.
971
	 *
972
	 * @param mixed $key
973
	 * @return bool
974
	 */
975
	public function key_is_valid( $key ) {
976
977
		if ( $key == $this->order_key ) {
978
			return true;
979
		}
980
981
		return false;
982
	}
983
984
	/**
985
	 * get_order_number function.
986
	 *
987
	 * Gets the order number for display (by default, order ID).
988
	 *
989
	 * @return string
990
	 */
991
	public function get_order_number() {
992
		return apply_filters( 'woocommerce_order_number', $this->id, $this );
993
	}
994
995
	/**
996
	 * Get a formatted billing address for the order.
997
	 *
998
	 * @return string
999
	 */
1000
	public function get_formatted_billing_address() {
1001
		if ( ! $this->formatted_billing_address ) {
1002
1003
			// Formatted Addresses.
1004
			$address = apply_filters( 'woocommerce_order_formatted_billing_address', array(
1005
				'first_name'    => $this->billing_first_name,
1006
				'last_name'     => $this->billing_last_name,
1007
				'company'       => $this->billing_company,
1008
				'address_1'     => $this->billing_address_1,
1009
				'address_2'     => $this->billing_address_2,
1010
				'city'          => $this->billing_city,
1011
				'state'         => $this->billing_state,
1012
				'postcode'      => $this->billing_postcode,
1013
				'country'       => $this->billing_country
1014
			), $this );
1015
1016
			$this->formatted_billing_address = WC()->countries->get_formatted_address( $address );
1017
		}
1018
1019
		return $this->formatted_billing_address;
1020
	}
1021
1022
	/**
1023
	 * Get a formatted shipping address for the order.
1024
	 *
1025
	 * @return string
1026
	 */
1027
	public function get_formatted_shipping_address() {
1028
		if ( ! $this->formatted_shipping_address ) {
1029
1030
			if ( $this->shipping_address_1 || $this->shipping_address_2 ) {
1031
1032
				// Formatted Addresses
1033
				$address = apply_filters( 'woocommerce_order_formatted_shipping_address', array(
1034
					'first_name'    => $this->shipping_first_name,
1035
					'last_name'     => $this->shipping_last_name,
1036
					'company'       => $this->shipping_company,
1037
					'address_1'     => $this->shipping_address_1,
1038
					'address_2'     => $this->shipping_address_2,
1039
					'city'          => $this->shipping_city,
1040
					'state'         => $this->shipping_state,
1041
					'postcode'      => $this->shipping_postcode,
1042
					'country'       => $this->shipping_country
1043
				), $this );
1044
1045
				$this->formatted_shipping_address = WC()->countries->get_formatted_address( $address );
1046
			}
1047
		}
1048
1049
		return $this->formatted_shipping_address;
1050
	}
1051
1052
	/**
1053
	 * Get a formatted shipping address for the order.
1054
	 *
1055
	 * @return string
1056
	 */
1057
	public function get_shipping_address_map_url() {
1058
		$address = apply_filters( 'woocommerce_shipping_address_map_url_parts', array(
1059
			'address_1'     => $this->shipping_address_1,
1060
			'address_2'     => $this->shipping_address_2,
1061
			'city'          => $this->shipping_city,
1062
			'state'         => $this->shipping_state,
1063
			'postcode'      => $this->shipping_postcode,
1064
			'country'       => $this->shipping_country
1065
		), $this );
1066
1067
		return apply_filters( 'woocommerce_shipping_address_map_url', 'http://maps.google.com/maps?&q=' . urlencode( implode( ', ', $address ) ) . '&z=16', $this );
1068
	}
1069
1070
	/**
1071
	 * Get the billing address in an array.
1072
	 * @deprecated 2.3
1073
	 * @return string
1074
	 */
1075
	public function get_billing_address() {
1076
		_deprecated_function( 'get_billing_address', '2.3', 'get_formatted_billing_address' );
1077
		return $this->get_formatted_billing_address();
1078
	}
1079
1080
	/**
1081
	 * Get the shipping address in an array.
1082
	 * @deprecated 2.3
1083
	 * @return string
1084
	 */
1085
	public function get_shipping_address() {
1086
		_deprecated_function( 'get_shipping_address', '2.3', 'get_formatted_shipping_address' );
1087
		return $this->get_formatted_shipping_address();
1088
	}
1089
1090
	/**
1091
	 * Get a formatted billing full name.
1092
	 *
1093
	 * @since 2.4.0
1094
	 *
1095
	 * @return string
1096
	 */
1097
	public function get_formatted_billing_full_name() {
1098
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ),  $this->billing_first_name, $this->billing_last_name );
1099
	}
1100
1101
	/**
1102
	 * Get a formatted shipping full name.
1103
	 *
1104
	 * @since 2.4.0
1105
	 *
1106
	 * @return string
1107
	 */
1108
	public function get_formatted_shipping_full_name() {
1109
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ),  $this->shipping_first_name, $this->shipping_last_name );
1110
	}
1111
1112
	/**
1113
	 * Return an array of items/products within this order.
1114
	 *
1115
	 * @param string|array $type Types of line items to get (array or string).
1116
	 * @return array
1117
	 */
1118
	public function get_items( $type = '' ) {
1119
		global $wpdb;
1120
1121
		if ( empty( $type ) ) {
1122
			$type = array( 'line_item' );
1123
		}
1124
1125
		if ( ! is_array( $type ) ) {
1126
			$type = array( $type );
1127
		}
1128
1129
		$items          = array();
1130
		$get_items_sql  = $wpdb->prepare( "SELECT order_item_id, order_item_name, order_item_type FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d ", $this->id );
1131
		$get_items_sql .= "AND order_item_type IN ( '" . implode( "','", array_map( 'esc_sql', $type ) ) . "' ) ORDER BY order_item_id;";
1132
		$line_items     = $wpdb->get_results( $get_items_sql );
1133
1134
		// Loop items
1135
		foreach ( $line_items as $item ) {
1136
			$items[ $item->order_item_id ]['name']            = $item->order_item_name;
1137
			$items[ $item->order_item_id ]['type']            = $item->order_item_type;
1138
			$items[ $item->order_item_id ]['item_meta']       = $this->get_item_meta( $item->order_item_id );
1139
			$items[ $item->order_item_id ]['item_meta_array'] = $this->get_item_meta_array( $item->order_item_id );
1140
			$items[ $item->order_item_id ]                    = $this->expand_item_meta( $items[ $item->order_item_id ] );
1141
		}
1142
1143
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
1144
	}
1145
1146
	/**
1147
	 * Expand item meta into the $item array.
1148
	 * @since 2.4.0
1149
	 * @param array $item before expansion.
1150
	 * @return array
1151
	 */
1152
	public function expand_item_meta( $item ) {
1153
		// Reserved meta keys
1154
		$reserved_item_meta_keys = array(
1155
			'name',
1156
			'type',
1157
			'item_meta',
1158
			'item_meta_array',
1159
			'qty',
1160
			'tax_class',
1161
			'product_id',
1162
			'variation_id',
1163
			'line_subtotal',
1164
			'line_total',
1165
			'line_tax',
1166
			'line_subtotal_tax'
1167
		);
1168
1169
		// Expand item meta if set.
1170
		if ( ! empty( $item['item_meta'] ) ) {
1171
			foreach ( $item['item_meta'] as $name => $value ) {
1172
				if ( in_array( $name, $reserved_item_meta_keys ) ) {
1173
					continue;
1174
				}
1175
				if ( '_' === substr( $name, 0, 1 ) ) {
1176
					$item[ substr( $name, 1 ) ] = $value[0];
1177
				} elseif ( ! in_array( $name, $reserved_item_meta_keys ) ) {
1178
					$item[ $name ] = make_clickable( $value[0] );
1179
				}
1180
			}
1181
		}
1182
		return $item;
1183
	}
1184
1185
	/**
1186
	 * Gets the count of order items of a certain type.
1187
	 *
1188
	 * @param string $item_type
1189
	 * @return string
1190
	 */
1191 View Code Duplication
	public function get_item_count( $item_type = '' ) {
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...
1192
		if ( empty( $item_type ) ) {
1193
			$item_type = array( 'line_item' );
1194
		}
1195
		if ( ! is_array( $item_type ) ) {
1196
			$item_type = array( $item_type );
1197
		}
1198
1199
		$items = $this->get_items( $item_type );
1200
		$count = 0;
1201
1202
		foreach ( $items as $item ) {
1203
			$count += empty( $item['qty'] ) ? 1 : $item['qty'];
1204
		}
1205
1206
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
1207
	}
1208
1209
	/**
1210
	 * Get refunds
1211
	 * @return array
1212
	 */
1213
	public function get_refunds() { return array(); }
1214
1215
	/**
1216
	 * Return an array of fees within this order.
1217
	 *
1218
	 * @return array
1219
	 */
1220
	public function get_fees() {
1221
		return $this->get_items( 'fee' );
1222
	}
1223
1224
	/**
1225
	 * Return an array of taxes within this order.
1226
	 *
1227
	 * @return array
1228
	 */
1229
	public function get_taxes() {
1230
		return $this->get_items( 'tax' );
1231
	}
1232
1233
	/**
1234
	 * Return an array of shipping costs within this order.
1235
	 *
1236
	 * @return array
1237
	 */
1238
	public function get_shipping_methods() {
1239
		return $this->get_items( 'shipping' );
1240
	}
1241
1242
	/**
1243
	 * Check whether this order has a specific shipping method or not.
1244
	 *
1245
	 * @param string $method_id
1246
	 *
1247
	 * @return bool
1248
	 */
1249
	public function has_shipping_method( $method_id ) {
1250
1251
		$shipping_methods = $this->get_shipping_methods();
1252
		$has_method = false;
1253
1254
		if ( empty( $shipping_methods ) ) {
1255
			return false;
1256
		}
1257
1258
		foreach ( $shipping_methods as $shipping_method ) {
1259
			if ( $shipping_method['method_id'] == $method_id ) {
1260
				$has_method = true;
1261
			}
1262
		}
1263
1264
		return $has_method;
1265
	}
1266
1267
	/**
1268
	 * Get taxes, merged by code, formatted ready for output.
1269
	 *
1270
	 * @return array
1271
	 */
1272
	public function get_tax_totals() {
1273
1274
		$taxes      = $this->get_items( 'tax' );
1275
		$tax_totals = array();
1276
1277
		foreach ( $taxes as $key => $tax ) {
1278
1279
			$code = $tax[ 'name' ];
1280
1281 View Code Duplication
			if ( ! isset( $tax_totals[ $code ] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1282
				$tax_totals[ $code ] = new stdClass();
1283
				$tax_totals[ $code ]->amount = 0;
1284
			}
1285
1286
			$tax_totals[ $code ]->id                = $key;
1287
			$tax_totals[ $code ]->rate_id           = $tax['rate_id'];
1288
			$tax_totals[ $code ]->is_compound       = $tax[ 'compound' ];
1289
			$tax_totals[ $code ]->label             = isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ];
1290
			$tax_totals[ $code ]->amount           += $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ];
1291
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array('currency' => $this->get_order_currency()) );
1292
		}
1293
1294
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
1295
	}
1296
1297
	/**
1298
	 * has_meta function for order items.
1299
	 *
1300
	 * @param string $order_item_id
1301
	 * @return array of meta data.
1302
	 */
1303
	public function has_meta( $order_item_id ) {
1304
		global $wpdb;
1305
1306
		return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, order_item_id
1307
			FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d
1308
			ORDER BY meta_id", absint( $order_item_id ) ), ARRAY_A );
1309
	}
1310
1311
	/**
1312
	 * Get all item meta data in array format in the order it was saved. Does not group meta by key like get_item_meta().
1313
	 *
1314
	 * @param mixed $order_item_id
1315
	 * @return array of objects
1316
	 */
1317
	public function get_item_meta_array( $order_item_id ) {
1318
		global $wpdb;
1319
1320
		// Get cache key - uses get_cache_prefix to invalidate when needed
1321
		$cache_key       = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $order_item_id;
1322
		$item_meta_array = wp_cache_get( $cache_key, 'orders' );
1323
1324
		if ( false === $item_meta_array ) {
1325
			$item_meta_array = array();
1326
			$metadata        = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d ORDER BY meta_id", absint( $order_item_id ) ) );
1327
			foreach ( $metadata as $metadata_row ) {
1328
				$item_meta_array[ $metadata_row->meta_id ] = (object) array( 'key' => $metadata_row->meta_key, 'value' => $metadata_row->meta_value );
1329
			}
1330
			wp_cache_set( $cache_key, $item_meta_array, 'orders' );
1331
		}
1332
1333
		return $item_meta_array ;
1334
	}
1335
1336
	/**
1337
	 * Display meta data belonging to an item.
1338
	 * @param  array $item
1339
	 */
1340
	public function display_item_meta( $item ) {
1341
		$product   = $this->get_product_from_item( $item );
1342
		$item_meta = new WC_Order_Item_Meta( $item, $product );
1343
		$item_meta->display();
1344
	}
1345
1346
	/**
1347
	 * Get order item meta.
1348
	 *
1349
	 * @param mixed $order_item_id
1350
	 * @param string $key (default: '')
1351
	 * @param bool $single (default: false)
1352
	 * @return array|string
1353
	 */
1354
	public function get_item_meta( $order_item_id, $key = '', $single = false ) {
1355
		return get_metadata( 'order_item', $order_item_id, $key, $single );
1356
	}
1357
1358
	/** Total Getters *******************************************************/
1359
1360
	/**
1361
	 * Gets the total discount amount.
1362
	 * @param  $ex_tax Show discount excl any tax.
1363
	 * @return float
1364
	 */
1365
	public function get_total_discount( $ex_tax = true ) {
1366
		if ( ! $this->order_version || version_compare( $this->order_version, '2.3.7', '<' ) ) {
1367
			// Backwards compatible total calculation - totals were not stored consistently in old versions.
1368
			if ( $ex_tax ) {
1369 View Code Duplication
				if ( $this->prices_include_tax ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1370
					$total_discount = (double) $this->cart_discount - (double) $this->cart_discount_tax;
1371
				} else {
1372
					$total_discount = (double) $this->cart_discount;
1373
				}
1374 View Code Duplication
			} else {
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...
1375
				if ( $this->prices_include_tax ) {
1376
					$total_discount = (double) $this->cart_discount;
1377
				} else {
1378
					$total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax;
1379
				}
1380
			}
1381
		// New logic - totals are always stored exclusive of tax, tax total is stored in cart_discount_tax
1382
		} else {
1383
			if ( $ex_tax ) {
1384
				$total_discount = (double) $this->cart_discount;
1385
			} else {
1386
				$total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax;
1387
			}
1388
		}
1389
		return apply_filters( 'woocommerce_order_amount_total_discount', $total_discount, $this );
1390
	}
1391
1392
	/**
1393
	 * Gets the discount amount.
1394
	 * @deprecated in favour of get_total_discount() since we now only have one discount type.
1395
	 * @return float
1396
	 */
1397
	public function get_cart_discount() {
1398
		_deprecated_function( 'get_cart_discount', '2.3', 'get_total_discount' );
1399
		return apply_filters( 'woocommerce_order_amount_cart_discount', $this->get_total_discount(), $this );
1400
	}
1401
1402
	/**
1403
	 * Get cart discount (formatted).
1404
	 *
1405
	 * @deprecated order (after tax) discounts removed in 2.3.0.
1406
	 * @return string
1407
	 */
1408
	public function get_order_discount_to_display() {
1409
		_deprecated_function( 'get_order_discount_to_display', '2.3' );
1410
	}
1411
1412
	/**
1413
	 * Gets the total (order) discount amount - these are applied after tax.
1414
	 *
1415
	 * @deprecated order (after tax) discounts removed in 2.3.0.
1416
	 * @return float
1417
	 */
1418
	public function get_order_discount() {
1419
		_deprecated_function( 'get_order_discount', '2.3' );
1420
		return apply_filters( 'woocommerce_order_amount_order_discount', (double) $this->order_discount, $this );
1421
	}
1422
1423
	/**
1424
	 * Gets cart tax amount.
1425
	 *
1426
	 * @return float
1427
	 */
1428
	public function get_cart_tax() {
1429
		return apply_filters( 'woocommerce_order_amount_cart_tax', (double) $this->order_tax, $this );
1430
	}
1431
1432
	/**
1433
	 * Gets shipping tax amount.
1434
	 *
1435
	 * @return float
1436
	 */
1437
	public function get_shipping_tax() {
1438
		return apply_filters( 'woocommerce_order_amount_shipping_tax', (double) $this->order_shipping_tax, $this );
1439
	}
1440
1441
	/**
1442
	 * Gets shipping and product tax.
1443
	 *
1444
	 * @return float
1445
	 */
1446
	public function get_total_tax() {
1447
		return apply_filters( 'woocommerce_order_amount_total_tax', wc_round_tax_total( $this->get_cart_tax() + $this->get_shipping_tax() ), $this );
1448
	}
1449
1450
	/**
1451
	 * Gets shipping total.
1452
	 *
1453
	 * @return float
1454
	 */
1455
	public function get_total_shipping() {
1456
		return apply_filters( 'woocommerce_order_amount_total_shipping', (double) $this->order_shipping, $this );
1457
	}
1458
1459
	/**
1460
	 * Gets order total.
1461
	 *
1462
	 * @return float
1463
	 */
1464
	public function get_total() {
1465
		return apply_filters( 'woocommerce_order_amount_total', (double) $this->order_total, $this );
1466
	}
1467
1468
	/**
1469
	 * Gets order subtotal.
1470
	 *
1471
	 * @return mixed|void
1472
	 */
1473
	public function get_subtotal() {
1474
		$subtotal = 0;
1475
1476
		foreach ( $this->get_items() as $item ) {
1477
			$subtotal += ( isset( $item['line_subtotal'] ) ) ? $item['line_subtotal'] : 0;
1478
		}
1479
1480
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
1481
	}
1482
1483
	/**
1484
	 * Get item subtotal - this is the cost before discount.
1485
	 *
1486
	 * @param mixed $item
1487
	 * @param bool $inc_tax (default: false).
1488
	 * @param bool $round (default: true).
1489
	 * @return float
1490
	 */
1491
	public function get_item_subtotal( $item, $inc_tax = false, $round = true ) {
1492
		if ( $inc_tax ) {
1493
			$price = ( $item['line_subtotal'] + $item['line_subtotal_tax'] ) / max( 1, $item['qty'] );
1494
		} else {
1495
			$price = ( $item['line_subtotal'] / max( 1, $item['qty'] ) );
1496
		}
1497
1498
		$price = $round ? number_format( (float) $price, wc_get_price_decimals(), '.', '' ) : $price;
1499
1500
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $price, $this, $item, $inc_tax, $round );
1501
	}
1502
1503
	/**
1504
	 * Get line subtotal - this is the cost before discount.
1505
	 *
1506
	 * @param mixed $item
1507
	 * @param bool $inc_tax (default: false).
1508
	 * @param bool $round (default: true).
1509
	 * @return float
1510
	 */
1511 View Code Duplication
	public function get_line_subtotal( $item, $inc_tax = false, $round = true ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1512
		if ( $inc_tax ) {
1513
			$price = $item['line_subtotal'] + $item['line_subtotal_tax'];
1514
		} else {
1515
			$price = $item['line_subtotal'];
1516
		}
1517
1518
		$price = $round ? round( $price, wc_get_price_decimals() ) : $price;
1519
1520
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $price, $this, $item, $inc_tax, $round );
1521
	}
1522
1523
	/**
1524
	 * Calculate item cost - useful for gateways.
1525
	 *
1526
	 * @param mixed $item
1527
	 * @param bool $inc_tax (default: false).
1528
	 * @param bool $round (default: true).
1529
	 * @return float
1530
	 */
1531
	public function get_item_total( $item, $inc_tax = false, $round = true ) {
1532
1533
		$qty = ( ! empty( $item['qty'] ) ) ? $item['qty'] : 1;
1534
1535
		if ( $inc_tax ) {
1536
			$price = ( $item['line_total'] + $item['line_tax'] ) / max( 1, $qty );
1537
		} else {
1538
			$price = $item['line_total'] / max( 1, $qty );
1539
		}
1540
1541
		$price = $round ? round( $price, wc_get_price_decimals() ) : $price;
1542
1543
		return apply_filters( 'woocommerce_order_amount_item_total', $price, $this, $item, $inc_tax, $round );
1544
	}
1545
1546
	/**
1547
	 * Calculate line total - useful for gateways.
1548
	 *
1549
	 * @param mixed $item
1550
	 * @param bool $inc_tax (default: false).
1551
	 * @param bool $round (default: true).
1552
	 * @return float
1553
	 */
1554 View Code Duplication
	public function get_line_total( $item, $inc_tax = false, $round = true ) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
1555
1556
		// Check if we need to add line tax to the line total.
1557
		$line_total = $inc_tax ? $item['line_total'] + $item['line_tax'] : $item['line_total'];
1558
1559
		// Check if we need to round.
1560
		$line_total = $round ? round( $line_total, wc_get_price_decimals() ) : $line_total;
1561
1562
		return apply_filters( 'woocommerce_order_amount_line_total', $line_total, $this, $item, $inc_tax, $round );
1563
	}
1564
1565
	/**
1566
	 * Calculate item tax - useful for gateways.
1567
	 *
1568
	 * @param mixed $item
1569
	 * @param bool $round (default: true).
1570
	 * @return float
1571
	 */
1572
	public function get_item_tax( $item, $round = true ) {
1573
1574
		$price = $item['line_tax'] / max( 1, $item['qty'] );
1575
		$price = $round ? wc_round_tax_total( $price ) : $price;
1576
1577
		return apply_filters( 'woocommerce_order_amount_item_tax', $price, $item, $round, $this );
1578
	}
1579
1580
	/**
1581
	 * Calculate line tax - useful for gateways.
1582
	 *
1583
	 * @param mixed $item
1584
	 * @return float
1585
	 */
1586
	public function get_line_tax( $item ) {
1587
		return apply_filters( 'woocommerce_order_amount_line_tax', wc_round_tax_total( $item['line_tax'] ), $item, $this );
1588
	}
1589
1590
	/** End Total Getters *******************************************************/
1591
1592
	/**
1593
	 * Gets formatted shipping method title.
1594
	 *
1595
	 * @return string
1596
	 */
1597
	public function get_shipping_method() {
1598
1599
		$labels = array();
1600
1601
		// Backwards compat < 2.1 - get shipping title stored in meta.
1602
		if ( $this->shipping_method_title ) {
1603
			$labels[] = $this->shipping_method_title;
1604
		} else {
1605
1606
			// 2.1+ get line items for shipping.
1607
			$shipping_methods = $this->get_shipping_methods();
1608
1609
			foreach ( $shipping_methods as $shipping ) {
1610
				$labels[] = $shipping['name'];
1611
			}
1612
		}
1613
1614
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $labels ), $this );
1615
	}
1616
1617
	/**
1618
	 * Gets line subtotal - formatted for display.
1619
	 *
1620
	 * @param array  $item
1621
	 * @param string $tax_display
1622
	 * @return string
1623
	 */
1624
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1625
1626
		if ( ! $tax_display ) {
1627
			$tax_display = $this->tax_display_cart;
1628
		}
1629
1630
		if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1631
			return '';
1632
		}
1633
1634
		if ( 'excl' == $tax_display ) {
1635
			$ex_tax_label = $this->prices_include_tax ? 1 : 0;
1636
1637
			$subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_order_currency() ) );
1638
		} else {
1639
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array('currency' => $this->get_order_currency()) );
1640
		}
1641
1642
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1643
	}
1644
1645
	/**
1646
	 * Gets order currency.
1647
	 *
1648
	 * @return string
1649
	 */
1650
	public function get_order_currency() {
1651
		return apply_filters( 'woocommerce_get_order_currency', $this->order_currency, $this );
1652
	}
1653
1654
	/**
1655
	 * Gets order total - formatted for display.
1656
	 *
1657
	 * @return string
1658
	 */
1659
	public function get_formatted_order_total() {
1660
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_order_currency() ) );
1661
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1662
	}
1663
1664
	/**
1665
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1666
	 *
1667
	 * @param bool $compound (default: false).
1668
	 * @param string $tax_display (default: the tax_display_cart value).
1669
	 * @return string
1670
	 */
1671
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1672
1673
		if ( ! $tax_display ) {
1674
			$tax_display = $this->tax_display_cart;
1675
		}
1676
1677
		$subtotal = 0;
1678
1679
		if ( ! $compound ) {
1680
			foreach ( $this->get_items() as $item ) {
1681
1682
				if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1683
					return '';
1684
				}
1685
1686
				$subtotal += $item['line_subtotal'];
1687
1688
				if ( 'incl' == $tax_display ) {
1689
					$subtotal += $item['line_subtotal_tax'];
1690
				}
1691
			}
1692
1693
			$subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) );
1694
1695
			if ( $tax_display == 'excl' && $this->prices_include_tax ) {
1696
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1697
			}
1698
1699
		} else {
1700
1701
			if ( 'incl' == $tax_display ) {
1702
				return '';
1703
			}
1704
1705
			foreach ( $this->get_items() as $item ) {
1706
1707
				$subtotal += $item['line_subtotal'];
1708
1709
			}
1710
1711
			// Add Shipping Costs.
1712
			$subtotal += $this->get_total_shipping();
1713
1714
			// Remove non-compound taxes.
1715
			foreach ( $this->get_taxes() as $tax ) {
1716
1717
				if ( ! empty( $tax['compound'] ) ) {
1718
					continue;
1719
				}
1720
1721
				$subtotal = $subtotal + $tax['tax_amount'] + $tax['shipping_tax_amount'];
1722
1723
			}
1724
1725
			// Remove discounts.
1726
			$subtotal = $subtotal - $this->get_total_discount();
1727
1728
			$subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) );
1729
		}
1730
1731
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1732
	}
1733
1734
1735
	/**
1736
	 * Gets shipping (formatted).
1737
	 *
1738
	 * @return string
1739
	 */
1740
	public function get_shipping_to_display( $tax_display = '' ) {
1741
		if ( ! $tax_display ) {
1742
			$tax_display = $this->tax_display_cart;
1743
		}
1744
1745
		if ( $this->order_shipping != 0 ) {
1746
1747
			if ( $tax_display == 'excl' ) {
1748
1749
				// Show shipping excluding tax.
1750
				$shipping = wc_price( $this->order_shipping, array('currency' => $this->get_order_currency()) );
1751
1752 View Code Duplication
				if ( $this->order_shipping_tax != 0 && $this->prices_include_tax ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1753
					$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>', $this, $tax_display );
1754
				}
1755
1756
			} else {
1757
1758
				// Show shipping including tax.
1759
				$shipping = wc_price( $this->order_shipping + $this->order_shipping_tax, array('currency' => $this->get_order_currency()) );
1760
1761 View Code Duplication
				if ( $this->order_shipping_tax != 0 && ! $this->prices_include_tax ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
1762
					$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_tax_label', '&nbsp;<small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>', $this, $tax_display );
1763
				}
1764
1765
			}
1766
1767
			$shipping .= apply_filters( 'woocommerce_order_shipping_to_display_shipped_via', '&nbsp;<small class="shipped_via">' . sprintf( __( 'via %s', 'woocommerce' ), $this->get_shipping_method() ) . '</small>', $this );
1768
1769
		} elseif ( $this->get_shipping_method() ) {
1770
			$shipping = $this->get_shipping_method();
1771
		} else {
1772
			$shipping = __( 'Free!', 'woocommerce' );
1773
		}
1774
1775
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1776
	}
1777
1778
	/**
1779
	 * Get the discount amount (formatted).
1780
	 * @since  2.3.0
1781
	 * @return string
1782
	 */
1783
	public function get_discount_to_display( $tax_display = '' ) {
1784
		if ( ! $tax_display ) {
1785
			$tax_display = $this->tax_display_cart;
1786
		}
1787
		return apply_filters( 'woocommerce_order_discount_to_display', wc_price( $this->get_total_discount( $tax_display === 'excl' && $this->display_totals_ex_tax ), array( 'currency' => $this->get_order_currency() ) ), $this );
1788
	}
1789
1790
	/**
1791
	 * Get cart discount (formatted).
1792
	 * @deprecated
1793
	 * @return string
1794
	 */
1795
	public function get_cart_discount_to_display( $tax_display = '' ) {
1796
		_deprecated_function( 'get_cart_discount_to_display', '2.3', 'get_discount_to_display' );
1797
		return apply_filters( 'woocommerce_order_cart_discount_to_display', $this->get_discount_to_display( $tax_display ), $this );
1798
	}
1799
1800
	/**
1801
	 * Get a product (either product or variation).
1802
	 *
1803
	 * @param mixed $item
1804
	 * @return WC_Product
1805
	 */
1806
	public function get_product_from_item( $item ) {
1807
1808
		if ( ! empty( $item['variation_id'] ) && 'product_variation' === get_post_type( $item['variation_id'] ) ) {
1809
			$_product = wc_get_product( $item['variation_id'] );
1810
		} elseif ( ! empty( $item['product_id']  ) ) {
1811
			$_product = wc_get_product( $item['product_id'] );
1812
		} else {
1813
			$_product = false;
1814
		}
1815
1816
		return apply_filters( 'woocommerce_get_product_from_item', $_product, $item, $this );
1817
	}
1818
1819
1820
	/**
1821
	 * Get totals for display on pages and in emails.
1822
	 *
1823
	 * @return array
1824
	 */
1825
	public function get_order_item_totals( $tax_display = '' ) {
1826
1827
		if ( ! $tax_display ) {
1828
			$tax_display = $this->tax_display_cart;
1829
		}
1830
1831
		$total_rows = array();
1832
1833
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1834
			$total_rows['cart_subtotal'] = array(
1835
				'label' => __( 'Subtotal:', 'woocommerce' ),
1836
				'value'	=> $subtotal
1837
			);
1838
		}
1839
1840
		if ( $this->get_total_discount() > 0 ) {
1841
			$total_rows['discount'] = array(
1842
				'label' => __( 'Discount:', 'woocommerce' ),
1843
				'value'	=> '-' . $this->get_discount_to_display( $tax_display )
1844
			);
1845
		}
1846
1847
		if ( $this->get_shipping_method() ) {
1848
			$total_rows['shipping'] = array(
1849
				'label' => __( 'Shipping:', 'woocommerce' ),
1850
				'value'	=> $this->get_shipping_to_display( $tax_display )
1851
			);
1852
		}
1853
1854
		if ( $fees = $this->get_fees() ) {
1855
			foreach ( $fees as $id => $fee ) {
1856
1857
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', $fee['line_total'] + $fee['line_tax'] == 0, $id ) ) {
1858
					continue;
1859
				}
1860
1861
				if ( 'excl' == $tax_display ) {
1862
1863
					$total_rows[ 'fee_' . $id ] = array(
1864
						'label' => ( $fee['name'] ? $fee['name'] : __( 'Fee', 'woocommerce' ) ) . ':',
1865
						'value'	=> wc_price( $fee['line_total'], array('currency' => $this->get_order_currency()) )
1866
					);
1867
1868
				} else {
1869
1870
					$total_rows[ 'fee_' . $id ] = array(
1871
						'label' => $fee['name'] . ':',
1872
						'value'	=> wc_price( $fee['line_total'] + $fee['line_tax'], array('currency' => $this->get_order_currency()) )
1873
					);
1874
				}
1875
			}
1876
		}
1877
1878
		// Tax for tax exclusive prices.
1879
		if ( 'excl' === $tax_display ) {
1880
1881
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1882
1883
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1884
1885
					$total_rows[ sanitize_title( $code ) ] = array(
1886
						'label' => $tax->label . ':',
1887
						'value'	=> $tax->formatted_amount
1888
					);
1889
				}
1890
1891
			} else {
1892
1893
				$total_rows['tax'] = array(
1894
					'label' => WC()->countries->tax_or_vat() . ':',
1895
					'value'	=> wc_price( $this->get_total_tax(), array( 'currency' => $this->get_order_currency() ) )
1896
				);
1897
			}
1898
		}
1899
1900
		if ( $this->get_total() > 0 && $this->payment_method_title ) {
1901
			$total_rows['payment_method'] = array(
1902
				'label' => __( 'Payment Method:', 'woocommerce' ),
1903
				'value' => $this->payment_method_title
1904
			);
1905
		}
1906
1907
		if ( $refunds = $this->get_refunds() ) {
1908
			foreach ( $refunds as $id => $refund ) {
1909
				$total_rows[ 'refund_' . $id ] = array(
1910
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1911
					'value'	=> wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_order_currency() ) )
1912
				);
1913
			}
1914
		}
1915
1916
		$total_rows['order_total'] = array(
1917
			'label' => __( 'Total:', 'woocommerce' ),
1918
			'value'	=> $this->get_formatted_order_total( $tax_display )
0 ignored issues
show
Unused Code introduced by
The call to WC_Abstract_Order::get_formatted_order_total() has too many arguments starting with $tax_display.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
1919
		);
1920
1921
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1922
	}
1923
1924
1925
	/**
1926
	 * Output items for display in html emails.
1927
	 *
1928
	 * @param array $args Items args.
1929
	 * @param null $deprecated1 Deprecated arg.
1930
	 * @param null $deprecated2 Deprecated arg.
1931
	 * @param null $deprecated3 Deprecated arg.
1932
	 * @param null $deprecated4 Deprecated arg.
1933
	 * @param null $deprecated5 Deprecated arg.
1934
	 * @return string
1935
	 */
1936
	public function email_order_items_table( $args = array(), $deprecated1 = null, $deprecated2 = null, $deprecated3 = null, $deprecated4 = null, $deprecated5 = null ) {
1937
		ob_start();
1938
1939
		if ( ! is_null( $deprecated1 ) || ! is_null( $deprecated2 ) || ! is_null( $deprecated3 ) || ! is_null( $deprecated4 ) || ! is_null( $deprecated5 ) ) {
1940
			_deprecated_argument( __FUNCTION__, '2.5.0' );
1941
		}
1942
1943
		$defaults = array(
1944
			'show_sku'   => false,
1945
			'show_image' => false,
1946
			'image_size' => array( 32, 32 ),
1947
			'plain_text' => false
1948
		);
1949
1950
		$args     = wp_parse_args( $args, $defaults );
1951
		$template = $args['plain_text'] ? 'emails/plain/email-order-items.php' : 'emails/email-order-items.php';
1952
1953
		wc_get_template( $template, array(
1954
			'order'               => $this,
1955
			'items'               => $this->get_items(),
1956
			'show_download_links' => $this->is_download_permitted(),
1957
			'show_sku'            => $args['show_sku'],
1958
			'show_purchase_note'  => $this->is_paid(),
1959
			'show_image'          => $args['show_image'],
1960
			'image_size'          => $args['image_size'],
1961
		) );
1962
1963
		return apply_filters( 'woocommerce_email_order_items_table', ob_get_clean(), $this );
1964
	}
1965
1966
	/**
1967
	 * Returns if an order has been paid for based on the order status.
1968
	 * @since 2.5.0
1969
	 * @return bool
1970
	 */
1971
	public function is_paid() {
1972
		return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this );
1973
	}
1974
1975
	/**
1976
	 * Checks if product download is permitted.
1977
	 *
1978
	 * @return bool
1979
	 */
1980
	public function is_download_permitted() {
1981
		return apply_filters( 'woocommerce_order_is_download_permitted', $this->has_status( 'completed' ) || ( get_option( 'woocommerce_downloads_grant_access_after_payment' ) == 'yes' && $this->has_status( 'processing' ) ), $this );
1982
	}
1983
1984
	/**
1985
	 * Returns true if the order contains a downloadable product.
1986
	 * @return bool
1987
	 */
1988
	public function has_downloadable_item() {
1989
		foreach ( $this->get_items() as $item ) {
1990
			$_product = $this->get_product_from_item( $item );
1991
1992
			if ( $_product && $_product->exists() && $_product->is_downloadable() && $_product->has_file() ) {
1993
				return true;
1994
			}
1995
		}
1996
		return false;
1997
	}
1998
1999
	/**
2000
	 * Returns true if the order contains a free product.
2001
	 * @since 2.5.0
2002
	 * @return bool
2003
	 */
2004
	public function has_free_item() {
2005
		foreach ( $this->get_items() as $item ) {
2006
			if ( ! $item['line_total'] ) {
2007
				return true;
2008
			}
2009
		}
2010
		return false;
2011
	}
2012
2013
	/**
2014
	 * 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.
2015
	 *
2016
	 * @param  boolean $on_checkout
2017
	 * @return string
2018
	 */
2019
	public function get_checkout_payment_url( $on_checkout = false ) {
2020
2021
		$pay_url = wc_get_endpoint_url( 'order-pay', $this->id, wc_get_page_permalink( 'checkout' ) );
2022
2023 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...
2024
			$pay_url = str_replace( 'http:', 'https:', $pay_url );
2025
		}
2026
2027
		if ( $on_checkout ) {
2028
			$pay_url = add_query_arg( 'key', $this->order_key, $pay_url );
2029
		} else {
2030
			$pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->order_key ), $pay_url );
2031
		}
2032
2033
		return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this );
2034
	}
2035
2036
	/**
2037
	 * Generates a URL for the thanks page (order received).
2038
	 *
2039
	 * @return string
2040
	 */
2041
	public function get_checkout_order_received_url() {
2042
2043
		$order_received_url = wc_get_endpoint_url( 'order-received', $this->id, wc_get_page_permalink( 'checkout' ) );
2044
2045 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...
2046
			$order_received_url = str_replace( 'http:', 'https:', $order_received_url );
2047
		}
2048
2049
		$order_received_url = add_query_arg( 'key', $this->order_key, $order_received_url );
2050
2051
		return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this );
2052
	}
2053
2054
	/**
2055
	 * Generates a URL so that a customer can cancel their (unpaid - pending) order.
2056
	 *
2057
	 * @param string $redirect
2058
	 *
2059
	 * @return string
2060
	 */
2061 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...
2062
2063
		// Get cancel endpoint
2064
		$cancel_endpoint = $this->get_cancel_endpoint();
2065
2066
		return apply_filters( 'woocommerce_get_cancel_order_url', wp_nonce_url( add_query_arg( array(
2067
			'cancel_order' => 'true',
2068
			'order'        => $this->order_key,
2069
			'order_id'     => $this->id,
2070
			'redirect'     => $redirect
2071
		), $cancel_endpoint ), 'woocommerce-cancel_order' ) );
2072
	}
2073
2074
	/**
2075
	 * Generates a raw (unescaped) cancel-order URL for use by payment gateways.
2076
	 *
2077
	 * @param string $redirect
2078
	 *
2079
	 * @return string The unescaped cancel-order URL.
2080
	 */
2081 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...
2082
2083
		// Get cancel endpoint
2084
		$cancel_endpoint = $this->get_cancel_endpoint();
2085
2086
		return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array(
2087
			'cancel_order' => 'true',
2088
			'order'        => $this->order_key,
2089
			'order_id'     => $this->id,
2090
			'redirect'     => $redirect,
2091
			'_wpnonce'     => wp_create_nonce( 'woocommerce-cancel_order' )
2092
		), $cancel_endpoint ) );
2093
	}
2094
2095
2096
	/**
2097
	 * Helper method to return the cancel endpoint.
2098
	 *
2099
	 * @return string the cancel endpoint; either the cart page or the home page.
2100
	 */
2101
	public function get_cancel_endpoint() {
2102
2103
		$cancel_endpoint = wc_get_page_permalink( 'cart' );
2104
		if ( ! $cancel_endpoint ) {
2105
			$cancel_endpoint = home_url();
2106
		}
2107
2108
		if ( false === strpos( $cancel_endpoint, '?' ) ) {
2109
			$cancel_endpoint = trailingslashit( $cancel_endpoint );
2110
		}
2111
2112
		return $cancel_endpoint;
2113
	}
2114
2115
2116
	/**
2117
	 * Generates a URL to view an order from the my account page.
2118
	 *
2119
	 * @return string
2120
	 */
2121
	public function get_view_order_url() {
2122
2123
		$view_order_url = wc_get_endpoint_url( 'view-order', $this->id, wc_get_page_permalink( 'myaccount' ) );
2124
2125
		return apply_filters( 'woocommerce_get_view_order_url', $view_order_url, $this );
2126
	}
2127
2128
	/**
2129
	 * Get the downloadable files for an item in this order.
2130
	 *
2131
	 * @param  array $item
2132
	 * @return array
2133
	 */
2134
	public function get_item_downloads( $item ) {
2135
		global $wpdb;
2136
2137
		$product_id   = $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'];
2138
		$product      = wc_get_product( $product_id );
2139
		if ( ! $product ) {
2140
			/**
2141
			 * $product can be `false`. Example: checking an old order, when a product or variation has been deleted.
2142
			 * @see \WC_Product_Factory::get_product
2143
			 */
2144
			return array();
2145
		}
2146
		$download_ids = $wpdb->get_col( $wpdb->prepare("
2147
			SELECT download_id
2148
			FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
2149
			WHERE user_email = %s
2150
			AND order_key = %s
2151
			AND product_id = %s
2152
			ORDER BY permission_id
2153
		", $this->billing_email, $this->order_key, $product_id ) );
2154
2155
		$files = array();
2156
2157
		foreach ( $download_ids as $download_id ) {
2158
2159
			if ( $product->has_file( $download_id ) ) {
2160
				$files[ $download_id ]                 = $product->get_file( $download_id );
2161
				$files[ $download_id ]['download_url'] = $this->get_download_url( $product_id, $download_id );
2162
			}
2163
		}
2164
2165
		return apply_filters( 'woocommerce_get_item_downloads', $files, $item, $this );
2166
	}
2167
2168
	/**
2169
	 * Display download links for an order item.
2170
	 * @param  array $item
2171
	 */
2172
	public function display_item_downloads( $item ) {
2173
		$product = $this->get_product_from_item( $item );
2174
2175
		if ( $product && $product->exists() && $product->is_downloadable() && $this->is_download_permitted() ) {
2176
			$download_files = $this->get_item_downloads( $item );
2177
			$i              = 0;
2178
			$links          = array();
2179
2180
			foreach ( $download_files as $download_id => $file ) {
2181
				$i++;
2182
				$prefix  = count( $download_files ) > 1 ? sprintf( __( 'Download %d', 'woocommerce' ), $i ) : __( 'Download', 'woocommerce' );
2183
				$links[] = '<small class="download-url">' . $prefix . ': <a href="' . esc_url( $file['download_url'] ) . '" target="_blank">' . esc_html( $file['name'] ) . '</a></small>' . "\n";
2184
			}
2185
2186
			echo '<br/>' . implode( '<br/>', $links );
2187
		}
2188
	}
2189
2190
	/**
2191
	 * Get the Download URL.
2192
	 *
2193
	 * @param  int $product_id
2194
	 * @param  int $download_id
2195
	 * @return string
2196
	 */
2197
	public function get_download_url( $product_id, $download_id ) {
2198
		return add_query_arg( array(
2199
			'download_file' => $product_id,
2200
			'order'         => $this->order_key,
2201
			'email'         => urlencode( $this->billing_email ),
2202
			'key'           => $download_id
2203
		), trailingslashit( home_url() ) );
2204
	}
2205
2206
	/**
2207
	 * Adds a note (comment) to the order.
2208
	 *
2209
	 * @param string $note Note to add.
2210
	 * @param int $is_customer_note (default: 0) Is this a note for the customer?
2211
	 * @param  bool added_by_user Was the note added by a user?
2212
	 * @return int Comment ID.
2213
	 */
2214
	public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) {
2215
		if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->id ) && $added_by_user ) {
2216
			$user                 = get_user_by( 'id', get_current_user_id() );
2217
			$comment_author       = $user->display_name;
2218
			$comment_author_email = $user->user_email;
2219
		} else {
2220
			$comment_author       = __( 'WooCommerce', 'woocommerce' );
2221
			$comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@';
2222
			$comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
2223
			$comment_author_email = sanitize_email( $comment_author_email );
2224
		}
2225
2226
		$comment_post_ID        = $this->id;
2227
		$comment_author_url     = '';
2228
		$comment_content        = $note;
2229
		$comment_agent          = 'WooCommerce';
2230
		$comment_type           = 'order_note';
2231
		$comment_parent         = 0;
2232
		$comment_approved       = 1;
2233
		$commentdata            = apply_filters( 'woocommerce_new_order_note_data', compact( 'comment_post_ID', 'comment_author', 'comment_author_email', 'comment_author_url', 'comment_content', 'comment_agent', 'comment_type', 'comment_parent', 'comment_approved' ), array( 'order_id' => $this->id, 'is_customer_note' => $is_customer_note ) );
2234
2235
		$comment_id = wp_insert_comment( $commentdata );
2236
2237
		if ( $is_customer_note ) {
2238
			add_comment_meta( $comment_id, 'is_customer_note', 1 );
2239
2240
			do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->id, 'customer_note' => $commentdata['comment_content'] ) );
2241
		}
2242
2243
		return $comment_id;
2244
	}
2245
2246
	/**
2247
	 * Updates status of order.
2248
	 *
2249
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
2250
	 * @param string $note (default: '') Optional note to add.
2251
	 * @param bool $manual is this a manual order status change?
2252
	 * @return bool Successful change or not
2253
	 */
2254
	public function update_status( $new_status, $note = '', $manual = false ) {
2255
		if ( ! $this->id ) {
2256
			return false;
2257
		}
2258
2259
		// Standardise status names.
2260
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
2261
		$old_status = $this->get_status();
2262
2263
		// If the statuses are the same there is no need to update, unless the post status is not a valid 'wc' status.
2264
		if ( $new_status === $old_status && in_array( $this->post_status, array_keys( wc_get_order_statuses() ) ) ) {
2265
			return false;
2266
		}
2267
2268
		// Update the order.
2269
		wp_update_post( array( 'ID' => $this->id, 'post_status' => 'wc-' . $new_status ) );
2270
		$this->post_status = 'wc-' . $new_status;
2271
2272
		$this->add_order_note( trim( $note . ' ' . sprintf( __( 'Order status changed from %s to %s.', 'woocommerce' ), wc_get_order_status_name( $old_status ), wc_get_order_status_name( $new_status ) ) ), 0, $manual );
2273
2274
		// Status was changed.
2275
		do_action( 'woocommerce_order_status_' . $new_status, $this->id );
2276
		do_action( 'woocommerce_order_status_' . $old_status . '_to_' . $new_status, $this->id );
2277
		do_action( 'woocommerce_order_status_changed', $this->id, $old_status, $new_status );
2278
2279
		switch ( $new_status ) {
2280
2281
			case 'completed' :
2282
				// Record the sales.
2283
				$this->record_product_sales();
2284
2285
				// Increase coupon usage counts.
2286
				$this->increase_coupon_usage_counts();
2287
2288
				// Record the completed date of the order.
2289
				update_post_meta( $this->id, '_completed_date', current_time('mysql') );
2290
2291
				// Update reports.
2292
				wc_delete_shop_order_transients( $this->id );
2293
				break;
2294
2295
			case 'processing' :
2296
			case 'on-hold' :
2297
				// Record the sales.
2298
				$this->record_product_sales();
2299
2300
				// Increase coupon usage counts.
2301
				$this->increase_coupon_usage_counts();
2302
2303
				// Update reports.
2304
				wc_delete_shop_order_transients( $this->id );
2305
				break;
2306
2307
			case 'cancelled' :
2308
				// If the order is cancelled, restore used coupons.
2309
				$this->decrease_coupon_usage_counts();
2310
2311
				// Update reports.
2312
				wc_delete_shop_order_transients( $this->id );
2313
				break;
2314
		}
2315
2316
		return true;
2317
	}
2318
2319
2320
	/**
2321
	 * Cancel the order and restore the cart (before payment).
2322
	 *
2323
	 * @param string $note (default: '') Optional note to add.
2324
	 */
2325
	public function cancel_order( $note = '' ) {
2326
		WC()->session->set( 'order_awaiting_payment', false );
2327
		$this->update_status( 'cancelled', $note );
2328
	}
2329
2330
	/**
2331
	 * When a payment is complete this function is called.
2332
	 *
2333
	 * Most of the time this should mark an order as 'processing' so that admin can process/post the items.
2334
	 * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action.
2335
	 * Stock levels are reduced at this point.
2336
	 * Sales are also recorded for products.
2337
	 * Finally, record the date of payment.
2338
	 *
2339
	 * @param $transaction_id string Optional transaction id to store in post meta.
2340
	 */
2341
	public function payment_complete( $transaction_id = '' ) {
2342
		do_action( 'woocommerce_pre_payment_complete', $this->id );
2343
2344
		if ( null !== WC()->session ) {
2345
			WC()->session->set( 'order_awaiting_payment', false );
2346
		}
2347
2348
		$valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this );
2349
2350
		if ( $this->id && $this->has_status( $valid_order_statuses ) ) {
2351
			$order_needs_processing = false;
2352
2353
			if ( sizeof( $this->get_items() ) > 0 ) {
2354
				foreach ( $this->get_items() as $item ) {
2355
					if ( $_product = $this->get_product_from_item( $item ) ) {
2356
						$virtual_downloadable_item = $_product->is_downloadable() && $_product->is_virtual();
2357
2358
						if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $_product, $this->id ) ) {
2359
							$order_needs_processing = true;
2360
							break;
2361
						}
2362
					}
2363
				}
2364
			}
2365
2366
			$this->update_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->id ) );
2367
2368
			add_post_meta( $this->id, '_paid_date', current_time( 'mysql' ), true );
2369
2370
			if ( ! empty( $transaction_id ) ) {
2371
				add_post_meta( $this->id, '_transaction_id', $transaction_id, true );
2372
			}
2373
2374
			wp_update_post( array(
2375
				'ID'            => $this->id,
2376
				'post_date'     => current_time( 'mysql', 0 ),
2377
				'post_date_gmt' => current_time( 'mysql', 1 )
2378
			) );
2379
2380
			// Payment is complete so reduce stock levels
2381
			if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $this->id, '_order_stock_reduced', true ), $this->id ) ) {
2382
				$this->reduce_order_stock();
2383
			}
2384
2385
			do_action( 'woocommerce_payment_complete', $this->id );
2386
		} else {
2387
			do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->id );
2388
		}
2389
	}
2390
2391
2392
	/**
2393
	 * Record sales.
2394
	 */
2395
	public function record_product_sales() {
2396
		if ( 'yes' === get_post_meta( $this->id, '_recorded_sales', true ) ) {
2397
			return;
2398
		}
2399
2400
		if ( sizeof( $this->get_items() ) > 0 ) {
2401
2402
			foreach ( $this->get_items() as $item ) {
2403
2404
				if ( $item['product_id'] > 0 ) {
2405
					$sales = (int) get_post_meta( $item['product_id'], 'total_sales', true );
2406
					$sales += (int) $item['qty'];
2407
2408
					if ( $sales ) {
2409
						update_post_meta( $item['product_id'], 'total_sales', $sales );
2410
					}
2411
				}
2412
			}
2413
		}
2414
2415
		update_post_meta( $this->id, '_recorded_sales', 'yes' );
2416
2417
		/**
2418
		 * Called when sales for an order are recorded
2419
		 *
2420
		 * @param int $order_id order id
2421
		 */
2422
		do_action( 'woocommerce_recorded_sales', $this->id );
2423
	}
2424
2425
2426
	/**
2427
	 * Get coupon codes only.
2428
	 *
2429
	 * @return array
2430
	 */
2431
	public function get_used_coupons() {
2432
2433
		$codes   = array();
2434
		$coupons = $this->get_items( 'coupon' );
2435
2436
		foreach ( $coupons as $item_id => $item ) {
2437
			$codes[] = trim( $item['name'] );
2438
		}
2439
2440
		return $codes;
2441
	}
2442
2443
2444
	/**
2445
	 * Increase applied coupon counts.
2446
	 */
2447 View Code Duplication
	public function increase_coupon_usage_counts() {
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...
2448
		if ( 'yes' == get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) {
2449
			return;
2450
		}
2451
2452
		if ( sizeof( $this->get_used_coupons() ) > 0 ) {
2453
2454
			foreach ( $this->get_used_coupons() as $code ) {
2455
				if ( ! $code ) {
2456
					continue;
2457
				}
2458
2459
				$coupon = new WC_Coupon( $code );
2460
2461
				$used_by = $this->get_user_id();
2462
2463
				if ( ! $used_by ) {
2464
					$used_by = $this->billing_email;
2465
				}
2466
2467
				$coupon->inc_usage_count( $used_by );
2468
			}
2469
2470
			update_post_meta( $this->id, '_recorded_coupon_usage_counts', 'yes' );
2471
		}
2472
	}
2473
2474
2475
	/**
2476
	 * Decrease applied coupon counts.
2477
	 */
2478 View Code Duplication
	public function decrease_coupon_usage_counts() {
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...
2479
2480
		if ( 'yes' != get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) {
2481
			return;
2482
		}
2483
2484
		if ( sizeof( $this->get_used_coupons() ) > 0 ) {
2485
2486
			foreach ( $this->get_used_coupons() as $code ) {
2487
2488
				if ( ! $code ) {
2489
					continue;
2490
				}
2491
2492
				$coupon = new WC_Coupon( $code );
2493
2494
				$used_by = $this->get_user_id();
2495
				if ( ! $used_by ) {
2496
					$used_by = $this->billing_email;
2497
				}
2498
2499
				$coupon->dcr_usage_count( $used_by );
2500
			}
2501
2502
			delete_post_meta( $this->id, '_recorded_coupon_usage_counts' );
2503
		}
2504
	}
2505
2506
	/**
2507
	 * Reduce stock levels for all line items in the order.
2508
	 * Runs if stock management is enabled, but can be disabled on per-order basis by extensions @since 2.4.0 via woocommerce_can_reduce_order_stock hook.
2509
	 */
2510
	public function reduce_order_stock() {
2511
		if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && apply_filters( 'woocommerce_can_reduce_order_stock', true, $this ) && sizeof( $this->get_items() ) > 0 ) {
2512
			foreach ( $this->get_items() as $item ) {
2513
				if ( $item['product_id'] > 0 ) {
2514
					$_product = $this->get_product_from_item( $item );
2515
2516
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2517
						$qty       = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $this, $item );
2518
						$new_stock = $_product->reduce_stock( $qty );
2519
						$item_name = $_product->get_sku() ? $_product->get_sku(): $item['product_id'];
2520
2521
						if ( isset( $item['variation_id'] ) && $item['variation_id'] ) {
2522
							$this->add_order_note( sprintf( __( 'Item %s variation #%s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $item['variation_id'], $new_stock + $qty, $new_stock) );
2523
						} else {
2524
							$this->add_order_note( sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock) );
2525
						}
2526
						$this->send_stock_notifications( $_product, $new_stock, $item['qty'] );
2527
					}
2528
				}
2529
			}
2530
2531
			add_post_meta( $this->id, '_order_stock_reduced', '1', true );
2532
2533
			do_action( 'woocommerce_reduce_order_stock', $this );
2534
		}
2535
	}
2536
2537
	/**
2538
	 * Send the stock notifications.
2539
	 *
2540
	 * @param WC_Product $product
2541
	 * @param int $new_stock
2542
	 * @param int $qty_ordered
2543
	 */
2544
	public function send_stock_notifications( $product, $new_stock, $qty_ordered ) {
2545
2546
		// Backorders
2547
		if ( $new_stock < 0 ) {
2548
			do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $this->id, 'quantity' => $qty_ordered ) );
2549
		}
2550
2551
		// stock status notifications
2552
		$notification_sent = false;
2553
2554
		if ( 'yes' == get_option( 'woocommerce_notify_no_stock' ) && get_option( 'woocommerce_notify_no_stock_amount' ) >= $new_stock ) {
2555
			do_action( 'woocommerce_no_stock', $product );
2556
			$notification_sent = true;
2557
		}
2558
2559
		if ( ! $notification_sent && 'yes' == get_option( 'woocommerce_notify_low_stock' ) && get_option( 'woocommerce_notify_low_stock_amount' ) >= $new_stock ) {
2560
			do_action( 'woocommerce_low_stock', $product );
2561
		}
2562
2563
		do_action( 'woocommerce_after_send_stock_notifications', $product, $new_stock, $qty_ordered );
2564
	}
2565
2566
2567
	/**
2568
	 * List order notes (public) for the customer.
2569
	 *
2570
	 * @return array
2571
	 */
2572
	public function get_customer_order_notes() {
2573
		$notes = array();
2574
		$args  = array(
2575
			'post_id' => $this->id,
2576
			'approve' => 'approve',
2577
			'type'    => ''
2578
		);
2579
2580
		remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
2581
2582
		$comments = get_comments( $args );
2583
2584
		foreach ( $comments as $comment ) {
2585
			if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) {
2586
				continue;
2587
			}
2588
			$comment->comment_content = make_clickable( $comment->comment_content );
2589
			$notes[] = $comment;
2590
		}
2591
2592
		add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
2593
2594
		return $notes;
2595
	}
2596
2597
	/**
2598
	 * Checks if an order needs payment, based on status and order total.
2599
	 *
2600
	 * @return bool
2601
	 */
2602
	public function needs_payment() {
2603
2604
		$valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this );
2605
2606
		if ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ) {
2607
			$needs_payment = true;
2608
		} else {
2609
			$needs_payment = false;
2610
		}
2611
2612
		return apply_filters( 'woocommerce_order_needs_payment', $needs_payment, $this, $valid_order_statuses );
2613
	}
2614
2615
	/**
2616
	 * Checks if an order needs display the shipping address, based on shipping method.
2617
	 *
2618
	 * @return boolean
2619
	 */
2620
	public function needs_shipping_address() {
2621
		if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
2622
			return false;
2623
		}
2624
2625
		$hide  = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this );
2626
		$needs_address = false;
2627
2628
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
2629
			if ( ! in_array( $shipping_method['method_id'], $hide ) ) {
2630
				$needs_address = true;
2631
				break;
2632
			}
2633
		}
2634
2635
		return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this );
2636
	}
2637
2638
	/**
2639
	 * Checks if an order can be edited, specifically for use on the Edit Order screen.
2640
	 *
2641
	 * @return bool
2642
	 */
2643
	public function is_editable() {
2644
		return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft' ) ), $this );
2645
	}
2646
}
2647