Completed
Push — master ( ad3f9d...dea212 )
by Mike
08:30
created

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

Complexity

Conditions 12
Paths 1152

Size

Total Lines 90
Code Lines 59

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 12
dl 0
loc 90
rs 2
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
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 49 and the first side effect is on line 14.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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
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'] );
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
function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) {
607
	return update_metadata( 'order_item', $item_id, $meta_key, $meta_value, $prev_value );
608
}
609
610
/**
611
 * WooCommerce Order Item Meta API - Add term meta.
612
 *
613
 * @access public
614
 * @param mixed $item_id
615
 * @param mixed $meta_key
616
 * @param mixed $meta_value
617
 * @param bool $unique (default: false)
618
 * @return bool
619
 */
620
function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = false ) {
621
	return add_metadata( 'order_item', $item_id, $meta_key, $meta_value, $unique );
622
}
623
624
/**
625
 * WooCommerce Order Item Meta API - Delete term meta.
626
 *
627
 * @access public
628
 * @param mixed $item_id
629
 * @param mixed $meta_key
630
 * @param string $meta_value (default: '')
631
 * @param bool $delete_all (default: false)
632
 * @return bool
633
 */
634
function wc_delete_order_item_meta( $item_id, $meta_key, $meta_value = '', $delete_all = false ) {
635
	return delete_metadata( 'order_item', $item_id, $meta_key, $meta_value, $delete_all );
636
}
637
638
/**
639
 * WooCommerce Order Item Meta API - Get term meta.
640
 *
641
 * @access public
642
 * @param mixed $item_id
643
 * @param mixed $key
644
 * @param bool $single (default: true)
645
 * @return mixed
646
 */
647
function wc_get_order_item_meta( $item_id, $key, $single = true ) {
648
	return get_metadata( 'order_item', $item_id, $key, $single );
649
}
650
651
/**
652
 * Cancel all unpaid orders after held duration to prevent stock lock for those products.
653
 *
654
 * @access public
655
 */
656
function wc_cancel_unpaid_orders() {
657
	global $wpdb;
658
659
	$held_duration = get_option( 'woocommerce_hold_stock_minutes' );
660
661
	if ( $held_duration < 1 || get_option( 'woocommerce_manage_stock' ) != 'yes' )
662
		return;
663
664
	$date = date( "Y-m-d H:i:s", strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) );
665
666
	$unpaid_orders = $wpdb->get_col( $wpdb->prepare( "
667
		SELECT posts.ID
668
		FROM {$wpdb->posts} AS posts
669
		WHERE 	posts.post_type   IN ('" . implode( "','", wc_get_order_types() ) . "')
670
		AND 	posts.post_status = 'wc-pending'
671
		AND 	posts.post_modified < %s
672
	", $date ) );
673
674
	if ( $unpaid_orders ) {
675
		foreach ( $unpaid_orders as $unpaid_order ) {
676
			$order = wc_get_order( $unpaid_order );
677
678
			if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === get_post_meta( $unpaid_order, '_created_via', true ), $order ) ) {
679
				$order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) );
680
			}
681
		}
682
	}
683
684
	wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
685
	wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
686
}
687
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' );
688
689
/**
690
 * Return the count of processing orders.
691
 *
692
 * @access public
693
 * @return int
694
 */
695
function wc_processing_order_count() {
696
	return wc_orders_count( 'processing' );
697
}
698
699
/**
700
 * Return the orders count of a specific order status.
701
 *
702
 * @access public
703
 * @param string $status
704
 * @return int
705
 */
706
function wc_orders_count( $status ) {
707
	$count = 0;
708
709
	$order_statuses = array_keys( wc_get_order_statuses() );
710
711
	if ( ! in_array( 'wc-' . $status, $order_statuses ) ) {
712
		return 0;
713
	}
714
715
	foreach ( wc_get_order_types( 'order-count' ) as $type ) {
716
		$this_count  = wp_count_posts( $type, 'readable' );
717
		$count      += isset( $this_count->{'wc-' . $status} ) ? $this_count->{'wc-' . $status} : 0;
718
	}
719
720
	return $count;
721
}
722
723
/**
724
 * Clear all transients cache for order data.
725
 *
726
 * @param int $post_id (default: 0)
727
 */
