wc-order-functions.php ➔ wc_get_orders()   F
last analyzed

Complexity

Conditions 12
Paths 1152

Size

Total Lines 90
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
dl 0
loc 90
rs 2
c 0
b 0
f 0
eloc 59
nc 1152
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * WooCommerce Order Functions
4
 *
5
 * Functions for order specific things.
6
 *
7
 * @author 		WooThemes
8
 * @category 	Core
9
 * @package 	WooCommerce/Functions
10
 * @version     2.1.0
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit; // Exit if accessed directly
15
}
16
17
/**
18
 * Wrapper for get_posts specific to orders.
19
 *
20
 * This function should be used for order retrieval so that when we move to
21
 * custom tables, functions still work.
22
 *
23
 * Args:
24
 * 		status array|string List of order statuses to find
25
 * 		type array|string Order type, e.g. shop_order or shop_order_refund
26
 * 		parent int post/order parent
27
 * 		customer int|string|array User ID or billing email to limit orders to a
28
 * 			particular user. Accepts array of values. Array of values is OR'ed. If array of array is passed, each array will be AND'ed.
29
 * 			e.g. [email protected], 1, array( 1, 2, 3 ), array( array( 1, '[email protected]' ), 2, 3 )
30
 * 		limit int Maximum of orders to retrieve.
31
 * 		offset int Offset of orders to retrieve.
32
 * 		page int Page of orders to retrieve. Ignored when using the 'offset' arg.
33
 * 		exclude array Order IDs to exclude from the query.
34
 * 		orderby string Order by date, title, id, modified, rand etc
35
 * 		order string ASC or DESC
36
 * 		return string Type of data to return. Allowed values:
37
 * 			ids array of order ids
38
 * 			objects array of order objects (default)
39
 * 		paginate bool If true, the return value will be an array with values:
40
 * 			'orders'        => array of data (return value above),
41
 * 			'total'         => total number of orders matching the query
42
 * 			'max_num_pages' => max number of pages found
43
 *
44
 * @since  2.6.0
45
 * @param  array $args Array of args (above)
46
 * @return array|stdClass Number of pages and an array of order objects if
47
 *                             paginate is true, or just an array of values.
48
 */
49
function wc_get_orders( $args ) {
50
	$args = wp_parse_args( $args, array(
51
		'status'   => array_keys( wc_get_order_statuses() ),
52
		'type'     => wc_get_order_types( 'view-orders' ),
53
		'parent'   => null,
54
		'customer' => null,
55
		'email'    => '',
56
		'limit'    => get_option( 'posts_per_page' ),
57
		'offset'   => null,
58
		'page'     => 1,
59
		'exclude'  => array(),
60
		'orderby'  => 'date',
61
		'order'    => 'DESC',
62
		'return'   => 'objects',
63
		'paginate' => false,
64
	) );
65
66
	// Handle some BW compatibility arg names where wp_query args differ in naming.
67
	$map_legacy = array(
68
		'numberposts'    => 'limit',
69
		'post_type'      => 'type',
70
		'post_status'    => 'status',
71
		'post_parent'    => 'parent',
72
		'author'         => 'customer',
73
		'posts_per_page' => 'limit',
74
		'paged'          => 'page',
75
	);
76
77
	foreach ( $map_legacy as $from => $to ) {
78
		if ( isset( $args[ $from ] ) ) {
79
			$args[ $to ] = $args[ $from ];
80
		}
81
	}
82
83
	/**
84
	 * Generate WP_Query args. This logic will change if orders are moved to
85
	 * custom tables in the future.
86
	 */
87
	$wp_query_args = array(
88
		'post_type'      => $args['type'] ? $args['type'] : 'shop_order',
89
		'post_status'    => $args['status'],
90
		'posts_per_page' => $args['limit'],
91
		'meta_query'     => array(),
92
		'fields'         => 'ids',
93
		'orderby'        => $args['orderby'],
94
		'order'          => $args['order'],
95
	);
96
97
	if ( ! is_null( $args['parent'] ) ) {
98
		$wp_query_args['post_parent'] = absint( $args['parent'] );
99
	}
100
101
	if ( ! is_null( $args['offset'] ) ) {
102
		$wp_query_args['offset'] = absint( $args['offset'] );
103
	} else {
104
		$wp_query_args['paged'] = absint( $args['page'] );
105
	}
106
107
	if ( ! empty( $args['customer'] ) ) {
108
		$values = is_array( $args['customer'] ) ? $args['customer'] : array( $args['customer'] );
109
		$wp_query_args['meta_query'][] = _wc_get_orders_generate_customer_meta_query( $values );
110
	}
111
112
	if ( ! empty( $args['exclude'] ) ) {
113
		$wp_query_args['post__not_in'] = array_map( 'absint', $args['exclude'] );
114
	}
115
116
	if ( ! $args['paginate' ] ) {
117
		$wp_query_args['no_found_rows'] = true;
118
	}
119
120
	// Get results.
121
	$orders = new WP_Query( $wp_query_args );
122
123
	if ( 'objects' === $args['return'] ) {
124
		$return = array_map( 'wc_get_order', $orders->posts );
125
	} else {
126
		$return = $orders->posts;
127
	}
128
129
	if ( $args['paginate' ] ) {
130
		return (object) array(
131
			'orders'        => $return,
132
			'total'         => $orders->found_posts,
133
			'max_num_pages' => $orders->max_num_pages,
134
		);
135
	} else {
136
		return $return;
137
	}
138
}
139
140
/**
141
 * Generate meta query for wc_get_orders. Used internally only.
142
 * @since  2.6.0
143
 * @param  array $values
144
 * @param  string $relation
145
 * @return array
146
 */
