Completed
Pull Request — master (#11552)
by
unknown
08:36
created

WC_Abstract_Order::needs_shipping_address()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 24
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 6
eloc 12
c 1
b 1
f 0
nc 6
nop 0
dl 0
loc 24
rs 8.5125
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|object|WC_Order $order Order to init.
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 ) {
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
	 * Returns a list of all payment tokens associated with the current order
149
	 *
150
	 * @since 2.6
151
	 * @return array An array of payment token objects
152
	 */
153
	public function get_payment_tokens() {
154
		return WC_Payment_Tokens::get_order_tokens( $this->id );
155
	}
156
157
	/**
158
	 * Add a payment token to an order
159
	 *
160
	 * @since 2.6
161
	 * @param  WC_Payment_Token   $token     Payment token object
162
	 * @return boolean True if the token was added, false if not
163
	 */
164
	public function add_payment_token( $token ) {
165
		if ( empty( $token ) || ! ( $token instanceof WC_Payment_Token ) ) {
166
			return false;
167
		}
168
169
		$token_ids = get_post_meta( $this->id, '_payment_tokens', true );
170
		if ( empty ( $token_ids ) ) {
171
			$token_ids = array();
172
		}
173
		$token_ids[] = $token->get_id();
174
175
		update_post_meta( $this->id, '_payment_tokens', $token_ids );
176
		do_action( 'woocommerce_payment_token_added_to_order', $this->id, $token->get_id(), $token, $token_ids );
177
		return true;
178
	}
179
180
	/**
181
	 * Set the payment method for the order.
182
	 *
183
	 * @param WC_Payment_Gateway|string $payment_method
184
	 */
185
	public function set_payment_method( $payment_method = '' ) {
186
		if ( is_object( $payment_method ) ) {
187
			update_post_meta( $this->id, '_payment_method', $payment_method->id );
188
			update_post_meta( $this->id, '_payment_method_title', $payment_method->get_title() );
189
		} else {
190
			update_post_meta( $this->id, '_payment_method', '' );
191
			update_post_meta( $this->id, '_payment_method_title', '' );
192
		}
193
	}
194
195
	/**
196
	 * Set the customer address.
197
	 *
198
	 * @param array $address Address data.
199
	 * @param string $type billing or shipping.
200
	 */
201
	public function set_address( $address, $type = 'billing' ) {
202
203
		foreach ( $address as $key => $value ) {
204
			update_post_meta( $this->id, "_{$type}_" . $key, $value );
205
		}
206
	}
207
208
	/**
209
	 * Returns the requested address in raw, non-formatted way.
210
	 * @since  2.4.0
211
	 * @param  string $type Billing or shipping. Anything else besides 'billing' will return shipping address.
212
	 * @return array The stored address after filter.
213
	 */
214
	public function get_address( $type = 'billing' ) {
215
216
		if ( 'billing' === $type ) {
217
			$address = array(
218
				'first_name' => $this->billing_first_name,
219
				'last_name'  => $this->billing_last_name,
220
				'company'    => $this->billing_company,
221
				'address_1'  => $this->billing_address_1,
222
				'address_2'  => $this->billing_address_2,
223
				'city'       => $this->billing_city,
224
				'state'      => $this->billing_state,
225
				'postcode'   => $this->billing_postcode,
226
				'country'    => $this->billing_country,
227
				'email'      => $this->billing_email,
228
				'phone'      => $this->billing_phone,
229
			);
230
		} else {
231
			$address = array(
232
				'first_name' => $this->shipping_first_name,
233
				'last_name'  => $this->shipping_last_name,
234
				'company'    => $this->shipping_company,
235
				'address_1'  => $this->shipping_address_1,
236
				'address_2'  => $this->shipping_address_2,
237
				'city'       => $this->shipping_city,
238
				'state'      => $this->shipping_state,
239
				'postcode'   => $this->shipping_postcode,
240
				'country'    => $this->shipping_country,
241
			);
242
		}
243
244
		return apply_filters( 'woocommerce_get_order_address', $address, $type, $this );
245
	}
246
247
	/**
248
	 * Add a product line item to the order.
249
	 *
250
	 * @since 2.2
251
	 * @param \WC_Product $product
252
	 * @param int $qty Line item quantity.
253
	 * @param array $args
254
	 * @return int|bool Item ID or false.
255
	 */
256
	public function add_product( $product, $qty = 1, $args = array() ) {
257
		$args = wp_parse_args( $args, array(
258
			'variation' => array(),
259
			'totals'    => array()
260
		) );
261
262
		if ( ! $product ) {
263
			return false;
264
		}
265
266
		$item_id = wc_add_order_item( $this->id, array(
267
			'order_item_name' => $product->get_title(),
268
			'order_item_type' => 'line_item'
269
		) );
270
271
		if ( ! $item_id ) {
272
			return false;
273
		}
274
275
		wc_add_order_item_meta( $item_id, '_qty',          wc_stock_amount( $qty ) );
276
		wc_add_order_item_meta( $item_id, '_tax_class',    $product->get_tax_class() );
277
		wc_add_order_item_meta( $item_id, '_product_id',   $product->id );
278
		wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 );
279
280
		// Set line item totals, either passed in or from the product
281
		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 ) ) );
282
		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 ) ) );
283
		wc_add_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) );
284
		wc_add_order_item_meta( $item_id, '_line_tax',          wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) );
285
286
		// Save tax data - Since 2.2
287
		if ( isset( $args['totals']['tax_data'] ) ) {
288
289
			$tax_data             = array();
290
			$tax_data['total']    = array_map( 'wc_format_decimal', $args['totals']['tax_data']['total'] );
291
			$tax_data['subtotal'] = array_map( 'wc_format_decimal', $args['totals']['tax_data']['subtotal'] );
292
293
			wc_add_order_item_meta( $item_id, '_line_tax_data', $tax_data );
294
		} else {
295
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
296
		}
297
298
		// Add variation meta
299 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...
300
			foreach ( $args['variation'] as $key => $value ) {
301
				wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
302
			}
303
		}
304
305
		// Backorders
306
		if ( $product->backorders_require_notification() && $product->is_on_backorder( $qty ) ) {
307
			wc_add_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $qty - max( 0, $product->get_total_stock() ) );
308
		}
309
310
		do_action( 'woocommerce_order_add_product', $this->id, $item_id, $product, $qty, $args );
311
312
		return $item_id;
313
	}
314
315
316
	/**
317
	 * Update a line item for the order.
318
	 *
319
	 * Note this does not update order totals.
320
	 *
321
	 * @since 2.2
322
	 * @param int $item_id order item ID.
323
	 * @param array $args data to update.
324
	 * @param WC_Product $product
325
	 * @return bool
326
	 */
327
	public function update_product( $item_id, $product, $args ) {
328
329
		if ( ! $item_id || ! is_object( $product ) ) {
330
			return false;
331
		}
332
333
		// quantity
334
		if ( isset( $args['qty'] ) ) {
335
			wc_update_order_item_meta( $item_id, '_qty', wc_stock_amount( $args['qty'] ) );
336
		}
337
338
		// tax class
339
		if ( isset( $args['tax_class'] ) ) {
340
			wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] );
341
		}
342
343
		// set item totals, either provided or from product
344
		if ( isset( $args['qty'] ) ) {
345
			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'] ) ) );
346
			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'] ) ) );
347
		}
348
349
		// set item tax totals
350
		wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) );
351
		wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) );
352
353
		// variation meta
354
		if ( isset( $args['variation'] ) && is_array( $args['variation'] ) ) {
355
356
			foreach ( $args['variation'] as $key => $value ) {
357
				wc_update_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
358
			}
359
		}
360
361
		// backorders
362
		if ( isset( $args['qty'] ) && $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) {
363
			wc_update_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ) );
364
		}
365
366
		do_action( 'woocommerce_order_edit_product', $this->id, $item_id, $args, $product );
367
368
		return true;
369
	}
370
371
372
	/**
373
	 * Add coupon code to the order.
374
	 *
375
	 * @param string $code
376
	 * @param int $discount_amount
377
	 * @param int $discount_amount_tax "Discounted" tax - used for tax inclusive prices.
378
	 * @return int|bool Item ID or false.
379
	 */
380
	public function add_coupon( $code, $discount_amount = 0, $discount_amount_tax = 0 ) {
381
		$item_id = wc_add_order_item( $this->id, array(
382
			'order_item_name' => $code,
383
			'order_item_type' => 'coupon'
384
		) );
385
386
		if ( ! $item_id ) {
387
			return false;
388
		}
389
390
		wc_add_order_item_meta( $item_id, 'discount_amount', $discount_amount );
391
		wc_add_order_item_meta( $item_id, 'discount_amount_tax', $discount_amount_tax );
392
393
		do_action( 'woocommerce_order_add_coupon', $this->id, $item_id, $code, $discount_amount, $discount_amount_tax );
394
395
		return $item_id;
396
	}
397
398
	/**
399
	 * Update coupon for order.
400
	 *
401
	 * Note this does not update order totals.
402
	 *
403
	 * @since 2.2
404
	 * @param int $item_id
405
	 * @param array $args
406
	 * @return bool
407
	 */
408
	public function update_coupon( $item_id, $args ) {
409
		if ( ! $item_id ) {
410
			return false;
411
		}
412
413
		// code
414
		if ( isset( $args['code'] ) ) {
415
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['code'] ) );
416
		}
417
418
		// amount
419
		if ( isset( $args['discount_amount'] ) ) {
420
			wc_update_order_item_meta( $item_id, 'discount_amount', wc_format_decimal( $args['discount_amount'] ) );
421
		}
422
		if ( isset( $args['discount_amount_tax'] ) ) {
423
			wc_add_order_item_meta( $item_id, 'discount_amount_tax', wc_format_decimal( $args['discount_amount_tax'] ) );
424
		}
425
426
		do_action( 'woocommerce_order_update_coupon', $this->id, $item_id, $args );
427
428
		return true;
429
	}
430
431
	/**
432
	 * Add a tax row to the order.
433
	 *
434
	 * @since 2.2
435
	 * @param int tax_rate_id
436
	 * @return int|bool Item ID or false.
437
	 */
438
	public function add_tax( $tax_rate_id, $tax_amount = 0, $shipping_tax_amount = 0 ) {
439
440
		$code = WC_Tax::get_rate_code( $tax_rate_id );
441
442
		if ( ! $code ) {
443
			return false;
444
		}
445
446
		$item_id = wc_add_order_item( $this->id, array(
447
			'order_item_name' => $code,
448
			'order_item_type' => 'tax'
449
		) );
450
451
		if ( ! $item_id ) {
452
			return false;
453
		}
454
455
		wc_add_order_item_meta( $item_id, 'rate_id', $tax_rate_id );
456
		wc_add_order_item_meta( $item_id, 'label', WC_Tax::get_rate_label( $tax_rate_id ) );
457
		wc_add_order_item_meta( $item_id, 'compound', WC_Tax::is_compound( $tax_rate_id ) ? 1 : 0 );
458
		wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( $tax_amount ) );
459
		wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( $shipping_tax_amount ) );
460
461
		do_action( 'woocommerce_order_add_tax', $this->id, $item_id, $tax_rate_id, $tax_amount, $shipping_tax_amount );
