Completed
Pull Request — master (#11762)
by Mike
09:50
created

wc-order-functions.php ➔ wc_create_refund()   D

Complexity

Conditions 14
Paths 248

Size

Total Lines 72
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 14
eloc 45
c 4
b 0
f 0
nc 248
nop 1
dl 0
loc 72
rs 4.1537

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->get_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->get_user_id() ),
433
		'user_email' 			=> $user_email,
434
		'order_id' 				=> $order->get_id(),
435
		'order_key' 			=> $order->get_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 = $item->get_product();
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
	global $wpdb;
723
724
	$count = 0;
725
	$status = 'wc-' . $status;
726
	$order_statuses = array_keys( wc_get_order_statuses() );
727
728
	if ( ! in_array( $status, $order_statuses ) ) {
729
		return 0;
730
	}
731
732
	$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . $status;
733
	$cached_count = wp_cache_get( $cache_key, 'counts' );
734
735
	if ( false !== $cached_count ) {
736
		return $cached_count;
737
	}
738
739
	foreach ( wc_get_order_types( 'order-count' ) as $type ) {
740
		$query = "SELECT COUNT( * ) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s";
741
		$count += $wpdb->get_var( $wpdb->prepare( $query, $type, $status ) );
742
	}
743
744
	wp_cache_set( $cache_key, $count, 'counts' );
745
746
	return $count;
747
}
748
749
/**
750
 * Clear all transients cache for order data.
751
 *
752
 * @param int $post_id (default: 0)
753
 */
754
function wc_delete_shop_order_transients( $post_id = 0 ) {
755
	$post_id             = absint( $post_id );
756
	$transients_to_clear = array();
757
758
	// Clear report transients
759
	$reports = WC_Admin_Reports::get_reports();
760
761
	foreach ( $reports as $report_group ) {
762
		foreach ( $report_group['reports'] as $report_key => $report ) {
763
			$transients_to_clear[] = 'wc_report_' . $report_key;
764
		}
765
	}
766
767
	// clear API report transient
768
	$transients_to_clear[] = 'wc_admin_report';
769
770
	// Clear transients where we have names
771
	foreach( $transients_to_clear as $transient ) {
772
		delete_transient( $transient );
773
	}
774
775
	// Clear money spent for user associated with order
776
	if ( $post_id && ( $user_id = get_post_meta( $post_id, '_customer_user', true ) ) ) {
777
		delete_user_meta( $user_id, '_money_spent' );
778
		delete_user_meta( $user_id, '_order_count' );
779
	}
780
781
	// Increments the transient version to invalidate cache
782
	WC_Cache_Helper::get_transient_version( 'orders', true );
783
784
	// Do the same for regular cache
785
	WC_Cache_Helper::incr_cache_prefix( 'orders' );
786
787
	do_action( 'woocommerce_delete_shop_order_transients', $post_id );
788
}
789
790
/**
791
 * See if we only ship to billing addresses.
792
 * @return bool
793
 */
794
function wc_ship_to_billing_address_only() {
795
	return 'billing_only' === get_option( 'woocommerce_ship_to_destination' );
796
}
797
798
/**
799
 * Create a new order refund programmatically.
800
 *
801
 * Returns a new refund object on success which can then be used to add additional data.
802
 *
803
 * @since 2.2
804
 * @param array $args
805
 * @return WC_Order_Refund|WP_Error
806
 */