728
function wc_delete_shop_order_transients( $post_id = 0 ) {
729
	$post_id             = absint( $post_id );
730
	$transients_to_clear = array();
731
732
	// Clear report transients
733
	$reports = WC_Admin_Reports::get_reports();
734
735
	foreach ( $reports as $report_group ) {
736
		foreach ( $report_group['reports'] as $report_key => $report ) {
737
			$transients_to_clear[] = 'wc_report_' . $report_key;
738
		}
739
	}
740
741
	// clear API report transient
742
	$transients_to_clear[] = 'wc_admin_report';
743
744
	// Clear transients where we have names
745
	foreach( $transients_to_clear as $transient ) {
746
		delete_transient( $transient );
747
	}
748
749
	// Clear money spent for user associated with order
750
	if ( $post_id && ( $user_id = get_post_meta( $post_id, '_customer_user', true ) ) ) {
751
		delete_user_meta( $user_id, '_money_spent' );
752
		delete_user_meta( $user_id, '_order_count' );
753
	}
754
755
	// Increments the transient version to invalidate cache
756
	WC_Cache_Helper::get_transient_version( 'orders', true );
757
758
	// Do the same for regular cache
759
	WC_Cache_Helper::incr_cache_prefix( 'orders' );
760
761
	do_action( 'woocommerce_delete_shop_order_transients', $post_id );
762
}
763
764
/**
765
 * See if we only ship to billing addresses.
766
 * @return bool
767
 */
768
function wc_ship_to_billing_address_only() {
769
	return 'billing_only' === get_option( 'woocommerce_ship_to_destination' );
770
}
771
772
/**
773
 * Create a new order refund programmatically.
774
 *
775
 * Returns a new refund object on success which can then be used to add additional data.
776
 *
777
 * @since 2.2
778
 * @param array $args
779
 * @return WC_Order_Refund|WP_Error
780
 */
781
function wc_create_refund( $args = array() ) {
782
	$default_args = array(
783
		'amount'     => '',
784
		'reason'     => null,
785
		'order_id'   => 0,
786
		'refund_id'  => 0,
787
		'line_items' => array(),
788
		'date'       => current_time( 'mysql', 0 )
789
	);
790
791
	$args        = wp_parse_args( $args, $default_args );
792
	$refund_data = array();
793
794
	// prevent negative refunds
795
	if ( 0 > $args['amount'] ) {
796
		$args['amount'] = 0;
797
	}
798
799
	if ( $args['refund_id'] > 0 ) {
800
		$updating          = true;
801
		$refund_data['ID'] = $args['refund_id'];
802
	} else {
803
		$updating                     = false;
804
		$refund_data['post_type']     = 'shop_order_refund';
805
		$refund_data['post_status']   = 'wc-completed';
806
		$refund_data['ping_status']   = 'closed';
807
		$refund_data['post_author']   = get_current_user_id() ? get_current_user_id() : 1;
808
		$refund_data['post_password'] = uniqid( 'refund_' );
809
		$refund_data['post_parent']   = absint( $args['order_id'] );
810
		$refund_data['post_title']    = sprintf( __( 'Refund &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
811
		$refund_data['post_date']     = $args['date'];
812
	}
813
814
	if ( ! is_null( $args['reason'] ) ) {
815
		$refund_data['post_excerpt'] = $args['reason'];
816
	}
817
818
	if ( $updating ) {
819
		$refund_id = wp_update_post( $refund_data );
820
	} else {
821
		$refund_id = wp_insert_post( apply_filters( 'woocommerce_new_refund_data', $refund_data ), true );
822
	}
823
824
	if ( is_wp_error( $refund_id ) ) {
825
		return $refund_id;
826
	}
827
828
	if ( ! $updating ) {
829
		// Default refund meta data
830
		update_post_meta( $refund_id, '_refund_amount', wc_format_decimal( $args['amount'] ) );
831
832
		// Get refund object
833
		$refund = wc_get_order( $refund_id );
834
		$order  = wc_get_order( $args['order_id'] );
835
836
		// Refund currency is the same used for the parent order
837
		update_post_meta( $refund_id, '_order_currency', $order->get_order_currency() );
838
839
		// Negative line items
840
		if ( sizeof( $args['line_items'] ) > 0 ) {
841
			$order_items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) );
842
843
			foreach ( $args['line_items'] as $refund_item_id => $refund_item ) {
844
				if ( isset( $order_items[ $refund_item_id ] ) ) {
845
					if ( empty( $refund_item['qty'] ) && empty( $refund_item['refund_total'] ) && empty( $refund_item['refund_tax'] ) ) {
846
						continue;
847
					}
848
849
					// Prevents errors when the order has no taxes
850
					if ( ! isset( $refund_item['refund_tax'] ) ) {
851
						$refund_item['refund_tax'] = array();
852
					}
853
854
					switch ( $order_items[ $refund_item_id ]['type'] ) {
855
						case 'line_item' :
856
							$line_item_args = array(
857
								'totals' => array(
858
									'subtotal'     => wc_format_refund_total( $refund_item['refund_total'] ),
859
									'total'        => wc_format_refund_total( $refund_item['refund_total'] ),
860
									'subtotal_tax' => wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) ),
861
									'tax'          => wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) ),
862
									'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'] ) )
863
								)