462
463
		return $item_id;
464
	}
465
466
	/**
467
	 * Add a shipping row to the order.
468
	 *
469
	 * @param WC_Shipping_Rate shipping_rate
470
	 * @return int|bool Item ID or false.
471
	 */
472
	public function add_shipping( $shipping_rate ) {
473
474
		$item_id = wc_add_order_item( $this->id, array(
475
			'order_item_name' 		=> $shipping_rate->label,
476
			'order_item_type' 		=> 'shipping'
477
		) );
478
479
		if ( ! $item_id ) {
480
			return false;
481
		}
482
483
		wc_add_order_item_meta( $item_id, 'method_id', $shipping_rate->id );
484
		wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_rate->cost ) );
485
486
		// Save shipping taxes - Since 2.2
487
		$taxes = array_map( 'wc_format_decimal', $shipping_rate->taxes );
488
		wc_add_order_item_meta( $item_id, 'taxes', $taxes );
489
490
		// Store meta
491
		$shipping_meta = $shipping_rate->get_meta_data();
492
		if ( ! empty( $shipping_meta ) ) {
493
			foreach ( $shipping_rate->get_meta_data() as $key => $value ) {
494
				wc_add_order_item_meta( $item_id, $key, $value );
495
			}
496
		}
497
498
		do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $shipping_rate );
499
500
		// Update total
501
		$this->set_total( $this->order_shipping + wc_format_decimal( $shipping_rate->cost ), 'shipping' );
502
503
		return $item_id;
504
	}
505
506
	/**
507
	 * Update shipping method for order.
508
	 *
509
	 * Note this does not update the order total.
510
	 *
511
	 * @since 2.2
512
	 * @param int $item_id
513
	 * @param array $args
514
	 * @return bool
515
	 */
516
	public function update_shipping( $item_id, $args ) {
517
518
		if ( ! $item_id ) {
519
			return false;
520
		}
521
522
		// method title
523
		if ( isset( $args['method_title'] ) ) {
524
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['method_title'] ) );
525
		}
526
527
		// method ID
528
		if ( isset( $args['method_id'] ) ) {
529
			wc_update_order_item_meta( $item_id, 'method_id', $args['method_id'] );
530
		}
531
532
		// method cost
533
		if ( isset( $args['cost'] ) ) {
534
			// Get old cost before updating
535
			$old_cost = wc_get_order_item_meta( $item_id, 'cost' );
536
537
			// Update
538
			wc_update_order_item_meta( $item_id, 'cost', wc_format_decimal( $args['cost'] ) );
539
540
			// Update total
541
			$this->set_total( $this->order_shipping - wc_format_decimal( $old_cost ) + wc_format_decimal( $args['cost'] ), 'shipping' );
542
		}
543
544
		do_action( 'woocommerce_order_update_shipping', $this->id, $item_id, $args );
545
546
		return true;
547
	}
548
549
	/**
550
	 * Add a fee to the order.
551
	 *
552
	 * @param object $fee
553
	 * @return int|bool Item ID or false.
554
	 */
555
	public function add_fee( $fee ) {
556
557
		$item_id = wc_add_order_item( $this->id, array(
558
			'order_item_name' => $fee->name,
559
			'order_item_type' => 'fee'
560
		) );
561
562
		if ( ! $item_id ) {
563
			return false;
564
		}
565
566
		if ( $fee->taxable ) {
567
			wc_add_order_item_meta( $item_id, '_tax_class', $fee->tax_class );
568
		} else {
569
			wc_add_order_item_meta( $item_id, '_tax_class', '0' );
570
		}
571
572
		wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $fee->amount ) );
573
		wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $fee->tax ) );
574
575
		// Save tax data - Since 2.2
576
		$tax_data = array_map( 'wc_format_decimal', $fee->tax_data );
577
		wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $tax_data ) );
578
579
		do_action( 'woocommerce_order_add_fee', $this->id, $item_id, $fee );
580
581
		return $item_id;
582
	}
583
584
	/**
585
	 * Update fee for order.
586
	 *
587
	 * Note this does not update order totals.
588
	 *
589
	 * @since 2.2
590
	 * @param int $item_id
591
	 * @param array $args
592
	 * @return bool
593
	 */
594
	public function update_fee( $item_id, $args ) {
595
596
		if ( ! $item_id ) {
597
			return false;
598
		}
599
600
		// name
601
		if ( isset( $args['name'] ) ) {
602
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['name'] ) );
603
		}
604
605
		// tax class
606
		if ( isset( $args['tax_class'] ) ) {
607
			wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] );
608
		}
609
610
		// total
611
		if ( isset( $args['line_total'] ) ) {
612
			wc_update_order_item_meta( $item_id, '_line_total', wc_format_decimal( $args['line_total'] ) );
613
		}
614
615
		// total tax
616
		if ( isset( $args['line_tax'] ) ) {
617
			wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $args['line_tax'] ) );
618
		}
619
620
		do_action( 'woocommerce_order_update_fee', $this->id, $item_id, $args );
621
622
		return true;
623
	}
624
625
	/**
626
	 * Set an order total.
627
	 *
628
	 * @param float $amount
629
	 * @param string $total_type
630
	 *
631
	 * @return bool
632
	 */
633
	public function set_total( $amount, $total_type = 'total' ) {
634
635
		if ( ! in_array( $total_type, array( 'shipping', 'tax', 'shipping_tax', 'total', 'cart_discount', 'cart_discount_tax' ) ) ) {
636
			return false;
637
		}
638
639
		switch ( $total_type ) {
640
			case 'total' :
641
				$key    = '_order_total';
642
				$amount = wc_format_decimal( $amount, wc_get_price_decimals() );
643
			break;
644
			case 'cart_discount' :
645
			case 'cart_discount_tax' :
646
				$key    = '_' . $total_type;
647
				$amount = wc_format_decimal( $amount );
648
			break;
649
			default :
650
				$key    = '_order_' . $total_type;
651
				$amount = wc_format_decimal( $amount );
652
			break;
653
		}
654
655
		update_post_meta( $this->id, $key, $amount );
656
657
		return true;
658
	}
659
660
	/**
661
	 * Get all tax classes for items in the order.
662
	 *
663
	 * @since 2.6.3
664
	 * @return array
665
	 */
666
	public function get_items_tax_classes() {
667
		$found_tax_classes = array();
668
669
		foreach ( $this->get_items() as $item ) {
670
			if ( $_product = $this->get_product_from_item( $item ) ) {
671
				$found_tax_classes[] = $_product->get_tax_class();
672
			}
673
		}
674
675
		return array_unique( $found_tax_classes );
676
	}
677
678
	/**
679
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
680
	 *
681
	 * Will use the base country unless customer addresses are set.
682
	 *
683
	 * @return bool success or fail.
684
	 */
685
	public function calculate_taxes() {
686
		$tax_total          = 0;
687
		$shipping_tax_total = 0;
688
		$taxes              = array();
689
		$shipping_taxes     = array();
690
		$tax_based_on       = get_option( 'woocommerce_tax_based_on' );
691
692
		// If is_vat_exempt is 'yes', or wc_tax_enabled is false, return and do nothing.
693
		if ( 'yes' === $this->is_vat_exempt || ! wc_tax_enabled() ) {
694
			return false;
695
		}
696
697
		if ( 'billing' === $tax_based_on ) {
698
			$country  = $this->billing_country;
699
			$state    = $this->billing_state;
700
			$postcode = $this->billing_postcode;
701
			$city     = $this->billing_city;
702
		} elseif ( 'shipping' === $tax_based_on ) {
703
			$country  = $this->shipping_country;
704
			$state    = $this->shipping_state;
705
			$postcode = $this->shipping_postcode;
706
			$city     = $this->shipping_city;
707
		}
708
709
		// Default to base
710 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...
711
			$default  = wc_get_base_location();
712
			$country  = $default['country'];
713
			$state    = $default['state'];
714
			$postcode = '';
715
			$city     = '';
716
		}
717
718
		// Get items
719
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
720
721
			$product           = $this->get_product_from_item( $item );
722
			$line_total        = isset( $item['line_total'] ) ? $item['line_total'] : 0;
723
			$line_subtotal     = isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0;
724
			$tax_class         = $item['tax_class'];
725
			$item_tax_status   = $product ? $product->get_tax_status() : 'taxable';
726
727
			if ( '0' !== $tax_class && 'taxable' === $item_tax_status ) {
728
729
				$tax_rates = WC_Tax::find_rates( array(
730
					'country'   => $country,
731
					'state'     => $state,
732
					'postcode'  => $postcode,
733
					'city'      => $city,
734
					'tax_class' => $tax_class
735
				) );
736
737
				$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal, $tax_rates, false );
738
				$line_taxes          = WC_Tax::calc_tax( $line_total, $tax_rates, false );
739
				$line_subtotal_tax   = max( 0, array_sum( $line_subtotal_taxes ) );
740
				$line_tax            = max( 0, array_sum( $line_taxes ) );
741
				$tax_total           += $line_tax;
742
743
				wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( $line_subtotal_tax ) );
744
				wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) );
745
				wc_update_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $line_taxes, 'subtotal' => $line_subtotal_taxes ) );
746
747
				// Sum the item taxes
748
				foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
749
					$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
750
				}
751
			}
752
		}
753
754
		// Calc taxes for shipping
755
		foreach ( $this->get_shipping_methods() as $item_id => $item ) {
756
			$shipping_tax_class = get_option( 'woocommerce_shipping_tax_class' );
757
758
			// Inherit tax class from items
759
			if ( '' === $shipping_tax_class ) {
760
				$tax_classes       = WC_Tax::get_tax_classes();
761
				$found_tax_classes = $this->get_items_tax_classes();
762
763
				foreach ( $tax_classes as $tax_class ) {
764
					$tax_class = sanitize_title( $tax_class );
765
					if ( in_array( $tax_class, $found_tax_classes ) ) {
766
						$tax_rates = WC_Tax::find_shipping_rates( array(
767
							'country'   => $country,
768
							'state'     => $state,
769
							'postcode'  => $postcode,
770
							'city'      => $city,
771
							'tax_class' => $tax_class,
772
						) );
773
						break;
774
					}
775
				}
776
			} else {
777
				$tax_rates = WC_Tax::find_shipping_rates( array(
778
					'country'   => $country,
779
					'state'     => $state,
780
					'postcode'  => $postcode,
781
					'city'      => $city,
782
					'tax_class' => 'standard' === $shipping_tax_class ? '' : $shipping_tax_class,
783
				) );
784
			}
785
786
			$line_taxes          = WC_Tax::calc_tax( $item['cost'], $tax_rates, false );
787
			$line_tax            = max( 0, array_sum( $line_taxes ) );
788
			$shipping_tax_total += $line_tax;
789
790
			wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) );
791
			wc_update_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $line_taxes ) );
792
793
			// Sum the item taxes
794 View Code Duplication
			foreach ( array_keys( $shipping_taxes + $line_taxes ) as $key ) {
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...
795
				$shipping_taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $shipping_taxes[ $key ] ) ? $shipping_taxes[ $key ] : 0 );
796
			}
797
		}
798
799
		// Save tax totals
800
		$this->set_total( $shipping_tax_total, 'shipping_tax' );
