Completed
Push — master ( f8df68...657521 )
by Mike
19:25
created

WC_Abstract_Order::email_order_items_table()   D

Complexity

Conditions 9
Paths 4

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 31
rs 4.909
cc 9
eloc 22
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 ) {
1 ignored issue
show
Duplication introduced by
This method seems to be duplicated in your project.

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

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

Loading history...
115
		if ( is_numeric( $order ) ) {
116
			$this->id   = absint( $order );
117
			$this->post = get_post( $order );
118
			$this->get_order( $this->id );
119
		} elseif ( $order instanceof WC_Order ) {
120
			$this->id   = absint( $order->id );
121
			$this->post = $order->post;
122
			$this->get_order( $this->id );
123
		} elseif ( isset( $order->ID ) ) {
124
			$this->id   = absint( $order->ID );
125
			$this->post = $order;
126
			$this->get_order( $this->id );
127
		}
128
	}
129
130
	/**
131
	 * Remove all line items (products, coupons, shipping, taxes) from the order.
132
	 *
133
	 * @param string $type Order item type. Default null.
134
	 */
135
	public function remove_order_items( $type = null ) {
136
		global $wpdb;
137
138
		if ( ! empty( $type ) ) {
139
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id AND items.order_id = %d AND items.order_item_type = %s", $this->id, $type ) );
140
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d AND order_item_type = %s", $this->id, $type ) );
141
		} else {
142
			$wpdb->query( $wpdb->prepare( "DELETE FROM itemmeta USING {$wpdb->prefix}woocommerce_order_itemmeta itemmeta INNER JOIN {$wpdb->prefix}woocommerce_order_items items WHERE itemmeta.order_item_id = items.order_item_id and items.order_id = %d", $this->id ) );
143
			$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d", $this->id ) );
144
		}
145
	}
146
147
	/**
148
	 * Set the payment method for the order.
149
	 *
150
	 * @param WC_Payment_Gateway $payment_method
151
	 */
152
	public function set_payment_method( $payment_method ) {
153
154
		if ( is_object( $payment_method ) ) {
155
			update_post_meta( $this->id, '_payment_method', $payment_method->id );
156
			update_post_meta( $this->id, '_payment_method_title', $payment_method->get_title() );
157
		}
158
	}
159
160
	/**
161
	 * Set the customer address.
162
	 *
163
	 * @param array $address Address data.
164
	 * @param string $type billing or shipping.
165
	 */
166
	public function set_address( $address, $type = 'billing' ) {
167
168
		foreach ( $address as $key => $value ) {
169
			update_post_meta( $this->id, "_{$type}_" . $key, $value );
170
		}
171
	}
172
173
	/**
174
	 * Returns the requested address in raw, non-formatted way.
175
	 * @since  2.4.0
176
	 * @param  string $type Billing or shipping. Anything else besides 'billing' will return shipping address.
177
	 * @return array The stored address after filter.
178
	 */
179
	public function get_address( $type = 'billing' ) {
180
181
		if ( 'billing' === $type ) {
182
			$address = array(
183
				'first_name' => $this->billing_first_name,
184
				'last_name'  => $this->billing_last_name,
185
				'company'    => $this->billing_company,
186
				'address_1'  => $this->billing_address_1,
187
				'address_2'  => $this->billing_address_2,
188
				'city'       => $this->billing_city,
189
				'state'      => $this->billing_state,
190
				'postcode'   => $this->billing_postcode,
191
				'country'    => $this->billing_country,
192
				'email'      => $this->billing_email,
193
				'phone'      => $this->billing_phone,
194
			);
195
		} else {
196
			$address = array(
197
				'first_name' => $this->shipping_first_name,
198
				'last_name'  => $this->shipping_last_name,
199
				'company'    => $this->shipping_company,
200
				'address_1'  => $this->shipping_address_1,
201
				'address_2'  => $this->shipping_address_2,
202
				'city'       => $this->shipping_city,
203
				'state'      => $this->shipping_state,
204
				'postcode'   => $this->shipping_postcode,
205
				'country'    => $this->shipping_country,
206
			);
207
		}
208
209
		return apply_filters( 'woocommerce_get_order_address', $address, $type, $this );
210
	}
211
212
	/**
213
	 * Add a product line item to the order.
214
	 *
215
	 * @since 2.2
216
	 * @param \WC_Product $product
217
	 * @param int $qty Line item quantity.
218
	 * @param array $args
219
	 * @return int|bool Item ID or false.
220
	 */
221
	public function add_product( $product, $qty = 1, $args = array() ) {
222
223
		$default_args = array(
224
			'variation' => array(),
225
			'totals'    => array()
226
		);
227
228
		$args    = wp_parse_args( $args, $default_args );
229
		$item_id = wc_add_order_item( $this->id, array(
230
			'order_item_name' => $product->get_title(),
231
			'order_item_type' => 'line_item'
232
		) );
233
234
		if ( ! $item_id ) {
235
			return false;
236
		}
237
238
		wc_add_order_item_meta( $item_id, '_qty',          wc_stock_amount( $qty ) );
239
		wc_add_order_item_meta( $item_id, '_tax_class',    $product->get_tax_class() );
240
		wc_add_order_item_meta( $item_id, '_product_id',   $product->id );
241
		wc_add_order_item_meta( $item_id, '_variation_id', isset( $product->variation_id ) ? $product->variation_id : 0 );
242
243
		// Set line item totals, either passed in or from the product
244
		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 ) ) );
245
		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 ) ) );
246
		wc_add_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) );
247
		wc_add_order_item_meta( $item_id, '_line_tax',          wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) );
248
249
		// Save tax data - Since 2.2
250
		if ( isset( $args['totals']['tax_data'] ) ) {
251
252
			$tax_data             = array();
253
			$tax_data['total']    = array_map( 'wc_format_decimal', $args['totals']['tax_data']['total'] );
254
			$tax_data['subtotal'] = array_map( 'wc_format_decimal', $args['totals']['tax_data']['subtotal'] );
255
256
			wc_add_order_item_meta( $item_id, '_line_tax_data', $tax_data );
257
		} else {
258
			wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => array(), 'subtotal' => array() ) );
259
		}
260
261
		// Add variation meta
262 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...
263
			foreach ( $args['variation'] as $key => $value ) {
264
				wc_add_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
265
			}
266
		}
267
268
		// Backorders
269
		if ( $product->backorders_require_notification() && $product->is_on_backorder( $qty ) ) {
270
			wc_add_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $qty - max( 0, $product->get_total_stock() ) );
271
		}
272
273
		do_action( 'woocommerce_order_add_product', $this->id, $item_id, $product, $qty, $args );
274
275
		return $item_id;
276
	}
277
278
279
	/**
280
	 * Update a line item for the order.
281
	 *
282
	 * Note this does not update order totals.
283
	 *
284
	 * @since 2.2
285
	 * @param int $item_id order item ID.
286
	 * @param array $args data to update.
287
	 * @param WC_Product $product
288
	 * @return bool
289
	 */
290
	public function update_product( $item_id, $product, $args ) {
291
292
		if ( ! $item_id || ! is_object( $product ) ) {
293
			return false;
294
		}
295
296
		// quantity
297
		if ( isset( $args['qty'] ) ) {
298
			wc_update_order_item_meta( $item_id, '_qty', wc_stock_amount( $args['qty'] ) );
299
		}
300
301
		// tax class
302
		if ( isset( $args['tax_class'] ) ) {
303
			wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] );
304
		}
305
306
		// set item totals, either provided or from product
307
		if ( isset( $args['qty'] ) ) {
308
			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'] ) ) );
309
			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'] ) ) );
310
		}
311
312
		// set item tax totals
313
		wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( isset( $args['totals']['subtotal_tax'] ) ? $args['totals']['subtotal_tax'] : 0 ) );
314
		wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( isset( $args['totals']['tax'] ) ? $args['totals']['tax'] : 0 ) );
315
316
		// variation meta
317
		if ( isset( $args['variation'] ) && is_array( $args['variation'] ) ) {
318
319
			foreach ( $args['variation'] as $key => $value ) {
320
				wc_update_order_item_meta( $item_id, str_replace( 'attribute_', '', $key ), $value );
321
			}
322
		}
323
324
		// backorders
325
		if ( isset( $args['qty'] ) && $product->backorders_require_notification() && $product->is_on_backorder( $args['qty'] ) ) {
326
			wc_update_order_item_meta( $item_id, apply_filters( 'woocommerce_backordered_item_meta_name', __( 'Backordered', 'woocommerce' ) ), $args['qty'] - max( 0, $product->get_total_stock() ) );
327
		}
328
329
		do_action( 'woocommerce_order_edit_product', $this->id, $item_id, $args, $product );
330
331
		return true;
332
	}
333
334
335
	/**
336
	 * Add coupon code to the order.
337
	 *
338
	 * @param string $code
339
	 * @param int $discount_amount
340
	 * @param int $discount_amount_tax "Discounted" tax - used for tax inclusive prices.
341
	 * @return int|bool Item ID or false.
342
	 */
343
	public function add_coupon( $code, $discount_amount = 0, $discount_amount_tax = 0 ) {
344
		$item_id = wc_add_order_item( $this->id, array(
345
			'order_item_name' => $code,
346
			'order_item_type' => 'coupon'
347
		) );
348
349
		if ( ! $item_id ) {
350
			return false;
351
		}
352
353
		wc_add_order_item_meta( $item_id, 'discount_amount', $discount_amount );
354
		wc_add_order_item_meta( $item_id, 'discount_amount_tax', $discount_amount_tax );
355
356
		do_action( 'woocommerce_order_add_coupon', $this->id, $item_id, $code, $discount_amount, $discount_amount_tax );
357
358
		return $item_id;
359
	}
360
361
	/**
362
	 * Update coupon for order.
363
	 *
364
	 * Note this does not update order totals.
365
	 *
366
	 * @since 2.2
367
	 * @param int $item_id
368
	 * @param array $args
369
	 * @return bool
370
	 */
371
	public function update_coupon( $item_id, $args ) {
372
		if ( ! $item_id ) {
373
			return false;
374
		}
375
376
		// code
377
		if ( isset( $args['code'] ) ) {
378
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['code'] ) );
379
		}
380
381
		// amount
382
		if ( isset( $args['discount_amount'] ) ) {
383
			wc_update_order_item_meta( $item_id, 'discount_amount', wc_format_decimal( $args['discount_amount'] ) );
384
		}
385
		if ( isset( $args['discount_amount_tax'] ) ) {
386
			wc_add_order_item_meta( $item_id, 'discount_amount_tax', wc_format_decimal( $args['discount_amount_tax'] ) );
387
		}
388
389
		do_action( 'woocommerce_order_update_coupon', $this->id, $item_id, $args );
390
391
		return true;
392
	}
393
394
	/**
395
	 * Add a tax row to the order.
396
	 *
397
	 * @since 2.2
398
	 * @param int tax_rate_id
399
	 * @return int|bool Item ID or false.
400
	 */
401
	public function add_tax( $tax_rate_id, $tax_amount = 0, $shipping_tax_amount = 0 ) {
402
403
		$code = WC_Tax::get_rate_code( $tax_rate_id );
404
405
		if ( ! $code ) {
406
			return false;
407
		}
408
409
		$item_id = wc_add_order_item( $this->id, array(
410
			'order_item_name' => $code,
411
			'order_item_type' => 'tax'
412
		) );
413
414
		if ( ! $item_id ) {
415
			return false;
416
		}
417
418
		wc_add_order_item_meta( $item_id, 'rate_id', $tax_rate_id );
419
		wc_add_order_item_meta( $item_id, 'label', WC_Tax::get_rate_label( $tax_rate_id ) );
420
		wc_add_order_item_meta( $item_id, 'compound', WC_Tax::is_compound( $tax_rate_id ) ? 1 : 0 );
421
		wc_add_order_item_meta( $item_id, 'tax_amount', wc_format_decimal( $tax_amount ) );
422
		wc_add_order_item_meta( $item_id, 'shipping_tax_amount', wc_format_decimal( $shipping_tax_amount ) );
423
424
		do_action( 'woocommerce_order_add_tax', $this->id, $item_id, $tax_rate_id, $tax_amount, $shipping_tax_amount );
425
426
		return $item_id;
427
	}
