Completed
Push — master ( cf5952...e4c91a )
by Mike
11:38
created

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

Complexity

Conditions 15
Paths 376

Size

Total Lines 77
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 15
eloc 48
c 3
b 1
f 0
nc 376
nop 1
dl 0
loc 77
rs 3.9271

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',
539
			'%s',
540
			'%d',
541
		)
542
	);
543
544
	$item_id = absint( $wpdb->insert_id );
545
546
	do_action( 'woocommerce_new_order_item', $item_id, $item, $order_id );
547
548
	return $item_id;
549
}
550
551
/**
552
 * Update an item for an order.
553
 *
554
 * @since 2.2
555
 * @param int $item_id
556
 * @param array $args either `order_item_type` or `order_item_name`
557
 * @return bool true if successfully updated, false otherwise
558
 */
559
function wc_update_order_item( $item_id, $args ) {
560
	global $wpdb;
561
562
	$update = $wpdb->update( $wpdb->prefix . 'woocommerce_order_items', $args, array( 'order_item_id' => $item_id ) );
563
564
	if ( false === $update ) {
565
		return false;
566
	}
567
568
	do_action( 'woocommerce_update_order_item', $item_id, $args );
569
570
	return true;
571
}
572
573
/**
574
 * Delete an item from the order it belongs to based on item id.
575
 *
576
 * @access public
577
 * @param int $item_id
578
 * @return bool
579
 */
580
function wc_delete_order_item( $item_id ) {
581
	global $wpdb;
582
583
	$item_id = absint( $item_id );
584
585
	if ( ! $item_id )
586
		return false;
587
588
	do_action( 'woocommerce_before_delete_order_item', $item_id );
589
590
	$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d", $item_id ) );
591
	$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d", $item_id ) );
592
593
	do_action( 'woocommerce_delete_order_item', $item_id );
594
595
	return true;
596
}
597
598
/**
599
 * WooCommerce Order Item Meta API - Update term meta.
600
 *
601
 * @access public
602
 * @param mixed $item_id
603
 * @param mixed $meta_key
604
 * @param mixed $meta_value
605
 * @param string $prev_value (default: '')
606
 * @return bool
607
 */
608 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...
609
	if ( update_metadata( 'order_item', $item_id, $meta_key, $meta_value, $prev_value ) ) {
610
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
611
		wp_cache_delete( $cache_key, 'orders' );
612
		return true;
613
	}
614
	return false;
615
}
616
617
/**
618
 * WooCommerce Order Item Meta API - Add term meta.
619
 *
620
 * @access public
621
 * @param mixed $item_id
622
 * @param mixed $meta_key
623
 * @param mixed $meta_value
624
 * @param bool $unique (default: false)
625
 * @return int New row ID or 0
626
 */
627 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...
628
	if ( $meta_id = add_metadata( 'order_item', $item_id, $meta_key, $meta_value, $unique ) ) {
629
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
630
		wp_cache_delete( $cache_key, 'orders' );
631
		return $meta_id;
632
	}
633
	return 0;
634
}
635
636
/**
637
 * WooCommerce Order Item Meta API - Delete term meta.
638
 *
639
 * @access public
640
 * @param mixed $item_id
641
 * @param mixed $meta_key
642
 * @param string $meta_value (default: '')
643
 * @param bool $delete_all (default: false)
644
 * @return bool
645
 */
646 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...
647
	if ( delete_metadata( 'order_item', $item_id, $meta_key, $meta_value, $delete_all ) ) {
648
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
649
		wp_cache_delete( $cache_key, 'orders' );
650
		return true;
651
	}
652
	return false;
653
}
654
655
/**
656
 * WooCommerce Order Item Meta API - Get term meta.
657
 *
658
 * @access public
659
 * @param mixed $item_id
660
 * @param mixed $key
661
 * @param bool $single (default: true)
662
 * @return mixed
663
 */
664
function wc_get_order_item_meta( $item_id, $key, $single = true ) {
665
	return get_metadata( 'order_item', $item_id, $key, $single );
666
}
667
668
/**
669
 * Cancel all unpaid orders after held duration to prevent stock lock for those products.
670
 *
671
 * @access public
672
 */
673
function wc_cancel_unpaid_orders() {
674
	global $wpdb;
675
676
	$held_duration = get_option( 'woocommerce_hold_stock_minutes' );
677
678
	if ( $held_duration < 1 || get_option( 'woocommerce_manage_stock' ) != 'yes' )
679
		return;
680
681
	$date = date( "Y-m-d H:i:s", strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) );