801
		$this->set_total( $tax_total, 'tax' );
802
803
		// Tax rows
804
		$this->remove_order_items( 'tax' );
805
806
		// Now merge to keep tax rows
807 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...
808
			$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 );
809
		}
810
811
		return true;
812
	}
813
814
815
	/**
816
	 * Calculate shipping total.
817
	 *
818
	 * @since 2.2
819
	 * @return float
820
	 */
821
	public function calculate_shipping() {
822
823
		$shipping_total = 0;
824
825
		foreach ( $this->get_shipping_methods() as $shipping ) {
826
			$shipping_total += $shipping['cost'];
827
		}
828
829
		$this->set_total( $shipping_total, 'shipping' );
830
831
		return $this->get_total_shipping();
832
	}
833
834
	/**
835
	 * Update tax lines at order level by looking at the line item taxes themselves.
836
	 *
837
	 * @return bool success or fail.
838
	 */
839
	public function update_taxes() {
840
		$order_taxes          = array();
841
		$order_shipping_taxes = array();
842
843
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
844
845
			$line_tax_data = maybe_unserialize( $item['line_tax_data'] );
846
847 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...
848
849
				foreach ( $line_tax_data['total'] as $tax_rate_id => $tax ) {
850
851
					if ( ! isset( $order_taxes[ $tax_rate_id ] ) ) {
852
						$order_taxes[ $tax_rate_id ] = 0;
853
					}
854
855
					$order_taxes[ $tax_rate_id ] += $tax;
856
				}
857
			}
858
		}
859
860
		foreach ( $this->get_items( array( 'shipping' ) ) as $item_id => $item ) {
861
862
			$line_tax_data = maybe_unserialize( $item['taxes'] );
863
864 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...
865
				foreach ( $line_tax_data as $tax_rate_id => $tax ) {
866
					if ( ! isset( $order_shipping_taxes[ $tax_rate_id ] ) ) {
867
						$order_shipping_taxes[ $tax_rate_id ] = 0;
868
					}
869
870
					$order_shipping_taxes[ $tax_rate_id ] += $tax;
871
				}
872
			}
873
		}
874
875
		// Remove old existing tax rows.
876
		$this->remove_order_items( 'tax' );
877
878
		// Now merge to keep tax rows.
879 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...
880
			$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 );
881
		}
882
883
		// Save tax totals
884
		$this->set_total( WC_Tax::round( array_sum( $order_shipping_taxes ) ), 'shipping_tax' );
885
		$this->set_total( WC_Tax::round( array_sum( $order_taxes ) ), 'tax' );
886
887
		return true;
888
	}
889
890
	/**
891
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
892
	 *
893
	 * @since 2.2
894
	 * @param  bool $and_taxes Calc taxes if true.
895
	 * @return float calculated grand total.
896
	 */
897
	public function calculate_totals( $and_taxes = true ) {
898
		$cart_subtotal     = 0;
899
		$cart_total        = 0;
900
		$fee_total         = 0;
901
		$cart_subtotal_tax = 0;
902
		$cart_total_tax    = 0;
903
904
		if ( $and_taxes && wc_tax_enabled() ) {
905
			$this->calculate_taxes();
906
		}
907
908
		// line items
909
		foreach ( $this->get_items() as $item ) {
910
			$cart_subtotal     += wc_format_decimal( isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0 );
911
			$cart_total        += wc_format_decimal( isset( $item['line_total'] ) ? $item['line_total'] : 0 );
912
			$cart_subtotal_tax += wc_format_decimal( isset( $item['line_subtotal_tax'] ) ? $item['line_subtotal_tax'] : 0 );
913
			$cart_total_tax    += wc_format_decimal( isset( $item['line_tax'] ) ? $item['line_tax'] : 0 );
914
		}
915
916
		$this->calculate_shipping();
917
918
		foreach ( $this->get_fees() as $item ) {
919
			$fee_total += $item['line_total'];
920
		}
921
922
		$this->set_total( $cart_subtotal - $cart_total, 'cart_discount' );
923
		$this->set_total( $cart_subtotal_tax - $cart_total_tax, 'cart_discount_tax' );
924
925
		$grand_total = round( $cart_total + $fee_total + $this->get_total_shipping() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
926
927
		$this->set_total( $grand_total, 'total' );
928
929
		return $grand_total;
930
	}
931
932
	/**
933
	 * Gets an order from the database.
934
	 *
935
	 * @param int $id (default: 0).
936
	 * @return bool
937
	 */
938 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...
939
940
		if ( ! $id ) {
941
			return false;
942
		}
943
944
		if ( $result = get_post( $id ) ) {
945
			$this->populate( $result );
946
			return true;
947
		}
948
949
		return false;
950
	}
951
952
	/**
953
	 * Populates an order from the loaded post data.
954
	 *
955
	 * @param mixed $result
956
	 */
957
	public function populate( $result ) {
958
959
		// Standard post data
960
		$this->id                  = $result->ID;
961
		$this->order_date          = $result->post_date;
962
		$this->modified_date       = $result->post_modified;
963
		$this->customer_message    = $result->post_excerpt;
964
		$this->customer_note       = $result->post_excerpt;
965
		$this->post_status         = $result->post_status;
966
967
		// Billing email can default to user if set.
968
		if ( empty( $this->billing_email ) && ! empty( $this->customer_user ) && ( $user = get_user_by( 'id', $this->customer_user ) ) ) {
969
			$this->billing_email = $user->user_email;
970
		}
971
972
		// Orders store the state of prices including tax when created.
973
		$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;
974
	}
975
976
	/**
977
	 * __isset function.
978
	 *
979
	 * @param mixed $key
980
	 * @return bool
981
	 */
982
	public function __isset( $key ) {
983
984
		if ( ! $this->id ) {
985
			return false;
986
		}
987
988
		return metadata_exists( 'post', $this->id, '_' . $key );
989
	}
990
991
	/**
992
	 * __get function.
993
	 *
994
	 * @param mixed $key
995
	 * @return mixed
996
	 */
997
	public function __get( $key ) {
998
		// Get values or default if not set.
999
		if ( 'completed_date' === $key ) {
1000
			$value = ( $value = get_post_meta( $this->id, '_completed_date', true ) ) ? $value : $this->modified_date;
1001
		} elseif ( 'user_id' === $key ) {
1002
			$value = ( $value = get_post_meta( $this->id, '_customer_user', true ) ) ? absint( $value ) : '';
1003
		} elseif ( 'status' === $key ) {
1004
			$value = $this->get_status();
1005
		} else {
1006
			$value = get_post_meta( $this->id, '_' . $key, true );
1007
		}
1008
1009
		return $value;
1010
	}
1011
1012
	/**
1013
	 * Return the order statuses without wc- internal prefix.
1014
	 *
1015
	 * Queries get_post_status() directly to avoid having out of date statuses, if updated elsewhere.
1016
	 *
1017
	 * @return string
1018
	 */
1019
	public function get_status() {
1020
		$this->post_status = get_post_status( $this->id );
1021
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->post_status, 0, 3 ) ? substr( $this->post_status, 3 ) : $this->post_status, $this );
1022
	}
1023
1024
	/**
1025
	 * Checks the order status against a passed in status.
1026
	 *
1027
	 * @return bool
1028
	 */
1029
	public function has_status( $status ) {
1030
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
1031
	}
1032
1033
	/**
1034
	 * Gets the user ID associated with the order. Guests are 0.
1035
	 *
1036
	 * @since  2.2
1037
	 * @return int
1038
	 */
1039
	public function get_user_id() {
1040
		return $this->customer_user ? intval( $this->customer_user ) : 0;
1041
	}
1042
1043
	/**
1044
	 * Get the user associated with the order. False for guests.
1045
	 *
1046
	 * @since  2.2
1047
	 * @return WP_User|false
1048
	 */
1049
	public function get_user() {
1050
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
1051
	}
1052
1053
	/**
1054
	 * Get transaction id for the order.
1055
	 *
1056
	 * @return string
1057
	 */
1058
	public function get_transaction_id() {
1059
		return get_post_meta( $this->id, '_transaction_id', true );
1060
	}
1061
1062
	/**
1063
	 * Check if an order key is valid.
1064
	 *
1065
	 * @param mixed $key
1066
	 * @return bool
1067
	 */
1068
	public function key_is_valid( $key ) {
1069
1070
		if ( $key == $this->order_key ) {
1071
			return true;
1072
		}
1073
1074
		return false;
1075
	}
1076
1077
	/**
1078
	 * get_order_number function.
1079
	 *
1080
	 * Gets the order number for display (by default, order ID).
1081
	 *
1082
	 * @return string
1083
	 */
1084
	public function get_order_number() {
1085
		return apply_filters( 'woocommerce_order_number', $this->id, $this );
1086
	}
1087
1088
	/**
1089
	 * Get a formatted billing address for the order.
1090
	 *
1091
	 * @return string
1092
	 */
1093
	public function get_formatted_billing_address() {
1094
		if ( ! $this->formatted_billing_address ) {
1095
1096
			// Formatted Addresses.
1097
			$address = apply_filters( 'woocommerce_order_formatted_billing_address', array(
1098
				'first_name'    => $this->billing_first_name,
1099
				'last_name'     => $this->billing_last_name,
1100
				'company'       => $this->billing_company,
1101
				'address_1'     => $this->billing_address_1,
1102
				'address_2'     => $this->billing_address_2,
1103
				'city'          => $this->billing_city,
1104
				'state'         => $this->billing_state,
1105
				'postcode'      => $this->billing_postcode,
1106
				'country'       => $this->billing_country
1107
			), $this );
1108
1109
			$this->formatted_billing_address = WC()->countries->get_formatted_address( $address );
1110
		}
1111
1112
		return $this->formatted_billing_address;
1113
	}
1114
1115
	/**
1116
	 * Get a formatted shipping address for the order.
1117
	 *
1118
	 * @return string
1119
	 */
1120
	public function get_formatted_shipping_address() {
1121
		if ( ! $this->formatted_shipping_address ) {
1122
1123
			if ( $this->shipping_address_1 || $this->shipping_address_2 ) {
1124
1125
				// Formatted Addresses
1126
				$address = apply_filters( 'woocommerce_order_formatted_shipping_address', array(
1127
					'first_name'    => $this->shipping_first_name,
1128
					'last_name'     => $this->shipping_last_name,
1129
					'company'       => $this->shipping_company,
1130
					'address_1'     => $this->shipping_address_1,
1131
					'address_2'     => $this->shipping_address_2,
1132
					'city'          => $this->shipping_city,
1133
					'state'         => $this->shipping_state,
1134
					'postcode'      => $this->shipping_postcode,
1135
					'country'       => $this->shipping_country
1136
				), $this );
1137
1138
				$this->formatted_shipping_address = WC()->countries->get_formatted_address( $address );
1139
			}
1140
		}
1141
1142
		return $this->formatted_shipping_address;
1143
	}
1144
1145
	/**
1146
	 * Get a formatted shipping address for the order.
1147
	 *
1148
	 * @return string
1149
	 */