428
429
	/**
430
	 * Add a shipping row to the order.
431
	 *
432
	 * @param WC_Shipping_Rate shipping_rate
433
	 * @return int|bool Item ID or false.
434
	 */
435
	public function add_shipping( $shipping_rate ) {
436
437
		$item_id = wc_add_order_item( $this->id, array(
438
			'order_item_name' 		=> $shipping_rate->label,
439
			'order_item_type' 		=> 'shipping'
440
		) );
441
442
		if ( ! $item_id ) {
443
			return false;
444
		}
445
446
		wc_add_order_item_meta( $item_id, 'method_id', $shipping_rate->id );
447
		wc_add_order_item_meta( $item_id, 'cost', wc_format_decimal( $shipping_rate->cost ) );
448
449
		// Save shipping taxes - Since 2.2
450
		$taxes = array_map( 'wc_format_decimal', $shipping_rate->taxes );
451
		wc_add_order_item_meta( $item_id, 'taxes', $taxes );
452
453
		do_action( 'woocommerce_order_add_shipping', $this->id, $item_id, $shipping_rate );
454
455
		// Update total
456
		$this->set_total( $this->order_shipping + wc_format_decimal( $shipping_rate->cost ), 'shipping' );
457
458
		return $item_id;
459
	}
460
461
	/**
462
	 * Update shipping method for order.
463
	 *
464
	 * Note this does not update the order total.
465
	 *
466
	 * @since 2.2
467
	 * @param int $item_id
468
	 * @param array $args
469
	 * @return bool
470
	 */
471
	public function update_shipping( $item_id, $args ) {
472
473
		if ( ! $item_id ) {
474
			return false;
475
		}
476
477
		// method title
478
		if ( isset( $args['method_title'] ) ) {
479
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['method_title'] ) );
480
		}
481
482
		// method ID
483
		if ( isset( $args['method_id'] ) ) {
484
			wc_update_order_item_meta( $item_id, 'method_id', $args['method_id'] );
485
		}
486
487
		// method cost
488
		if ( isset( $args['cost'] ) ) {
489
			// Get old cost before updating
490
			$old_cost = wc_get_order_item_meta( $item_id, 'cost' );
491
492
			// Update
493
			wc_update_order_item_meta( $item_id, 'cost', wc_format_decimal( $args['cost'] ) );
494
495
			// Update total
496
			$this->set_total( $this->order_shipping - wc_format_decimal( $old_cost ) + wc_format_decimal( $args['cost'] ), 'shipping' );
497
		}
498
499
		do_action( 'woocommerce_order_update_shipping', $this->id, $item_id, $args );
500
501
		return true;
502
	}
503
504
	/**
505
	 * Add a fee to the order.
506
	 *
507
	 * @param object $fee
508
	 * @return int|bool Item ID or false.
509
	 */
510
	public function add_fee( $fee ) {
511
512
		$item_id = wc_add_order_item( $this->id, array(
513
			'order_item_name' => $fee->name,
514
			'order_item_type' => 'fee'
515
		) );
516
517
		if ( ! $item_id ) {
518
			return false;
519
		}
520
521
		if ( $fee->taxable ) {
522
			wc_add_order_item_meta( $item_id, '_tax_class', $fee->tax_class );
523
		} else {
524
			wc_add_order_item_meta( $item_id, '_tax_class', '0' );
525
		}
526
527
		wc_add_order_item_meta( $item_id, '_line_total', wc_format_decimal( $fee->amount ) );
528
		wc_add_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $fee->tax ) );
529
530
		// Save tax data - Since 2.2
531
		$tax_data = array_map( 'wc_format_decimal', $fee->tax_data );
532
		wc_add_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $tax_data ) );
533
534
		do_action( 'woocommerce_order_add_fee', $this->id, $item_id, $fee );
535
536
		return $item_id;
537
	}
538
539
	/**
540
	 * Update fee for order.
541
	 *
542
	 * Note this does not update order totals.
543
	 *
544
	 * @since 2.2
545
	 * @param int $item_id
546
	 * @param array $args
547
	 * @return bool
548
	 */
549
	public function update_fee( $item_id, $args ) {
550
551
		if ( ! $item_id ) {
552
			return false;
553
		}
554
555
		// name
556
		if ( isset( $args['name'] ) ) {
557
			wc_update_order_item( $item_id, array( 'order_item_name' => $args['name'] ) );
558
		}
559
560
		// tax class
561
		if ( isset( $args['tax_class'] ) ) {
562
			wc_update_order_item_meta( $item_id, '_tax_class', $args['tax_class'] );
563
		}
564
565
		// total
566
		if ( isset( $args['line_total'] ) ) {
567
			wc_update_order_item_meta( $item_id, '_line_total', wc_format_decimal( $args['line_total'] ) );
568
		}
569
570
		// total tax
571
		if ( isset( $args['line_tax'] ) ) {
572
			wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $args['line_tax'] ) );
573
		}
574
575
		do_action( 'woocommerce_order_update_fee', $this->id, $item_id, $args );
576
577
		return true;
578
	}
579
580
	/**
581
	 * Set an order total.
582
	 *
583
	 * @param float $amount
584
	 * @param string $total_type
585
	 *
586
	 * @return bool
587
	 */
588
	public function set_total( $amount, $total_type = 'total' ) {
589
590
		if ( ! in_array( $total_type, array( 'shipping', 'tax', 'shipping_tax', 'total', 'cart_discount', 'cart_discount_tax' ) ) ) {
591
			return false;
592
		}
593
594
		switch ( $total_type ) {
595
			case 'total' :
596
				$key    = '_order_total';
597
				$amount = wc_format_decimal( $amount, wc_get_price_decimals() );
598
			break;
599
			case 'cart_discount' :
600
			case 'cart_discount_tax' :
601
				$key    = '_' . $total_type;
602
				$amount = wc_format_decimal( $amount );
603
			break;
604
			default :
605
				$key    = '_order_' . $total_type;
606
				$amount = wc_format_decimal( $amount );
607
			break;
608
		}
609
610
		update_post_meta( $this->id, $key, $amount );
611
612
		return true;
613
	}
614
615
	/**
616
	 * Calculate taxes for all line items and shipping, and store the totals and tax rows.
617
	 *
618
	 * Will use the base country unless customer addresses are set.
619
	 *
620
	 * @return bool success or fail.
621
	 */
622
	public function calculate_taxes() {
623
		$tax_total    = 0;
624
		$taxes        = array();
625
		$tax_based_on = get_option( 'woocommerce_tax_based_on' );
626
627
		if ( 'billing' === $tax_based_on ) {
628
			$country  = $this->billing_country;
629
			$state    = $this->billing_state;
630
			$postcode = $this->billing_postcode;
631
			$city     = $this->billing_city;
632
		} elseif ( 'shipping' === $tax_based_on ) {
633
			$country  = $this->shipping_country;
634
			$state    = $this->shipping_state;
635
			$postcode = $this->shipping_postcode;
636
			$city     = $this->shipping_city;
637
		}
638
639
		// Default to base
640 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...
641
			$default  = wc_get_base_location();
642
			$country  = $default['country'];
643
			$state    = $default['state'];
644
			$postcode = '';
645
			$city     = '';
646
		}
647
648
		// Get items
649
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
650
651
			$product           = $this->get_product_from_item( $item );
652
			$line_total        = isset( $item['line_total'] ) ? $item['line_total'] : 0;
653
			$line_subtotal     = isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0;
654
			$tax_class         = $item['tax_class'];
655
			$item_tax_status   = $product ? $product->get_tax_status() : 'taxable';
656
657
			if ( '0' !== $tax_class && 'taxable' === $item_tax_status ) {
658
659
				$tax_rates = WC_Tax::find_rates( array(
660
					'country'   => $country,
661
					'state'     => $state,
662
					'postcode'  => $postcode,
663
					'city'      => $city,
664
					'tax_class' => $tax_class
665
				) );
666
667
				$line_subtotal_taxes = WC_Tax::calc_tax( $line_subtotal, $tax_rates, false );
668
				$line_taxes          = WC_Tax::calc_tax( $line_total, $tax_rates, false );
669
				$line_subtotal_tax   = max( 0, array_sum( $line_subtotal_taxes ) );
670
				$line_tax            = max( 0, array_sum( $line_taxes ) );
671
				$tax_total           += $line_tax;
672
673
				wc_update_order_item_meta( $item_id, '_line_subtotal_tax', wc_format_decimal( $line_subtotal_tax ) );
674
				wc_update_order_item_meta( $item_id, '_line_tax', wc_format_decimal( $line_tax ) );
675
				wc_update_order_item_meta( $item_id, '_line_tax_data', array( 'total' => $line_taxes, 'subtotal' => $line_subtotal_taxes ) );
676
677
				// Sum the item taxes
678
				foreach ( array_keys( $taxes + $line_taxes ) as $key ) {
679
					$taxes[ $key ] = ( isset( $line_taxes[ $key ] ) ? $line_taxes[ $key ] : 0 ) + ( isset( $taxes[ $key ] ) ? $taxes[ $key ] : 0 );
680
				}
681
			}
682
		}
683
684
		// Now calculate shipping tax
685
		$shipping_methods = $this->get_shipping_methods();
686
687
		if ( ! empty( $shipping_methods ) ) {
688
			$matched_tax_rates = array();
689
			$tax_rates         = WC_Tax::find_rates( array(
690
				'country'   => $country,
691
				'state'     => $state,
692
				'postcode'  => $postcode,
693
				'city'      => $city,
694
				'tax_class' => ''
695
			) );
696
697 View Code Duplication
			if ( ! empty( $tax_rates ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
698
				foreach ( $tax_rates as $key => $rate ) {
699
					if ( isset( $rate['shipping'] ) && 'yes' === $rate['shipping'] ) {
700
						$matched_tax_rates[ $key ] = $rate;
701
					}
702
				}
703
			}
704
705
			$shipping_taxes     = WC_Tax::calc_shipping_tax( $this->order_shipping, $matched_tax_rates );
706
			$shipping_tax_total = WC_Tax::round( array_sum( $shipping_taxes ) );
707
		} else {
708
			$shipping_taxes     = array();
709
			$shipping_tax_total = 0;
710
		}
711
712
		// Save tax totals
713
		$this->set_total( $shipping_tax_total, 'shipping_tax' );
714
		$this->set_total( $tax_total, 'tax' );
715
716
		// Tax rows
717
		$this->remove_order_items( 'tax' );
718
719
		// Now merge to keep tax rows
720 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...
721
			$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 );
722
		}
723
724
		return true;
725
	}
726
727
728
	/**
729
	 * Calculate shipping total.
730
	 *
731
	 * @since 2.2
732
	 * @return float
733
	 */
734
	public function calculate_shipping() {
735
736
		$shipping_total = 0;
737
738
		foreach ( $this->get_shipping_methods() as $shipping ) {
739
			$shipping_total += $shipping['cost'];
740
		}
741
742
		$this->set_total( $shipping_total, 'shipping' );
743
744
		return $this->get_total_shipping();
745
	}
746
747
	/**
748
	 * Update tax lines at order level by looking at the line item taxes themselves.
749
	 *
750
	 * @return bool success or fail.
751
	 */