682
683
	$unpaid_orders = $wpdb->get_col( $wpdb->prepare( "
684
		SELECT posts.ID
685
		FROM {$wpdb->posts} AS posts
686
		WHERE 	posts.post_type   IN ('" . implode( "','", wc_get_order_types() ) . "')
687
		AND 	posts.post_status = 'wc-pending'
688
		AND 	posts.post_modified < %s
689
	", $date ) );
690
691
	if ( $unpaid_orders ) {
692
		foreach ( $unpaid_orders as $unpaid_order ) {
693
			$order = wc_get_order( $unpaid_order );
694
695
			if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === get_post_meta( $unpaid_order, '_created_via', true ), $order ) ) {
696
				$order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) );
697
			}
698
		}
699
	}
700
701
	wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
702
	wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
703
}
704
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' );
705
706
/**
707
 * Return the count of processing orders.
708
 *
709
 * @access public
710
 * @return int
711
 */
712
function wc_processing_order_count() {
713
	return wc_orders_count( 'processing' );
714
}
715
716
/**
717
 * Return the orders count of a specific order status.
718
 *
719
 * @access public
720
 * @param string $status
721
 * @return int
722
 */
723
function wc_orders_count( $status ) {
724
	global $wpdb;
725
726
	$count = 0;
727
	$status = 'wc-' . $status;
728
	$order_statuses = array_keys( wc_get_order_statuses() );
729
730
	if ( ! in_array( $status, $order_statuses ) ) {
731
		return 0;
732
	}
733
734
	$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . $status;
735
	$cached_count = wp_cache_get( $cache_key, 'counts' );
736
737
	if ( false !== $cached_count ) {
738
		return $cached_count;
739
	}
740
741
	foreach ( wc_get_order_types( 'order-count' ) as $type ) {
742
		$query = "SELECT COUNT( * ) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = %s";
743
		$count += $wpdb->get_var( $wpdb->prepare( $query, $type, $status ) );
744
	}
745
746
	wp_cache_set( $cache_key, $count, 'counts' );
747
748
	return $count;
749
}
750
751
/**
752
 * Clear all transients cache for order data.
753
 *
754
 * @param int $post_id (default: 0)
755
 */
756
function wc_delete_shop_order_transients( $post_id = 0 ) {
757
	$post_id             = absint( $post_id );
758
	$transients_to_clear = array();
759
760
	// Clear report transients
761
	$reports = WC_Admin_Reports::get_reports();
762
763
	foreach ( $reports as $report_group ) {
764
		foreach ( $report_group['reports'] as $report_key => $report ) {
765
			$transients_to_clear[] = 'wc_report_' . $report_key;
766
		}
767
	}
768
769
	// clear API report transient
770
	$transients_to_clear[] = 'wc_admin_report';
771
772
	// Clear transients where we have names
773
	foreach ( $transients_to_clear as $transient ) {
774
		delete_transient( $transient );
775
	}
776
777
	// Clear money spent for user associated with order
778
	if ( $post_id && ( $user_id = get_post_meta( $post_id, '_customer_user', true ) ) ) {
779
		delete_user_meta( $user_id, '_money_spent' );
780
		delete_user_meta( $user_id, '_order_count' );
781
	}
782
783
	// Increments the transient version to invalidate cache
784
	WC_Cache_Helper::get_transient_version( 'orders', true );
785
786
	// Do the same for regular cache
787
	WC_Cache_Helper::incr_cache_prefix( 'orders' );
788
789
	do_action( 'woocommerce_delete_shop_order_transients', $post_id );
790
}
791
792
/**
793
 * See if we only ship to billing addresses.
794
 * @return bool
795
 */
796
function wc_ship_to_billing_address_only() {
797
	return 'billing_only' === get_option( 'woocommerce_ship_to_destination' );
798
}
799
800
/**
801
 * Create a new order refund programmatically.
802
 *
803
 * Returns a new refund object on success which can then be used to add additional data.
804
 *
805
 * @since 2.2
806
 * @param array $args
807
 * @return WC_Order_Refund|WP_Error
808
 */