1150
	public function get_shipping_address_map_url() {
1151
		$address = apply_filters( 'woocommerce_shipping_address_map_url_parts', array(
1152
			'address_1'     => $this->shipping_address_1,
1153
			'address_2'     => $this->shipping_address_2,
1154
			'city'          => $this->shipping_city,
1155
			'state'         => $this->shipping_state,
1156
			'postcode'      => $this->shipping_postcode,
1157
			'country'       => $this->shipping_country
1158
		), $this );
1159
1160
		return apply_filters( 'woocommerce_shipping_address_map_url', 'https://maps.google.com/maps?&q=' . urlencode( implode( ', ', $address ) ) . '&z=16', $this );
1161
	}
1162
1163
	/**
1164
	 * Get the billing address in an array.
1165
	 * @deprecated 2.3
1166
	 * @return string
1167
	 */
1168
	public function get_billing_address() {
1169
		_deprecated_function( 'get_billing_address', '2.3', 'get_formatted_billing_address' );
1170
		return $this->get_formatted_billing_address();
1171
	}
1172
1173
	/**
1174
	 * Get the shipping address in an array.
1175
	 * @deprecated 2.3
1176
	 * @return string
1177
	 */
1178
	public function get_shipping_address() {
1179
		_deprecated_function( 'get_shipping_address', '2.3', 'get_formatted_shipping_address' );
1180
		return $this->get_formatted_shipping_address();
1181
	}
1182
1183
	/**
1184
	 * Get a formatted billing full name.
1185
	 *
1186
	 * @since 2.4.0
1187
	 *
1188
	 * @return string
1189
	 */
1190
	public function get_formatted_billing_full_name() {
1191
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ),  $this->billing_first_name, $this->billing_last_name );
1192
	}
1193
1194
	/**
1195
	 * Get a formatted shipping full name.
1196
	 *
1197
	 * @since 2.4.0
1198
	 *
1199
	 * @return string
1200
	 */
1201
	public function get_formatted_shipping_full_name() {
1202
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ),  $this->shipping_first_name, $this->shipping_last_name );
1203
	}
1204
1205
	/**
1206
	 * Return an array of items/products within this order.
1207
	 *
1208
	 * @param string|array $type Types of line items to get (array or string).
1209
	 * @return array
1210
	 */
1211
	public function get_items( $type = '' ) {
1212
		global $wpdb;
1213
1214
		if ( empty( $type ) ) {
1215
			$type = array( 'line_item' );
1216
		}
1217
1218
		if ( ! is_array( $type ) ) {
1219
			$type = array( $type );
1220
		}
1221
1222
		$items          = array();
1223
		$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 );
1224
		$get_items_sql .= "AND order_item_type IN ( '" . implode( "','", array_map( 'esc_sql', $type ) ) . "' ) ORDER BY order_item_id;";
1225
		$line_items     = $wpdb->get_results( $get_items_sql );
1226
1227
		// Loop items
1228
		foreach ( $line_items as $item ) {
1229
			$items[ $item->order_item_id ]['name']            = $item->order_item_name;
1230
			$items[ $item->order_item_id ]['type']            = $item->order_item_type;
1231
			$items[ $item->order_item_id ]['item_meta']       = $this->get_item_meta( $item->order_item_id );
1232
			$items[ $item->order_item_id ]['item_meta_array'] = $this->get_item_meta_array( $item->order_item_id );
1233
			$items[ $item->order_item_id ]                    = $this->expand_item_meta( $items[ $item->order_item_id ] );
1234
		}
1235
1236
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
1237
	}
1238
1239
	/**
1240
	 * Expand item meta into the $item array.
1241
	 * @since 2.4.0
1242
	 * @param array $item before expansion.
1243
	 * @return array
1244
	 */
1245
	public function expand_item_meta( $item ) {
1246
		// Reserved meta keys
1247
		$reserved_item_meta_keys = array(
1248
			'name',
1249
			'type',
1250
			'item_meta',
1251
			'item_meta_array',
1252
			'qty',
1253
			'tax_class',
1254
			'product_id',
1255
			'variation_id',
1256
			'line_subtotal',
1257
			'line_total',
1258
			'line_tax',
1259
			'line_subtotal_tax'
1260
		);
1261
1262
		// Expand item meta if set.
1263
		if ( ! empty( $item['item_meta'] ) ) {
1264
			foreach ( $item['item_meta'] as $name => $value ) {
1265
				if ( in_array( $name, $reserved_item_meta_keys ) ) {
1266
					continue;
1267
				}
1268
				if ( '_' === substr( $name, 0, 1 ) ) {
1269
					$item[ substr( $name, 1 ) ] = $value[0];
1270
				} elseif ( ! in_array( $name, $reserved_item_meta_keys ) ) {
1271
					$item[ $name ] = make_clickable( $value[0] );
1272
				}
1273
			}
1274
		}
1275
		return $item;
1276
	}
1277
1278
	/**
1279
	 * Gets the count of order items of a certain type.
1280
	 *
1281
	 * @param string $item_type
1282
	 * @return string
1283
	 */
1284 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...
1285
		if ( empty( $item_type ) ) {
1286
			$item_type = array( 'line_item' );
1287
		}
1288
		if ( ! is_array( $item_type ) ) {
1289
			$item_type = array( $item_type );
1290
		}
1291
1292
		$items = $this->get_items( $item_type );
1293
		$count = 0;
1294
1295
		foreach ( $items as $item ) {
1296
			$count += empty( $item['qty'] ) ? 1 : $item['qty'];
1297
		}
1298
1299
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
1300
	}
1301
1302
	/**
1303
	 * Get refunds
1304
	 * @return array
1305
	 */
1306
	public function get_refunds() { return array(); }
1307
1308
	/**
1309
	 * Return an array of fees within this order.
1310
	 *
1311
	 * @return array
1312
	 */
1313
	public function get_fees() {
1314
		return $this->get_items( 'fee' );
1315
	}
1316
1317
	/**
1318
	 * Return an array of taxes within this order.
1319
	 *
1320
	 * @return array
1321
	 */
1322
	public function get_taxes() {
1323
		return $this->get_items( 'tax' );
1324
	}
1325
1326
	/**
1327
	 * Return an array of shipping costs within this order.
1328
	 *
1329
	 * @return array
1330
	 */
1331
	public function get_shipping_methods() {
1332
		return $this->get_items( 'shipping' );
1333
	}
1334
1335
	/**
1336
	 * Check whether this order has a specific shipping method or not.
1337
	 *
1338
	 * @param string $method_id
1339
	 *
1340
	 * @return bool
1341
	 */
1342
	public function has_shipping_method( $method_id ) {
1343
1344
		$shipping_methods = $this->get_shipping_methods();
1345
		$has_method = false;
1346
1347
		if ( empty( $shipping_methods ) ) {
1348
			return false;
1349
		}
1350
1351
		foreach ( $shipping_methods as $shipping_method ) {
1352
			if ( $shipping_method['method_id'] == $method_id ) {
1353
				$has_method = true;
1354
			}
1355
		}
1356
1357
		return $has_method;
1358
	}
1359
1360
	/**
1361
	 * Get taxes, merged by code, formatted ready for output.
1362
	 *
1363
	 * @return array
1364
	 */
1365
	public function get_tax_totals() {
1366
1367
		$taxes      = $this->get_items( 'tax' );
1368
		$tax_totals = array();
1369
1370
		foreach ( $taxes as $key => $tax ) {
1371
1372
			$code = $tax[ 'name' ];
1373
1374 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...
1375
				$tax_totals[ $code ] = new stdClass();
1376
				$tax_totals[ $code ]->amount = 0;
1377
			}
1378
1379
			$tax_totals[ $code ]->id                = $key;
1380
			$tax_totals[ $code ]->rate_id           = $tax['rate_id'];
1381
			$tax_totals[ $code ]->is_compound       = $tax[ 'compound' ];
1382
			$tax_totals[ $code ]->label             = isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ];
1383
			$tax_totals[ $code ]->amount           += $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ];
1384
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array('currency' => $this->get_order_currency()) );
1385
		}
1386
1387 View Code Duplication
		if ( apply_filters( 'woocommerce_order_hide_zero_taxes', true ) ) {
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...
1388
			$amounts    = array_filter( wp_list_pluck( $tax_totals, 'amount' ) );
1389
			$tax_totals = array_intersect_key( $tax_totals, $amounts );
1390
		}
1391
1392
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
1393
	}
1394
1395
	/**
1396
	 * has_meta function for order items.
1397
	 *
1398
	 * @param string $order_item_id
1399
	 * @return array of meta data.
1400
	 */
1401
	public function has_meta( $order_item_id ) {
1402
		global $wpdb;
1403
1404
		return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, order_item_id
1405
			FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d
1406
			ORDER BY meta_id", absint( $order_item_id ) ), ARRAY_A );
1407
	}
1408
1409
	/**
1410
	 * Get all item meta data in array format in the order it was saved. Does not group meta by key like get_item_meta().
1411
	 *
1412
	 * @param mixed $order_item_id
1413
	 * @return array of objects
1414
	 */
1415
	public function get_item_meta_array( $order_item_id ) {
1416
		global $wpdb;
1417
1418
		// Get cache key - uses get_cache_prefix to invalidate when needed
1419
		$cache_key       = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $order_item_id;
1420
		$item_meta_array = wp_cache_get( $cache_key, 'orders' );
1421
1422
		if ( false === $item_meta_array ) {
1423
			$item_meta_array = array();
1424
			$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 ) ) );
1425
			foreach ( $metadata as $metadata_row ) {
1426
				$item_meta_array[ $metadata_row->meta_id ] = (object) array( 'key' => $metadata_row->meta_key, 'value' => $metadata_row->meta_value );
1427
			}
1428
			wp_cache_set( $cache_key, $item_meta_array, 'orders' );
1429
		}
1430
1431
		return $item_meta_array ;
1432
	}
1433
1434
	/**
1435
	 * Display meta data belonging to an item.
1436
	 * @param  array $item
1437
	 */
1438
	public function display_item_meta( $item ) {
1439
		$product   = $this->get_product_from_item( $item );
1440
		$item_meta = new WC_Order_Item_Meta( $item, $product );
1441
		$item_meta->display();
1442
	}
1443
1444
	/**
1445
	 * Get order item meta.
1446
	 *
1447
	 * @param mixed $order_item_id
1448
	 * @param string $key (default: '')
1449
	 * @param bool $single (default: false)
1450
	 * @return array|string
1451
	 */
1452
	public function get_item_meta( $order_item_id, $key = '', $single = false ) {
1453
		return get_metadata( 'order_item', $order_item_id, $key, $single );
1454
	}
1455
1456
	/** Total Getters *******************************************************/
1457
1458
	/**
1459
	 * Gets the total discount amount.
1460
	 * @param  bool $ex_tax Show discount excl any tax.
1461
	 * @return float
1462
	 */
1463
	public function get_total_discount( $ex_tax = true ) {
1464
		if ( ! $this->order_version || version_compare( $this->order_version, '2.3.7', '<' ) ) {
1465
			// Backwards compatible total calculation - totals were not stored consistently in old versions.
1466
			if ( $ex_tax ) {
1467 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...
1468
					$total_discount = (double) $this->cart_discount - (double) $this->cart_discount_tax;
1469
				} else {
1470
					$total_discount = (double) $this->cart_discount;
1471
				}
1472 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...
1473
				if ( $this->prices_include_tax ) {
1474
					$total_discount = (double) $this->cart_discount;
1475
				} else {
1476
					$total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax;
1477
				}
1478
			}
1479
		// New logic - totals are always stored exclusive of tax, tax total is stored in cart_discount_tax
1480
		} else {
1481
			if ( $ex_tax ) {
1482
				$total_discount = (double) $this->cart_discount;
1483
			} else {
1484
				$total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax;
1485
			}
1486
		}