147
function _wc_get_orders_generate_customer_meta_query( $values, $relation = 'or' ) {
148
	$meta_query = array(
149
		'relation' => strtoupper( $relation ),
150
		'customer_emails' => array(
151
			'key'     => '_billing_email',
152
			'value'   => array(),
153
			'compare' => 'IN',
154
		),
155
		'customer_ids' => array(
156
			'key'     => '_customer_user',
157
			'value'   => array(),
158
			'compare' => 'IN',
159
		)
160
	);
161
	foreach ( $values as $value ) {
162
		if ( is_array( $value ) ) {
163
			$meta_query[] = _wc_get_orders_generate_customer_meta_query( $value, 'and' );
164
		} elseif ( is_email( $value ) ) {
165
			$meta_query['customer_emails']['value'][] = sanitize_email( $value );
166
		} else {
167
			$meta_query['customer_ids']['value'][] = strval( absint( $value ) );
168
		}
169
	}
170
171
	if ( empty( $meta_query['customer_emails']['value'] ) ) {
172
		unset( $meta_query['customer_emails'] );
173
		unset( $meta_query['relation'] );
174
	}
175
176
	if ( empty( $meta_query['customer_ids']['value'] ) ) {
177
		unset( $meta_query['customer_ids'] );
178
		unset( $meta_query['relation'] );
179
	}
180
181
	return $meta_query;
182
}
183
184
/**
185
 * Get all order statuses.
186
 *
187
 * @since 2.2
188
 * @return array
189
 */
190
function wc_get_order_statuses() {
191
	$order_statuses = array(
192
		'wc-pending'    => _x( 'Pending Payment', 'Order status', 'woocommerce' ),
193
		'wc-processing' => _x( 'Processing', 'Order status', 'woocommerce' ),
194
		'wc-on-hold'    => _x( 'On Hold', 'Order status', 'woocommerce' ),
195
		'wc-completed'  => _x( 'Completed', 'Order status', 'woocommerce' ),
196
		'wc-cancelled'  => _x( 'Cancelled', 'Order status', 'woocommerce' ),
197
		'wc-refunded'   => _x( 'Refunded', 'Order status', 'woocommerce' ),
198
		'wc-failed'     => _x( 'Failed', 'Order status', 'woocommerce' ),
199
	);
200
	return apply_filters( 'wc_order_statuses', $order_statuses );
201
}
202
203
/**
204
 * See if a string is an order status.
205
 * @param  string $maybe_status Status, including any wc- prefix
206
 * @return bool
207
 */
208
function wc_is_order_status( $maybe_status ) {
209
	$order_statuses = wc_get_order_statuses();
210
	return isset( $order_statuses[ $maybe_status ] );
211
}
212
213
/**
214
 * Main function for returning orders, uses the WC_Order_Factory class.
215
 *
216
 * @since  2.2
217
 * @param  mixed $the_order Post object or post ID of the order.
218
 * @return WC_Order|WC_Refund
219
 */
220
function wc_get_order( $the_order = false ) {
221
	if ( ! did_action( 'woocommerce_init' ) ) {
222
		_doing_it_wrong( __FUNCTION__, __( 'wc_get_order should not be called before the woocommerce_init action.', 'woocommerce' ), '2.5' );
223
		return false;
224
	}
225
	return WC()->order_factory->get_order( $the_order );
226
}
227
228
/**
229
 * Get the nice name for an order status.
230
 *
231
 * @since  2.2
232
 * @param  string $status
233
 * @return string
234
 */