752
	public function update_taxes() {
753
		$order_taxes          = array();
754
		$order_shipping_taxes = array();
755
756
		foreach ( $this->get_items( array( 'line_item', 'fee' ) ) as $item_id => $item ) {
757
758
			$line_tax_data = maybe_unserialize( $item['line_tax_data'] );
759
760 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...
761
762
				foreach ( $line_tax_data['total'] as $tax_rate_id => $tax ) {
763
764
					if ( ! isset( $order_taxes[ $tax_rate_id ] ) ) {
765
						$order_taxes[ $tax_rate_id ] = 0;
766
					}
767
768
					$order_taxes[ $tax_rate_id ] += $tax;
769
				}
770
			}
771
		}
772
773
		foreach ( $this->get_items( array( 'shipping' ) ) as $item_id => $item ) {
774
775
			$line_tax_data = maybe_unserialize( $item['taxes'] );
776
777 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...
778
				foreach ( $line_tax_data as $tax_rate_id => $tax ) {
779
					if ( ! isset( $order_shipping_taxes[ $tax_rate_id ] ) ) {
780
						$order_shipping_taxes[ $tax_rate_id ] = 0;
781
					}
782
783
					$order_shipping_taxes[ $tax_rate_id ] += $tax;
784
				}
785
			}
786
		}
787
788
		// Remove old existing tax rows.
789
		$this->remove_order_items( 'tax' );
790
791
		// Now merge to keep tax rows.
792 View Code Duplication
		foreach ( array_keys( $order_taxes + $order_shipping_taxes ) as $tax_rate_id ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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

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

Loading history...
793
			$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 );
794
		}
795
796
		// Save tax totals
797
		$this->set_total( WC_Tax::round( array_sum( $order_shipping_taxes ) ), 'shipping_tax' );
798
		$this->set_total( WC_Tax::round( array_sum( $order_taxes ) ), 'tax' );
799
800
		return true;
801
	}
802
803
	/**
804
	 * Calculate totals by looking at the contents of the order. Stores the totals and returns the orders final total.
805
	 *
806
	 * @since 2.2
807
	 * @param  bool $and_taxes Calc taxes if true.
808
	 * @return float calculated grand total.
809
	 */
810
	public function calculate_totals( $and_taxes = true ) {
811
		$cart_subtotal     = 0;
812
		$cart_total        = 0;
813
		$fee_total         = 0;
814
		$cart_subtotal_tax = 0;
815
		$cart_total_tax    = 0;
816
817
		if ( $and_taxes && wc_tax_enabled() ) {
818
			$this->calculate_taxes();
819
		}
820
821
		// line items
822
		foreach ( $this->get_items() as $item ) {
823
			$cart_subtotal     += wc_format_decimal( isset( $item['line_subtotal'] ) ? $item['line_subtotal'] : 0 );
824
			$cart_total        += wc_format_decimal( isset( $item['line_total'] ) ? $item['line_total'] : 0 );
825
			$cart_subtotal_tax += wc_format_decimal( isset( $item['line_subtotal_tax'] ) ? $item['line_subtotal_tax'] : 0 );
826
			$cart_total_tax    += wc_format_decimal( isset( $item['line_tax'] ) ? $item['line_tax'] : 0 );
827
		}
828
829
		$this->calculate_shipping();
830
831
		foreach ( $this->get_fees() as $item ) {
832
			$fee_total += $item['line_total'];
833
		}
834
835
		$this->set_total( $cart_subtotal - $cart_total, 'cart_discount' );
836
		$this->set_total( $cart_subtotal_tax - $cart_total_tax, 'cart_discount_tax' );
837
838
		$grand_total = round( $cart_total + $fee_total + $this->get_total_shipping() + $this->get_cart_tax() + $this->get_shipping_tax(), wc_get_price_decimals() );
839
840
		$this->set_total( $grand_total, 'total' );
841
842
		return $grand_total;
843
	}
844
845
	/**
846
	 * Gets an order from the database.
847
	 *
848
	 * @param int $id (default: 0).
849
	 * @return bool
850
	 */
851 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...
852
853
		if ( ! $id ) {
854
			return false;
855
		}
856
857
		if ( $result = get_post( $id ) ) {
858
			$this->populate( $result );
859
			return true;
860
		}
861
862
		return false;
863
	}
864
865
	/**
866
	 * Populates an order from the loaded post data.
867
	 *
868
	 * @param mixed $result
869
	 */
870
	public function populate( $result ) {
871
872
		// Standard post data
873
		$this->id                  = $result->ID;
874
		$this->order_date          = $result->post_date;
875
		$this->modified_date       = $result->post_modified;
876
		$this->customer_message    = $result->post_excerpt;
877
		$this->customer_note       = $result->post_excerpt;
878
		$this->post_status         = $result->post_status;
879
880
		// Billing email can default to user if set.
881
		if ( empty( $this->billing_email ) && ! empty( $this->customer_user ) && ( $user = get_user_by( 'id', $this->customer_user ) ) ) {
882
			$this->billing_email = $user->user_email;
883
		}
884
885
		// Orders store the state of prices including tax when created.
886
		$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;
887
	}
888
889
	/**
890
	 * __isset function.
891
	 *
892
	 * @param mixed $key
893
	 * @return bool
894
	 */
895
	public function __isset( $key ) {
896
897
		if ( ! $this->id ) {
898
			return false;
899
		}
900
901
		return metadata_exists( 'post', $this->id, '_' . $key );
902
	}
903
904
	/**
905
	 * __get function.
906
	 *
907
	 * @param mixed $key
908
	 * @return mixed
909
	 */
910
	public function __get( $key ) {
911
		// Get values or default if not set.
912
		if ( 'completed_date' === $key ) {
913
			$value = ( $value = get_post_meta( $this->id, '_completed_date', true ) ) ? $value : $this->modified_date;
914
		} elseif ( 'user_id' === $key ) {
915
			$value = ( $value = get_post_meta( $this->id, '_customer_user', true ) ) ? absint( $value ) : '';
916
		} elseif ( 'status' === $key ) {
917
			$value = $this->get_status();
918
		} else {
919
			$value = get_post_meta( $this->id, '_' . $key, true );
920
		}
921
922
		return $value;
923
	}
924
925
	/**
926
	 * Return the order statuses without wc- internal prefix.
927
	 *
928
	 * Queries get_post_status() directly to avoid having out of date statuses, if updated elsewhere.
929
	 *
930
	 * @return string
931
	 */
932
	public function get_status() {
933
		$this->post_status = get_post_status( $this->id );
934
		return apply_filters( 'woocommerce_order_get_status', 'wc-' === substr( $this->post_status, 0, 3 ) ? substr( $this->post_status, 3 ) : $this->post_status, $this );
935
	}
936
937
	/**
938
	 * Checks the order status against a passed in status.
939
	 *
940
	 * @return bool
941
	 */
942
	public function has_status( $status ) {
943
		return apply_filters( 'woocommerce_order_has_status', ( is_array( $status ) && in_array( $this->get_status(), $status ) ) || $this->get_status() === $status ? true : false, $this, $status );
944
	}
945
946
	/**
947
	 * Gets the user ID associated with the order. Guests are 0.
948
	 *
949
	 * @since  2.2
950
	 * @return int
951
	 */
952
	public function get_user_id() {
953
		return $this->customer_user ? intval( $this->customer_user ) : 0;
954
	}
955
956
	/**
957
	 * Get the user associated with the order. False for guests.
958
	 *
959
	 * @since  2.2
960
	 * @return WP_User|false
961
	 */
962
	public function get_user() {
963
		return $this->get_user_id() ? get_user_by( 'id', $this->get_user_id() ) : false;
964
	}
965
966
	/**
967
	 * Get transaction id for the order.
968
	 *
969
	 * @return string
970
	 */
971
	public function get_transaction_id() {
972
		return get_post_meta( $this->id, '_transaction_id', true );
973
	}
974
975
	/**
976
	 * Check if an order key is valid.
977
	 *
978
	 * @param mixed $key
979
	 * @return bool
980
	 */
981
	public function key_is_valid( $key ) {
982
983
		if ( $key == $this->order_key ) {
984
			return true;
985
		}
986
987
		return false;
988
	}
989
990
	/**
991
	 * get_order_number function.
992
	 *
993
	 * Gets the order number for display (by default, order ID).
994
	 *
995
	 * @return string
996
	 */
997
	public function get_order_number() {
998
		return apply_filters( 'woocommerce_order_number', $this->id, $this );
999
	}
1000
1001
	/**
1002
	 * Get a formatted billing address for the order.
1003
	 *
1004
	 * @return string
1005
	 */
1006
	public function get_formatted_billing_address() {
1007
		if ( ! $this->formatted_billing_address ) {
1008
1009
			// Formatted Addresses.
1010
			$address = apply_filters( 'woocommerce_order_formatted_billing_address', array(
1011
				'first_name'    => $this->billing_first_name,
1012
				'last_name'     => $this->billing_last_name,
1013
				'company'       => $this->billing_company,
1014
				'address_1'     => $this->billing_address_1,
1015
				'address_2'     => $this->billing_address_2,
1016
				'city'          => $this->billing_city,
1017
				'state'         => $this->billing_state,
1018
				'postcode'      => $this->billing_postcode,
1019
				'country'       => $this->billing_country
1020
			), $this );
1021
1022
			$this->formatted_billing_address = WC()->countries->get_formatted_address( $address );
1023
		}
1024
1025
		return $this->formatted_billing_address;
1026
	}
1027
1028
	/**
1029
	 * Get a formatted shipping address for the order.
1030
	 *
1031
	 * @return string
1032
	 */
1033
	public function get_formatted_shipping_address() {
1034
		if ( ! $this->formatted_shipping_address ) {
1035
1036
			if ( $this->shipping_address_1 || $this->shipping_address_2 ) {
1037
1038
				// Formatted Addresses
1039
				$address = apply_filters( 'woocommerce_order_formatted_shipping_address', array(
1040
					'first_name'    => $this->shipping_first_name,
1041
					'last_name'     => $this->shipping_last_name,
1042
					'company'       => $this->shipping_company,
1043
					'address_1'     => $this->shipping_address_1,
1044
					'address_2'     => $this->shipping_address_2,
1045
					'city'          => $this->shipping_city,
1046
					'state'         => $this->shipping_state,
1047
					'postcode'      => $this->shipping_postcode,
1048
					'country'       => $this->shipping_country
1049
				), $this );
1050
1051
				$this->formatted_shipping_address = WC()->countries->get_formatted_address( $address );
1052
			}
1053
		}
1054
1055
		return $this->formatted_shipping_address;
1056
	}
1057
1058
	/**
1059
	 * Get a formatted shipping address for the order.
1060
	 *
1061
	 * @return string
1062
	 */
1063
	public function get_shipping_address_map_url() {
1064
		$address = apply_filters( 'woocommerce_shipping_address_map_url_parts', array(
1065
			'address_1'     => $this->shipping_address_1,
1066
			'address_2'     => $this->shipping_address_2,
1067
			'city'          => $this->shipping_city,
1068
			'state'         => $this->shipping_state,
1069
			'postcode'      => $this->shipping_postcode,
1070
			'country'       => $this->shipping_country
1071
		), $this );
1072
1073
		return apply_filters( 'woocommerce_shipping_address_map_url', 'http://maps.google.com/maps?&q=' . urlencode( implode( ', ', $address ) ) . '&z=16', $this );
1074
	}
1075
1076
	/**
1077
	 * Get the billing address in an array.
1078
	 * @deprecated 2.3
1079
	 * @return string
1080
	 */
1081
	public function get_billing_address() {
1082
		_deprecated_function( 'get_billing_address', '2.3', 'get_formatted_billing_address' );
1083
		return $this->get_formatted_billing_address();
1084
	}