809
function wc_create_refund( $args = array() ) {
810
	$default_args = array(
811
		'amount'     => 0,
812
		'reason'     => null,
813
		'order_id'   => 0,
814
		'refund_id'  => 0,
815
		'line_items' => array(),
816
	);
817
818
	try {
819
		$args   = wp_parse_args( $args, $default_args );
820
		$order  = wc_get_order( $args['order_id'] );
821
		$refund = new WC_Order_Refund( $args['refund_id'] );
822
823
		if ( ! $order ) {
824
			throw new Exception( __( 'Invalid order ID.', 'woocommerce' ) );
825
		}
826
827
		// prevent negative refunds
828
		if ( 0 > $args['amount'] ) {
829
			$args['amount'] = 0;
830
		}
831
		$refund->set_amount( $args['amount'] );
832
		$refund->set_parent_id( absint( $args['order_id'] ) );
833
		$refund->set_refunded_by( get_current_user_id() ? get_current_user_id() : 1 );
834
835
		if ( ! is_null( $args['reason'] ) ) {
836
			$refund->set_reason( $args['reason'] );
837
		}
838
839
		// Negative line items
840
		if ( sizeof( $args['line_items'] ) > 0 ) {
841
			$items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) );
842
843
			foreach ( $items as $item_id => $item ) {
844
				if ( ! isset( $args['line_items'][ $item_id ] ) ) {
845
					continue;
846
				}
847
848
				$qty          = $args['line_items'][ $item_id ]['qty'];
849
				$refund_total = $args['line_items'][ $item_id ]['refund_total'];
850
				$refund_tax   = isset( $args['line_items'][ $item_id ]['refund_tax'] ) ? array_filter( (array) $args['line_items'][ $item_id ]['refund_tax'] ) : array();
851
852
				if ( empty( $qty ) && empty( $refund_total ) && empty( $args['line_items'][ $item_id ]['refund_tax'] ) ) {
853
					continue;
854
				}
855
856
				$class         = get_class( $item );
857
				$refunded_item = new $class( $item );
858
				$refunded_item->set_id( 0 );
859
				$refunded_item->add_meta_data( '_refunded_item_id', $item_id, true );
860
				$refunded_item->set_total( wc_format_refund_total( $refund_total ) );
861
				$refunded_item->set_taxes( array( 'total' => array_map( 'wc_format_refund_total', $refund_tax ), 'subtotal' => array_map( 'wc_format_refund_total', $refund_tax ) ) );
862
863
				if ( is_callable( array( $refunded_item, 'set_subtotal' ) ) ) {
864
					$refunded_item->set_subtotal( wc_format_refund_total( $refund_total ) );
865
				}
866
867
				if ( is_callable( array( $refunded_item, 'set_quantity' ) ) ) {
868
					$refunded_item->set_quantity( $qty );
869
				}
870
871
				$refund->add_item( $refunded_item );
872
			}
873
		}
874
875
		$refund->update_taxes();
876
		$refund->calculate_totals( false );
877
		$refund->set_total( $args['amount'] * -1 );
878
		$refund->save();
879
880
	} catch ( Exception $e ) {
881
		return new WP_Error( 'error', $e->getMessage() );
882
	}
883
884
	return $refund;
885
}
886
887
/**
888
 * Get tax class by tax id.
889
 *
890
 * @since 2.2
891
 * @param int $tax_id
892
 * @return string
893
 */
894
function wc_get_tax_class_by_tax_id( $tax_id ) {
895
	global $wpdb;
896
897
	$tax_class = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) );
898
899
	return wc_clean( $tax_class );
900
}
901
902
/**
903
 * Get payment gateway class by order data.
904
 *
905
 * @since 2.2
906
 * @param int|WC_Order $order
907
 * @return WC_Payment_Gateway|bool
908
 */
909
function wc_get_payment_gateway_by_order( $order ) {
910
	if ( WC()->payment_gateways() ) {
911
		$payment_gateways = WC()->payment_gateways->payment_gateways();
912
	} else {
913
		$payment_gateways = array();
914
	}
915
916
	if ( ! is_object( $order ) ) {
917
		$order_id = absint( $order );
918
		$order    = wc_get_order( $order_id );
919
	}
920
921
	return isset( $payment_gateways[ $order->get_payment_method() ] ) ? $payment_gateways[ $order->get_payment_method() ] : false;
922
}
923
924
/**
925
 * When refunding an order, create a refund line item if the partial refunds do not match order total.
926
 *
927
 * This is manual; no gateway refund will be performed.
928
 *
929
 * @since 2.4
930
 * @param int $order_id
931
 */