235
function wc_get_order_status_name( $status ) {
236
	$statuses = wc_get_order_statuses();
237
	$status   = 'wc-' === substr( $status, 0, 3 ) ? substr( $status, 3 ) : $status;
238
	$status   = isset( $statuses[ 'wc-' . $status ] ) ? $statuses[ 'wc-' . $status ] : $status;
239
240
	return $status;
241
}
242
243
/**
244
 * Finds an Order ID based on an order key.
245
 *
246
 * @access public
247
 * @param string $order_key An order key has generated by
248
 * @return int The ID of an order, or 0 if the order could not be found
249
 */
250
function wc_get_order_id_by_order_key( $order_key ) {
251
	global $wpdb;
252
253
	// Faster than get_posts()
254
	$order_id = $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM {$wpdb->prefix}postmeta WHERE meta_key = '_order_key' AND meta_value = %s", $order_key ) );
255
256
	return $order_id;
257
}
258
259
/**
260
 * Get all registered order types.
261
 *
262
 * $for optionally define what you are getting order types for so only relevent types are returned.
263
 *
264
 * e.g. for 'order-meta-boxes', 'order-count'
265
 *
266
 * @since  2.2
267
 * @param  string $for
268
 * @return array
269
 */
270
function wc_get_order_types( $for = '' ) {
271
	global $wc_order_types;
272
273
	if ( ! is_array( $wc_order_types ) ) {
274
		$wc_order_types = array();
275
	}
276
277
	$order_types = array();
278
279
	switch ( $for ) {
280
		case 'order-count' :
281
			foreach ( $wc_order_types as $type => $args ) {
282
				if ( ! $args['exclude_from_order_count'] ) {
283
					$order_types[] = $type;
284
				}
285
			}
286
		break;
287
		case 'order-meta-boxes' :
288
			foreach ( $wc_order_types as $type => $args ) {
289
				if ( $args['add_order_meta_boxes'] ) {
290
					$order_types[] = $type;
291
				}
292
			}
293
		break;
294
		case 'view-orders' :
295
			foreach ( $wc_order_types as $type => $args ) {
296
				if ( ! $args['exclude_from_order_views'] ) {
297
					$order_types[] = $type;
298
				}
299
			}
300
		break;
301
		case 'reports' :
302
			foreach ( $wc_order_types as $type => $args ) {
303
				if ( ! $args['exclude_from_order_reports'] ) {
304
					$order_types[] = $type;
305
				}
306
			}
307
		break;
308
		case 'sales-reports' :
309
			foreach ( $wc_order_types as $type => $args ) {
310
				if ( ! $args['exclude_from_order_sales_reports'] ) {
311
					$order_types[] = $type;
312
				}
313
			}
314
		break;
315
		case 'order-webhooks' :
316
			foreach ( $wc_order_types as $type => $args ) {
317
				if ( ! $args['exclude_from_order_webhooks'] ) {
318
					$order_types[] = $type;
319
				}
320
			}
321
		break;
322
		default :
323
			$order_types = array_keys( $wc_order_types );
324
		break;
325
	}
326
327
	return apply_filters( 'wc_order_types', $order_types, $for );
328
}
329
330
/**
331
 * Get an order type by post type name.
332
 * @param  string post type name
333
 * @return bool|array of datails about the order type
334
 */
335
function wc_get_order_type( $type ) {
336
	global $wc_order_types;
337
338
	if ( isset( $wc_order_types[ $type ] ) ) {
339
		return $wc_order_types[ $type ];
340
	} else {
341
		return false;
342
	}
343
}
344
345
/**
346
 * Register order type. Do not use before init.
347
 *
348
 * Wrapper for register post type, as well as a method of telling WC which.
349
 * post types are types of orders, and having them treated as such.
350
 *
351
 * $args are passed to register_post_type, but there are a few specific to this function:
352
 * 		- exclude_from_orders_screen (bool) Whether or not this order type also get shown in the main.
353
 * 		orders screen.
354
 * 		- add_order_meta_boxes (bool) Whether or not the order type gets shop_order meta boxes.
355
 * 		- exclude_from_order_count (bool) Whether or not this order type is excluded from counts.
356
 * 		- exclude_from_order_views (bool) Whether or not this order type is visible by customers when.
357
 * 		viewing orders e.g. on the my account page.
358
 * 		- exclude_from_order_reports (bool) Whether or not to exclude this type from core reports.
359
 * 		- exclude_from_order_sales_reports (bool) Whether or not to exclude this type from core sales reports.
360
 *
361
 * @since  2.2
362
 * @see    register_post_type for $args used in that function
363
 * @param  string $type Post type. (max. 20 characters, can not contain capital letters or spaces)
364
 * @param  array $args An array of arguments.
365
 * @return bool Success or failure
366
 */
