WC_Abstract_Order::email_order_items_table()   D
last analyzed

Complexity

Conditions 9
Paths 4

Size

Total Lines 32
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

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