932
function wc_order_fully_refunded( $order_id ) {
933
	$order       = wc_get_order( $order_id );
934
	$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
935
936
	if ( ! $max_refund ) {
937
		return;
938
	}
939
940
	// Create the refund object
941
	wc_create_refund( array(
942
		'amount'     => $max_refund,
943
		'reason'     => __( 'Order Fully Refunded', 'woocommerce' ),
944
		'order_id'   => $order_id,
945
		'line_items' => array(),
946
	) );
947
948
	wc_delete_shop_order_transients( $order_id );
949
}
950
add_action( 'woocommerce_order_status_refunded', 'wc_order_fully_refunded' );
951
952
/**
953
 * Search in orders.
954
 *
955
 * @since  2.6.0
956
 * @param  string $term Term to search.
957
 * @return array List of orders ID.
958
 */
959
function wc_order_search( $term ) {
960
	global $wpdb;
961
962
	$term     = str_replace( 'Order #', '', wc_clean( $term ) );
963
	$post_ids = array();
964
965
	// Search fields.
966
	$search_fields = array_map( 'wc_clean', apply_filters( 'woocommerce_shop_order_search_fields', array(
967
		'_order_key',
968
		'_billing_company',
969
		'_billing_address_1',
970
		'_billing_address_2',
971
		'_billing_city',
972
		'_billing_postcode',
973
		'_billing_country',
974
		'_billing_state',
975
		'_billing_email',
976
		'_billing_phone',
977
		'_shipping_address_1',
978
		'_shipping_address_2',
979
		'_shipping_city',
980
		'_shipping_postcode',
981
		'_shipping_country',
982
		'_shipping_state',
983
	) ) );
984
985
	// Search orders.
986
	if ( is_numeric( $term ) ) {
987
		$post_ids = array_unique( array_merge(
988
			$wpdb->get_col(
989
				$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 ) )
990
			),
991
			array( absint( $term ) )
992
		) );
993
	} elseif ( ! empty( $search_fields ) ) {
994
		$post_ids = array_unique( array_merge(
995
			$wpdb->get_col(
996
				$wpdb->prepare( "
997
					SELECT DISTINCT p1.post_id
998
					FROM {$wpdb->postmeta} p1
999
					INNER JOIN {$wpdb->postmeta} p2 ON p1.post_id = p2.post_id
1000
					WHERE
1001
						( p1.meta_key = '_billing_first_name' AND p2.meta_key = '_billing_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' )
1002
					OR
1003
						( p1.meta_key = '_shipping_first_name' AND p2.meta_key = '_shipping_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' )
1004
					OR
1005
						( p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "') AND p1.meta_value LIKE '%%%s%%' )
1006
					",
1007
					$term, $term, $term
1008
				)
1009
			),
1010
			$wpdb->get_col(
1011
				$wpdb->prepare( "
1012
					SELECT order_id
1013
					FROM {$wpdb->prefix}woocommerce_order_items as order_items
1014
					WHERE order_item_name LIKE '%%%s%%'
1015
					",
1016
					$term
1017
				)
1018
			)
1019
		) );
1020
	}
1021
1022
	return $post_ids;
1023
}
1024
1025
1026
/**
1027
 * Update total sales amount for each product within a paid order.
1028
 *
1029
 * @since 2.7.0
1030
 * @param int $order_id
1031
 */
1032
function wc_update_total_sales_counts( $order_id ) {
1033
	$order = wc_get_order( $order_id );
1034
1035
	if ( ! $order || 'yes' === get_post_meta( $order_id, '_recorded_sales', true ) ) {
1036
		return;
1037
	}
1038
1039
	if ( sizeof( $order->get_items() ) > 0 ) {
1040
		foreach ( $order->get_items() as $item ) {
1041
			if ( $item['product_id'] > 0 ) {
1042
				update_post_meta( $item['product_id'], 'total_sales', absint( get_post_meta( $item['product_id'], 'total_sales', true ) ) + absint( $item['qty'] ) );
1043
			}
1044
		}
1045
	}
1046
1047
	update_post_meta( $order_id, '_recorded_sales', 'yes' );
1048
1049
	/**
1050
	 * Called when sales for an order are recorded
1051
	 *
1052
	 * @param int $order_id order id
1053
	 */
1054
	do_action( 'woocommerce_recorded_sales', $order_id );
1055
}
1056
add_action( 'woocommerce_order_status_completed', 'wc_update_total_sales_counts' );
1057
add_action( 'woocommerce_order_status_processing', 'wc_update_total_sales_counts' );
1058
add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' );
1059
1060
/**
1061
 * Update used coupon amount for each coupon within an order.
1062
 *
1063
 * @since 2.7.0
1064
 * @param int $order_id
1065
 */