367
function wc_register_order_type( $type, $args = array() ) {
368
	if ( post_type_exists( $type ) ) {
369
		return false;
370
	}
371
372
	global $wc_order_types;
373
374
	if ( ! is_array( $wc_order_types ) ) {
375
		$wc_order_types = array();
376
	}
377
378
	// Register as a post type
379
	if ( is_wp_error( register_post_type( $type, $args ) ) ) {
380
		return false;
381
	}
382
383
	// Register for WC usage
384
	$order_type_args = array(
385
		'exclude_from_orders_screen'       => false,
386
		'add_order_meta_boxes'             => true,
387
		'exclude_from_order_count'         => false,
388
		'exclude_from_order_views'         => false,
389
		'exclude_from_order_webhooks'      => false,
390
		'exclude_from_order_reports'       => false,
391
		'exclude_from_order_sales_reports' => false,
392
		'class_name'                       => 'WC_Order'
393
	);
394
395
	$args                    = array_intersect_key( $args, $order_type_args );
396
	$args                    = wp_parse_args( $args, $order_type_args );
397
	$wc_order_types[ $type ] = $args;
398
399
	return true;
400
}
401
402
/**
403
 * Grant downloadable product access to the file identified by $download_id.
404
 *
405
 * @access public
406
 * @param string $download_id file identifier
407
 * @param int $product_id product identifier
408
 * @param WC_Order $order the order
409
 * @param  int $qty purchased
410
 * @return int|bool insert id or false on failure
411
 */
412
function wc_downloadable_file_permission( $download_id, $product_id, $order, $qty = 1 ) {
413
	global $wpdb;
414
415
	$user_email = sanitize_email( $order->billing_email );
416
	$limit      = trim( get_post_meta( $product_id, '_download_limit', true ) );
417
	$expiry     = trim( get_post_meta( $product_id, '_download_expiry', true ) );
418
419
	$limit      = empty( $limit ) ? '' : absint( $limit ) * $qty;
420
421
	// Default value is NULL in the table schema
422
	$expiry     = empty( $expiry ) ? null : absint( $expiry );
423
424
	if ( $expiry ) {
425
		$order_completed_date = date_i18n( "Y-m-d", strtotime( $order->completed_date ) );
426
		$expiry = date_i18n( "Y-m-d", strtotime( $order_completed_date . ' + ' . $expiry . ' DAY' ) );
427
	}
428
429
	$data = apply_filters( 'woocommerce_downloadable_file_permission_data', array(
430
		'download_id'			=> $download_id,
431
		'product_id' 			=> $product_id,
432
		'user_id' 				=> absint( $order->user_id ),
433
		'user_email' 			=> $user_email,
434
		'order_id' 				=> $order->id,
435
		'order_key' 			=> $order->order_key,
436
		'downloads_remaining' 	=> $limit,
437
		'access_granted'		=> current_time( 'mysql' ),
438
		'download_count'		=> 0
439
	));
440
441
	$format = apply_filters( 'woocommerce_downloadable_file_permission_format', array(
442
		'%s',
443
		'%s',
444
		'%s',
445
		'%s',
446
		'%s',
447
		'%s',
448
		'%s',
449
		'%s',
450
		'%d'
451
	), $data);
452
453
	if ( ! is_null( $expiry ) ) {
454
			$data['access_expires'] = $expiry;
455
			$format[] = '%s';
456
	}
457
458
	// Downloadable product - give access to the customer
459
	$result = $wpdb->insert( $wpdb->prefix . 'woocommerce_downloadable_product_permissions',
460
		$data,
461
		$format
462
	);
463
464
	do_action( 'woocommerce_grant_product_download_access', $data );
465
466
	return $result ? $wpdb->insert_id : false;
467
}
468
469
/**
470
 * Order Status completed - GIVE DOWNLOADABLE PRODUCT ACCESS TO CUSTOMER.
471
 *
472
 * @access public
473
 * @param int $order_id
474
 */