1085
1086
	/**
1087
	 * Get the shipping address in an array.
1088
	 * @deprecated 2.3
1089
	 * @return string
1090
	 */
1091
	public function get_shipping_address() {
1092
		_deprecated_function( 'get_shipping_address', '2.3', 'get_formatted_shipping_address' );
1093
		return $this->get_formatted_shipping_address();
1094
	}
1095
1096
	/**
1097
	 * Get a formatted billing full name.
1098
	 *
1099
	 * @since 2.4.0
1100
	 *
1101
	 * @return string
1102
	 */
1103
	public function get_formatted_billing_full_name() {
1104
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ),  $this->billing_first_name, $this->billing_last_name );
1105
	}
1106
1107
	/**
1108
	 * Get a formatted shipping full name.
1109
	 *
1110
	 * @since 2.4.0
1111
	 *
1112
	 * @return string
1113
	 */
1114
	public function get_formatted_shipping_full_name() {
1115
		return sprintf( _x( '%1$s %2$s', 'full name', 'woocommerce' ),  $this->shipping_first_name, $this->shipping_last_name );
1116
	}
1117
1118
	/**
1119
	 * Return an array of items/products within this order.
1120
	 *
1121
	 * @param string|array $type Types of line items to get (array or string).
1122
	 * @return array
1123
	 */
1124
	public function get_items( $type = '' ) {
1125
		global $wpdb;
1126
1127
		if ( empty( $type ) ) {
1128
			$type = array( 'line_item' );
1129
		}
1130
1131
		if ( ! is_array( $type ) ) {
1132
			$type = array( $type );
1133
		}
1134
1135
		$items          = array();
1136
		$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 );
1137
		$get_items_sql .= "AND order_item_type IN ( '" . implode( "','", array_map( 'esc_sql', $type ) ) . "' ) ORDER BY order_item_id;";
1138
		$line_items     = $wpdb->get_results( $get_items_sql );
1139
1140
		// Loop items
1141
		foreach ( $line_items as $item ) {
1142
			$items[ $item->order_item_id ]['name']            = $item->order_item_name;
1143
			$items[ $item->order_item_id ]['type']            = $item->order_item_type;
1144
			$items[ $item->order_item_id ]['item_meta']       = $this->get_item_meta( $item->order_item_id );
1145
			$items[ $item->order_item_id ]['item_meta_array'] = $this->get_item_meta_array( $item->order_item_id );
1146
			$items[ $item->order_item_id ]                    = $this->expand_item_meta( $items[ $item->order_item_id ] );
1147
		}
1148
1149
		return apply_filters( 'woocommerce_order_get_items', $items, $this );
1150
	}
1151
1152
	/**
1153
	 * Expand item meta into the $item array.
1154
	 * @since 2.4.0
1155
	 * @param array $item before expansion.
1156
	 * @return array
1157
	 */
1158
	public function expand_item_meta( $item ) {
1159
		// Reserved meta keys
1160
		$reserved_item_meta_keys = array(
1161
			'name',
1162
			'type',
1163
			'item_meta',
1164
			'item_meta_array',
1165
			'qty',
1166
			'tax_class',
1167
			'product_id',
1168
			'variation_id',
1169
			'line_subtotal',
1170
			'line_total',
1171
			'line_tax',
1172
			'line_subtotal_tax'
1173
		);
1174
1175
		// Expand item meta if set.
1176
		if ( ! empty( $item['item_meta'] ) ) {
1177
			foreach ( $item['item_meta'] as $name => $value ) {
1178
				if ( in_array( $name, $reserved_item_meta_keys ) ) {
1179
					continue;
1180
				}
1181
				if ( '_' === substr( $name, 0, 1 ) ) {
1182
					$item[ substr( $name, 1 ) ] = $value[0];
1183
				} elseif ( ! in_array( $name, $reserved_item_meta_keys ) ) {
1184
					$item[ $name ] = make_clickable( $value[0] );
1185
				}
1186
			}
1187
		}
1188
		return $item;
1189
	}
1190
1191
	/**
1192
	 * Gets the count of order items of a certain type.
1193
	 *
1194
	 * @param string $item_type
1195
	 * @return string
1196
	 */
1197 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...
1198
		if ( empty( $item_type ) ) {
1199
			$item_type = array( 'line_item' );
1200
		}
1201
		if ( ! is_array( $item_type ) ) {
1202
			$item_type = array( $item_type );
1203
		}
1204
1205
		$items = $this->get_items( $item_type );
1206
		$count = 0;
1207
1208
		foreach ( $items as $item ) {
1209
			$count += empty( $item['qty'] ) ? 1 : $item['qty'];
1210
		}
1211
1212
		return apply_filters( 'woocommerce_get_item_count', $count, $item_type, $this );
1213
	}
1214
1215
	/**
1216
	 * Get refunds
1217
	 * @return array
1218
	 */
1219
	public function get_refunds() { return array(); }
1220
1221
	/**
1222
	 * Return an array of fees within this order.
1223
	 *
1224
	 * @return array
1225
	 */
1226
	public function get_fees() {
1227
		return $this->get_items( 'fee' );
1228
	}
1229
1230
	/**
1231
	 * Return an array of taxes within this order.
1232
	 *
1233
	 * @return array
1234
	 */
1235
	public function get_taxes() {
1236
		return $this->get_items( 'tax' );
1237
	}
1238
1239
	/**
1240
	 * Return an array of shipping costs within this order.
1241
	 *
1242
	 * @return array
1243
	 */
1244
	public function get_shipping_methods() {
1245
		return $this->get_items( 'shipping' );
1246
	}
1247
1248
	/**
1249
	 * Check whether this order has a specific shipping method or not.
1250
	 *
1251
	 * @param string $method_id
1252
	 *
1253
	 * @return bool
1254
	 */
1255
	public function has_shipping_method( $method_id ) {
1256
1257
		$shipping_methods = $this->get_shipping_methods();
1258
		$has_method = false;
1259
1260
		if ( empty( $shipping_methods ) ) {
1261
			return false;
1262
		}
1263
1264
		foreach ( $shipping_methods as $shipping_method ) {
1265
			if ( $shipping_method['method_id'] == $method_id ) {
1266
				$has_method = true;
1267
			}
1268
		}
1269
1270
		return $has_method;
1271
	}
1272
1273
	/**
1274
	 * Get taxes, merged by code, formatted ready for output.
1275
	 *
1276
	 * @return array
1277
	 */
1278
	public function get_tax_totals() {
1279
1280
		$taxes      = $this->get_items( 'tax' );
1281
		$tax_totals = array();
1282
1283
		foreach ( $taxes as $key => $tax ) {
1284
1285
			$code = $tax[ 'name' ];
1286
1287 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...
1288
				$tax_totals[ $code ] = new stdClass();
1289
				$tax_totals[ $code ]->amount = 0;
1290
			}
1291
1292
			$tax_totals[ $code ]->id                = $key;
1293
			$tax_totals[ $code ]->rate_id           = $tax['rate_id'];
1294
			$tax_totals[ $code ]->is_compound       = $tax[ 'compound' ];
1295
			$tax_totals[ $code ]->label             = isset( $tax[ 'label' ] ) ? $tax[ 'label' ] : $tax[ 'name' ];
1296
			$tax_totals[ $code ]->amount           += $tax[ 'tax_amount' ] + $tax[ 'shipping_tax_amount' ];
1297
			$tax_totals[ $code ]->formatted_amount  = wc_price( wc_round_tax_total( $tax_totals[ $code ]->amount ), array('currency' => $this->get_order_currency()) );
1298
		}
1299
1300
		return apply_filters( 'woocommerce_order_tax_totals', $tax_totals, $this );
1301
	}
1302
1303
	/**
1304
	 * has_meta function for order items.
1305
	 *
1306
	 * @param string $order_item_id
1307
	 * @return array of meta data.
1308
	 */
1309
	public function has_meta( $order_item_id ) {
1310
		global $wpdb;
1311
1312
		return $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value, meta_id, order_item_id
1313
			FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d
1314
			ORDER BY meta_id", absint( $order_item_id ) ), ARRAY_A );
1315
	}
1316
1317
	/**
1318
	 * Get all item meta data in array format in the order it was saved. Does not group meta by key like get_item_meta().
1319
	 *
1320
	 * @param mixed $order_item_id
1321
	 * @return array of objects
1322
	 */
1323
	public function get_item_meta_array( $order_item_id ) {
1324
		global $wpdb;
1325
1326
		// Get cache key - uses get_cache_prefix to invalidate when needed
1327
		$cache_key       = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $order_item_id;
1328
		$item_meta_array = wp_cache_get( $cache_key, 'orders' );
1329
1330
		if ( false === $item_meta_array ) {
1331
			$item_meta_array = array();
1332
			$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 ) ) );
1333
			foreach ( $metadata as $metadata_row ) {
1334
				$item_meta_array[ $metadata_row->meta_id ] = (object) array( 'key' => $metadata_row->meta_key, 'value' => $metadata_row->meta_value );
1335
			}
1336
			wp_cache_set( $cache_key, $item_meta_array, 'orders' );
1337
		}
1338
1339
		return $item_meta_array ;
1340
	}
1341
1342
	/**
1343
	 * Display meta data belonging to an item.
1344
	 * @param  array $item
1345
	 */
1346
	public function display_item_meta( $item ) {
1347
		$product   = $this->get_product_from_item( $item );
1348
		$item_meta = new WC_Order_Item_Meta( $item, $product );
1349
		$item_meta->display();
1350
	}
1351
1352
	/**
1353
	 * Get order item meta.
1354
	 *
1355
	 * @param mixed $order_item_id
1356
	 * @param string $key (default: '')
1357
	 * @param bool $single (default: false)
1358
	 * @return array|string
1359
	 */
1360
	public function get_item_meta( $order_item_id, $key = '', $single = false ) {
1361
		return get_metadata( 'order_item', $order_item_id, $key, $single );
1362
	}
1363
1364
	/** Total Getters *******************************************************/
1365
1366
	/**
1367
	 * Gets the total discount amount.
1368
	 * @param  bool $ex_tax Show discount excl any tax.
1369
	 * @return float
1370
	 */
1371
	public function get_total_discount( $ex_tax = true ) {
1372
		if ( ! $this->order_version || version_compare( $this->order_version, '2.3.7', '<' ) ) {
1373
			// Backwards compatible total calculation - totals were not stored consistently in old versions.
1374
			if ( $ex_tax ) {
1375 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...
1376
					$total_discount = (double) $this->cart_discount - (double) $this->cart_discount_tax;
1377
				} else {
1378
					$total_discount = (double) $this->cart_discount;
1379
				}
1380 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...
1381
				if ( $this->prices_include_tax ) {
1382
					$total_discount = (double) $this->cart_discount;
1383
				} else {
1384
					$total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax;
1385
				}
1386
			}
1387
		// New logic - totals are always stored exclusive of tax, tax total is stored in cart_discount_tax
1388
		} else {
1389
			if ( $ex_tax ) {
1390
				$total_discount = (double) $this->cart_discount;
1391
			} else {
1392
				$total_discount = (double) $this->cart_discount + (double) $this->cart_discount_tax;
1393
			}
1394
		}
1395
		return apply_filters( 'woocommerce_order_amount_total_discount', round( $total_discount, WC_ROUNDING_PRECISION ), $this );
1396
	}
1397
1398
	/**
1399
	 * Gets the discount amount.
1400
	 * @deprecated in favour of get_total_discount() since we now only have one discount type.
1401
	 * @return float
1402
	 */
