Completed
Pull Request — master (#11291)
by
unknown
07:32
created

wc-order-functions.php ➔ wc_orders_count()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 27
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
495
				}
496
			}
497
		}
498
	}
499
500
	update_post_meta( $order_id, '_download_permissions_granted', 1 );
501
502
	do_action( 'woocommerce_grant_product_download_permissions', $order_id );
503
}
504
add_action( 'woocommerce_order_status_completed', 'wc_downloadable_product_permissions' );
505
add_action( 'woocommerce_order_status_processing', 'wc_downloadable_product_permissions' );
506
507
508
/**
509
 * Add a item to an order (for example a line item).
510
 *
511
 * @access public
512
 * @param int $order_id
513
 * @return mixed
514
 */
515
function wc_add_order_item( $order_id, $item ) {
516
	global $wpdb;
517
518
	$order_id = absint( $order_id );
519
520
	if ( ! $order_id )
521
		return false;
522
523
	$defaults = array(
524
		'order_item_name' 		=> '',
525
		'order_item_type' 		=> 'line_item',
526
	);
527
528
	$item = wp_parse_args( $item, $defaults );
529
530
	$wpdb->insert(
531
		$wpdb->prefix . "woocommerce_order_items",
532
		array(
533
			'order_item_name' 		=> $item['order_item_name'],
534
			'order_item_type' 		=> $item['order_item_type'],
535
			'order_id'				=> $order_id
536
		),
537
		array(
538
			'%s', '%s', '%d'
539
		)
540
	);
541
542
	$item_id = absint( $wpdb->insert_id );
543
544
	do_action( 'woocommerce_new_order_item', $item_id, $item, $order_id );
545
546
	return $item_id;
547
}
548
549
/**
550
 * Update an item for an order.
551
 *
552
 * @since 2.2
553
 * @param int $item_id
554
 * @param array $args either `order_item_type` or `order_item_name`
555
 * @return bool true if successfully updated, false otherwise
556
 */
557
function wc_update_order_item( $item_id, $args ) {
558
	global $wpdb;
559
560
	$update = $wpdb->update( $wpdb->prefix . 'woocommerce_order_items', $args, array( 'order_item_id' => $item_id ) );
561
562
	if ( false === $update ) {
563
		return false;
564
	}
565
566
	do_action( 'woocommerce_update_order_item', $item_id, $args );
567
568
	return true;
569
}
570
571
/**
572
 * Delete an item from the order it belongs to based on item id.
573
 *
574
 * @access public
575
 * @param int $item_id
576
 * @return bool
577
 */
578
function wc_delete_order_item( $item_id ) {
579
	global $wpdb;
580
581
	$item_id = absint( $item_id );
582
583
	if ( ! $item_id )
584
		return false;
585
586
	do_action( 'woocommerce_before_delete_order_item', $item_id );
587
588
	$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_items WHERE order_item_id = %d", $item_id ) );
589
	$wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->prefix}woocommerce_order_itemmeta WHERE order_item_id = %d", $item_id ) );
590
591
	do_action( 'woocommerce_delete_order_item', $item_id );
592
593
	return true;
594
}
595
596
/**
597
 * WooCommerce Order Item Meta API - Update term meta.
598
 *
599
 * @access public
600
 * @param mixed $item_id
601
 * @param mixed $meta_key
602
 * @param mixed $meta_value
603
 * @param string $prev_value (default: '')
604
 * @return bool
605
 */
606 View Code Duplication
function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
607
	if ( update_metadata( 'order_item', $item_id, $meta_key, $meta_value, $prev_value ) ) {
608
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
609
		wp_cache_delete( $cache_key, 'orders' );
610
		return true;
611
	}
612
	return false;
613
}
614
615
/**
616
 * WooCommerce Order Item Meta API - Add term meta.
617
 *
618
 * @access public
619
 * @param mixed $item_id
620
 * @param mixed $meta_key
621
 * @param mixed $meta_value
622
 * @param bool $unique (default: false)
623
 * @return int New row ID or 0
624
 */
625 View Code Duplication
function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = false ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
626
	if ( $meta_id = add_metadata( 'order_item', $item_id, $meta_key, $meta_value, $unique ) ) {
627
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
628
		wp_cache_delete( $cache_key, 'orders' );
629
		return $meta_id;
630
	}
631
	return 0;
632
}
633
634
/**
635
 * WooCommerce Order Item Meta API - Delete term meta.
636
 *
637
 * @access public
638
 * @param mixed $item_id
639
 * @param mixed $meta_key
640
 * @param string $meta_value (default: '')
641
 * @param bool $delete_all (default: false)
642
 * @return bool
643
 */