475
function wc_downloadable_product_permissions( $order_id ) {
476
	if ( get_post_meta( $order_id, '_download_permissions_granted', true ) == 1 ) {
477
		return; // Only do this once
478
	}
479
480
	$order = wc_get_order( $order_id );
481
482
	if ( $order && $order->has_status( 'processing' ) && get_option( 'woocommerce_downloads_grant_access_after_payment' ) == 'no' ) {
483
		return;
484
	}
485
486
	if ( sizeof( $order->get_items() ) > 0 ) {
487
		foreach ( $order->get_items() as $item ) {
488
			$_product = $order->get_product_from_item( $item );
489
490
			if ( $_product && $_product->exists() && $_product->is_downloadable() ) {
491
				$downloads = $_product->get_files();
492
493
				foreach ( array_keys( $downloads ) as $download_id ) {
494
					wc_downloadable_file_permission( $download_id, $item['variation_id'] > 0 ? $item['variation_id'] : $item['product_id'], $order, $item['qty'] );
0 ignored issues
show
Documentation introduced by
$order is of type false|object, but the function expects a object<WC_Order>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
495
				}
496
			}
497
		}
498
	}
499
500
	update_post_meta( $order_id, '_download_permissions_granted', 1 );
501
502
	do_action( 'woocommerce_grant_product_download_permissions', $order_id );
503
}
504
add_action( 'woocommerce_order_status_completed', 'wc_downloadable_product_permissions' );
505
add_action( 'woocommerce_order_status_processing', 'wc_downloadable_product_permissions' );
506
507
508
/**
509
 * Add a item to an order (for example a line item).
510
 *
511
 * @access public
512
 * @param int $order_id
513
 * @return mixed
514
 */
515
function wc_add_order_item( $order_id, $item ) {
516
	global $wpdb;
517
518
	$order_id = absint( $order_id );
519
520
	if ( ! $order_id )
521
		return false;
522
523
	$defaults = array(
524
		'order_item_name' 		=> '',
525
		'order_item_type' 		=> 'line_item',
526
	);
527
528
	$item = wp_parse_args( $item, $defaults );
529
530
	$wpdb->insert(
531
		$wpdb->prefix . "woocommerce_order_items",
532
		array(
533
			'order_item_name' 		=> $item['order_item_name'],
534
			'order_item_type' 		=> $item['order_item_type'],
535
			'order_id'				=> $order_id
536
		),
537
		array(
538
			'%s', '%s', '%d'
539
		)
540
	);
541
542
	$item_id = absint( $wpdb->insert_id );
543
544
	do_action( 'woocommerce_new_order_item', $item_id, $item, $order_id );
545
546
	return $item_id;
547
}
548
549
/**
550
 * Update an item for an order.
551
 *
552
 * @since 2.2
553
 * @param int $item_id
554
 * @param array $args either `order_item_type` or `order_item_name`
555
 * @return bool true if successfully updated, false otherwise
556
 */
557
function wc_update_order_item( $item_id, $args ) {
558
	global $wpdb;
559
560
	$update = $wpdb->update( $wpdb->prefix . 'woocommerce_order_items', $args, array( 'order_item_id' => $item_id ) );
561
562
	if ( false === $update ) {
563
		return false;
564
	}
565
566
	do_action( 'woocommerce_update_order_item', $item_id, $args );
567
568
	return true;
569
}
570
571
/**
572
 * Delete an item from the order it belongs to based on item id.
573
 *
574
 * @access public
575
 * @param int $item_id
576
 * @return bool
577
 */
578
function wc_delete_order_item( $item_id ) {
579
	global $wpdb;
580
581
	$item_id = absint( $item_id );
582
583
	if ( ! $item_id )
584
		return false;
585
586
	do_action( 'woocommerce_before_delete_order_item', $item_id );
587
588
	$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d", $item_id ) );
589
	$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d", $item_id ) );
590
591
	do_action( 'woocommerce_delete_order_item', $item_id );
592
593
	return true;
594
}
595
596
/**
597
 * WooCommerce Order Item Meta API - Update term meta.
598
 *
599
 * @access public
600
 * @param mixed $item_id
601
 * @param mixed $meta_key
602
 * @param mixed $meta_value
603
 * @param string $prev_value (default: '')
604
 * @return bool
605
 */
606 View Code Duplication
function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) {
0 ignored issues
show
Duplication introduced by
This function 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...
607
	if ( update_metadata( 'order_item', $item_id, $meta_key, $meta_value, $prev_value ) ) {
608
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
609
		wp_cache_delete( $cache_key, 'orders' );
610
		return true;
611
	}
612
	return false;
613
}
614
615
/**
616
 * WooCommerce Order Item Meta API - Add term meta.
617
 *
618
 * @access public
619
 * @param mixed $item_id
620
 * @param mixed $meta_key
621
 * @param mixed $meta_value
622
 * @param bool $unique (default: false)
623
 * @return int New row ID or 0
624
 */