1403
	public function get_cart_discount() {
1404
		_deprecated_function( 'get_cart_discount', '2.3', 'get_total_discount' );
1405
		return apply_filters( 'woocommerce_order_amount_cart_discount', $this->get_total_discount(), $this );
1406
	}
1407
1408
	/**
1409
	 * Get cart discount (formatted).
1410
	 *
1411
	 * @deprecated order (after tax) discounts removed in 2.3.0.
1412
	 * @return string
1413
	 */
1414
	public function get_order_discount_to_display() {
1415
		_deprecated_function( 'get_order_discount_to_display', '2.3' );
1416
	}
1417
1418
	/**
1419
	 * Gets the total (order) discount amount - these are applied after tax.
1420
	 *
1421
	 * @deprecated order (after tax) discounts removed in 2.3.0.
1422
	 * @return float
1423
	 */
1424
	public function get_order_discount() {
1425
		_deprecated_function( 'get_order_discount', '2.3' );
1426
		return apply_filters( 'woocommerce_order_amount_order_discount', (double) $this->order_discount, $this );
1427
	}
1428
1429
	/**
1430
	 * Gets cart tax amount.
1431
	 *
1432
	 * @return float
1433
	 */
1434
	public function get_cart_tax() {
1435
		return apply_filters( 'woocommerce_order_amount_cart_tax', (double) $this->order_tax, $this );
1436
	}
1437
1438
	/**
1439
	 * Gets shipping tax amount.
1440
	 *
1441
	 * @return float
1442
	 */
1443
	public function get_shipping_tax() {
1444
		return apply_filters( 'woocommerce_order_amount_shipping_tax', (double) $this->order_shipping_tax, $this );
1445
	}
1446
1447
	/**
1448
	 * Gets shipping and product tax.
1449
	 *
1450
	 * @return float
1451
	 */
1452
	public function get_total_tax() {
1453
		return apply_filters( 'woocommerce_order_amount_total_tax', wc_round_tax_total( $this->get_cart_tax() + $this->get_shipping_tax() ), $this );
1454
	}
1455
1456
	/**
1457
	 * Gets shipping total.
1458
	 *
1459
	 * @return float
1460
	 */
1461
	public function get_total_shipping() {
1462
		return apply_filters( 'woocommerce_order_amount_total_shipping', (double) $this->order_shipping, $this );
1463
	}
1464
1465
	/**
1466
	 * Gets order total.
1467
	 *
1468
	 * @return float
1469
	 */
1470
	public function get_total() {
1471
		return apply_filters( 'woocommerce_order_amount_total', (double) $this->order_total, $this );
1472
	}
1473
1474
	/**
1475
	 * Gets order subtotal.
1476
	 *
1477
	 * @return mixed|void
1478
	 */
1479
	public function get_subtotal() {
1480
		$subtotal = 0;
1481
1482
		foreach ( $this->get_items() as $item ) {
1483
			$subtotal += ( isset( $item['line_subtotal'] ) ) ? $item['line_subtotal'] : 0;
1484
		}
1485
1486
		return apply_filters( 'woocommerce_order_amount_subtotal', (double) $subtotal, $this );
1487
	}
1488
1489
	/**
1490
	 * Get item subtotal - this is the cost before discount.
1491
	 *
1492
	 * @param mixed $item
1493
	 * @param bool $inc_tax (default: false).
1494
	 * @param bool $round (default: true).
1495
	 * @return float
1496
	 */
1497
	public function get_item_subtotal( $item, $inc_tax = false, $round = true ) {
1498
		if ( $inc_tax ) {
1499
			$price = ( $item['line_subtotal'] + $item['line_subtotal_tax'] ) / max( 1, $item['qty'] );
1500
		} else {
1501
			$price = ( $item['line_subtotal'] / max( 1, $item['qty'] ) );
1502
		}
1503
1504
		$price = $round ? number_format( (float) $price, wc_get_price_decimals(), '.', '' ) : $price;
1505
1506
		return apply_filters( 'woocommerce_order_amount_item_subtotal', $price, $this, $item, $inc_tax, $round );
1507
	}
1508
1509
	/**
1510
	 * Get line subtotal - this is the cost before discount.
1511
	 *
1512
	 * @param mixed $item
1513
	 * @param bool $inc_tax (default: false).
1514
	 * @param bool $round (default: true).
1515
	 * @return float
1516
	 */
1517 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...
1518
		if ( $inc_tax ) {
1519
			$price = $item['line_subtotal'] + $item['line_subtotal_tax'];
1520
		} else {
1521
			$price = $item['line_subtotal'];
1522
		}
1523
1524
		$price = $round ? round( $price, wc_get_price_decimals() ) : $price;
1525
1526
		return apply_filters( 'woocommerce_order_amount_line_subtotal', $price, $this, $item, $inc_tax, $round );
1527
	}
1528
1529
	/**
1530
	 * Calculate item cost - useful for gateways.
1531
	 *
1532
	 * @param mixed $item
1533
	 * @param bool $inc_tax (default: false).
1534
	 * @param bool $round (default: true).
1535
	 * @return float
1536
	 */
1537
	public function get_item_total( $item, $inc_tax = false, $round = true ) {
1538
1539
		$qty = ( ! empty( $item['qty'] ) ) ? $item['qty'] : 1;
1540
1541
		if ( $inc_tax ) {
1542
			$price = ( $item['line_total'] + $item['line_tax'] ) / max( 1, $qty );
1543
		} else {
1544
			$price = $item['line_total'] / max( 1, $qty );
1545
		}
1546
1547
		$price = $round ? round( $price, wc_get_price_decimals() ) : $price;
1548
1549
		return apply_filters( 'woocommerce_order_amount_item_total', $price, $this, $item, $inc_tax, $round );
1550
	}
1551
1552
	/**
1553
	 * Calculate line total - useful for gateways.
1554
	 *
1555
	 * @param mixed $item
1556
	 * @param bool $inc_tax (default: false).
1557
	 * @param bool $round (default: true).
1558
	 * @return float
1559
	 */
1560 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...
1561
1562
		// Check if we need to add line tax to the line total.
1563
		$line_total = $inc_tax ? $item['line_total'] + $item['line_tax'] : $item['line_total'];
1564
1565
		// Check if we need to round.
1566
		$line_total = $round ? round( $line_total, wc_get_price_decimals() ) : $line_total;
1567
1568
		return apply_filters( 'woocommerce_order_amount_line_total', $line_total, $this, $item, $inc_tax, $round );
1569
	}
1570
1571
	/**
1572
	 * Calculate item tax - useful for gateways.
1573
	 *
1574
	 * @param mixed $item
1575
	 * @param bool $round (default: true).
1576
	 * @return float
1577
	 */
1578
	public function get_item_tax( $item, $round = true ) {
1579
1580
		$price = $item['line_tax'] / max( 1, $item['qty'] );
1581
		$price = $round ? wc_round_tax_total( $price ) : $price;
1582
1583
		return apply_filters( 'woocommerce_order_amount_item_tax', $price, $item, $round, $this );
1584
	}
1585
1586
	/**
1587
	 * Calculate line tax - useful for gateways.
1588
	 *
1589
	 * @param mixed $item
1590
	 * @return float
1591
	 */
1592
	public function get_line_tax( $item ) {
1593
		return apply_filters( 'woocommerce_order_amount_line_tax', wc_round_tax_total( $item['line_tax'] ), $item, $this );
1594
	}
1595
1596
	/** End Total Getters *******************************************************/
1597
1598
	/**
1599
	 * Gets formatted shipping method title.
1600
	 *
1601
	 * @return string
1602
	 */
1603
	public function get_shipping_method() {
1604
1605
		$labels = array();
1606
1607
		// Backwards compat < 2.1 - get shipping title stored in meta.
1608
		if ( $this->shipping_method_title ) {
1609
			$labels[] = $this->shipping_method_title;
1610
		} else {
1611
1612
			// 2.1+ get line items for shipping.
1613
			$shipping_methods = $this->get_shipping_methods();
1614
1615
			foreach ( $shipping_methods as $shipping ) {
1616
				$labels[] = $shipping['name'];
1617
			}
1618
		}
1619
1620
		return apply_filters( 'woocommerce_order_shipping_method', implode( ', ', $labels ), $this );
1621
	}
1622
1623
	/**
1624
	 * Gets line subtotal - formatted for display.
1625
	 *
1626
	 * @param array  $item
1627
	 * @param string $tax_display
1628
	 * @return string
1629
	 */
1630
	public function get_formatted_line_subtotal( $item, $tax_display = '' ) {
1631
1632
		if ( ! $tax_display ) {
1633
			$tax_display = $this->tax_display_cart;
1634
		}
1635
1636
		if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1637
			return '';
1638
		}
1639
1640
		if ( 'excl' == $tax_display ) {
1641
			$ex_tax_label = $this->prices_include_tax ? 1 : 0;
1642
1643
			$subtotal = wc_price( $this->get_line_subtotal( $item ), array( 'ex_tax_label' => $ex_tax_label, 'currency' => $this->get_order_currency() ) );
1644
		} else {
1645
			$subtotal = wc_price( $this->get_line_subtotal( $item, true ), array('currency' => $this->get_order_currency()) );
1646
		}
1647
1648
		return apply_filters( 'woocommerce_order_formatted_line_subtotal', $subtotal, $item, $this );
1649
	}
1650
1651
	/**
1652
	 * Gets order currency.
1653
	 *
1654
	 * @return string
1655
	 */
1656
	public function get_order_currency() {
1657
		return apply_filters( 'woocommerce_get_order_currency', $this->order_currency, $this );
1658
	}
1659
1660
	/**
1661
	 * Gets order total - formatted for display.
1662
	 *
1663
	 * @return string
1664
	 */
1665
	public function get_formatted_order_total() {
1666
		$formatted_total = wc_price( $this->get_total(), array( 'currency' => $this->get_order_currency() ) );
1667
		return apply_filters( 'woocommerce_get_formatted_order_total', $formatted_total, $this );
1668
	}
1669
1670
	/**
1671
	 * Gets subtotal - subtotal is shown before discounts, but with localised taxes.
1672
	 *
1673
	 * @param bool $compound (default: false).
1674
	 * @param string $tax_display (default: the tax_display_cart value).
1675
	 * @return string
1676
	 */
1677
	public function get_subtotal_to_display( $compound = false, $tax_display = '' ) {
1678
1679
		if ( ! $tax_display ) {
1680
			$tax_display = $this->tax_display_cart;
1681
		}
1682
1683
		$subtotal = 0;
1684
1685
		if ( ! $compound ) {
1686
			foreach ( $this->get_items() as $item ) {
1687
1688
				if ( ! isset( $item['line_subtotal'] ) || ! isset( $item['line_subtotal_tax'] ) ) {
1689
					return '';
1690
				}
1691
1692
				$subtotal += $item['line_subtotal'];
1693
1694
				if ( 'incl' == $tax_display ) {
1695
					$subtotal += $item['line_subtotal_tax'];
1696
				}
1697
			}
1698
1699
			$subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) );
1700
1701
			if ( $tax_display == 'excl' && $this->prices_include_tax ) {
1702
				$subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
1703
			}
1704
1705
		} else {
1706
1707
			if ( 'incl' == $tax_display ) {
1708
				return '';
1709
			}
1710
1711
			foreach ( $this->get_items() as $item ) {
1712
1713
				$subtotal += $item['line_subtotal'];
1714
1715
			}
1716
1717
			// Add Shipping Costs.
1718
			$subtotal += $this->get_total_shipping();
1719
1720
			// Remove non-compound taxes.
1721
			foreach ( $this->get_taxes() as $tax ) {
1722
1723
				if ( ! empty( $tax['compound'] ) ) {
1724
					continue;
1725
				}
1726
1727
				$subtotal = $subtotal + $tax['tax_amount'] + $tax['shipping_tax_amount'];
1728
1729
			}
1730
1731
			// Remove discounts.
1732
			$subtotal = $subtotal - $this->get_total_discount();
1733
1734
			$subtotal = wc_price( $subtotal, array('currency' => $this->get_order_currency()) );
1735
		}