807
function wc_create_refund( $args = array() ) {
808
	$default_args = array(
809
		'amount'     => 0,
810
		'reason'     => null,
811
		'order_id'   => 0,
812
		'refund_id'  => 0,
813
		'line_items' => array(),
814
	);
815
816
	try {
817
		$args   = wp_parse_args( $args, $default_args );
818
		$order  = wc_get_order( $args['order_id'] );
819
		$refund = new WC_Order_Refund( $args['refund_id'] );
820
821
		if ( ! $order ) {
822
			throw new Exception( __( 'Invalid order ID.', 'woocommerce' ) );
823
		}
824
825
		// prevent negative refunds
826
		if ( 0 > $args['amount'] ) {
827
			$args['amount'] = 0;
828
		}
829
		$refund->set_amount( $args['amount'] );
830
		$refund->set_parent_id( absint( $args['order_id'] ) );
831
		$refund->set_refunded_by( get_current_user_id() ? get_current_user_id() : 1 );
832
833
		if ( ! is_null( $args['reason'] ) ) {
834
			$refund->set_reason( $args['reason'] );
835
		}
836
837
		// Negative line items
838
		if ( sizeof( $args['line_items'] ) > 0 ) {
839
			$items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) );
840
841
			foreach ( $items as $item_id => $item ) {
842
				if ( ! isset( $args['line_items'][ $item_id ] ) || ( empty( $args['line_items'][ $item_id ]['qty'] ) && empty( $args['line_items'][ $item_id ]['refund_total'] ) && empty( $args['line_items'][ $item_id ]['refund_tax'] ) ) ) {
843
					continue;
844
				}
845
846
				if ( ! isset( $args['line_items'][ $item_id ]['refund_tax'] ) ) {
847
					$args['line_items'][ $item_id ]['refund_tax'] = array();
848
				}
849
850
				$class         = get_class( $item );
851
				$refunded_item = new $class( $item );
852
853
				$refunded_item->set_id( 0 );
854
				$refunded_item->add_meta_data( '_refunded_item_id', $item_id, true );
855
				$refunded_item->set_total( wc_format_refund_total( $args['line_items'][ $item_id ]['refund_total'] ) );
856
				$refunded_item->set_total_tax( wc_format_refund_total( array_sum( $args['line_items'][ $item_id ]['refund_tax'] ) ) );
857
				$refunded_item->set_taxes( array( 'total' => array_map( 'wc_format_refund_total', $args['line_items'][ $item_id ]['refund_tax'] ), 'subtotal' => array_map( 'wc_format_refund_total', $args['line_items'][ $item_id ]['refund_tax'] ) ) );
858
859
				if ( is_callable( array( $refunded_item, 'set_subtotal' ) ) ) {
860
					$refunded_item->set_subtotal( wc_format_refund_total( $args['line_items'][ $item_id ]['refund_total'] ) );
861
					$refunded_item->set_subtotal_tax( wc_format_refund_total( array_sum( $args['line_items'][ $item_id ]['refund_tax'] ) ) );
862
				}
863
864
				$refund->add_item( $refunded_item );
865
			}
866
		}
867
868
		$refund->update_taxes();
869
		$refund->calculate_totals( false );
870
		$refund->set_total( $args['amount'] * -1 );
871
		$refund->save();
872
873
	} catch ( Exception $e ) {
874
		return new WP_Error( 'error', $e->getMessage() );
875
	}
876
877
	return $refund;
878
}
879
880
/**
881
 * Get tax class by tax id.
882
 *
883
 * @since 2.2
884
 * @param int $tax_id
885
 * @return string
886
 */
887
function wc_get_tax_class_by_tax_id( $tax_id ) {
888
	global $wpdb;
889
890
	$tax_class = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) );
891
892
	return wc_clean( $tax_class );
893
}
894
895
/**
896
 * Get payment gateway class by order data.
897
 *
898
 * @since 2.2
899
 * @param int|WC_Order $order
900
 * @return WC_Payment_Gateway|bool
901
 */
902
function wc_get_payment_gateway_by_order( $order ) {
903
	if ( WC()->payment_gateways() ) {
904
		$payment_gateways = WC()->payment_gateways->payment_gateways();
905
	} else {
906
		$payment_gateways = array();
907
	}
908
909
	if ( ! is_object( $order ) ) {
910
		$order_id = absint( $order );
911
		$order    = wc_get_order( $order_id );
912
	}
913
914
	return isset( $payment_gateways[ $order->get_payment_method() ] ) ? $payment_gateways[ $order->get_payment_method() ] : false;
915
}
916
917
/**
918
 * When refunding an order, create a refund line item if the partial refunds do not match order total.
919
 *
920
 * This is manual; no gateway refund will be performed.
921
 *
922
 * @since 2.4
923
 * @param int $order_id
924
 */