644 View Code Duplication
function wc_delete_order_item_meta( $item_id, $meta_key, $meta_value = '', $delete_all = false ) {
0 ignored issues
show
Duplication introduced by
This function seems to be duplicated in your project.

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

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

Loading history...
645
	if ( delete_metadata( 'order_item', $item_id, $meta_key, $meta_value, $delete_all ) ) {
646
		$cache_key = WC_Cache_Helper::get_cache_prefix( 'orders' ) . 'item_meta_array_' . $item_id;
647
		wp_cache_delete( $cache_key, 'orders' );
648
		return true;
649
	}
650
	return false;
651
}
652
653
/**
654
 * WooCommerce Order Item Meta API - Get term meta.
655
 *
656
 * @access public
657
 * @param mixed $item_id
658
 * @param mixed $key
659
 * @param bool $single (default: true)
660
 * @return mixed
661
 */
662
function wc_get_order_item_meta( $item_id, $key, $single = true ) {
663
	return get_metadata( 'order_item', $item_id, $key, $single );
664
}
665
666
/**
667
 * Cancel all unpaid orders after held duration to prevent stock lock for those products.
668
 *
669
 * @access public
670
 */
671
function wc_cancel_unpaid_orders() {
672
	global $wpdb;
673
674
	$held_duration = get_option( 'woocommerce_hold_stock_minutes' );
675
676
	if ( $held_duration < 1 || get_option( 'woocommerce_manage_stock' ) != 'yes' )
677
		return;
678
679
	$date = date( "Y-m-d H:i:s", strtotime( '-' . absint( $held_duration ) . ' MINUTES', current_time( 'timestamp' ) ) );
680
681
	$unpaid_orders = $wpdb->get_col( $wpdb->prepare( "
682
		SELECT posts.ID
683
		FROM {$wpdb->posts} AS posts
684
		WHERE 	posts.post_type   IN ('" . implode( "','", wc_get_order_types() ) . "')
685
		AND 	posts.post_status = 'wc-pending'
686
		AND 	posts.post_modified < %s
687
	", $date ) );
688
689
	if ( $unpaid_orders ) {
690
		foreach ( $unpaid_orders as $unpaid_order ) {
691
			$order = wc_get_order( $unpaid_order );
692
693
			if ( apply_filters( 'woocommerce_cancel_unpaid_order', 'checkout' === get_post_meta( $unpaid_order, '_created_via', true ), $order ) ) {
694
				$order->update_status( 'cancelled', __( 'Unpaid order cancelled - time limit reached.', 'woocommerce' ) );
695
			}
696
		}
697
	}
698
699
	wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
700
	wp_schedule_single_event( time() + ( absint( $held_duration ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
701
}
702
add_action( 'woocommerce_cancel_unpaid_orders', 'wc_cancel_unpaid_orders' );
703
704
/**
705
 * Return the count of processing orders.
706
 *
707
 * @access public
708
 * @return int
709
 */
710
function wc_processing_order_count() {
711
	return wc_orders_count( 'processing' );
712
}
713
714
/**
715
 * Return the orders count of a specific order status.
716
 *
717
 * @access public
718
 * @param string $status
719
 * @return int
720
 */
721
function wc_orders_count( $status ) {
722
	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'     => '',
810
		'reason'     => null,
811
		'order_id'   => 0,
812
		'refund_id'  => 0,
813
		'line_items' => array(),
814
		'date'       => current_time( 'mysql', 0 )
815
	);
816
817
	$args        = wp_parse_args( $args, $default_args );
818
	$refund_data = array();
819
820
	// prevent negative refunds
821
	if ( 0 > $args['amount'] ) {
822
		$args['amount'] = 0;
823
	}
824
825
	if ( $args['refund_id'] > 0 ) {
826
		$updating          = true;
827
		$refund_data['ID'] = $args['refund_id'];
828
	} else {
829
		$updating                     = false;
830
		$refund_data['post_type']     = 'shop_order_refund';
831
		$refund_data['post_status']   = 'wc-completed';
832
		$refund_data['ping_status']   = 'closed';
833
		$refund_data['post_author']   = get_current_user_id() ? get_current_user_id() : 1;
834
		$refund_data['post_password'] = uniqid( 'refund_' );
835
		$refund_data['post_parent']   = absint( $args['order_id'] );
836
		$refund_data['post_title']    = sprintf( __( 'Refund &ndash; %s', 'woocommerce' ), strftime( _x( '%b %d, %Y @ %I:%M %p', 'Order date parsed by strftime', 'woocommerce' ) ) );
837
		$refund_data['post_date']     = $args['date'];
838
	}
839
840
	if ( ! is_null( $args['reason'] ) ) {
841
		$refund_data['post_excerpt'] = $args['reason'];
842
	}
843
844
	if ( $updating ) {
845
		$refund_id = wp_update_post( $refund_data );
846
	} else {
847
		$refund_id = wp_insert_post( apply_filters( 'woocommerce_new_refund_data', $refund_data ), true );
848
	}
849
850
	if ( is_wp_error( $refund_id ) ) {
851
		return $refund_id;
852
	}
853
854
	if ( ! $updating ) {
855
		// Default refund meta data
856
		update_post_meta( $refund_id, '_refund_amount', wc_format_decimal( $args['amount'] ) );
857
858
		// Get refund object
859
		$refund = wc_get_order( $refund_id );
860
		$order  = wc_get_order( $args['order_id'] );
861
862
		// Refund currency is the same used for the parent order
863
		update_post_meta( $refund_id, '_order_currency', $order->get_order_currency() );
864
865
		// Negative line items
866
		if ( sizeof( $args['line_items'] ) > 0 ) {
867
			$order_items = $order->get_items( array( 'line_item', 'fee', 'shipping' ) );
868
869
			foreach ( $args['line_items'] as $refund_item_id => $refund_item ) {
870
				if ( isset( $order_items[ $refund_item_id ] ) ) {
871
					if ( empty( $refund_item['qty'] ) && empty( $refund_item['refund_total'] ) && empty( $refund_item['refund_tax'] ) ) {
872
						continue;
873
					}
874
875
					// Prevents errors when the order has no taxes
876
					if ( ! isset( $refund_item['refund_tax'] ) ) {
877
						$refund_item['refund_tax'] = array();
878
					}
879
880
					switch ( $order_items[ $refund_item_id ]['type'] ) {
881
						case 'line_item' :
882
							$line_item_args = array(
883
								'totals' => array(
884
									'subtotal'     => wc_format_refund_total( $refund_item['refund_total'] ),
885
									'total'        => wc_format_refund_total( $refund_item['refund_total'] ),
886
									'subtotal_tax' => wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) ),
887
									'tax'          => wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) ),
888
									'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'] ) )
889
								)
890
							);
891
							$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 );
892
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
893
						break;
894
						case 'shipping' :
895
							$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'] );
896
							$new_item_id = $refund->add_shipping( $shipping );
897
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
898
						break;
899
						case 'fee' :
900
							$fee            = new stdClass();
901
							$fee->name      = $order_items[ $refund_item_id ]['name'];
902
							$fee->tax_class = $order_items[ $refund_item_id ]['tax_class'];
903
							$fee->taxable   = $fee->tax_class !== '0';
904
							$fee->amount    = wc_format_refund_total( $refund_item['refund_total'] );
905
							$fee->tax       = wc_format_refund_total( array_sum( $refund_item['refund_tax'] ) );
906
							$fee->tax_data  = array_map( 'wc_format_refund_total', $refund_item['refund_tax'] );
907
908
							$new_item_id = $refund->add_fee( $fee );
909
							wc_add_order_item_meta( $new_item_id, '_refunded_item_id', $refund_item_id );
910
						break;
911
					}
912
				}