1487
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, wc_get_rounding_precision() ), $this );
1488
	}
1489
1490
	/**
1491
	 * Gets the discount amount.
1492
	 * @deprecated in favour of get_total_discount() since we now only have one discount type.
1493
	 * @return float
1494
	 */
1495
	public function get_cart_discount() {
1496
		_deprecated_function( 'get_cart_discount', '2.3', 'get_total_discount' );
1497
		return apply_filters( 'woocommerce_order_amount_cart_discount', $this->get_total_discount(), $this );
1498
	}
1499
1500
	/**
1501
	 * Get cart discount (formatted).
1502
	 *
1503
	 * @deprecated order (after tax) discounts removed in 2.3.0.
1504
	 * @return string
1505
	 */
1506
	public function get_order_discount_to_display() {
1507
		_deprecated_function( 'get_order_discount_to_display', '2.3' );
1508
	}
1509
1510
	/**
1511
	 * Gets the total (order) discount amount - these are applied after tax.
1512
	 *
1513
	 * @deprecated order (after tax) discounts removed in 2.3.0.
1514
	 * @return float
1515
	 */
1516
	public function get_order_discount() {
1517
		_deprecated_function( 'get_order_discount', '2.3' );
1518
		return apply_filters( 'woocommerce_order_amount_order_discount', (double) $this->order_discount, $this );
1519
	}
1520
1521
	/**
1522
	 * Gets cart tax amount.
1523
	 *
1524
	 * @return float
1525
	 */
1526
	public function get_cart_tax() {
1527
		return apply_filters( 'woocommerce_order_amount_cart_tax', (double) $this->order_tax, $this );
1528
	}
1529
1530
	/**
1531
	 * Gets shipping tax amount.
1532
	 *
1533
	 * @return float
1534
	 */
1535
	public function get_shipping_tax() {
1536
		return apply_filters( 'woocommerce_order_amount_shipping_tax', (double) $this->order_shipping_tax, $this );
1537
	}
1538
1539
	/**
1540
	 * Gets shipping and product tax.
1541
	 *
1542
	 * @return float
1543
	 */
1544
	public function get_total_tax() {
1545
		return apply_filters( 'woocommerce_order_amount_total_tax', wc_round_tax_total( $this->get_cart_tax() + $this->get_shipping_tax() ), $this );
1546
	}
1547
1548
	/**
1549
	 * Gets shipping total.
1550
	 *
1551
	 * @return float
1552
	 */
1553
	public function get_total_shipping() {
1554
		return apply_filters( 'woocommerce_order_amount_total_shipping', (double) $this->order_shipping, $this );
1555
	}
1556
1557
	/**
1558
	 * Gets order total.
1559
	 *
1560
	 * @return float
1561
	 */
1562
	public function get_total() {
1563
		return apply_filters( 'woocommerce_order_amount_total', (double) $this->order_total, $this );
1564
	}
1565
1566
	/**
1567
	 * Gets order subtotal.
1568
	 *
1569
	 * @return mixed|void
1570
	 */
1571
	public function get_subtotal() {
1572
		$subtotal = 0;
1573
1574
		foreach ( $this->get_items() as $item ) {
1575
			$subtotal += ( isset( $item['line_subtotal'] ) ) ? $item['line_subtotal'] : 0;
1576
		}
1577
1578
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
1579
	}
1580
1581
	/**
1582
	 * Get item subtotal - this is the cost before discount.
1583
	 *
1584
	 * @param mixed $item
1585
	 * @param bool $inc_tax (default: false).
1586
	 * @param bool $round (default: true).
1587
	 * @return float
1588
	 */
1589
	public function get_item_subtotal( $item, $inc_tax = false, $round = true ) {
1590
		if ( $inc_tax ) {
1591
			$price = ( $item['line_subtotal'] + $item['line_subtotal_tax'] ) / max( 1, $item['qty'] );
1592
		} else {
1593
			$price = ( $item['line_subtotal'] / max( 1, $item['qty'] ) );
1594
		}
1595
1596
		$price = $round ? number_format( (float) $price, wc_get_price_decimals(), '.', '' ) : $price;
1597
1598
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $price, $this, $item, $inc_tax, $round );
1599
	}
1600
1601
	/**
1602
	 * Get line subtotal - this is the cost before discount.
1603
	 *
1604
	 * @param mixed $item
1605
	 * @param bool $inc_tax (default: false).
1606
	 * @param bool $round (default: true).
1607
	 * @return float
1608
	 */
1609 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...
1610
		if ( $inc_tax ) {
1611
			$price = $item['line_subtotal'] + $item['line_subtotal_tax'];
1612
		} else {
1613
			$price = $item['line_subtotal'];
1614
		}
1615
1616
		$price = $round ? round( $price, wc_get_price_decimals() ) : $price;
1617
1618
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $price, $this, $item, $inc_tax, $round );
1619
	}
1620
1621
	/**
1622
	 * Calculate item cost - useful for gateways.
1623
	 *
1624
	 * @param mixed $item
1625
	 * @param bool $inc_tax (default: false).
1626
	 * @param bool $round (default: true).
1627
	 * @return float
1628
	 */
1629
	public function get_item_total( $item, $inc_tax = false, $round = true ) {
1630
1631
		$qty = ( ! empty( $item['qty'] ) ) ? $item['qty'] : 1;
1632
1633
		if ( $inc_tax ) {
1634
			$price = ( $item['line_total'] + $item['line_tax'] ) / max( 1, $qty );
1635
		} else {
1636
			$price = $item['line_total'] / max( 1, $qty );
1637
		}
1638
1639
		$price = $round ? round( $price, wc_get_price_decimals() ) : $price;
1640
1641
		return apply_filters( 'woocommerce_order_amount_item_total', $price, $this, $item, $inc_tax, $round );
1642
	}
1643
1644
	/**
1645
	 * Calculate line total - useful for gateways.
1646
	 *
1647
	 * @param mixed $item
1648
	 * @param bool $inc_tax (default: false).
1649
	 * @param bool $round (default: true).
1650
	 * @return float
1651
	 */
1652 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...
1653
1654
		// Check if we need to add line tax to the line total.
1655
		$line_total = $inc_tax ? $item['line_total'] + $item['line_tax'] : $item['line_total'];
1656
1657
		// Check if we need to round.
1658
		$line_total = $round ? round( $line_total, wc_get_price_decimals() ) : $line_total;
1659
1660
		return apply_filters( 'woocommerce_order_amount_line_total', $line_total, $this, $item, $inc_tax, $round );
1661
	}
1662
1663
	/**
1664
	 * Calculate item tax - useful for gateways.
1665
	 *
1666
	 * @param mixed $item
1667
	 * @param bool $round (default: true).
1668
	 * @return float
1669
	 */
1670
	public function get_item_tax( $item, $round = true ) {
1671
1672
		$price = $item['line_tax'] / max( 1, $item['qty'] );
1673
		$price = $round ? wc_round_tax_total( $price ) : $price;
1674
1675
		return apply_filters( 'woocommerce_order_amount_item_tax', $price, $item, $round, $this );
1676
	}
1677
1678
	/**
1679
	 * Calculate line tax - useful for gateways.
1680
	 *
1681
	 * @param mixed $item
1682
	 * @return float
1683
	 */
1684
	public function get_line_tax( $item ) {
1685
		return apply_filters( 'woocommerce_order_amount_line_tax', wc_round_tax_total( $item['line_tax'] ), $item, $this );
1686
	}
1687
1688
	/** End Total Getters *******************************************************/
1689
1690
	/**
1691
	 * Gets formatted shipping method title.
1692
	 *
1693
	 * @return string
1694
	 */
1695
	public function get_shipping_method() {
1696
1697
		$labels = array();
1698
1699
		// Backwards compat < 2.1 - get shipping title stored in meta.
1700
		if ( $this->shipping_method_title ) {
1701
			$labels[] = $this->shipping_method_title;
1702
		} else {
1703
1704
			// 2.1+ get line items for shipping.
1705
			$shipping_methods = $this->get_shipping_methods();
1706
1707
			foreach ( $shipping_methods as $shipping ) {
1708
				$labels[] = $shipping['name'] ? $shipping['name'] : __( 'Shipping', 'woocommerce' );
1709
			}
1710
		}
1711
1712
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $labels ), $this );
1713
	}
1714
1715
	/**
1716
	 * Gets line subtotal - formatted for display.
1717
	 *
1718
	 * @param array  $item
1719
	 * @param string $tax_display
1720
	 * @return string
1721
	 */
1722
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1723
1724
		if ( ! $tax_display ) {
1725
			$tax_display = $this->tax_display_cart;
1726
		}
1727
1728
		if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1729
			return '';
1730
		}
1731
1732
		if ( 'excl' == $tax_display ) {
1733
			$ex_tax_label = $this->prices_include_tax ? 1 : 0;
1734
1735
			$subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_order_currency() ) );
1736
		} else {
1737
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array('currency' => $this->get_order_currency()) );
1738
		}
1739
1740
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1741
	}
1742
1743
	/**
1744
	 * Gets order currency.
1745
	 *
1746
	 * @return string
1747
	 */
1748
	public function get_order_currency() {
1749
		return apply_filters( 'woocommerce_get_order_currency', $this->order_currency, $this );
1750
	}
1751
1752
	/**
1753
	 * Gets order total - formatted for display.
1754
	 *
1755
	 * @return string
1756
	 */
1757
	public function get_formatted_order_total() {
1758
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_order_currency() ) );
1759
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1760
	}
1761
1762
	/**
1763
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1764
	 *
1765
	 * @param bool $compound (default: false).
1766
	 * @param string $tax_display (default: the tax_display_cart value).
1767
	 * @return string
1768
	 */
1769
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1770
1771
		if ( ! $tax_display ) {
1772
			$tax_display = $this->tax_display_cart;
1773
		}
1774
1775
		$subtotal = 0;
1776
1777
		if ( ! $compound ) {
1778
			foreach ( $this->get_items() as $item ) {
1779
1780
				if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1781
					return '';
1782
				}
1783
1784
				$subtotal += $item['line_subtotal'];
1785
1786
				if ( 'incl' == $tax_display ) {
1787
					$subtotal += $item['line_subtotal_tax'];
1788
				}
1789
			}
1790
1791
			$subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) );
1792
1793
			if ( $tax_display == 'excl' && $this->prices_include_tax ) {
1794
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1795
			}