1736
1737
		return apply_filters( 'woocommerce_order_subtotal_to_display', $subtotal, $compound, $this );
1738
	}
1739
1740
1741
	/**
1742
	 * Gets shipping (formatted).
1743
	 *
1744
	 * @return string
1745
	 */
1746
	public function get_shipping_to_display( $tax_display = '' ) {
1747
		if ( ! $tax_display ) {
1748
			$tax_display = $this->tax_display_cart;
1749
		}
1750
1751
		if ( $this->order_shipping != 0 ) {
1752
1753
			if ( $tax_display == 'excl' ) {
1754
1755
				// Show shipping excluding tax.
1756
				$shipping = wc_price( $this->order_shipping, array('currency' => $this->get_order_currency()) );
1757
1758 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...
1759
					$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 );
1760
				}
1761
1762
			} else {
1763
1764
				// Show shipping including tax.
1765
				$shipping = wc_price( $this->order_shipping + $this->order_shipping_tax, array('currency' => $this->get_order_currency()) );
1766
1767 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...
1768
					$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 );
1769
				}
1770
1771
			}
1772
1773
			$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 );
1774
1775
		} elseif ( $this->get_shipping_method() ) {
1776
			$shipping = $this->get_shipping_method();
1777
		} else {
1778
			$shipping = __( 'Free!', 'woocommerce' );
1779
		}
1780
1781
		return apply_filters( 'woocommerce_order_shipping_to_display', $shipping, $this );
1782
	}
1783
1784
	/**
1785
	 * Get the discount amount (formatted).
1786
	 * @since  2.3.0
1787
	 * @return string
1788
	 */
1789
	public function get_discount_to_display( $tax_display = '' ) {
1790
		if ( ! $tax_display ) {
1791
			$tax_display = $this->tax_display_cart;
1792
		}
1793
		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 );
1794
	}
1795
1796
	/**
1797
	 * Get cart discount (formatted).
1798
	 * @deprecated
1799
	 * @return string
1800
	 */
1801
	public function get_cart_discount_to_display( $tax_display = '' ) {
1802
		_deprecated_function( 'get_cart_discount_to_display', '2.3', 'get_discount_to_display' );
1803
		return apply_filters( 'woocommerce_order_cart_discount_to_display', $this->get_discount_to_display( $tax_display ), $this );
1804
	}
1805
1806
	/**
1807
	 * Get a product (either product or variation).
1808
	 *
1809
	 * @param mixed $item
1810
	 * @return WC_Product
1811
	 */
1812
	public function get_product_from_item( $item ) {
1813
1814
		if ( ! empty( $item['variation_id'] ) && 'product_variation' === get_post_type( $item['variation_id'] ) ) {
1815
			$_product = wc_get_product( $item['variation_id'] );
1816
		} elseif ( ! empty( $item['product_id']  ) ) {
1817
			$_product = wc_get_product( $item['product_id'] );
1818
		} else {
1819
			$_product = false;
1820
		}
1821
1822
		return apply_filters( 'woocommerce_get_product_from_item', $_product, $item, $this );
1823
	}
1824
1825
1826
	/**
1827
	 * Get totals for display on pages and in emails.
1828
	 *
1829
	 * @param mixed $tax_display
1830
	 * @return array
1831
	 */
1832
	public function get_order_item_totals( $tax_display = '' ) {
1833
1834
		if ( ! $tax_display ) {
1835
			$tax_display = $this->tax_display_cart;
1836
		}
1837
1838
		$total_rows = array();
1839
1840
		if ( $subtotal = $this->get_subtotal_to_display( false, $tax_display ) ) {
1841
			$total_rows['cart_subtotal'] = array(
1842
				'label' => __( 'Subtotal:', 'woocommerce' ),
1843
				'value'	=> $subtotal
1844
			);
1845
		}
1846
1847
		if ( $this->get_total_discount() > 0 ) {
1848
			$total_rows['discount'] = array(
1849
				'label' => __( 'Discount:', 'woocommerce' ),
1850
				'value'	=> '-' . $this->get_discount_to_display( $tax_display )
1851
			);
1852
		}
1853
1854
		if ( $this->get_shipping_method() ) {
1855
			$total_rows['shipping'] = array(
1856
				'label' => __( 'Shipping:', 'woocommerce' ),
1857
				'value'	=> $this->get_shipping_to_display( $tax_display )
1858
			);
1859
		}
1860
1861
		if ( $fees = $this->get_fees() ) {
1862
			foreach ( $fees as $id => $fee ) {
1863
1864
				if ( apply_filters( 'woocommerce_get_order_item_totals_excl_free_fees', $fee['line_total'] + $fee['line_tax'] == 0, $id ) ) {
1865
					continue;
1866
				}
1867
1868
				if ( 'excl' == $tax_display ) {
1869
1870
					$total_rows[ 'fee_' . $id ] = array(
1871
						'label' => ( $fee['name'] ? $fee['name'] : __( 'Fee', 'woocommerce' ) ) . ':',
1872
						'value'	=> wc_price( $fee['line_total'], array('currency' => $this->get_order_currency()) )
1873
					);
1874
1875
				} else {
1876
1877
					$total_rows[ 'fee_' . $id ] = array(
1878
						'label' => $fee['name'] . ':',
1879
						'value'	=> wc_price( $fee['line_total'] + $fee['line_tax'], array('currency' => $this->get_order_currency()) )
1880
					);
1881
				}
1882
			}
1883
		}
1884
1885
		// Tax for tax exclusive prices.
1886
		if ( 'excl' === $tax_display ) {
1887
1888
			if ( get_option( 'woocommerce_tax_total_display' ) == 'itemized' ) {
1889
1890
				foreach ( $this->get_tax_totals() as $code => $tax ) {
1891
1892
					$total_rows[ sanitize_title( $code ) ] = array(
1893
						'label' => $tax->label . ':',
1894
						'value'	=> $tax->formatted_amount
1895
					);
1896
				}
1897
1898
			} else {
1899
1900
				$total_rows['tax'] = array(
1901
					'label' => WC()->countries->tax_or_vat() . ':',
1902
					'value'	=> wc_price( $this->get_total_tax(), array( 'currency' => $this->get_order_currency() ) )
1903
				);
1904
			}
1905
		}
1906
1907
		if ( $this->get_total() > 0 && $this->payment_method_title ) {
1908
			$total_rows['payment_method'] = array(
1909
				'label' => __( 'Payment Method:', 'woocommerce' ),
1910
				'value' => $this->payment_method_title
1911
			);
1912
		}
1913
1914
		if ( $refunds = $this->get_refunds() ) {
1915
			foreach ( $refunds as $id => $refund ) {
1916
				$total_rows[ 'refund_' . $id ] = array(
1917
					'label' => $refund->get_refund_reason() ? $refund->get_refund_reason() : __( 'Refund', 'woocommerce' ) . ':',
1918
					'value'	=> wc_price( '-' . $refund->get_refund_amount(), array( 'currency' => $this->get_order_currency() ) )
1919
				);
1920
			}
1921
		}
1922
1923
		$total_rows['order_total'] = array(
1924
			'label' => __( 'Total:', 'woocommerce' ),
1925
			'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...
1926
		);
1927
1928
		return apply_filters( 'woocommerce_get_order_item_totals', $total_rows, $this );
1929
	}
1930
1931
1932
	/**
1933
	 * Output items for display in html emails.
1934
	 *
1935
	 * @param array $args Items args.
1936
	 * @param null $deprecated1 Deprecated arg.
1937
	 * @param null $deprecated2 Deprecated arg.
1938
	 * @param null $deprecated3 Deprecated arg.
1939
	 * @param null $deprecated4 Deprecated arg.
1940
	 * @param null $deprecated5 Deprecated arg.
1941
	 * @return string
1942
	 */
1943
	public function email_order_items_table( $args = array(), $deprecated1 = null, $deprecated2 = null, $deprecated3 = null, $deprecated4 = null, $deprecated5 = null ) {
1944
		ob_start();
1945
1946
		if ( ! is_null( $deprecated1 ) || ! is_null( $deprecated2 ) || ! is_null( $deprecated3 ) || ! is_null( $deprecated4 ) || ! is_null( $deprecated5 ) ) {
1947
			_deprecated_argument( __FUNCTION__, '2.5.0' );
1948
		}
1949
1950
		$defaults = array(
1951
			'show_sku'      => false,
1952
			'show_image'    => false,
1953
			'image_size'    => array( 32, 32 ),
1954
			'plain_text'    => false,
1955
			'sent_to_admin' => false
1956
		);
1957
1958
		$args     = wp_parse_args( $args, $defaults );
1959
		$template = $args['plain_text'] ? 'emails/plain/email-order-items.php' : 'emails/email-order-items.php';
1960
1961
		wc_get_template( $template, array(
1962
			'order'               => $this,
1963
			'items'               => $this->get_items(),
1964
			'show_download_links' => $this->is_download_permitted() && ! $args['sent_to_admin'],
1965
			'show_sku'            => $args['show_sku'],
1966
			'show_purchase_note'  => $this->is_paid() && ! $args['sent_to_admin'],
1967
			'show_image'          => $args['show_image'],
1968
			'image_size'          => $args['image_size'],
1969
			'sent_to_admin'       => $args['sent_to_admin']
1970
		) );
1971
1972
		return apply_filters( 'woocommerce_email_order_items_table', ob_get_clean(), $this );
1973
	}
1974
1975
	/**
1976
	 * Returns if an order has been paid for based on the order status.
1977
	 * @since 2.5.0
1978
	 * @return bool
1979
	 */
1980
	public function is_paid() {
1981
		return apply_filters( 'woocommerce_order_is_paid', $this->has_status( apply_filters( 'woocommerce_order_is_paid_statuses', array( 'processing', 'completed' ) ) ), $this );
1982
	}
1983
1984
	/**
1985
	 * Checks if product download is permitted.
1986
	 *
1987
	 * @return bool
1988
	 */
1989
	public function is_download_permitted() {
1990
		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 );
1991
	}
1992
1993
	/**
1994
	 * Returns true if the order contains a downloadable product.
1995
	 * @return bool
1996
	 */
1997
	public function has_downloadable_item() {
1998
		foreach ( $this->get_items() as $item ) {
1999
			$_product = $this->get_product_from_item( $item );
2000
2001
			if ( $_product && $_product->exists() && $_product->is_downloadable() && $_product->has_file() ) {
2002
				return true;
2003
			}
2004
		}
2005
		return false;
2006
	}
2007
2008
	/**
2009
	 * Returns true if the order contains a free product.
2010
	 * @since 2.5.0
2011
	 * @return bool
2012
	 */
2013
	public function has_free_item() {
2014
		foreach ( $this->get_items() as $item ) {
2015
			if ( ! $item['line_total'] ) {
2016
				return true;
2017
			}
2018
		}
2019
		return false;
2020
	}
2021
2022
	/**
2023
	 * 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.
2024
	 *
2025
	 * @param  bool $on_checkout
2026
	 * @return string
2027
	 */
2028
	public function get_checkout_payment_url( $on_checkout = false ) {
2029
2030
		$pay_url = wc_get_endpoint_url( 'order-pay', $this->id, wc_get_page_permalink( 'checkout' ) );
2031
2032 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...
2033
			$pay_url = str_replace( 'http:', 'https:', $pay_url );
2034
		}