913
			}
914
			$refund->update_taxes();
915
		}
916
917
		$refund->calculate_totals( false );
918
919
		// Set total to total refunded which may vary from order items
920
		$refund->set_total( wc_format_decimal( $args['amount'] ) * -1, 'total' );
921
922
		do_action( 'woocommerce_refund_created', $refund_id, $args );
923
	}
924
925
	// Clear transients
926
	wc_delete_shop_order_transients( $args['order_id'] );
927
928
	return new WC_Order_Refund( $refund_id );
929
}
930
931
/**
932
 * Get tax class by tax id.
933
 *
934
 * @since 2.2
935
 * @param int $tax_id
936
 * @return string
937
 */
938
function wc_get_tax_class_by_tax_id( $tax_id ) {
939
	global $wpdb;
940
941
	$tax_class = $wpdb->get_var( $wpdb->prepare( "SELECT tax_rate_class FROM {$wpdb->prefix}woocommerce_tax_rates WHERE tax_rate_id = %d", $tax_id ) );
942
943
	return wc_clean( $tax_class );
944
}
945
946
/**
947
 * Get payment gateway class by order data.
948
 *
949
 * @since 2.2
950
 * @param int|WC_Order $order
951
 * @return WC_Payment_Gateway|bool
952
 */
953
function wc_get_payment_gateway_by_order( $order ) {
954
	if ( WC()->payment_gateways() ) {
955
		$payment_gateways = WC()->payment_gateways->payment_gateways();
956
	} else {
957
		$payment_gateways = array();
958
	}
959
960
	if ( ! is_object( $order ) ) {
961
		$order_id = absint( $order );
962
		$order    = wc_get_order( $order_id );
963
	}
964
965
	return isset( $payment_gateways[ $order->payment_method ] ) ? $payment_gateways[ $order->payment_method ] : false;
966
}
967
968
/**
969
 * When refunding an order, create a refund line item if the partial refunds do not match order total.
970
 *
971
 * This is manual; no gateway refund will be performed.
972
 *
973
 * @since 2.4
974
 * @param int $order_id
975
 */