1796
1797
		} else {
1798
1799
			if ( 'incl' == $tax_display ) {
1800
				return '';
1801
			}
1802
1803
			foreach ( $this->get_items() as $item ) {
1804
1805
				$subtotal += $item['line_subtotal'];
1806
1807
			}
1808
1809
			// Add Shipping Costs.
1810
			$subtotal += $this->get_total_shipping();
1811
1812
			// Remove non-compound taxes.
1813
			foreach ( $this->get_taxes() as $tax ) {
1814
1815
				if ( ! empty( $tax['compound'] ) ) {
1816
					continue;
1817
				}
1818
1819
				$subtotal = $subtotal + $tax['tax_amount'] + $tax['shipping_tax_amount'];
1820
1821
			}
1822
1823
			// Remove discounts.
1824
			$subtotal = $subtotal - $this->get_total_discount();
1825
1826
			$subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) );
1827
		}
1828
1829
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1830
	}
1831
1832
1833
	/**
1834
	 * Gets shipping (formatted).
1835
	 *
1836
	 * @return string
1837
	 */
1838
	public function get_shipping_to_display( $tax_display = '' ) {
1839
		if ( ! $tax_display ) {
1840
			$tax_display = $this->tax_display_cart;
1841
		}
1842
1843
		if ( $this->order_shipping != 0 ) {
1844
1845
			if ( $tax_display == 'excl' ) {
1846
1847
				// Show shipping excluding tax.
1848
				$shipping = wc_price( $this->order_shipping, array('currency' => $this->get_order_currency()) );
1849
1850 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...
1851
					$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 );
1852
				}
1853
1854
			} else {
1855
1856
				// Show shipping including tax.
1857
				$shipping = wc_price( $this->order_shipping + $this->order_shipping_tax, array('currency' => $this->get_order_currency()) );
1858
1859 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...
1860
					$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 );
1861
				}
1862
1863
			}
1864
1865
			$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 );
1866
1867
		} elseif ( $this->get_shipping_method() ) {
1868
			$shipping = $this->get_shipping_method();
1869
		} else {
1870
			$shipping = __( 'Free!', 'woocommerce' );
1871
		}
1872
1873
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1874
	}
1875
1876
	/**
1877
	 * Get the discount amount (formatted).
1878
	 * @since  2.3.0
1879
	 * @return string
1880
	 */
1881
	public function get_discount_to_display( $tax_display = '' ) {
1882
		if ( ! $tax_display ) {
1883
			$tax_display = $this->tax_display_cart;
1884
		}
1885
		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 );
1886
	}
1887
1888
	/**
1889
	 * Get cart discount (formatted).
1890
	 * @deprecated
1891
	 * @return string
1892
	 */
1893
	public function get_cart_discount_to_display( $tax_display = '' ) {
1894
		_deprecated_function( 'get_cart_discount_to_display', '2.3', 'get_discount_to_display' );
1895
		return apply_filters( 'woocommerce_order_cart_discount_to_display', $this->get_discount_to_display( $tax_display ), $this );
1896
	}
1897
1898
	/**
1899
	 * Get a product (either product or variation).
1900
	 *
1901
	 * @param mixed $item
1902
	 * @return WC_Product
1903
	 */
1904
	public function get_product_from_item( $item ) {
1905
1906
		if ( ! empty( $item['variation_id'] ) && 'product_variation' === get_post_type( $item['variation_id'] ) ) {
1907
			$_product = wc_get_product( $item['variation_id'] );
1908
		} elseif ( ! empty( $item['product_id']  ) ) {
1909
			$_product = wc_get_product( $item['product_id'] );
1910
		} else {
1911
			$_product = false;
1912
		}
1913
1914
		return apply_filters( 'woocommerce_get_product_from_item', $_product, $item, $this );
1915
	}
1916
1917
1918
	/**
1919
	 * Get totals for display on pages and in emails.
1920
	 *
1921
	 * @param mixed $tax_display
1922
	 * @return array
1923
	 */
1924
	public function get_order_item_totals( $tax_display = '' ) {
1925
1926
		if ( ! $tax_display ) {
1927
			$tax_display = $this->tax_display_cart;
1928
		}
1929
1930
		$total_rows = array();
1931
1932
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1933
			$total_rows['cart_subtotal'] = array(
1934
				'label' => __( 'Subtotal:', 'woocommerce' ),
1935
				'value'	=> $subtotal
1936
			);
1937
		}
1938
1939
		if ( $this->get_total_discount() > 0 ) {
1940
			$total_rows['discount'] = array(
1941
				'label' => __( 'Discount:', 'woocommerce' ),
1942
				'value'	=> '-' . $this->get_discount_to_display( $tax_display )
1943
			);
1944
		}
1945
1946
		if ( $this->get_shipping_method() ) {
1947
			$total_rows['shipping'] = array(
1948
				'label' => __( 'Shipping:', 'woocommerce' ),
1949
				'value'	=> $this->get_shipping_to_display( $tax_display )
1950
			);
1951
		}
1952
1953
		if ( $fees = $this->get_fees() ) {
1954
			foreach ( $fees as $id => $fee ) {
1955
1956
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', $fee['line_total'] + $fee['line_tax'] == 0, $id ) ) {
1957
					continue;
1958
				}
1959
1960
				if ( 'excl' == $tax_display ) {
1961
1962
					$total_rows[ 'fee_' . $id ] = array(
1963
						'label' => ( $fee['name'] ? $fee['name'] : __( 'Fee', 'woocommerce' ) ) . ':',
1964
						'value'	=> wc_price( $fee['line_total'], array('currency' => $this->get_order_currency()) )
1965
					);
1966
1967
				} else {
1968
1969
					$total_rows[ 'fee_' . $id ] = array(
1970
						'label' => $fee['name'] . ':',
1971
						'value'	=> wc_price( $fee['line_total'] + $fee['line_tax'], array('currency' => $this->get_order_currency()) )
1972
					);
1973
				}
1974
			}
1975
		}
1976
1977
		// Tax for tax exclusive prices.
1978
		if ( 'excl' === $tax_display ) {
1979
1980
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1981
1982
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1983
1984
					$total_rows[ sanitize_title( $code ) ] = array(
1985
						'label' => $tax->label . ':',
1986
						'value'	=> $tax->formatted_amount
1987
					);
1988
				}
1989
1990
			} else {
1991
1992
				$total_rows['tax'] = array(
1993
					'label' => WC()->countries->tax_or_vat() . ':',
1994
					'value'	=> wc_price( $this->get_total_tax(), array( 'currency' => $this->get_order_currency() ) )
1995
				);
1996
			}
1997
		}
1998
1999
		if ( $this->get_total() > 0 && $this->payment_method_title ) {
2000
			$total_rows['payment_method'] = array(
2001
				'label' => __( 'Payment Method:', 'woocommerce' ),
2002
				'value' => $this->payment_method_title
2003
			);
2004
		}
2005
2006
		if ( $refunds = $this->get_refunds() ) {
2007
			foreach ( $refunds as $id => $refund ) {
2008
				$total_rows[ 'refund_' . $id ] = array(
2009
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
2010
					'value'	=> wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_order_currency() ) )
2011
				);
2012
			}
2013
		}
2014
2015
		$total_rows['order_total'] = array(
2016
			'label' => __( 'Total:', 'woocommerce' ),
2017
			'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...
2018
		);
2019
2020
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
2021
	}
2022
2023
2024
	/**
2025
	 * Output items for display in html emails.
2026
	 *
2027
	 * @param array $args Items args.
2028
	 * @param null $deprecated1 Deprecated arg.
2029
	 * @param null $deprecated2 Deprecated arg.
2030
	 * @param null $deprecated3 Deprecated arg.
2031
	 * @param null $deprecated4 Deprecated arg.
2032
	 * @param null $deprecated5 Deprecated arg.
2033
	 * @return string
2034
	 */
2035
	public function email_order_items_table( $args = array(), $deprecated1 = null, $deprecated2 = null, $deprecated3 = null, $deprecated4 = null, $deprecated5 = null ) {
2036
		ob_start();
2037
2038
		if ( ! is_null( $deprecated1 ) || ! is_null( $deprecated2 ) || ! is_null( $deprecated3 ) || ! is_null( $deprecated4 ) || ! is_null( $deprecated5 ) ) {
2039
			_deprecated_argument( __FUNCTION__, '2.5.0' );
2040
		}
2041
2042
		$defaults = array(
2043
			'show_sku'      => false,
2044
			'show_image'    => false,
2045
			'image_size'    => array( 32, 32 ),
2046
			'plain_text'    => false,
2047
			'sent_to_admin' => false
2048
		);
2049
2050
		$args     = wp_parse_args( $args, $defaults );
2051
		$template = $args['plain_text'] ? 'emails/plain/email-order-items.php' : 'emails/email-order-items.php';
2052
2053
		wc_get_template( $template, apply_filters( 'woocommerce_email_order_items_args', array(
2054
			'order'               => $this,
2055
			'items'               => $this->get_items(),
2056
			'show_download_links' => $this->is_download_permitted() && ! $args['sent_to_admin'],
2057
			'show_sku'            => $args['show_sku'],
2058
			'show_purchase_note'  => $this->is_paid() && ! $args['sent_to_admin'],
2059
			'show_image'          => $args['show_image'],
2060
			'image_size'          => $args['image_size'],
2061
			'plain_text'          => $args['plain_text'],
2062
			'sent_to_admin'       => $args['sent_to_admin']
2063
		) ) );
2064
2065
		return apply_filters( 'woocommerce_email_order_items_table', ob_get_clean(), $this );
2066
	}
2067
2068
	/**
2069
	 * Returns if an order has been paid for based on the order status.
2070
	 * @since 2.5.0
2071
	 * @return bool
2072
	 */
2073
	public function is_paid() {
2074
		return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this );
2075
	}
2076
2077
	/**
2078
	 * Checks if product download is permitted.
2079
	 *
2080
	 * @return bool
2081
	 */
2082
	public function is_download_permitted() {
2083
		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 );
2084
	}
2085
2086
	/**
2087
	 * Returns true if the order contains a downloadable product.
2088
	 * @return bool
2089
	 */
2090
	public function has_downloadable_item() {
2091
		foreach ( $this->get_items() as $item ) {
2092
			$_product = $this->get_product_from_item( $item );
2093
2094
			if ( $_product && $_product->exists() && $_product->is_downloadable() && $_product->has_file() ) {
2095
				return true;
2096
			}
2097
		}
2098
		return false;
2099
	}
2100
2101
	/**
2102
	 * Returns true if the order contains a free product.
2103
	 * @since 2.5.0
2104
	 * @return bool
2105
	 */
2106
	public function has_free_item() {
2107
		foreach ( $this->get_items() as $item ) {
2108
			if ( ! $item['line_total'] ) {
2109
				return true;
2110
			}
2111
		}
2112
		return false;
2113
	}
2114
2115
	/**
2116
	 * 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.
2117
	 *
2118
	 * @param  bool $on_checkout
2119
	 * @return string
2120
	 */
2121
	public function get_checkout_payment_url( $on_checkout = false ) {
2122
2123
		$pay_url = wc_get_endpoint_url( 'order-pay', $this->id, wc_get_page_permalink( 'checkout' ) );
2124
2125 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...
2126
			$pay_url = str_replace( 'http:', 'https:', $pay_url );
2127
		}
2128
2129
		if ( $on_checkout ) {
2130
			$pay_url = add_query_arg( 'key', $this->order_key, $pay_url );
2131
		} else {
2132
			$pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->order_key ), $pay_url );
2133
		}
2134
2135
		return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this );