625 View Code Duplication
function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = false ) {
0 ignored issues
show
Duplication introduced by
This function 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...
626
	if ( $meta_id = add_metadata( 'order_item', $item_id, $meta_key, $meta_value, $unique ) ) {
627
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
628
		wp_cache_delete( $cache_key, 'orders' );
629
		return $meta_id;
630
	}
631
	return 0;
632
}
633
634
/**
635
 * WooCommerce Order Item Meta API - Delete term meta.
636
 *
637
 * @access public
638
 * @param mixed $item_id
639
 * @param mixed $meta_key
640
 * @param string $meta_value (default: '')
641
 * @param bool $delete_all (default: false)
642
 * @return bool
643
 */
644 View Code Duplication
function wc_delete_order_item_meta( $item_id, $meta_key, $meta_value = '', $delete_all = false ) {
0 ignored issues
show
Duplication introduced by
This function 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...
645
	if ( delete_metadata( 'order_item', $item_id, $meta_key, $meta_value, $delete_all ) ) {
646
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
647
		wp_cache_delete( $cache_key, 'orders' );
648
		return true;
649
	}
650
	return false;
651
}
652
653
/**
654
 * WooCommerce Order Item Meta API - Get term meta.
655
 *
656
 * @access public
657
 * @param mixed $item_id
658
 * @param mixed $key
659
 * @param bool $single (default: true)
660
 * @return mixed
661
 */
662
function wc_get_order_item_meta( $item_id, $key, $single = true ) {
663
	return get_metadata( 'order_item', $item_id, $key, $single );
664
}
665
666
/**
667
 * Cancel all unpaid orders after held duration to prevent stock lock for those products.
668
 *
669
 * @access public
670
 */
671
function wc_cancel_unpaid_orders() {
672
	global $wpdb;
673
674
	$held_duration = get_option( 'woocommerce_hold_stock_minutes' );
675
676
	if ( $held_duration < 1 || get_option( 'woocommerce_manage_stock' ) != 'yes' )
677
		return;
678
679
	$date = date( "Y-m-d H:i:s", strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) );
680
681
	$unpaid_orders = $wpdb->get_col( $wpdb->prepare( "
682
		SELECT posts.ID
683
		FROM {$wpdb->posts} AS posts
684
		WHERE 	posts.post_type   IN ('" . implode( "','", wc_get_order_types() ) . "')
685
		AND 	posts.post_status = 'wc-pending'
686
		AND 	posts.post_modified < %s
687
	", $date ) );
688
689
	if ( $unpaid_orders ) {
690
		foreach ( $unpaid_orders as $unpaid_order ) {
691
			$order = wc_get_order( $unpaid_order );
692
693
			if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === get_post_meta( $unpaid_order, '_created_via', true ), $order ) ) {
694
				$order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) );
695
			}
696
		}
697
	}
698
699
	wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
700
	wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
701
}
702
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' );
703
704
/**
705
 * Return the count of processing orders.
706
 *
707
 * @access public
708
 * @return int
709
 */
710
function wc_processing_order_count() {
711
	return wc_orders_count( 'processing' );
712
}
713
714
/**
715
 * Return the orders count of a specific order status.
716
 *
717
 * @access public
718
 * @param string $status
719
 * @return int
720
 */
721
function wc_orders_count( $status ) {
722
	$count = 0;
723
724
	$order_statuses = array_keys( wc_get_order_statuses() );
725
726
	if ( ! in_array( 'wc-' . $status, $order_statuses ) ) {
727
		return 0;
728
	}
729
730
	foreach ( wc_get_order_types( 'order-count' ) as $type ) {
731
		$this_count  = wp_count_posts( $type, 'readable' );
732
		$count      += isset( $this_count->{'wc-' . $status} ) ? $this_count->{'wc-' . $status} : 0;
733
	}
734
735
	return $count;
736
}
737
738
/**
739
 * Clear all transients cache for order data.
740
 *
741
 * @param int $post_id (default: 0)
742
 */