976
function wc_order_fully_refunded( $order_id ) {
977
	$order       = wc_get_order( $order_id );
978
	$max_refund  = wc_format_decimal( $order->get_total() - $order->get_total_refunded() );
979
980
	if ( ! $max_refund ) {
981
		return;
982
	}
983
984
	// Create the refund object
985
	wc_create_refund( array(
986
		'amount'     => $max_refund,
987
		'reason'     => __( 'Order Fully Refunded', 'woocommerce' ),
988
		'order_id'   => $order_id,
989
		'line_items' => array()
990
	) );
991
992
	wc_delete_shop_order_transients( $order_id );
993
}
994
add_action( 'woocommerce_order_status_refunded', 'wc_order_fully_refunded' );
995
996
/**
997
 * Search in orders.
998
 *
999
 * @since  2.6.0
1000
 * @param  string $term Term to search.
1001
 * @return array List of orders ID.
1002
 */
1003
function wc_order_search( $term ) {
1004
	global $wpdb;
1005
1006
	$term     = str_replace( 'Order #', '', wc_clean( $term ) );
1007
	$post_ids = array();
1008
1009
	// Search fields.
1010
	$search_fields = array_map( 'wc_clean', apply_filters( 'woocommerce_shop_order_search_fields', array(
1011
		'_order_key',
1012
		'_billing_company',
1013
		'_billing_address_1',
1014
		'_billing_address_2',
1015
		'_billing_city',
1016
		'_billing_postcode',
1017
		'_billing_country',
1018
		'_billing_state',
1019
		'_billing_email',
1020
		'_billing_phone',
1021
		'_shipping_address_1',
1022
		'_shipping_address_2',
1023
		'_shipping_city',
1024
		'_shipping_postcode',
1025
		'_shipping_country',
1026
		'_shipping_state'
1027
	) ) );
1028
1029
	// Search orders.
1030
	if ( is_numeric( $term ) ) {
1031
		$post_ids = array_unique( array_merge(
1032
			$wpdb->get_col(
1033
				$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 ) )
1034
			),
1035
			array( absint( $term ) )
1036
		) );
1037
	} elseif ( ! empty( $search_fields ) ) {
1038
		$post_ids = array_unique( array_merge(
1039
			$wpdb->get_col(
1040
				$wpdb->prepare( "
1041
					SELECT DISTINCT p1.post_id
1042
					FROM {$wpdb->postmeta} p1
1043
					INNER JOIN {$wpdb->postmeta} p2 ON p1.post_id = p2.post_id
1044
					WHERE
1045
						( p1.meta_key = '_billing_first_name' AND p2.meta_key = '_billing_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' )
1046
					OR
1047
						( p1.meta_key = '_shipping_first_name' AND p2.meta_key = '_shipping_last_name' AND CONCAT(p1.meta_value, ' ', p2.meta_value) LIKE '%%%s%%' )
1048
					OR
1049
						( p1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', $search_fields ) ) . "') AND p1.meta_value LIKE '%%%s%%' )
1050
					",
1051
					$term, $term, $term
1052
				)
1053
			),
1054
			$wpdb->get_col(
1055
				$wpdb->prepare( "
1056
					SELECT order_id
1057
					FROM {$wpdb->prefix}woocommerce_order_items as order_items
1058
					WHERE order_item_name LIKE '%%%s%%'
1059
					",
1060
					$term
1061
				)
1062
			)
1063
		) );
1064
	}
1065
1066
	return $post_ids;
1067
}
1068