2136
	}
2137
2138
	/**
2139
	 * Generates a URL for the thanks page (order received).
2140
	 *
2141
	 * @return string
2142
	 */
2143
	public function get_checkout_order_received_url() {
2144
2145
		$order_received_url = wc_get_endpoint_url( 'order-received', $this->id, wc_get_page_permalink( 'checkout' ) );
2146
2147 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...
2148
			$order_received_url = str_replace( 'http:', 'https:', $order_received_url );
2149
		}
2150
2151
		$order_received_url = add_query_arg( 'key', $this->order_key, $order_received_url );
2152
2153
		return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this );
2154
	}
2155
2156
	/**
2157
	 * Generates a URL so that a customer can cancel their (unpaid - pending) order.
2158
	 *
2159
	 * @param string $redirect
2160
	 *
2161
	 * @return string
2162
	 */
2163 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...
2164
2165
		// Get cancel endpoint
2166
		$cancel_endpoint = $this->get_cancel_endpoint();
2167
2168
		return apply_filters( 'woocommerce_get_cancel_order_url', esc_url( add_query_arg( array(
2169
			'cancel_order' => 'true',
2170
			'order'        => $this->order_key,
2171
			'order_id'     => $this->id,
2172
			'redirect'     => $redirect,
2173
		), $cancel_endpoint ) ) );
2174
	}
2175
2176
	/**
2177
	 * Generates a raw (unescaped) cancel-order URL for use by payment gateways.
2178
	 *
2179
	 * @param string $redirect
2180
	 * @return string The unescaped cancel-order URL.
2181
	 */
2182 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...
2183
2184
		// Get cancel endpoint
2185
		$cancel_endpoint = $this->get_cancel_endpoint();
2186
2187
		return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array(
2188
			'cancel_order' => 'true',
2189
			'order'        => $this->order_key,
2190
			'order_id'     => $this->id,
2191
			'redirect'     => $redirect,
2192
		), $cancel_endpoint ) );
2193
	}
2194
2195
2196
	/**
2197
	 * Helper method to return the cancel endpoint.
2198
	 *
2199
	 * @return string the cancel endpoint; either the cart page or the home page.
2200
	 */
2201
	public function get_cancel_endpoint() {
2202
2203
		$cancel_endpoint = wc_get_page_permalink( 'cart' );
2204
		if ( ! $cancel_endpoint ) {
2205
			$cancel_endpoint = home_url();
2206
		}
2207
2208
		if ( false === strpos( $cancel_endpoint, '?' ) ) {
2209
			$cancel_endpoint = trailingslashit( $cancel_endpoint );
2210
		}
2211
2212
		return $cancel_endpoint;
2213
	}
2214
2215
2216
	/**
2217
	 * Generates a URL to view an order from the my account page.
2218
	 *
2219
	 * @return string
2220
	 */
2221
	public function get_view_order_url() {
2222
2223
		$view_order_url = wc_get_endpoint_url( 'view-order', $this->id, wc_get_page_permalink( 'myaccount' ) );
2224
2225
		return apply_filters( 'woocommerce_get_view_order_url', $view_order_url, $this );
2226
	}
2227
2228
	/**
2229
	 * Get the downloadable files for an item in this order.
2230
	 *
2231
	 * @param  array $item
2232
	 * @return array
2233
	 */
2234
	public function get_item_downloads( $item ) {
2235
		global $wpdb;
2236
2237
		$product_id   = $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'];
2238
		$product      = wc_get_product( $product_id );
2239
		if ( ! $product ) {
2240
			/**
2241
			 * $product can be `false`. Example: checking an old order, when a product or variation has been deleted.
2242
			 * @see \WC_Product_Factory::get_product
2243
			 */
2244
			return array();
2245
		}
2246
		$download_ids = $wpdb->get_col( $wpdb->prepare("
2247
			SELECT download_id
2248
			FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
2249
			WHERE user_email = %s
2250
			AND order_key = %s
2251
			AND product_id = %s
2252
			ORDER BY permission_id
2253
		", $this->billing_email, $this->order_key, $product_id ) );
2254
2255
		$files = array();
2256
2257
		foreach ( $download_ids as $download_id ) {
2258
2259
			if ( $product->has_file( $download_id ) ) {
2260
				$files[ $download_id ]                 = $product->get_file( $download_id );
2261
				$files[ $download_id ]['download_url'] = $this->get_download_url( $product_id, $download_id );
2262
			}
2263
		}
2264
2265
		return apply_filters( 'woocommerce_get_item_downloads', $files, $item, $this );
2266
	}
2267
2268
	/**
2269
	 * Display download links for an order item.
2270
	 * @param  array $item
2271
	 */
2272
	public function display_item_downloads( $item ) {
2273
		$product = $this->get_product_from_item( $item );
2274
2275
		if ( $product && $product->exists() && $product->is_downloadable() && $this->is_download_permitted() ) {
2276
			$download_files = $this->get_item_downloads( $item );
2277
			$i              = 0;
2278
			$links          = array();
2279
2280
			foreach ( $download_files as $download_id => $file ) {
2281
				$i++;
2282
				$prefix  = count( $download_files ) > 1 ? sprintf( __( 'Download %d', 'woocommerce' ), $i ) : __( 'Download', 'woocommerce' );
2283
				$links[] = '<small class="download-url">' . $prefix . ': <a href="' . esc_url( $file['download_url'] ) . '" target="_blank">' . esc_html( $file['name'] ) . '</a></small>' . "\n";
2284
			}
2285
2286
			echo '<br/>' . implode( '<br/>', $links );
2287
		}
2288
	}
2289
2290
	/**
2291
	 * Get the Download URL.
2292
	 *
2293
	 * @param  int $product_id
2294
	 * @param  int $download_id
2295
	 * @return string
2296
	 */
2297
	public function get_download_url( $product_id, $download_id ) {
2298
		return add_query_arg( array(
2299
			'download_file' => $product_id,
2300
			'order'         => $this->order_key,
2301
			'email'         => urlencode( $this->billing_email ),
2302
			'key'           => $download_id
2303
		), trailingslashit( home_url() ) );
2304
	}
2305
2306
	/**
2307
	 * Adds a note (comment) to the order.
2308
	 *
2309
	 * @param string $note Note to add.
2310
	 * @param int $is_customer_note (default: 0) Is this a note for the customer?
2311
	 * @param  bool added_by_user Was the note added by a user?
2312
	 * @return int Comment ID.
2313
	 */
2314
	public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) {
2315
		if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->id ) && $added_by_user ) {
2316
			$user                 = get_user_by( 'id', get_current_user_id() );
2317
			$comment_author       = $user->display_name;
2318
			$comment_author_email = $user->user_email;
2319
		} else {
2320
			$comment_author       = __( 'WooCommerce', 'woocommerce' );
2321
			$comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@';
2322
			$comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
2323
			$comment_author_email = sanitize_email( $comment_author_email );
2324
		}
2325
2326
		$comment_post_ID        = $this->id;
2327
		$comment_author_url     = '';
2328
		$comment_content        = $note;
2329
		$comment_agent          = 'WooCommerce';
2330
		$comment_type           = 'order_note';
2331
		$comment_parent         = 0;
2332
		$comment_approved       = 1;
2333
		$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 ) );
2334
2335
		$comment_id = wp_insert_comment( $commentdata );
2336
2337
		if ( $is_customer_note ) {
2338
			add_comment_meta( $comment_id, 'is_customer_note', 1 );
2339
2340
			do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->id, 'customer_note' => $commentdata['comment_content'] ) );
2341
		}
2342
2343
		return $comment_id;
2344
	}
2345
2346
	/**
2347
	 * Updates status of order.
2348
	 *
2349
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
2350
	 * @param string $note (default: '') Optional note to add.
2351
	 * @param bool $manual is this a manual order status change?
2352
	 * @return bool Successful change or not
2353
	 */
2354
	public function update_status( $new_status, $note = '', $manual = false ) {
2355
		if ( ! $this->id ) {
2356
			return false;
2357
		}
2358
2359
		// Standardise status names.
2360
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
2361
		$old_status = $this->get_status();
2362
2363
		// If the old status is unknown (e.g. draft) assume its pending for action usage.
2364
		if ( ! in_array( 'wc-' . $old_status, array_keys( wc_get_order_statuses() ) ) ) {
2365
			$old_status = 'pending';
2366
		}
2367
2368
		// If the statuses are the same there is no need to update, unless the post status is not a valid 'wc' status.
2369
		if ( $new_status === $old_status && in_array( $this->post_status, array_keys( wc_get_order_statuses() ) ) ) {
2370
			return false;
2371
		}
2372
2373
		$this->post_status = 'wc-' . $new_status;
2374
		$update_post_data  = array(
2375
			'ID'          => $this->id,
2376
			'post_status' => $this->post_status,
2377
		);
2378
2379
		if ( 'pending' === $old_status && ! $manual ) {
2380
			$update_post_data[ 'post_date' ]     = current_time( 'mysql', 0 );
2381
			$update_post_data[ 'post_date_gmt' ] = current_time( 'mysql', 1 );
2382
		}
2383
2384
		if ( ! wp_update_post( $update_post_data ) ) {
2385
			$this->add_order_note( sprintf( __( 'Unable to update order from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $old_status ), wc_get_order_status_name( $new_status ) ), 0, $manual );
2386
			return false;
2387
		}
2388
2389
		// Status was set.
2390
		do_action( 'woocommerce_order_status_' . $new_status, $this->id );
2391
2392
		// Status was changed.
2393
		if ( $new_status !== $old_status ) {
2394
			$this->add_order_note( trim( $note . ' ' . sprintf( __( 'Order status changed from %1$s to %2$s.', 'woocommerce' ), wc_get_order_status_name( $old_status ), wc_get_order_status_name( $new_status ) ) ), 0, $manual );
2395
			do_action( 'woocommerce_order_status_' . $old_status . '_to_' . $new_status, $this->id );
2396
			do_action( 'woocommerce_order_status_changed', $this->id, $old_status, $new_status );
2397
		} else {
2398
			$this->add_order_note( trim( $note . ' ' . sprintf( __( 'Order status changed to %s.', 'woocommerce' ), wc_get_order_status_name( $new_status ) ) ), 0, $manual );
2399
		}
2400
2401
		switch ( $new_status ) {
2402
2403
			case 'completed' :
2404
				// Record the sales.
2405
				$this->record_product_sales();
2406
2407
				// Increase coupon usage counts.
2408
				$this->increase_coupon_usage_counts();
2409
2410
				// Record the completed date of the order.
2411
				update_post_meta( $this->id, '_completed_date', current_time('mysql') );
2412
2413
				// Update reports.
2414
				wc_delete_shop_order_transients( $this->id );
2415
				break;
2416
2417
			case 'processing' :
2418
			case 'on-hold' :
2419
				// Record the sales.
2420
				$this->record_product_sales();
2421
2422
				// Increase coupon usage counts.
2423
				$this->increase_coupon_usage_counts();
2424
2425
				// Update reports.
2426
				wc_delete_shop_order_transients( $this->id );
2427
				break;
2428
2429
			case 'cancelled' :
2430
				// If the order is cancelled, restore used coupons.
2431
				$this->decrease_coupon_usage_counts();
2432
2433
				// Update reports.
2434
				wc_delete_shop_order_transients( $this->id );
2435
				break;
2436
		}
2437
2438
		return true;
2439
	}
2440
2441
2442
	/**
2443
	 * Cancel the order and restore the cart (before payment).
2444
	 *
2445
	 * @param string $note (default: '') Optional note to add.
2446
	 */
2447
	public function cancel_order( $note = '' ) {
2448
		WC()->session->set( 'order_awaiting_payment', false );
2449
		$this->update_status( 'cancelled', $note );
2450
	}
2451
2452
	/**
2453
	 * When a payment is complete this function is called.
2454
	 *
2455
	 * Most of the time this should mark an order as 'processing' so that admin can process/post the items.
2456
	 * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action.
2457
	 * Stock levels are reduced at this point.
2458
	 * Sales are also recorded for products.
2459
	 * Finally, record the date of payment.
2460
	 *
2461
	 * @param string $transaction_id Optional transaction id to store in post meta.
2462
	 */
2463
	public function payment_complete( $transaction_id = '' ) {
2464
		do_action( 'woocommerce_pre_payment_complete', $this->id );
2465
2466
		if ( null !== WC()->session ) {
2467
			WC()->session->set( 'order_awaiting_payment', false );
2468
		}
2469
2470
		$valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this );
2471
2472
		if ( $this->id && $this->has_status( $valid_order_statuses ) ) {
2473
			$order_needs_processing = false;
2474
2475
			if ( sizeof( $this->get_items() ) > 0 ) {
2476
				foreach ( $this->get_items() as $item ) {
2477
					if ( $_product = $this->get_product_from_item( $item ) ) {
2478
						$virtual_downloadable_item = $_product->is_downloadable() && $_product->is_virtual();
2479
2480
						if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $_product, $this->id ) ) {
2481
							$order_needs_processing = true;
2482
							break;
2483
						}
2484
					} else {
2485
						$order_needs_processing = true;
2486
						break;
2487
					}
2488
				}
2489
			}
2490
2491
			$this->update_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->id ) );