743
function wc_delete_shop_order_transients( $post_id = 0 ) {
744
	$post_id             = absint( $post_id );
745
	$transients_to_clear = array();
746
747
	// Clear report transients
748
	$reports = WC_Admin_Reports::get_reports();
749
750
	foreach ( $reports as $report_group ) {
751
		foreach ( $report_group['reports'] as $report_key => $report ) {
752
			$transients_to_clear[] = 'wc_report_' . $report_key;
753
		}
754
	}
755
756
	// clear API report transient
757
	$transients_to_clear[] = 'wc_admin_report';
758
759
	// Clear transients where we have names
760
	foreach( $transients_to_clear as $transient ) {
761
		delete_transient( $transient );
762
	}
763
764
	// Clear money spent for user associated with order
765
	if ( $post_id && ( $user_id = get_post_meta( $post_id, '_customer_user', true ) ) ) {
766
		delete_user_meta( $user_id, '_money_spent' );
767
		delete_user_meta( $user_id, '_order_count' );
768
	}
769
770
	// Increments the transient version to invalidate cache
771
	WC_Cache_Helper::get_transient_version( 'orders', true );
772
773
	// Do the same for regular cache
774
	WC_Cache_Helper::incr_cache_prefix( 'orders' );
775
776
	do_action( 'woocommerce_delete_shop_order_transients', $post_id );
777
}
778
779
/**
780
 * See if we only ship to billing addresses.
781
 * @return bool
782
 */
783
function wc_ship_to_billing_address_only() {
784
	return 'billing_only' === get_option( 'woocommerce_ship_to_destination' );
785
}
786
787
/**
788
 * Create a new order refund programmatically.
789
 *
790
 * Returns a new refund object on success which can then be used to add additional data.
791
 *
792
 * @since 2.2
793
 * @param array $args
794
 * @return WC_Order_Refund|WP_Error
795
 */
796
function wc_create_refund( $args = array() ) {
797
	$default_args = array(
798
		'amount'     => '',
799
		'reason'     => null,
800
		'order_id'   => 0,
801
		'refund_id'  => 0,
802
		'line_items' => array(),
803
		'date'       => current_time( 'mysql', 0 )
804
	);
805
806
	$args        = wp_parse_args( $args, $default_args );
807
	$refund_data = array();
808
809
	// prevent negative refunds
810
	if ( 0 > $args['amount'] ) {
811
		$args['amount'] = 0;
812
	}
813
814
	if ( $args['refund_id'] > 0 ) {
815
		$updating          = true;
816
		$refund_data['ID'] = $args['refund_id'];
817
	} else {
818
		$updating                     = false;
819
		$refund_data['post_type']     = 'shop_order_refund';
820
		$refund_data['post_status']   = 'wc-completed';
821
		$refund_data['ping_status']   = 'closed';
822
		$refund_data['post_author']   = get_current_user_id() ? get_current_user_id() : 1;
823
		$refund_data['post_password'] = uniqid( 'refund_' );
824
		$refund_data['post_parent']   = absint( $args['order_id'] );
825
		$refund_data['post_title']    = sprintf( __( 'Refund &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
826
		$refund_data['post_date']     = $args['date'];
827
	}
828
829
	if ( ! is_null( $args['reason'] ) ) {
830
		$refund_data['post_excerpt'] = $args['reason'];
831
	}
832
833
	if ( $updating ) {
834
		$refund_id = wp_update_post( $refund_data );
835
	} else {
836
		$refund_id = wp_insert_post( apply_filters( 'woocommerce_new_refund_data', $refund_data ), true );
837
	}
838
839
	if ( is_wp_error( $refund_id ) ) {
840
		return $refund_id;
841
	}
842
843
	if ( ! $updating ) {
844
		// Default refund meta data
845
		update_post_meta( $refund_id, '_refund_amount', wc_format_decimal( $args['amount'] ) );
846
847
		// Get refund object
848
		$refund = wc_get_order( $refund_id );
849
		$order  = wc_get_order( $args['order_id'] );
850
851
		// Refund currency is the same used for the parent order
852
		update_post_meta( $refund_id, '_order_currency', $order->get_order_currency() );
853
854
		// Negative line items
855
		if ( sizeof( $args['line_items'] ) > 0 ) {
856
			$order_items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) );
857
858
			foreach ( $args['line_items'] as $refund_item_id => $refund_item ) {
859
				if ( isset( $order_items[ $refund_item_id ] ) ) {
860
					if ( empty( $refund_item['qty'] ) && empty( $refund_item['refund_total'] ) && empty( $refund_item['refund_tax'] ) ) {
861
						continue;
862
					}
863
864
					// Prevents errors when the order has no taxes
865
					if ( ! isset( $refund_item['refund_tax'] ) ) {
866
						$refund_item['refund_tax'] = array();
867
					}
868
869
					switch ( $order_items[ $refund_item_id ]['type'] ) {
870
						case 'line_item' :
871
							$line_item_args = array(
872
								'totals' => array(
873
									'subtotal'     => wc_format_refund_total( $refund_item['refund_total'] ),
874
									'total'        => wc_format_refund_total( $refund_item['refund_total'] ),
875
									'subtotal_tax' => wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) ),
876
									'tax'          => wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) ),
877
									'tax_data'     => array( 'total' => array_map( 'wc_format_refund_total', $refund_item['refund_tax'] ), 'subtotal' => array_map( 'wc_format_refund_total', $refund_item['refund_tax'] ) )
878
								)
879
							);
880
							$new_item_id = $refund->add_product( $order->get_product_from_item( $order_items[ $refund_item_id ] ), isset( $refund_item['qty'] ) ? $refund_item['qty'] * -1 : 0, $line_item_args );
881
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
882
						break;
883
						case 'shipping' :
884
							$shipping    = new WC_Shipping_Rate( $order_items[ $refund_item_id ]['method_id'], $order_items[ $refund_item_id ]['name'], wc_format_refund_total( $refund_item['refund_total'] ), array_map( 'wc_format_refund_total', $refund_item['refund_tax'] ), $order_items[ $refund_item_id ]['method_id'] );
885
							$new_item_id = $refund->add_shipping( $shipping );
886
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
887
						break;
888
						case 'fee' :
889
							$fee            = new stdClass();
890
							$fee->name      = $order_items[ $refund_item_id ]['name'];
891
							$fee->tax_class = $order_items[ $refund_item_id ]['tax_class'];
892
							$fee->taxable   = $fee->tax_class !== '0';
893
							$fee->amount    = wc_format_refund_total( $refund_item['refund_total'] );
894
							$fee->tax       = wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) );