2035
2036
		if ( $on_checkout ) {
2037
			$pay_url = add_query_arg( 'key', $this->order_key, $pay_url );
2038
		} else {
2039
			$pay_url = add_query_arg( array( 'pay_for_order' => 'true', 'key' => $this->order_key ), $pay_url );
2040
		}
2041
2042
		return apply_filters( 'woocommerce_get_checkout_payment_url', $pay_url, $this );
2043
	}
2044
2045
	/**
2046
	 * Generates a URL for the thanks page (order received).
2047
	 *
2048
	 * @return string
2049
	 */
2050
	public function get_checkout_order_received_url() {
2051
2052
		$order_received_url = wc_get_endpoint_url( 'order-received', $this->id, wc_get_page_permalink( 'checkout' ) );
2053
2054 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...
2055
			$order_received_url = str_replace( 'http:', 'https:', $order_received_url );
2056
		}
2057
2058
		$order_received_url = add_query_arg( 'key', $this->order_key, $order_received_url );
2059
2060
		return apply_filters( 'woocommerce_get_checkout_order_received_url', $order_received_url, $this );
2061
	}
2062
2063
	/**
2064
	 * Generates a URL so that a customer can cancel their (unpaid - pending) order.
2065
	 *
2066
	 * @param string $redirect
2067
	 *
2068
	 * @return string
2069
	 */
2070 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...
2071
2072
		// Get cancel endpoint
2073
		$cancel_endpoint = $this->get_cancel_endpoint();
2074
2075
		return apply_filters( 'woocommerce_get_cancel_order_url', wp_nonce_url( add_query_arg( array(
2076
			'cancel_order' => 'true',
2077
			'order'        => $this->order_key,
2078
			'order_id'     => $this->id,
2079
			'redirect'     => $redirect
2080
		), $cancel_endpoint ), 'woocommerce-cancel_order' ) );
2081
	}
2082
2083
	/**
2084
	 * Generates a raw (unescaped) cancel-order URL for use by payment gateways.
2085
	 *
2086
	 * @param string $redirect
2087
	 *
2088
	 * @return string The unescaped cancel-order URL.
2089
	 */
2090 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...
2091
2092
		// Get cancel endpoint
2093
		$cancel_endpoint = $this->get_cancel_endpoint();
2094
2095
		return apply_filters( 'woocommerce_get_cancel_order_url_raw', add_query_arg( array(
2096
			'cancel_order' => 'true',
2097
			'order'        => $this->order_key,
2098
			'order_id'     => $this->id,
2099
			'redirect'     => $redirect,
2100
			'_wpnonce'     => wp_create_nonce( 'woocommerce-cancel_order' )
2101
		), $cancel_endpoint ) );
2102
	}
2103
2104
2105
	/**
2106
	 * Helper method to return the cancel endpoint.
2107
	 *
2108
	 * @return string the cancel endpoint; either the cart page or the home page.
2109
	 */
2110
	public function get_cancel_endpoint() {
2111
2112
		$cancel_endpoint = wc_get_page_permalink( 'cart' );
2113
		if ( ! $cancel_endpoint ) {
2114
			$cancel_endpoint = home_url();
2115
		}
2116
2117
		if ( false === strpos( $cancel_endpoint, '?' ) ) {
2118
			$cancel_endpoint = trailingslashit( $cancel_endpoint );
2119
		}
2120
2121
		return $cancel_endpoint;
2122
	}
2123
2124
2125
	/**
2126
	 * Generates a URL to view an order from the my account page.
2127
	 *
2128
	 * @return string
2129
	 */
2130
	public function get_view_order_url() {
2131
2132
		$view_order_url = wc_get_endpoint_url( 'view-order', $this->id, wc_get_page_permalink( 'myaccount' ) );
2133
2134
		return apply_filters( 'woocommerce_get_view_order_url', $view_order_url, $this );
2135
	}
2136
2137
	/**
2138
	 * Get the downloadable files for an item in this order.
2139
	 *
2140
	 * @param  array $item
2141
	 * @return array
2142
	 */
2143
	public function get_item_downloads( $item ) {
2144
		global $wpdb;
2145
2146
		$product_id   = $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'];
2147
		$product      = wc_get_product( $product_id );
2148
		if ( ! $product ) {
2149
			/**
2150
			 * $product can be `false`. Example: checking an old order, when a product or variation has been deleted.
2151
			 * @see \WC_Product_Factory::get_product
2152
			 */
2153
			return array();
2154
		}
2155
		$download_ids = $wpdb->get_col( $wpdb->prepare("
2156
			SELECT download_id
2157
			FROM {$wpdb->prefix}woocommerce_downloadable_product_permissions
2158
			WHERE user_email = %s
2159
			AND order_key = %s
2160
			AND product_id = %s
2161
			ORDER BY permission_id
2162
		", $this->billing_email, $this->order_key, $product_id ) );
2163
2164
		$files = array();
2165
2166
		foreach ( $download_ids as $download_id ) {
2167
2168
			if ( $product->has_file( $download_id ) ) {
2169
				$files[ $download_id ]                 = $product->get_file( $download_id );
2170
				$files[ $download_id ]['download_url'] = $this->get_download_url( $product_id, $download_id );
2171
			}
2172
		}
2173
2174
		return apply_filters( 'woocommerce_get_item_downloads', $files, $item, $this );
2175
	}
2176
2177
	/**
2178
	 * Display download links for an order item.
2179
	 * @param  array $item
2180
	 */
2181
	public function display_item_downloads( $item ) {
2182
		$product = $this->get_product_from_item( $item );
2183
2184
		if ( $product && $product->exists() && $product->is_downloadable() && $this->is_download_permitted() ) {
2185
			$download_files = $this->get_item_downloads( $item );
2186
			$i              = 0;
2187
			$links          = array();
2188
2189
			foreach ( $download_files as $download_id => $file ) {
2190
				$i++;
2191
				$prefix  = count( $download_files ) > 1 ? sprintf( __( 'Download %d', 'woocommerce' ), $i ) : __( 'Download', 'woocommerce' );
2192
				$links[] = '<small class="download-url">' . $prefix . ': <a href="' . esc_url( $file['download_url'] ) . '" target="_blank">' . esc_html( $file['name'] ) . '</a></small>' . "\n";
2193
			}
2194
2195
			echo '<br/>' . implode( '<br/>', $links );
2196
		}
2197
	}
2198
2199
	/**
2200
	 * Get the Download URL.
2201
	 *
2202
	 * @param  int $product_id
2203
	 * @param  int $download_id
2204
	 * @return string
2205
	 */
2206
	public function get_download_url( $product_id, $download_id ) {
2207
		return add_query_arg( array(
2208
			'download_file' => $product_id,
2209
			'order'         => $this->order_key,
2210
			'email'         => urlencode( $this->billing_email ),
2211
			'key'           => $download_id
2212
		), trailingslashit( home_url() ) );
2213
	}
2214
2215
	/**
2216
	 * Adds a note (comment) to the order.
2217
	 *
2218
	 * @param string $note Note to add.
2219
	 * @param int $is_customer_note (default: 0) Is this a note for the customer?
2220
	 * @param  bool added_by_user Was the note added by a user?
2221
	 * @return int Comment ID.
2222
	 */
2223
	public function add_order_note( $note, $is_customer_note = 0, $added_by_user = false ) {
2224
		if ( is_user_logged_in() && current_user_can( 'edit_shop_order', $this->id ) && $added_by_user ) {
2225
			$user                 = get_user_by( 'id', get_current_user_id() );
2226
			$comment_author       = $user->display_name;
2227
			$comment_author_email = $user->user_email;
2228
		} else {
2229
			$comment_author       = __( 'WooCommerce', 'woocommerce' );
2230
			$comment_author_email = strtolower( __( 'WooCommerce', 'woocommerce' ) ) . '@';
2231
			$comment_author_email .= isset( $_SERVER['HTTP_HOST'] ) ? str_replace( 'www.', '', $_SERVER['HTTP_HOST'] ) : 'noreply.com';
2232
			$comment_author_email = sanitize_email( $comment_author_email );
2233
		}
2234
2235
		$comment_post_ID        = $this->id;
2236
		$comment_author_url     = '';
2237
		$comment_content        = $note;
2238
		$comment_agent          = 'WooCommerce';
2239
		$comment_type           = 'order_note';
2240
		$comment_parent         = 0;
2241
		$comment_approved       = 1;
2242
		$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 ) );
2243
2244
		$comment_id = wp_insert_comment( $commentdata );
2245
2246
		if ( $is_customer_note ) {
2247
			add_comment_meta( $comment_id, 'is_customer_note', 1 );
2248
2249
			do_action( 'woocommerce_new_customer_note', array( 'order_id' => $this->id, 'customer_note' => $commentdata['comment_content'] ) );
2250
		}
2251
2252
		return $comment_id;
2253
	}
2254
2255
	/**
2256
	 * Updates status of order.
2257
	 *
2258
	 * @param string $new_status Status to change the order to. No internal wc- prefix is required.
2259
	 * @param string $note (default: '') Optional note to add.
2260
	 * @param bool $manual is this a manual order status change?
2261
	 * @return bool Successful change or not
2262
	 */
2263
	public function update_status( $new_status, $note = '', $manual = false ) {
2264
		if ( ! $this->id ) {
2265
			return false;
2266
		}
2267
2268
		// Standardise status names.
2269
		$new_status = 'wc-' === substr( $new_status, 0, 3 ) ? substr( $new_status, 3 ) : $new_status;
2270
		$old_status = $this->get_status();
2271
2272
		// If the statuses are the same there is no need to update, unless the post status is not a valid 'wc' status.
2273
		if ( $new_status === $old_status && in_array( $this->post_status, array_keys( wc_get_order_statuses() ) ) ) {
2274
			return false;
2275
		}
2276
2277
		// Update the order.
2278
		wp_update_post( array( 'ID' => $this->id, 'post_status' => 'wc-' . $new_status ) );
2279
		$this->post_status = 'wc-' . $new_status;
2280
2281
		$this->add_order_note( trim( $note . ' ' . sprintf( __( 'Order status changed from %s to %s.', 'woocommerce' ), wc_get_order_status_name( $old_status ), wc_get_order_status_name( $new_status ) ) ), 0, $manual );
2282
2283
		// Status was changed.
2284
		do_action( 'woocommerce_order_status_' . $new_status, $this->id );
2285
		do_action( 'woocommerce_order_status_' . $old_status . '_to_' . $new_status, $this->id );
2286
		do_action( 'woocommerce_order_status_changed', $this->id, $old_status, $new_status );
2287
2288
		switch ( $new_status ) {
2289
2290
			case 'completed' :
2291
				// Record the sales.
2292
				$this->record_product_sales();
2293
2294
				// Increase coupon usage counts.
2295
				$this->increase_coupon_usage_counts();
2296
2297
				// Record the completed date of the order.
2298
				update_post_meta( $this->id, '_completed_date', current_time('mysql') );
2299
2300
				// Update reports.
2301
				wc_delete_shop_order_transients( $this->id );
2302
				break;
2303
2304
			case 'processing' :
2305
			case 'on-hold' :
2306
				// Record the sales.
2307
				$this->record_product_sales();
2308
2309
				// Increase coupon usage counts.
2310
				$this->increase_coupon_usage_counts();
2311
2312
				// Update reports.
2313
				wc_delete_shop_order_transients( $this->id );
2314
				break;
2315
2316
			case 'cancelled' :
2317
				// If the order is cancelled, restore used coupons.
2318
				$this->decrease_coupon_usage_counts();
2319
2320
				// Update reports.
2321
				wc_delete_shop_order_transients( $this->id );
2322
				break;
2323
		}
2324
2325
		return true;
2326
	}
2327
2328
2329
	/**
2330
	 * Cancel the order and restore the cart (before payment).
2331
	 *
2332
	 * @param string $note (default: '') Optional note to add.
2333
	 */