2492
2493
			add_post_meta( $this->id, '_paid_date', current_time( 'mysql' ), true );
2494
2495
			if ( ! empty( $transaction_id ) ) {
2496
				update_post_meta( $this->id, '_transaction_id', $transaction_id );
2497
			}
2498
2499
			// Payment is complete so reduce stock levels
2500
			if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $this->id, '_order_stock_reduced', true ), $this->id ) ) {
2501
				$this->reduce_order_stock();
2502
			}
2503
2504
			do_action( 'woocommerce_payment_complete', $this->id );
2505
		} else {
2506
			do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->id );
2507
		}
2508
	}
2509
2510
2511
	/**
2512
	 * Record sales.
2513
	 */
2514
	public function record_product_sales() {
2515
		if ( 'yes' === get_post_meta( $this->id, '_recorded_sales', true ) ) {
2516
			return;
2517
		}
2518
2519
		if ( sizeof( $this->get_items() ) > 0 ) {
2520
2521
			foreach ( $this->get_items() as $item ) {
2522
2523
				if ( $item['product_id'] > 0 ) {
2524
					$sales = (int) get_post_meta( $item['product_id'], 'total_sales', true );
2525
					$sales += (int) $item['qty'];
2526
2527
					if ( $sales ) {
2528
						update_post_meta( $item['product_id'], 'total_sales', $sales );
2529
					}
2530
				}
2531
			}
2532
		}
2533
2534
		update_post_meta( $this->id, '_recorded_sales', 'yes' );
2535
2536
		/**
2537
		 * Called when sales for an order are recorded
2538
		 *
2539
		 * @param int $order_id order id
2540
		 */
2541
		do_action( 'woocommerce_recorded_sales', $this->id );
2542
	}
2543
2544
2545
	/**
2546
	 * Get coupon codes only.
2547
	 *
2548
	 * @return array
2549
	 */
2550
	public function get_used_coupons() {
2551
2552
		$codes   = array();
2553
		$coupons = $this->get_items( 'coupon' );
2554
2555
		foreach ( $coupons as $item_id => $item ) {
2556
			$codes[] = trim( $item['name'] );
2557
		}
2558
2559
		return $codes;
2560
	}
2561
2562
2563
	/**
2564
	 * Increase applied coupon counts.
2565
	 */
2566 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...
2567
		if ( 'yes' == get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) {
2568
			return;
2569
		}
2570
2571
		if ( sizeof( $this->get_used_coupons() ) > 0 ) {
2572
2573
			foreach ( $this->get_used_coupons() as $code ) {
2574
				if ( ! $code ) {
2575
					continue;
2576
				}
2577
2578
				$coupon = new WC_Coupon( $code );
2579
2580
				$used_by = $this->get_user_id();
2581
2582
				if ( ! $used_by ) {
2583
					$used_by = $this->billing_email;
2584
				}
2585
2586
				$coupon->inc_usage_count( $used_by );
2587
			}
2588
2589
			update_post_meta( $this->id, '_recorded_coupon_usage_counts', 'yes' );
2590
		}
2591
	}
2592
2593
2594
	/**
2595
	 * Decrease applied coupon counts.
2596
	 */
2597 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...
2598
2599
		if ( 'yes' != get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) {
2600
			return;
2601
		}
2602
2603
		if ( sizeof( $this->get_used_coupons() ) > 0 ) {
2604
2605
			foreach ( $this->get_used_coupons() as $code ) {
2606
2607
				if ( ! $code ) {
2608
					continue;
2609
				}
2610
2611
				$coupon = new WC_Coupon( $code );
2612
2613
				$used_by = $this->get_user_id();
2614
				if ( ! $used_by ) {
2615
					$used_by = $this->billing_email;
2616
				}
2617
2618
				$coupon->dcr_usage_count( $used_by );
2619
			}
2620
2621
			delete_post_meta( $this->id, '_recorded_coupon_usage_counts' );
2622
		}
2623
	}
2624
2625
	/**
2626
	 * Reduce stock levels for all line items in the order.
2627
	 * 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.
2628
	 */
2629
	public function reduce_order_stock() {
2630
		if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && apply_filters( 'woocommerce_can_reduce_order_stock', true, $this ) && sizeof( $this->get_items() ) > 0 ) {
2631
			foreach ( $this->get_items() as $item ) {
2632
				if ( $item['product_id'] > 0 ) {
2633
					$_product = $this->get_product_from_item( $item );
2634
2635
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2636
						$qty       = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $this, $item );
2637
						$new_stock = $_product->reduce_stock( $qty );
2638
						$item_name = $_product->get_sku() ? $_product->get_sku(): $item['product_id'];
2639
2640
						if ( isset( $item['variation_id'] ) && $item['variation_id'] ) {
2641
							$this->add_order_note( sprintf( __( 'Item %1$s variation #%2$s stock reduced from %3$s to %4$s.', 'woocommerce' ), $item_name, $item['variation_id'], $new_stock + $qty, $new_stock) );
2642
						} else {
2643
							$this->add_order_note( sprintf( __( 'Item %1$s stock reduced from %2$s to %3$s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock) );
2644
						}
2645
						$this->send_stock_notifications( $_product, $new_stock, $item['qty'] );
2646
					}
2647
				}
2648
			}
2649
2650
			add_post_meta( $this->id, '_order_stock_reduced', '1', true );
2651
2652
			do_action( 'woocommerce_reduce_order_stock', $this );
2653
		}
2654
	}
2655
2656
	/**
2657
	 * Send the stock notifications.
2658
	 *
2659
	 * @param WC_Product $product
2660
	 * @param int $new_stock
2661
	 * @param int $qty_ordered
2662
	 */
2663
	public function send_stock_notifications( $product, $new_stock, $qty_ordered ) {
2664
2665
		// Backorders
2666
		if ( $new_stock < 0 ) {
2667
			do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $this->id, 'quantity' => $qty_ordered ) );
2668
		}
2669
2670
		// stock status notifications
2671
		$notification_sent = false;
2672
2673
		if ( 'yes' == get_option( 'woocommerce_notify_no_stock' ) && get_option( 'woocommerce_notify_no_stock_amount' ) >= $new_stock ) {
2674
			do_action( 'woocommerce_no_stock', $product );
2675
			$notification_sent = true;
2676
		}
2677
2678
		if ( ! $notification_sent && 'yes' == get_option( 'woocommerce_notify_low_stock' ) && get_option( 'woocommerce_notify_low_stock_amount' ) >= $new_stock ) {
2679
			do_action( 'woocommerce_low_stock', $product );
2680
		}
2681
2682
		do_action( 'woocommerce_after_send_stock_notifications', $product, $new_stock, $qty_ordered );
2683
	}
2684
2685
2686
	/**
2687
	 * List order notes (public) for the customer.
2688
	 *
2689
	 * @return array
2690
	 */
2691
	public function get_customer_order_notes() {
2692
		$notes = array();
2693
		$args  = array(
2694
			'post_id' => $this->id,
2695
			'approve' => 'approve',
2696
			'type'    => ''
2697
		);
2698
2699
		remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
2700
2701
		$comments = get_comments( $args );
2702
2703
		foreach ( $comments as $comment ) {
2704
			if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) {
2705
				continue;
2706
			}
2707
			$comment->comment_content = make_clickable( $comment->comment_content );
2708
			$notes[] = $comment;
2709
		}
2710
2711
		add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
2712
2713
		return $notes;
2714
	}
2715
2716
	/**
2717
	 * Checks if an order needs payment, based on status and order total.
2718
	 *
2719
	 * @return bool
2720
	 */
2721
	public function needs_payment() {
2722
2723
		$valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this );
2724
2725
		if ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ) {
2726
			$needs_payment = true;
2727
		} else {
2728
			$needs_payment = false;
2729
		}
2730
2731
		return apply_filters( 'woocommerce_order_needs_payment', $needs_payment, $this, $valid_order_statuses );
2732
	}
2733
2734
	/**
2735
	 * Checks if an order needs display the shipping address, based on shipping method.
2736
	 *
2737
	 * @return boolean
2738
	 */
2739
	public function needs_shipping_address() {
2740
		if ( ! wc_shipping_enabled() ) {
2741
			return false;
2742
		}
2743
2744
		$hide  = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this );
2745
		$needs_address = false;
2746
2747
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
2748
2749
			// Added due to shipping zones in 2.6 will set the method_id with a # like local_pickup:2
2750
			// if 'local_pickup' is in the hide array & there's a string match to 'local_pickup', set method to 'local_pickup'
2751
			if ( in_array( 'local_pickup', $hide ) && stristr( $shipping_method['method_id'], 'local_pickup' ) ) {
2752
				$shipping_method['method_id'] = 'local_pickup';
2753
			}
2754
2755
			if ( ! in_array( $shipping_method['method_id'], $hide ) ) {
2756
				$needs_address = true;
2757
				break;
2758
			}
2759
		}
2760
2761
		return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this );
2762
	}
2763
2764
	/**
2765
	 * Checks if an order can be edited, specifically for use on the Edit Order screen.
2766
	 *
2767
	 * @return bool
2768
	 */
2769
	public function is_editable() {
2770
		return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft', 'failed' ) ), $this );
2771
	}
2772
}
2773