1066
function wc_update_coupon_usage_counts( $order_id ) {
1067
	$order        = wc_get_order( $order_id );
1068
	$has_recorded = get_post_meta( $order_id, '_recorded_coupon_usage_counts', true );
1069
1070
	if ( ! $order ) {
1071
		return;
1072
	}
1073
1074
	if ( $order->has_status( 'cancelled' ) && 'yes' === $has_recorded ) {
1075
		$action = 'reduce';
1076
		delete_post_meta( $order_id, '_recorded_coupon_usage_counts' );
1077
	} elseif ( ! $order->has_status( 'cancelled' ) && 'yes' !== $has_recorded ) {
1078
		$action = 'increase';
1079
		update_post_meta( $order_id, '_recorded_coupon_usage_counts', 'yes' );
1080
	} else {
1081
		return;
1082
	}
1083
1084
	if ( sizeof( $order->get_used_coupons() ) > 0 ) {
1085
		foreach ( $order->get_used_coupons() as $code ) {
1086
			if ( ! $code ) {
1087
				continue;
1088
			}
1089
1090
			$coupon = new WC_Coupon( $code );
1091
1092
			if ( ! $used_by = $order->get_user_id() ) {
1093
				$used_by = $order->get_billing_email();
1094
			}
1095
1096
			switch ( $action ) {
1097
				case 'reduce' :
1098
					$coupon->dcr_usage_count( $used_by );
1099
				break;
1100
				case 'increase' :
1101
					$coupon->inc_usage_count( $used_by );
1102
				break;
1103
			}
1104
		}
1105
	}
1106
}
1107
add_action( 'woocommerce_order_status_completed', 'wc_update_total_sales_counts' );
1108
add_action( 'woocommerce_order_status_processing', 'wc_update_total_sales_counts' );
1109
add_action( 'woocommerce_order_status_on-hold', 'wc_update_total_sales_counts' );
1110
add_action( 'woocommerce_order_status_cancelled', 'wc_update_total_sales_counts' );
1111
1112
/**
1113
 * When a payment is complete, we can reduce stock levels for items within an order.
1114
 * @since 2.7.0
1115
 * @param int $order_id
1116
 */
1117
function wc_maybe_reduce_stock_levels( $order_id ) {
1118
	if ( apply_filters( 'woocommerce_payment_complete_reduce_order_stock', ! get_post_meta( $order_id, '_order_stock_reduced', true ), $order_id ) ) {
1119
		wc_reduce_stock_levels( $order_id );
1120
		add_post_meta( $order_id, '_order_stock_reduced', '1', true );
1121
	}
1122
}
1123
add_action( 'woocommerce_payment_complete', 'wc_maybe_reduce_stock_levels' );
1124
1125
/**
1126
 * Reduce stock levels for items within an order.
1127
 * @since 2.7.0
1128
 * @param int $order_id
1129
 */
1130
function wc_reduce_stock_levels( $order_id ) {
1131
	$order = wc_get_order( $order_id );
1132
1133
	if ( 'yes' === get_option( 'woocommerce_manage_stock' ) && $order && apply_filters( 'woocommerce_can_reduce_order_stock', true, $order ) && sizeof( $order->get_items() ) > 0 ) {
1134
		foreach ( $order->get_items() as $item ) {
1135
			if ( $item->is_type( 'line_item' ) && ( $product = $item->get_product() ) && $product->managing_stock() ) {
1136
				$qty       = apply_filters( 'woocommerce_order_item_quantity', $item['qty'], $order, $item );
1137
				$new_stock = $product->reduce_stock( $qty );
1138
				$item_name = $product->get_sku() ? $product->get_sku(): $item['product_id'];
1139
1140
				if ( ! empty( $item['variation_id'] ) ) {
1141
					$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 ) );
1142
				} else {
1143
					$order->add_order_note( sprintf( __( 'Item %s stock reduced from %s to %s.', 'woocommerce' ), $item_name, $new_stock + $qty, $new_stock ) );
1144
				}
1145
1146
				if ( $new_stock < 0 ) {
1147
		            do_action( 'woocommerce_product_on_backorder', array( 'product' => $product, 'order_id' => $order_id, 'quantity' => $qty_ordered ) );
1148
		        }
1149
			}
1150
		}
1151
1152
		do_action( 'woocommerce_reduce_order_stock', $order );
1153
	}
1154
}
1155