864
							);
865
							$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 );
866
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
867
						break;
868
						case 'shipping' :
869
							$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'] );
870
							$new_item_id = $refund->add_shipping( $shipping );
871
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
872
						break;
873
						case 'fee' :
874
							$fee            = new stdClass();
875
							$fee->name      = $order_items[ $refund_item_id ]['name'];
876
							$fee->tax_class = $order_items[ $refund_item_id ]['tax_class'];
877
							$fee->taxable   = $fee->tax_class !== '0';
878
							$fee->amount    = wc_format_refund_total( $refund_item['refund_total'] );
879
							$fee->tax       = wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) );
880
							$fee->tax_data  = array_map( 'wc_format_refund_total', $refund_item['refund_tax'] );
881
882
							$new_item_id = $refund->add_fee( $fee );
883
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
884
						break;
885
					}
886
				}
887
			}
888
			$refund->update_taxes();
889
		}
890
891
		$refund->calculate_totals( false );
892
893
		// Set total to total refunded which may vary from order items
894
		$refund->set_total( wc_format_decimal( $args['amount'] ) * -1, 'total' );
895
896
		do_action( 'woocommerce_refund_created', $refund_id, $args );
897
	}
898
899
	// Clear transients
900
	wc_delete_shop_order_transients( $args['order_id'] );
901
902
	return new WC_Order_Refund( $refund_id );
903
}
904
905
/**
906
 * Get tax class by tax id.
907
 *
908
 * @since 2.2
909
 * @param int $tax_id
910
 * @return string
911
 */
912
function wc_get_tax_class_by_tax_id( $tax_id ) {
913
	global $wpdb;
914
915
	$tax_class = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) );
916
917
	return wc_clean( $tax_class );
918
}
919
920
/**
921
 * Get payment gateway class by order data.
922
 *
923
 * @since 2.2
924
 * @param int|WC_Order $order
925
 * @return WC_Payment_Gateway|bool
926
 */
927
function wc_get_payment_gateway_by_order( $order ) {
928
	if ( WC()->payment_gateways() ) {
929
		$payment_gateways = WC()->payment_gateways->payment_gateways();
930
	} else {
931
		$payment_gateways = array();
932
	}
933
934
	if ( ! is_object( $order ) ) {
935
		$order_id = absint( $order );
936
		$order    = wc_get_order( $order_id );
937
	}
938
939
	return isset( $payment_gateways[ $order->payment_method ] ) ? $payment_gateways[ $order->payment_method ] : false;
940
}
941
942
/**
943
 * When refunding an order, create a refund line item if the partial refunds do not match order total.
944
 *
945
 * This is manual; no gateway refund will be performed.
946
 *
947
 * @since 2.4
948
 * @param int $order_id
949
 */
950
function wc_order_fully_refunded( $order_id ) {
951
	$order       = wc_get_order( $order_id );
952
	$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
953
954
	if ( ! $max_refund ) {
955
		return;
956
	}
957
958
	// Create the refund object
959
	$refund = wc_create_refund( array(
0 ignored issues
show
Unused Code introduced by
$refund is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
960
		'amount'     => $max_refund,
961
		'reason'     => __( 'Order Fully Refunded', 'woocommerce' ),
962
		'order_id'   => $order_id,
963
		'line_items' => array()
964
	) );
965
966
	wc_delete_shop_order_transients( $order_id );
967
}
968
add_action( 'woocommerce_order_status_refunded', 'wc_order_fully_refunded' );
969