2334
	public function cancel_order( $note = '' ) {
2335
		WC()->session->set( 'order_awaiting_payment', false );
2336
		$this->update_status( 'cancelled', $note );
2337
	}
2338
2339
	/**
2340
	 * When a payment is complete this function is called.
2341
	 *
2342
	 * Most of the time this should mark an order as 'processing' so that admin can process/post the items.
2343
	 * If the cart contains only downloadable items then the order is 'completed' since the admin needs to take no action.
2344
	 * Stock levels are reduced at this point.
2345
	 * Sales are also recorded for products.
2346
	 * Finally, record the date of payment.
2347
	 *
2348
	 * @param string $transaction_id Optional transaction id to store in post meta.
2349
	 */
2350
	public function payment_complete( $transaction_id = '' ) {
2351
		do_action( 'woocommerce_pre_payment_complete', $this->id );
2352
2353
		if ( null !== WC()->session ) {
2354
			WC()->session->set( 'order_awaiting_payment', false );
2355
		}
2356
2357
		$valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment_complete', array( 'on-hold', 'pending', 'failed', 'cancelled' ), $this );
2358
2359
		if ( $this->id && $this->has_status( $valid_order_statuses ) ) {
2360
			$order_needs_processing = false;
2361
2362
			if ( sizeof( $this->get_items() ) > 0 ) {
2363
				foreach ( $this->get_items() as $item ) {
2364
					if ( $_product = $this->get_product_from_item( $item ) ) {
2365
						$virtual_downloadable_item = $_product->is_downloadable() && $_product->is_virtual();
2366
2367
						if ( apply_filters( 'woocommerce_order_item_needs_processing', ! $virtual_downloadable_item, $_product, $this->id ) ) {
2368
							$order_needs_processing = true;
2369
							break;
2370
						}
2371
					}
2372
				}
2373
			}
2374
2375
			$this->update_status( apply_filters( 'woocommerce_payment_complete_order_status', $order_needs_processing ? 'processing' : 'completed', $this->id ) );
2376
2377
			add_post_meta( $this->id, '_paid_date', current_time( 'mysql' ), true );
2378
2379
			if ( ! empty( $transaction_id ) ) {
2380
				update_post_meta( $this->id, '_transaction_id', $transaction_id );
2381
			}
2382
2383
			wp_update_post( array(
2384
				'ID'            => $this->id,
2385
				'post_date'     => current_time( 'mysql', 0 ),
2386
				'post_date_gmt' => current_time( 'mysql', 1 )
2387
			) );
2388
2389
			// Payment is complete so reduce stock levels
2390
			if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $this->id, '_order_stock_reduced', true ), $this->id ) ) {
2391
				$this->reduce_order_stock();
2392
			}
2393
2394
			do_action( 'woocommerce_payment_complete', $this->id );
2395
		} else {
2396
			do_action( 'woocommerce_payment_complete_order_status_' . $this->get_status(), $this->id );
2397
		}
2398
	}
2399
2400
2401
	/**
2402
	 * Record sales.
2403
	 */
2404
	public function record_product_sales() {
2405
		if ( 'yes' === get_post_meta( $this->id, '_recorded_sales', true ) ) {
2406
			return;
2407
		}
2408
2409
		if ( sizeof( $this->get_items() ) > 0 ) {
2410
2411
			foreach ( $this->get_items() as $item ) {
2412
2413
				if ( $item['product_id'] > 0 ) {
2414
					$sales = (int) get_post_meta( $item['product_id'], 'total_sales', true );
2415
					$sales += (int) $item['qty'];
2416
2417
					if ( $sales ) {
2418
						update_post_meta( $item['product_id'], 'total_sales', $sales );
2419
					}
2420
				}
2421
			}
2422
		}
2423
2424
		update_post_meta( $this->id, '_recorded_sales', 'yes' );
2425
2426
		/**
2427
		 * Called when sales for an order are recorded
2428
		 *
2429
		 * @param int $order_id order id
2430
		 */
2431
		do_action( 'woocommerce_recorded_sales', $this->id );
2432
	}
2433
2434
2435
	/**
2436
	 * Get coupon codes only.
2437
	 *
2438
	 * @return array
2439
	 */
2440
	public function get_used_coupons() {
2441
2442
		$codes   = array();
2443
		$coupons = $this->get_items( 'coupon' );
2444
2445
		foreach ( $coupons as $item_id => $item ) {
2446
			$codes[] = trim( $item['name'] );
2447
		}
2448
2449
		return $codes;
2450
	}
2451
2452
2453
	/**
2454
	 * Increase applied coupon counts.
2455
	 */
2456 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...
2457
		if ( 'yes' == get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) {
2458
			return;
2459
		}
2460
2461
		if ( sizeof( $this->get_used_coupons() ) > 0 ) {
2462
2463
			foreach ( $this->get_used_coupons() as $code ) {
2464
				if ( ! $code ) {
2465
					continue;
2466
				}
2467
2468
				$coupon = new WC_Coupon( $code );
2469
2470
				$used_by = $this->get_user_id();
2471
2472
				if ( ! $used_by ) {
2473
					$used_by = $this->billing_email;
2474
				}
2475
2476
				$coupon->inc_usage_count( $used_by );
2477
			}
2478
2479
			update_post_meta( $this->id, '_recorded_coupon_usage_counts', 'yes' );
2480
		}
2481
	}
2482
2483
2484
	/**
2485
	 * Decrease applied coupon counts.
2486
	 */
2487 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...
2488
2489
		if ( 'yes' != get_post_meta( $this->id, '_recorded_coupon_usage_counts', true ) ) {
2490
			return;
2491
		}
2492
2493
		if ( sizeof( $this->get_used_coupons() ) > 0 ) {
2494
2495
			foreach ( $this->get_used_coupons() as $code ) {
2496
2497
				if ( ! $code ) {
2498
					continue;
2499
				}
2500
2501
				$coupon = new WC_Coupon( $code );
2502
2503
				$used_by = $this->get_user_id();
2504
				if ( ! $used_by ) {
2505
					$used_by = $this->billing_email;
2506
				}
2507
2508
				$coupon->dcr_usage_count( $used_by );
2509
			}
2510
2511
			delete_post_meta( $this->id, '_recorded_coupon_usage_counts' );
2512
		}
2513
	}
2514
2515
	/**
2516
	 * Reduce stock levels for all line items in the order.
2517
	 * 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.
2518
	 */
2519
	public function reduce_order_stock() {
2520
		if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && apply_filters( 'woocommerce_can_reduce_order_stock', true, $this ) && sizeof( $this->get_items() ) > 0 ) {
2521
			foreach ( $this->get_items() as $item ) {
2522
				if ( $item['product_id'] > 0 ) {
2523
					$_product = $this->get_product_from_item( $item );
2524
2525
					if ( $_product && $_product->exists() && $_product->managing_stock() ) {
2526
						$qty       = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $this, $item );
2527
						$new_stock = $_product->reduce_stock( $qty );
2528
						$item_name = $_product->get_sku() ? $_product->get_sku(): $item['product_id'];
2529
2530
						if ( isset( $item['variation_id'] ) && $item['variation_id'] ) {
2531
							$this->add_order_note( sprintf( __( 'Item %s variation #%s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $item['variation_id'], $new_stock + $qty, $new_stock) );
2532
						} else {
2533
							$this->add_order_note( sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock) );
2534
						}
2535
						$this->send_stock_notifications( $_product, $new_stock, $item['qty'] );
2536
					}
2537
				}
2538
			}
2539
2540
			add_post_meta( $this->id, '_order_stock_reduced', '1', true );
2541
2542
			do_action( 'woocommerce_reduce_order_stock', $this );
2543
		}
2544
	}
2545
2546
	/**
2547
	 * Send the stock notifications.
2548
	 *
2549
	 * @param WC_Product $product
2550
	 * @param int $new_stock
2551
	 * @param int $qty_ordered
2552
	 */
2553
	public function send_stock_notifications( $product, $new_stock, $qty_ordered ) {
2554
2555
		// Backorders
2556
		if ( $new_stock < 0 ) {
2557
			do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $this->id, 'quantity' => $qty_ordered ) );
2558
		}
2559
2560
		// stock status notifications
2561
		$notification_sent = false;
2562
2563
		if ( 'yes' == get_option( 'woocommerce_notify_no_stock' ) && get_option( 'woocommerce_notify_no_stock_amount' ) >= $new_stock ) {
2564
			do_action( 'woocommerce_no_stock', $product );
2565
			$notification_sent = true;
2566
		}
2567
2568
		if ( ! $notification_sent && 'yes' == get_option( 'woocommerce_notify_low_stock' ) && get_option( 'woocommerce_notify_low_stock_amount' ) >= $new_stock ) {
2569
			do_action( 'woocommerce_low_stock', $product );
2570
		}
2571
2572
		do_action( 'woocommerce_after_send_stock_notifications', $product, $new_stock, $qty_ordered );
2573
	}
2574
2575
2576
	/**
2577
	 * List order notes (public) for the customer.
2578
	 *
2579
	 * @return array
2580
	 */
2581
	public function get_customer_order_notes() {
2582
		$notes = array();
2583
		$args  = array(
2584
			'post_id' => $this->id,
2585
			'approve' => 'approve',
2586
			'type'    => ''
2587
		);
2588
2589
		remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
2590
2591
		$comments = get_comments( $args );
2592
2593
		foreach ( $comments as $comment ) {
2594
			if ( ! get_comment_meta( $comment->comment_ID, 'is_customer_note', true ) ) {
2595
				continue;
2596
			}
2597
			$comment->comment_content = make_clickable( $comment->comment_content );
2598
			$notes[] = $comment;
2599
		}
2600
2601
		add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
2602
2603
		return $notes;
2604
	}
2605
2606
	/**
2607
	 * Checks if an order needs payment, based on status and order total.
2608
	 *
2609
	 * @return bool
2610
	 */
2611
	public function needs_payment() {
2612
2613
		$valid_order_statuses = apply_filters( 'woocommerce_valid_order_statuses_for_payment', array( 'pending', 'failed' ), $this );
2614
2615
		if ( $this->has_status( $valid_order_statuses ) && $this->get_total() > 0 ) {
2616
			$needs_payment = true;
2617
		} else {
2618
			$needs_payment = false;
2619
		}
2620
2621
		return apply_filters( 'woocommerce_order_needs_payment', $needs_payment, $this, $valid_order_statuses );
2622
	}
2623
2624
	/**
2625
	 * Checks if an order needs display the shipping address, based on shipping method.
2626
	 *
2627
	 * @return boolean
2628
	 */
2629
	public function needs_shipping_address() {
2630
		if ( 'no' === get_option( 'woocommerce_calc_shipping' ) ) {
2631
			return false;
2632
		}
2633
2634
		$hide  = apply_filters( 'woocommerce_order_hide_shipping_address', array( 'local_pickup' ), $this );
2635
		$needs_address = false;
2636
2637
		foreach ( $this->get_shipping_methods() as $shipping_method ) {
2638
			if ( ! in_array( $shipping_method['method_id'], $hide ) ) {
2639
				$needs_address = true;
2640
				break;
2641
			}
2642
		}
2643
2644
		return apply_filters( 'woocommerce_order_needs_shipping_address', $needs_address, $hide, $this );
2645
	}
2646
2647
	/**
2648
	 * Checks if an order can be edited, specifically for use on the Edit Order screen.
2649
	 *
2650
	 * @return bool
2651
	 */
2652
	public function is_editable() {
2653
		return apply_filters( 'wc_order_is_editable', in_array( $this->get_status(), array( 'pending', 'on-hold', 'auto-draft' ) ), $this );
2654
	}
2655
}
2656