895
							$fee->tax_data  = array_map( 'wc_format_refund_total', $refund_item['refund_tax'] );
896
897
							$new_item_id = $refund->add_fee( $fee );
898
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
899
						break;
900
					}
901
				}
902
			}
903
			$refund->update_taxes();
904
		}
905
906
		$refund->calculate_totals( false );
907
908
		// Set total to total refunded which may vary from order items
909
		$refund->set_total( wc_format_decimal( $args['amount'] ) * -1, 'total' );
910
911
		do_action( 'woocommerce_refund_created', $refund_id, $args );
912
	}
913
914
	// Clear transients
915
	wc_delete_shop_order_transients( $args['order_id'] );
916
917
	return new WC_Order_Refund( $refund_id );
918
}
919
920
/**
921
 * Get tax class by tax id.
922
 *
923
 * @since 2.2
924
 * @param int $tax_id
925
 * @return string
926
 */
927
function wc_get_tax_class_by_tax_id( $tax_id ) {
928
	global $wpdb;
929
930
	$tax_class = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) );
931
932
	return wc_clean( $tax_class );
933
}
934
935
/**
936
 * Get payment gateway class by order data.
937
 *
938
 * @since 2.2
939
 * @param int|WC_Order $order
940
 * @return WC_Payment_Gateway|bool
941
 */
942
function wc_get_payment_gateway_by_order( $order ) {
943
	if ( WC()->payment_gateways() ) {
944
		$payment_gateways = WC()->payment_gateways->payment_gateways();
945
	} else {
946
		$payment_gateways = array();
947
	}
948
949
	if ( ! is_object( $order ) ) {
950
		$order_id = absint( $order );
951
		$order    = wc_get_order( $order_id );
952
	}
953
954
	return isset( $payment_gateways[ $order->payment_method ] ) ? $payment_gateways[ $order->payment_method ] : false;
955
}
956
957
/**
958
 * When refunding an order, create a refund line item if the partial refunds do not match order total.
959
 *
960
 * This is manual; no gateway refund will be performed.
961
 *
962
 * @since 2.4
963
 * @param int $order_id
964
 */
965
function wc_order_fully_refunded( $order_id ) {
966
	$order       = wc_get_order( $order_id );
967
	$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
968
969
	if ( ! $max_refund ) {
970
		return;
971
	}
972
973
	// Create the refund object
974
	wc_create_refund( array(
975
		'amount'     => $max_refund,
976
		'reason'     => __( 'Order Fully Refunded', 'woocommerce' ),
977
		'order_id'   => $order_id,
978
		'line_items' => array()
979
	) );
980
981
	wc_delete_shop_order_transients( $order_id );
982
}
983
add_action( 'woocommerce_order_status_refunded', 'wc_order_fully_refunded' );
984