925
function wc_order_fully_refunded( $order_id ) {
926
	$order       = wc_get_order( $order_id );
927
	$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
928
929
	if ( ! $max_refund ) {
930
		return;
931
	}
932
933
	// Create the refund object
934
	wc_create_refund( array(
935
		'amount'     => $max_refund,
936
		'reason'     => __( 'Order Fully Refunded', 'woocommerce' ),
937
		'order_id'   => $order_id,
938
		'line_items' => array()
939
	) );
940
941
	wc_delete_shop_order_transients( $order_id );
942
}
943
add_action( 'woocommerce_order_status_refunded', 'wc_order_fully_refunded' );
944
945
/**
946
 * Search in orders.
947
 *
948
 * @since  2.6.0
949
 * @param  string $term Term to search.
950
 * @return array List of orders ID.
951
 */
952
function wc_order_search( $term ) {
953
	global $wpdb;
954
955
	$term     = str_replace( 'Order #', '', wc_clean( $term ) );
956
	$post_ids = array();
957
958
	// Search fields.
959
	$search_fields = array_map( 'wc_clean', apply_filters( 'woocommerce_shop_order_search_fields', array(
960
		'_order_key',
961
		'_billing_company',
962
		'_billing_address_1',
963
		'_billing_address_2',
964
		'_billing_city',
965
		'_billing_postcode',
966
		'_billing_country',
967
		'_billing_state',
968
		'_billing_email',
969
		'_billing_phone',
970
		'_shipping_address_1',
971
		'_shipping_address_2',
972
		'_shipping_city',
973
		'_shipping_postcode',
974
		'_shipping_country',
975
		'_shipping_state'
976
	) ) );
977
978
	// Search orders.
979
	if ( is_numeric( $term ) ) {
980
		$post_ids = array_unique( array_merge(
981
			$wpdb->get_col(
982
				$wpdb->prepare( "SELECT DISTINCT p1.post_id FROM {$wpdb->postmeta} p1 WHERE p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "') AND p1.meta_value LIKE '%%%s%%';", wc_clean( $term ) )
983
			),
984
			array( absint( $term ) )
985
		) );
986
	} elseif ( ! empty( $search_fields ) ) {
987
		$post_ids = array_unique( array_merge(
988
			$wpdb->get_col(
989
				$wpdb->prepare( "
990
					SELECT DISTINCT p1.post_id
991
					FROM {$wpdb->postmeta} p1
992
					INNER JOIN {$wpdb->postmeta} p2 ON p1.post_id = p2.post_id
993
					WHERE
994
						( p1.meta_key = '_billing_first_name' AND p2.meta_key = '_billing_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' )
995
					OR
996
						( p1.meta_key = '_shipping_first_name' AND p2.meta_key = '_shipping_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' )
997
					OR
998
						( p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "') AND p1.meta_value LIKE '%%%s%%' )
999
					",
1000
					$term, $term, $term
1001
				)
1002
			),
1003
			$wpdb->get_col(
1004
				$wpdb->prepare( "
1005
					SELECT order_id
1006
					FROM {$wpdb->prefix}woocommerce_order_items as order_items
1007
					WHERE order_item_name LIKE '%%%s%%'
1008
					",
1009
					$term
1010
				)
1011
			)
1012
		) );
1013
	}
1014
1015
	return $post_ids;
1016
}
1017
1018
1019
/**
1020
 * Update total sales amount for each product within a paid order.
1021
 *
1022
 * @since 2.7.0
1023
 * @param int $order_id
1024
 */
1025
function wc_update_total_sales_counts( $order_id ) {
1026
	$order = wc_get_order( $order_id );
1027
1028
	if ( ! $order || 'yes' === get_post_meta( $order_id, '_recorded_sales', true ) ) {
1029
		return;
1030
	}
1031
1032
	if ( sizeof( $order->get_items() ) > 0 ) {
1033
		foreach ( $order->get_items() as $item ) {
1034
			if ( $item['product_id'] > 0 ) {
1035
				update_post_meta( $item['product_id'], 'total_sales', absint( get_post_meta( $item['product_id'], 'total_sales', true ) ) + absint( $item['qty'] ) );
1036
			}
1037
		}
1038
	}
1039
1040
	update_post_meta( $order_id, '_recorded_sales', 'yes' );
1041
1042
	/**
1043
	 * Called when sales for an order are recorded
1044
	 *
1045
	 * @param int $order_id order id
1046
	 */
1047
	do_action( 'woocommerce_recorded_sales', $order_id );
1048
}
1049
add_action( 'woocommerce_order_status_completed', 'wc_update_total_sales_counts' );
1050
add_action( 'woocommerce_order_status_processing', 'wc_update_total_sales_counts' );
1051
add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' );
1052
1053
/**
1054
 * Update used coupon amount for each coupon within an order.
1055
 *
1056
 * @since 2.7.0
1057
 * @param int $order_id
1058
 */
1059
function wc_update_coupon_usage_counts( $order_id ) {
1060
	$order        = wc_get_order( $order_id );
1061
	$has_recorded = get_post_meta( $order_id, '_recorded_coupon_usage_counts', true );
1062
1063
	if ( ! $order ) {
1064
		return;
1065
	}
1066
1067
	if ( $order->has_status( 'cancelled' ) && 'yes' === $has_recorded ) {
1068
		$action = 'reduce';
1069
		delete_post_meta( $order_id, '_recorded_coupon_usage_counts' );
1070
	} elseif ( ! $order->has_status( 'cancelled' ) && 'yes' !== $has_recorded ) {
1071
		$action = 'increase';
1072
		update_post_meta( $order_id, '_recorded_coupon_usage_counts', 'yes' );
1073
	} else {
1074
		return;
1075
	}
1076
1077
	if ( sizeof( $order->get_used_coupons() ) > 0 ) {
1078
		foreach ( $order->get_used_coupons() as $code ) {
1079
			if ( ! $code ) {
1080
				continue;
1081
			}
1082
1083
			$coupon = new WC_Coupon( $code );
1084
1085
			if ( ! $used_by = $order->get_user_id() ) {
1086
				$used_by = $order->get_billing_email();
1087
			}
1088
1089
			switch ( $action ) {
1090
				case 'reduce' :
1091
					$coupon->dcr_usage_count( $used_by );
1092
				break;
1093
				case 'increase' :
1094
					$coupon->inc_usage_count( $used_by );
1095
				break;
1096
			}
1097
		}
1098
	}
1099
}
1100
add_action( 'woocommerce_order_status_completed', 'wc_update_total_sales_counts' );
1101
add_action( 'woocommerce_order_status_processing', 'wc_update_total_sales_counts' );
1102
add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' );
1103
add_action( 'woocommerce_order_status_cancelled', 'wc_update_total_sales_counts' );
1104
1105
/**
1106
 * When a payment is complete, we can reduce stock levels for items within an order.
1107
 * @since 2.7.0
1108
 * @param int $order_id
1109
 */
1110
function wc_maybe_reduce_stock_levels( $order_id ) {
1111
	if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $order_id, '_order_stock_reduced', true ), $order_id ) ) {
1112
		wc_reduce_stock_levels( $order_id );
1113
		add_post_meta( $order_id, '_order_stock_reduced', '1', true );
1114
	}
1115
}
1116
add_action( 'woocommerce_payment_complete', 'wc_maybe_reduce_stock_levels' );
1117
1118
/**
1119
 * Reduce stock levels for items within an order.
1120
 * @since 2.7.0
1121
 * @param int $order_id
1122
 */
1123
function wc_reduce_stock_levels( $order_id ) {
1124
	$order = wc_get_order( $order_id );
1125
1126
	if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && $order && apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) && sizeof( $order->get_items() ) > 0 ) {
1127
		foreach ( $order->get_items() as $item ) {
1128
			if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->managing_stock() ) {
1129
				$qty       = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $order, $item );
1130
				$new_stock = $product->reduce_stock( $qty );
1131
				$item_name = $product->get_sku() ? $product->get_sku(): $item['product_id'];
1132
1133
				if ( ! empty( $item['variation_id'] ) ) {
1134
					$order->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 ) );
1135
				} else {
1136
					$order->add_order_note( sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock ) );
1137
				}
1138
1139
				if ( $new_stock < 0 ) {
1140
		            do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $order_id, 'quantity' => $qty_ordered ) );
1141
		        }
1142
			}
1143
		}
1144
1145
		do_action( 'woocommerce_reduce_order_stock', $order );
1146
	}
1147
}
1148