Completed
Push — master ( 527840...a5d62b )
by Mike
53:46 queued 43:33
created

includes/wc-product-functions.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * WooCommerce Product Functions
4
 *
5
 * Functions for product specific things.
6
 *
7
 * @package WooCommerce/Functions
8
 * @version 3.0.0
9
 */
10
11
defined( 'ABSPATH' ) || exit;
12
13
/**
14
 * Standard way of retrieving products based on certain parameters.
15
 *
16
 * This function should be used for product retrieval so that we have a data agnostic
17
 * way to get a list of products.
18
 *
19
 * Args and usage: https://github.com/woocommerce/woocommerce/wiki/wc_get_products-and-WC_Product_Query
20
 *
21
 * @since  3.0.0
22
 * @param  array $args Array of args (above).
23
 * @return array|stdClass Number of pages and an array of product objects if
24
 *                             paginate is true, or just an array of values.
25
 */
26
function wc_get_products( $args ) {
27
	// Handle some BW compatibility arg names where wp_query args differ in naming.
28
	$map_legacy = array(
29 12
		'numberposts'    => 'limit',
30
		'post_status'    => 'status',
31
		'post_parent'    => 'parent',
32
		'posts_per_page' => 'limit',
33
		'paged'          => 'page',
34
	);
35
36 12 View Code Duplication
	foreach ( $map_legacy as $from => $to ) {
37 12
		if ( isset( $args[ $from ] ) ) {
38 12
			$args[ $to ] = $args[ $from ];
39
		}
40
	}
41
42 12
	$query = new WC_Product_Query( $args );
43 12
	return $query->get_products();
44
}
45
46
/**
47
 * Main function for returning products, uses the WC_Product_Factory class.
48
 *
49
 * @since 2.2.0
50
 *
51
 * @param mixed $the_product Post object or post ID of the product.
52
 * @param array $deprecated Previously used to pass arguments to the factory, e.g. to force a type.
53
 * @return WC_Product|null|false
54
 */
55
function wc_get_product( $the_product = false, $deprecated = array() ) {
56 386 View Code Duplication
	if ( ! did_action( 'woocommerce_init' ) ) {
57
		/* translators: 1: wc_get_product 2: woocommerce_init */
58
		wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%1$s should not be called before the %2$s action.', 'woocommerce' ), 'wc_get_product', 'woocommerce_init' ), '2.5' );
59
		return false;
60
	}
61 386
	if ( ! empty( $deprecated ) ) {
62
		wc_deprecated_argument( 'args', '3.0', 'Passing args to wc_get_product is deprecated. If you need to force a type, construct the product class directly.' );
63
	}
64 386
	return WC()->product_factory->get_product( $the_product, $deprecated );
65
}
66
67
/**
68
 * Returns whether or not SKUS are enabled.
69
 *
70
 * @return bool
71
 */
72
function wc_product_sku_enabled() {
73
	return apply_filters( 'wc_product_sku_enabled', true );
74
}
75
76
/**
77
 * Returns whether or not product weights are enabled.
78
 *
79
 * @return bool
80
 */
81
function wc_product_weight_enabled() {
82
	return apply_filters( 'wc_product_weight_enabled', true );
83
}
84
85
/**
86
 * Returns whether or not product dimensions (HxWxD) are enabled.
87
 *
88
 * @return bool
89
 */
90
function wc_product_dimensions_enabled() {
91
	return apply_filters( 'wc_product_dimensions_enabled', true );
92
}
93
94
/**
95
 * Clear all transients cache for product data.
96
 *
97
 * @param int $post_id (default: 0).
98
 */
99
function wc_delete_product_transients( $post_id = 0 ) {
100
	// Core transients.
101
	$transients_to_clear = array(
102 382
		'wc_products_onsale',
103
		'wc_featured_products',
104
		'wc_outofstock_count',
105
		'wc_low_stock_count',
106
		'wc_count_comments',
107
	);
108
109
	// Transient names that include an ID.
110
	$post_transient_names = array(
111 382
		'wc_product_children_',
112
		'wc_var_prices_',
113
		'wc_related_',
114
		'wc_child_has_weight_',
115
		'wc_child_has_dimensions_',
116
	);
117
118 382
	if ( $post_id > 0 ) {
119 382
		foreach ( $post_transient_names as $transient ) {
120 382
			$transients_to_clear[] = $transient . $post_id;
121
		}
122
123
		// Does this product have a parent?
124 382
		$product = wc_get_product( $post_id );
125
126 382
		if ( $product ) {
127 382
			if ( $product->get_parent_id() > 0 ) {
128 58
				wc_delete_product_transients( $product->get_parent_id() );
129
			}
130
131 382
			if ( 'variable' === $product->get_type() ) {
132 60
				wp_cache_delete(
133 60
					WC_Cache_Helper::get_cache_prefix( 'products' ) . 'product_variation_attributes_' . $product->get_id(),
134 60
					'products'
135
				);
136
			}
137
138
			$attributes = $product->get_attributes();
139
140
			if ( $attributes ) {
141 382
				foreach ( $attributes as $attribute_key => $attribute ) {
142 382
					$transients_to_clear[] = 'wc_layered_nav_counts_' . $attribute_key;
143
				}
144
			}
145
		}
146 382
	}
147
148 382
	// Delete transients.
149
	foreach ( $transients_to_clear as $transient ) {
150
		delete_transient( $transient );
151
	}
152
153
	// Increments the transient version to invalidate cache.
154
	WC_Cache_Helper::get_transient_version( 'product', true );
155
156
	do_action( 'woocommerce_delete_product_transients', $post_id );
157
}
158
159 3
/**
160
 * Function that returns an array containing the IDs of the products that are on sale.
161
 *
162 3
 * @since 2.0
163 1
 * @return array
164
 */
165
function wc_get_product_ids_on_sale() {
166 3
	// Load from cache.
167 3
	$product_ids_on_sale = get_transient( 'wc_products_onsale' );
168 3
169
	// Valid cache found.
170 3
	if ( false !== $product_ids_on_sale ) {
171
		return $product_ids_on_sale;
172 3
	}
173
174
	$data_store          = WC_Data_Store::load( 'product' );
175
	$on_sale_products    = $data_store->get_on_sale_products();
176
	$product_ids_on_sale = wp_parse_id_list( array_merge( wp_list_pluck( $on_sale_products, 'id' ), array_diff( wp_list_pluck( $on_sale_products, 'parent_id' ), array( 0 ) ) ) );
177
178
	set_transient( 'wc_products_onsale', $product_ids_on_sale, DAY_IN_SECONDS * 30 );
179
180
	return $product_ids_on_sale;
181
}
182
183 2
/**
184
 * Function that returns an array containing the IDs of the featured products.
185
 *
186 2
 * @since 2.1
187
 * @return array
188
 */
189
function wc_get_featured_product_ids() {
190 2
	// Load from cache.
191 2
	$featured_product_ids = get_transient( 'wc_featured_products' );
192 2
193 2
	// Valid cache found.
194 2
	if ( false !== $featured_product_ids ) {
195
		return $featured_product_ids;
196 2
	}
197
198 2
	$data_store           = WC_Data_Store::load( 'product' );
199
	$featured             = $data_store->get_featured_product_ids();
200
	$product_ids          = array_keys( $featured );
201
	$parent_ids           = array_values( array_filter( $featured ) );
202
	$featured_product_ids = array_unique( array_merge( $product_ids, $parent_ids ) );
203
204
	set_transient( 'wc_featured_products', $featured_product_ids, DAY_IN_SECONDS * 30 );
205
206
	return $featured_product_ids;
207
}
208
209
/**
210 470
 * Filter to allow product_cat in the permalinks for products.
211 309
 *
212
 * @param  string  $permalink The existing permalink URL.
213
 * @param  WP_Post $post WP_Post object.
214
 * @return string
215 381
 */
216 381
function wc_product_post_type_link( $permalink, $post ) {
217
	// Abort if post is not a product.
218
	if ( 'product' !== $post->post_type ) {
219
		return $permalink;
220
	}
221
222
	// Abort early if the placeholder rewrite tag isn't in the generated URL.
223
	if ( false === strpos( $permalink, '%' ) ) {
224
		return $permalink;
225
	}
226
227
	// Get the custom taxonomy terms in use by this post.
228
	$terms = get_the_terms( $post->ID, 'product_cat' );
229
230
	if ( ! empty( $terms ) ) {
231
		$terms           = wp_list_sort(
232
			$terms,
233
			array(
234
				'parent'  => 'DESC',
235
				'term_id' => 'ASC',
236
			)
237
		);
238
		$category_object = apply_filters( 'wc_product_post_type_link_product_cat', $terms[0], $terms, $post );
239
		$product_cat     = $category_object->slug;
240
241 View Code Duplication
		if ( $category_object->parent ) {
242
			$ancestors = get_ancestors( $category_object->term_id, 'product_cat' );
243
			foreach ( $ancestors as $ancestor ) {
244
				$ancestor_object = get_term( $ancestor, 'product_cat' );
245
				$product_cat     = $ancestor_object->slug . '/' . $product_cat;
246
			}
247
		}
248
	} else {
249
		// If no terms are assigned to this post, use a string instead (can't leave the placeholder there).
250
		$product_cat = _x( 'uncategorized', 'slug', 'woocommerce' );
251
	}
252
253
	$find = array(
254
		'%year%',
255
		'%monthnum%',
256
		'%day%',
257
		'%hour%',
258
		'%minute%',
259
		'%second%',
260
		'%post_id%',
261
		'%category%',
262
		'%product_cat%',
263
	);
264
265
	$replace = array(
266
		date_i18n( 'Y', strtotime( $post->post_date ) ),
267
		date_i18n( 'm', strtotime( $post->post_date ) ),
268
		date_i18n( 'd', strtotime( $post->post_date ) ),
269
		date_i18n( 'H', strtotime( $post->post_date ) ),
270
		date_i18n( 'i', strtotime( $post->post_date ) ),
271
		date_i18n( 's', strtotime( $post->post_date ) ),
272
		$post->ID,
273
		$product_cat,
274
		$product_cat,
275
	);
276
277
	$permalink = str_replace( $find, $replace, $permalink );
278
279
	return $permalink;
280
}
281
add_filter( 'post_type_link', 'wc_product_post_type_link', 10, 2 );
282 23
283 23
/**
284
 * Get the placeholder image URL either from media, or use the fallback image.
285 23
 *
286
 * @param string $size Thumbnail size to use.
287
 * @return string
288
 */
289
function wc_placeholder_img_src( $size = 'woocommerce_thumbnail' ) {
290
	$src               = WC()->plugin_url() . '/assets/images/placeholder.png';
291
	$placeholder_image = get_option( 'woocommerce_placeholder_image', 0 );
292
293
	if ( ! empty( $placeholder_image ) ) {
294
		if ( is_numeric( $placeholder_image ) ) {
295
			$image = wp_get_attachment_image_src( $placeholder_image, $size );
296
297 23
			if ( ! empty( $image[0] ) ) {
298
				$src = $image[0];
299
			}
300
		} else {
301
			$src = $placeholder_image;
302
		}
303
	}
304
305
	return apply_filters( 'woocommerce_placeholder_img_src', $src );
306
}
307 2
308
/**
309 2
 * Get the placeholder image.
310
 *
311
 * Uses wp_get_attachment_image if using an attachment ID @since 3.6.0 to handle responsiveness.
312
 *
313
 * @param string $size Image size.
314
 * @return string
315
 */
316
function wc_placeholder_img( $size = 'woocommerce_thumbnail' ) {
317
	$dimensions        = wc_get_image_size( $size );
318
	$placeholder_image = get_option( 'woocommerce_placeholder_image', 0 );
319
320
	if ( ! empty( $placeholder_image ) && is_numeric( $placeholder_image ) ) {
321
		$image_html = wp_get_attachment_image(
322
			$placeholder_image,
323
			$size,
324 59
			false,
325
			array(
326 59
				'alt'   => __( 'Placeholder', 'woocommerce' ),
327 59
				'class' => 'woocommerce-placeholder wp-post-image',
328 59
			)
329 59
		);
330
	} else {
331
		$image      = wc_placeholder_img_src( $size );
332
		$image_html = '<img src="' . esc_attr( $image ) . '" alt="' . esc_attr__( 'Placeholder', 'woocommerce' ) . '" width="' . esc_attr( $dimensions['width'] ) . '" class="woocommerce-placeholder wp-post-image" height="' . esc_attr( $dimensions['height'] ) . '" />';
333
	}
334
335
	return apply_filters( 'woocommerce_placeholder_img', $image_html, $size, $dimensions );
336
}
337
338
/**
339
 * Variation Formatting.
340
 *
341
 * Gets a formatted version of variation data or item meta.
342 59
 *
343
 * @param array|WC_Product_Variation $variation Variation object.
344 59
 * @param bool                       $flat Should this be a flat list or HTML list? (default: false).
345
 * @param bool                       $include_names include attribute names/labels in the list.
346 59
 * @param bool                       $skip_attributes_in_name Do not list attributes already part of the variation name.
347
 * @return string
348
 */
349
function wc_get_formatted_variation( $variation, $flat = false, $include_names = true, $skip_attributes_in_name = false ) {
350 59
	$return = '';
351
352 59
	if ( is_a( $variation, 'WC_Product_Variation' ) ) {
353
		$variation_attributes = $variation->get_attributes();
354 51
		$product              = $variation;
355 45
		$variation_name       = $variation->get_name();
356 45
	} else {
357 45
		$product        = false;
358
		$variation_name = '';
359
		// Remove attribute_ prefix from names.
360
		$variation_attributes = array();
361
		if ( is_array( $variation ) ) {
362 51
			foreach ( $variation as $key => $value ) {
363 48
				$variation_attributes[ str_replace( 'attribute_', '', $key ) ] = $value;
364
			}
365
		}
366 51
	}
367
368
	$list_type = $include_names ? 'dl' : 'ul';
369
370
	if ( is_array( $variation_attributes ) ) {
371
372
		if ( ! $flat ) {
373 51
			$return = '<' . $list_type . ' class="variation">';
374 51
		}
375
376 51
		$variation_list = array();
377
378
		foreach ( $variation_attributes as $name => $value ) {
379
			// If this is a term slug, get the term's nice name.
380
			if ( taxonomy_exists( $name ) ) {
381 59
				$term = get_term_by( 'slug', $value, $name );
382 59
				if ( ! is_wp_error( $term ) && ! empty( $term->name ) ) {
383
					$value = $term->name;
384
				}
385
			}
386
387 59
			// Do not list attributes already part of the variation name.
388
			if ( '' === $value || ( $skip_attributes_in_name && wc_is_attribute_in_product_name( $value, $variation_name ) ) ) {
389
				continue;
390
			}
391 59
392
			if ( $include_names ) {
393
				if ( $flat ) {
394
					$variation_list[] = wc_attribute_label( $name, $product ) . ': ' . rawurldecode( $value );
395
				} else {
396
					$variation_list[] = '<dt>' . wc_attribute_label( $name, $product ) . ':</dt><dd>' . rawurldecode( $value ) . '</dd>';
397
				}
398
			} else {
399
				if ( $flat ) {
400
					$variation_list[] = rawurldecode( $value );
401
				} else {
402
					$variation_list[] = '<li>' . rawurldecode( $value ) . '</li>';
403
				}
404
			}
405
		}
406
407
		if ( $flat ) {
408
			$return .= implode( ', ', $variation_list );
409
		} else {
410
			$return .= implode( '', $variation_list );
411
		}
412
413
		if ( ! $flat ) {
414
			$return .= '</' . $list_type . '>';
415
		}
416
	}
417
	return $return;
418
}
419
420
/**
421
 * Function which handles the start and end of scheduled sales via cron.
422
 */
423
function wc_scheduled_sales() {
424
	$data_store = WC_Data_Store::load( 'product' );
425
426
	// Sales which are due to start.
427
	$product_ids = $data_store->get_starting_sales();
428
	if ( $product_ids ) {
429
		do_action( 'wc_before_products_starting_sales', $product_ids );
430 View Code Duplication
		foreach ( $product_ids as $product_id ) {
431
			$product = wc_get_product( $product_id );
432
433
			if ( $product ) {
434
				$sale_price = $product->get_sale_price();
435
436
				if ( $sale_price ) {
437
					$product->set_price( $sale_price );
438
					$product->set_date_on_sale_from( '' );
439
				} else {
440
					$product->set_date_on_sale_to( '' );
441
					$product->set_date_on_sale_from( '' );
442
				}
443
444
				$product->save();
445
			}
446
		}
447
		do_action( 'wc_after_products_starting_sales', $product_ids );
448
449
		delete_transient( 'wc_products_onsale' );
450
	}
451
452
	// Sales which are due to end.
453
	$product_ids = $data_store->get_ending_sales();
454
	if ( $product_ids ) {
455
		do_action( 'wc_before_products_ending_sales', $product_ids );
456 View Code Duplication
		foreach ( $product_ids as $product_id ) {
457 11
			$product = wc_get_product( $product_id );
458 1
459
			if ( $product ) {
460 1
				$regular_price = $product->get_regular_price();
461 1
				$product->set_price( $regular_price );
462
				$product->set_sale_price( '' );
463
				$product->set_date_on_sale_to( '' );
464 11
				$product->set_date_on_sale_from( '' );
465
				$product->save();
466
			}
467
		}
468
		do_action( 'wc_after_products_ending_sales', $product_ids );
469
470
		WC_Cache_Helper::get_transient_version( 'product', true );
471
		delete_transient( 'wc_products_onsale' );
472
	}
473
}
474
add_action( 'woocommerce_scheduled_sales', 'wc_scheduled_sales' );
475
476
/**
477
 * Get attachment image attributes.
478
 *
479
 * @param array $attr Image attributes.
480
 * @return array
481
 */
482
function wc_get_attachment_image_attributes( $attr ) {
483
	if ( isset( $attr['src'] ) && strstr( $attr['src'], 'woocommerce_uploads/' ) ) {
484
		$attr['src'] = wc_placeholder_img_src();
485
486
		if ( isset( $attr['srcset'] ) ) {
487
			$attr['srcset'] = '';
488
		}
489
	}
490
	return $attr;
491
}
492
add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attributes' );
493
494
495
/**
496
 * Prepare attachment for JavaScript.
497
 *
498
 * @param array $response JS version of a attachment post object.
499
 * @return array
500
 */
501
function wc_prepare_attachment_for_js( $response ) {
502
503
	if ( isset( $response['url'] ) && strstr( $response['url'], 'woocommerce_uploads/' ) ) {
504
		$response['full']['url'] = wc_placeholder_img_src();
505
		if ( isset( $response['sizes'] ) ) {
506
			foreach ( $response['sizes'] as $size => $value ) {
507
				$response['sizes'][ $size ]['url'] = wc_placeholder_img_src();
508
			}
509
		}
510
	}
511
512
	return $response;
513
}
514
add_filter( 'wp_prepare_attachment_for_js', 'wc_prepare_attachment_for_js' );
515
516
/**
517
 * Track product views.
518
 */
519
function wc_track_product_view() {
520
	if ( ! is_singular( 'product' ) || ! is_active_widget( false, false, 'woocommerce_recently_viewed_products', true ) ) {
521
		return;
522
	}
523
524
	global $post;
525
526
	if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) ) { // @codingStandardsIgnoreLine.
527
		$viewed_products = array();
528
	} else {
529
		$viewed_products = wp_parse_id_list( (array) explode( '|', wp_unslash( $_COOKIE['woocommerce_recently_viewed'] ) ) ); // @codingStandardsIgnoreLine.
530
	}
531
532 427
	// Unset if already in viewed products list.
533 427
	$keys = array_flip( $viewed_products );
534 427
535 427
	if ( isset( $keys[ $post->ID ] ) ) {
536 427
		unset( $viewed_products[ $keys[ $post->ID ] ] );
537 427
	}
538
539
	$viewed_products[] = $post->ID;
540
541
	if ( count( $viewed_products ) > 15 ) {
542
		array_shift( $viewed_products );
543
	}
544
545
	// Store for session only.
546
	wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) );
547
}
548
549
add_action( 'template_redirect', 'wc_track_product_view', 20 );
550
551 328
/**
552 328
 * Get product types.
553
 *
554 328
 * @since 2.2
555 79
 * @return array
556
 */
557
function wc_get_product_types() {
558 328
	return (array) apply_filters(
559
		'product_type_selector',
560
		array(
561
			'simple'   => __( 'Simple product', 'woocommerce' ),
562
			'grouped'  => __( 'Grouped product', 'woocommerce' ),
563
			'external' => __( 'External/Affiliate product', 'woocommerce' ),
564
			'variable' => __( 'Variable product', 'woocommerce' ),
565
		)
566
	);
567
}
568 1
569 1
/**
570
 * Check if product sku is unique.
571 1
 *
572
 * @since 2.2
573 1
 * @param int    $product_id Product ID.
574
 * @param string $sku Product SKU.
575 1
 * @return bool
576 1
 */
577 1
function wc_product_has_unique_sku( $product_id, $sku ) {
578
	$data_store = WC_Data_Store::load( 'product' );
579
	$sku_found  = $data_store->is_existing_sku( $product_id, $sku );
580
581
	if ( apply_filters( 'wc_product_has_unique_sku', $sku_found, $product_id, $sku ) ) {
582
		return false;
583
	}
584
585
	return true;
586
}
587
588
/**
589
 * Force a unique SKU.
590
 *
591
 * @since  3.0.0
592
 * @param  integer $product_id Product ID.
593 1
 */
594
function wc_product_force_unique_sku( $product_id ) {
595 1
	$product     = wc_get_product( $product_id );
596 1
	$current_sku = $product ? $product->get_sku( 'edit' ) : '';
597
598
	if ( $current_sku ) {
599 1
		try {
600
			$new_sku = wc_product_generate_unique_sku( $product_id, $current_sku );
601
602
			if ( $current_sku !== $new_sku ) {
603
				$product->set_sku( $new_sku );
604
				$product->save();
605
			}
606
		} catch ( Exception $e ) {} // @codingStandardsIgnoreLine.
607
	}
608
}
609
610 82
/**
611 82
 * Recursively appends a suffix until a unique SKU is found.
612
 *
613
 * @since  3.0.0
614
 * @param  integer $product_id Product ID.
615
 * @param  string  $sku Product SKU.
616
 * @param  integer $index An optional index that can be added to the product SKU.
617
 * @return string
618
 */
619
function wc_product_generate_unique_sku( $product_id, $sku, $index = 0 ) {
620
	$generated_sku = 0 < $index ? $sku . '-' . $index : $sku;
621
622
	if ( ! wc_product_has_unique_sku( $product_id, $generated_sku ) ) {
623 60
		$generated_sku = wc_product_generate_unique_sku( $product_id, $sku, ( $index + 1 ) );
624 60
	}
625 60
626 60
	return $generated_sku;
627 60
}
628
629
/**
630 60
 * Get product ID by SKU.
631 48
 *
632 48
 * @since  2.3.0
633 48
 * @param  string $sku Product SKU.
634 48
 * @return int
635 48
 */
636
function wc_get_product_id_by_sku( $sku ) {
637
	$data_store = WC_Data_Store::load( 'product' );
638
	return $data_store->get_product_id_by_sku( $sku );
639
}
640
641 60
/**
642
 * Get attibutes/data for an individual variation from the database and maintain it's integrity.
643 60
 *
644 60
 * @since  2.4.0
645 60
 * @param  int $variation_id Variation ID.
646
 * @return array
647
 */
648
function wc_get_product_variation_attributes( $variation_id ) {
649
	// Build variation data from meta.
650
	$all_meta                = get_post_meta( $variation_id );
651 48
	$parent_id               = wp_get_post_parent_id( $variation_id );
652
	$parent_attributes       = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) );
653
	$found_parent_attributes = array();
654
	$variation_attributes    = array();
655
656
	// Compare to parent variable product attributes and ensure they match.
657
	foreach ( $parent_attributes as $attribute_name => $options ) {
658
		if ( ! empty( $options['is_variation'] ) ) {
659
			$attribute                 = 'attribute_' . sanitize_title( $attribute_name );
660
			$found_parent_attributes[] = $attribute;
661
			if ( ! array_key_exists( $attribute, $variation_attributes ) ) {
662
				$variation_attributes[ $attribute ] = ''; // Add it - 'any' will be asumed.
663
			}
664
		}
665
	}
666
667 48
	// Get the variation attributes from meta.
668
	foreach ( $all_meta as $name => $value ) {
669
		// Only look at valid attribute meta, and also compare variation level attributes and remove any which do not exist at parent level.
670 60
		if ( 0 !== strpos( $name, 'attribute_' ) || ! in_array( $name, $found_parent_attributes, true ) ) {
671
			unset( $variation_attributes[ $name ] );
672
			continue;
673
		}
674
		/**
675
		 * Pre 2.4 handling where 'slugs' were saved instead of the full text attribute.
676
		 * Attempt to get full version of the text attribute from the parent.
677
		 */
678
		if ( sanitize_title( $value[0] ) === $value[0] && version_compare( get_post_meta( $parent_id, '_product_version', true ), '2.4.0', '<' ) ) {
679
			foreach ( $parent_attributes as $attribute ) {
680
				if ( 'attribute_' . sanitize_title( $attribute['name'] ) !== $name ) {
681 45
					continue;
682
				}
683 45
				$text_attributes = wc_get_text_attributes( $attribute['value'] );
684 45
685
				foreach ( $text_attributes as $text_attribute ) {
686
					if ( sanitize_title( $text_attribute ) === $value[0] ) {
687 45
						$value[0] = $text_attribute;
688
						break;
689
					}
690
				}
691
			}
692
		}
693
694
		$variation_attributes[ $name ] = $value[0];
695
	}
696
697
	return $variation_attributes;
698
}
699
700
/**
701
 * Get all product cats for a product by ID, including hierarchy
702 5
 *
703
 * @since  2.5.0
704
 * @param  int $product_id Product ID.
705
 * @return array
706
 */
707
function wc_get_product_cat_ids( $product_id ) {
708
	$product_cats = wc_get_product_term_ids( $product_id, 'product_cat' );
709
710 5
	foreach ( $product_cats as $product_cat ) {
711
		$product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) );
712 5
	}
713
714
	return $product_cats;
715
}
716
717
/**
718
 * Gets data about an attachment, such as alt text and captions.
719
 *
720
 * @since 2.6.0
721
 *
722
 * @param int|null        $attachment_id Attachment ID.
723
 * @param WC_Product|bool $product WC_Product object.
724
 *
725
 * @return array
726
 */
727
function wc_get_product_attachment_props( $attachment_id = null, $product = false ) {
728
	$props      = array(
729
		'title'   => '',
730
		'caption' => '',
731
		'url'     => '',
732
		'alt'     => '',
733
		'src'     => '',
734
		'srcset'  => false,
735
		'sizes'   => false,
736
	);
737
	$attachment = get_post( $attachment_id );
738
739
	if ( $attachment ) {
740
		$props['title']   = wp_strip_all_tags( $attachment->post_title );
741
		$props['caption'] = wp_strip_all_tags( $attachment->post_excerpt );
742
		$props['url']     = wp_get_attachment_url( $attachment_id );
743
744
		// Alt text.
745
		$alt_text = array( wp_strip_all_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ), $props['caption'], wp_strip_all_tags( $attachment->post_title ) );
746
747
		if ( $product && $product instanceof WC_Product ) {
748
			$alt_text[] = wp_strip_all_tags( get_the_title( $product->get_id() ) );
749
		}
750
751
		$alt_text     = array_filter( $alt_text );
752
		$props['alt'] = isset( $alt_text[0] ) ? $alt_text[0] : '';
753
754
		// Large version.
755
		$full_size           = apply_filters( 'woocommerce_gallery_full_size', apply_filters( 'woocommerce_product_thumbnails_large_size', 'full' ) );
756
		$src                 = wp_get_attachment_image_src( $attachment_id, $full_size );
757
		$props['full_src']   = $src[0];
758 5
		$props['full_src_w'] = $src[1];
759
		$props['full_src_h'] = $src[2];
760
761
		// Gallery thumbnail.
762
		$gallery_thumbnail                = wc_get_image_size( 'gallery_thumbnail' );
763
		$gallery_thumbnail_size           = apply_filters( 'woocommerce_gallery_thumbnail_size', array( $gallery_thumbnail['width'], $gallery_thumbnail['height'] ) );
764
		$src                              = wp_get_attachment_image_src( $attachment_id, $gallery_thumbnail_size );
765
		$props['gallery_thumbnail_src']   = $src[0];
766
		$props['gallery_thumbnail_src_w'] = $src[1];
767
		$props['gallery_thumbnail_src_h'] = $src[2];
768 381
769 381
		// Thumbnail version.
770 381
		$thumbnail_size       = apply_filters( 'woocommerce_thumbnail_size', 'woocommerce_thumbnail' );
771 381
		$src                  = wp_get_attachment_image_src( $attachment_id, $thumbnail_size );
772 381
		$props['thumb_src']   = $src[0];
773 381
		$props['thumb_src_w'] = $src[1];
774
		$props['thumb_src_h'] = $src[2];
775
776
		// Image source.
777
		$image_size      = apply_filters( 'woocommerce_gallery_image_size', 'woocommerce_single' );
778
		$src             = wp_get_attachment_image_src( $attachment_id, $image_size );
779
		$props['src']    = $src[0];
780
		$props['src_w']  = $src[1];
781
		$props['src_h']  = $src[2];
782
		$props['srcset'] = function_exists( 'wp_get_attachment_image_srcset' ) ? wp_get_attachment_image_srcset( $attachment_id, $image_size ) : false;
783
		$props['sizes']  = function_exists( 'wp_get_attachment_image_sizes' ) ? wp_get_attachment_image_sizes( $attachment_id, $image_size ) : false;
784
	}
785
	return $props;
786 1
}
787 1
788
/**
789
 * Get product visibility options.
790
 *
791
 * @since 3.0.0
792
 * @return array
793 1
 */
794
function wc_get_product_visibility_options() {
795
	return apply_filters(
796
		'woocommerce_product_visibility_options',
797
		array(
798
			'visible' => __( 'Shop and search results', 'woocommerce' ),
799
			'catalog' => __( 'Shop only', 'woocommerce' ),
800
			'search'  => __( 'Search results only', 'woocommerce' ),
801
			'hidden'  => __( 'Hidden', 'woocommerce' ),
802
		)
803
	);
804
}
805
806
/**
807
 * Get min/max price meta query args.
808
 *
809
 * @since 3.0.0
810
 * @param array $args Min price and max price arguments.
811
 * @return array
812 1
 */
813 1
function wc_get_min_max_price_meta_query( $args ) {
814 1
	$min = isset( $args['min_price'] ) ? floatval( $args['min_price'] ) : 0;
815 1
	$max = isset( $args['max_price'] ) ? floatval( $args['max_price'] ) : 9999999999;
816
817
	/**
818
	 * Adjust if the store taxes are not displayed how they are stored.
819
	 * Kicks in when prices excluding tax are displayed including tax.
820
	 */
821
	if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) {
822
		$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() );
823
		$class_min   = $min;
824
		$class_max   = $max;
825
826 2
		foreach ( $tax_classes as $tax_class ) {
827 2
			$tax_rates = WC_Tax::get_rates( $tax_class );
828 2
829
			if ( $tax_rates ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $tax_rates of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
830 2
				$class_min = $min + WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $min, $tax_rates ) );
831 1
				$class_max = $max - WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $max, $tax_rates ) );
832 1
			}
833
		}
834
835 2
		$min = $class_min;
836
		$max = $class_max;
837
	}
838
839
	return apply_filters(
840
		'woocommerce_get_min_max_price_meta_query',
841
		array(
842
			'key'     => '_price',
843
			'value'   => array( $min, $max ),
844
			'compare' => 'BETWEEN',
845
			'type'    => 'DECIMAL(10,' . wc_get_price_decimals() . ')',
846 653
		),
847 653
		$args
848 653
	);
849
}
850
851
/**
852
 * Get product tax class options.
853
 *
854
 * @since 3.0.0
855
 * @return array
856
 */
857
function wc_get_product_tax_class_options() {
858
	$tax_classes           = WC_Tax::get_tax_classes();
859
	$tax_class_options     = array();
860
	$tax_class_options[''] = __( 'Standard', 'woocommerce' );
861
862
	if ( ! empty( $tax_classes ) ) {
863
		foreach ( $tax_classes as $class ) {
864
			$tax_class_options[ sanitize_title( $class ) ] = $class;
865
		}
866
	}
867
	return $tax_class_options;
868
}
869
870
/**
871
 * Get stock status options.
872
 *
873
 * @since 3.0.0
874
 * @return array
875
 */
876
function wc_get_product_stock_status_options() {
877 20
	return array(
878 20
		'instock'     => __( 'In stock', 'woocommerce' ),
879 20
		'outofstock'  => __( 'Out of stock', 'woocommerce' ),
880 20
		'onbackorder' => __( 'On backorder', 'woocommerce' ),
881 20
	);
882
}
883 20
884 20
/**
885
 * Get backorder options.
886
 *
887
 * @since 3.0.0
888 20
 * @return array
889 20
 */
890
function wc_get_product_backorder_options() {
891
	return array(
892 20
		'no'     => __( 'Do not allow', 'woocommerce' ),
893
		'notify' => __( 'Allow, but notify customer', 'woocommerce' ),
894 20
		'yes'    => __( 'Allow', 'woocommerce' ),
895 20
	);
896
}
897
898 20
/**
899
 * Get related products based on product category and tags.
900
 *
901 20
 * @since  3.0.0
902 20
 * @param  int   $product_id  Product ID.
903
 * @param  int   $limit       Limit of results.
904
 * @param  array $exclude_ids Exclude IDs from the results.
905 20
 * @return array
906 7
 */
907
function wc_get_related_products( $product_id, $limit = 5, $exclude_ids = array() ) {
908 20
909
	$product_id     = absint( $product_id );
910
	$limit          = $limit >= -1 ? $limit : 5;
911 20
	$exclude_ids    = array_merge( array( 0, $product_id ), $exclude_ids );
912
	$transient_name = 'wc_related_' . $product_id;
913
	$query_args     = http_build_query(
914 20
		array(
915 20
			'limit'       => $limit,
916 20
			'exclude_ids' => $exclude_ids,
917 20
		)
918
	);
919
920
	$transient     = get_transient( $transient_name );
921 20
	$related_posts = $transient && isset( $transient[ $query_args ] ) ? $transient[ $query_args ] : false;
922
923 20
	// We want to query related posts if they are not cached, or we don't have enough.
924
	if ( false === $related_posts || count( $related_posts ) < $limit ) {
925
926
		$cats_array = apply_filters( 'woocommerce_product_related_posts_relate_by_category', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_cat_terms', wc_get_product_term_ids( $product_id, 'product_cat' ), $product_id ) : array();
927
		$tags_array = apply_filters( 'woocommerce_product_related_posts_relate_by_tag', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_tag_terms', wc_get_product_term_ids( $product_id, 'product_tag' ), $product_id ) : array();
928
929
		// Don't bother if none are set, unless woocommerce_product_related_posts_force_display is set to true in which case all products are related.
930
		if ( empty( $cats_array ) && empty( $tags_array ) && ! apply_filters( 'woocommerce_product_related_posts_force_display', false, $product_id ) ) {
931
			$related_posts = array();
932
		} else {
933
			$data_store    = WC_Data_Store::load( 'product' );
934
			$related_posts = $data_store->get_related_products( $cats_array, $tags_array, $exclude_ids, $limit + 10, $product_id );
935 65
		}
936 65
937
		if ( $transient ) {
938
			$transient[ $query_args ] = $related_posts;
939
		} else {
940
			$transient = array( $query_args => $related_posts );
941
		}
942
943
		set_transient( $transient_name, $transient, DAY_IN_SECONDS );
944
	}
945
946
	$related_posts = apply_filters(
947
		'woocommerce_related_products',
948
		$related_posts,
949
		$product_id,
950
		array(
951
			'limit'        => $limit,
952
			'excluded_ids' => $exclude_ids,
953
		)
954
	);
955
956
	shuffle( $related_posts );
957
958
	return array_slice( $related_posts, 0, $limit );
959
}
960
961
/**
962
 * Retrieves product term ids for a taxonomy.
963
 *
964
 * @since  3.0.0
965
 * @param  int    $product_id Product ID.
966
 * @param  string $taxonomy   Taxonomy slug.
967
 * @return array
968
 */
969
function wc_get_product_term_ids( $product_id, $taxonomy ) {
970
	$terms = get_the_terms( $product_id, $taxonomy );
971
	return ( empty( $terms ) || is_wp_error( $terms ) ) ? array() : wp_list_pluck( $terms, 'term_id' );
972
}
973
974
/**
975
 * For a given product, and optionally price/qty, work out the price with tax included, based on store settings.
976
 *
977
 * @since  3.0.0
978
 * @param  WC_Product $product WC_Product object.
979
 * @param  array      $args Optional arguments to pass product quantity and price.
980
 * @return float
981
 */
982
function wc_get_price_including_tax( $product, $args = array() ) {
983
	$args = wp_parse_args(
984
		$args,
985
		array(
986
			'qty'   => '',
987
			'price' => '',
988
		)
989
	);
990
991
	$price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : $product->get_price();
992
	$qty   = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1;
993
994
	if ( '' === $price ) {
995
		return '';
996
	} elseif ( empty( $qty ) ) {
997
		return 0.0;
998
	}
999
1000
	$line_price   = $price * $qty;
1001
	$return_price = $line_price;
1002
1003
	if ( $product->is_taxable() ) {
1004
		if ( ! wc_prices_include_tax() ) {
1005
			$tax_rates = WC_Tax::get_rates( $product->get_tax_class() );
1006
			$taxes     = WC_Tax::calc_tax( $line_price, $tax_rates, false );
1007
1008 View Code Duplication
			if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1009
				$taxes_total = array_sum( $taxes );
1010 122
			} else {
1011 122
				$taxes_total = array_sum( array_map( 'wc_round_tax_total', $taxes ) );
1012 122
			}
1013
1014
			$return_price = round( $line_price + $taxes_total, wc_get_price_decimals() );
1015
		} else {
1016
			$tax_rates      = WC_Tax::get_rates( $product->get_tax_class() );
1017 122
			$base_tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) );
1018 122
1019
			/**
1020 122
			 * If the customer is excempt from VAT, remove the taxes here.
1021 1
			 * Either remove the base or the user taxes depending on woocommerce_adjust_non_base_location_prices setting.
1022 121
			 */
1023
			if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) { // @codingStandardsIgnoreLine.
1024
				$remove_taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $line_price, $base_tax_rates, true ) : WC_Tax::calc_tax( $line_price, $tax_rates, true );
1025
1026 121 View Code Duplication
				if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1027
					$remove_taxes_total = array_sum( $remove_taxes );
1028 121
				} else {
1029
					$remove_taxes_total = array_sum( array_map( 'wc_round_tax_total', $remove_taxes ) );
1030
				}
1031
1032
				$return_price = round( $line_price - $remove_taxes_total, wc_get_price_decimals() );
1033
1034 121
				/**
1035
			 * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations.
1036
			 * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes.
1037 121
			 * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk.
1038
			 */
1039
			} elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
1040
				$base_taxes   = WC_Tax::calc_tax( $line_price, $base_tax_rates, true );
1041
				$modded_taxes = WC_Tax::calc_tax( $line_price - array_sum( $base_taxes ), $tax_rates, false );
1042
1043
				if ( 'yes' === get_option( 'woocommerce_tax_round_at_subtotal' ) ) {
1044
					$base_taxes_total   = array_sum( $base_taxes );
1045
					$modded_taxes_total = array_sum( $modded_taxes );
1046
				} else {
1047
					$base_taxes_total   = array_sum( array_map( 'wc_round_tax_total', $base_taxes ) );
1048
					$modded_taxes_total = array_sum( array_map( 'wc_round_tax_total', $modded_taxes ) );
1049 24
				}
1050 24
1051 24
				$return_price = round( $line_price - $base_taxes_total + $modded_taxes_total, wc_get_price_decimals() );
1052 24
			}
1053
		}
1054
	}
1055
	return apply_filters( 'woocommerce_get_price_including_tax', $return_price, $qty, $product );
1056 24
}
1057 24
1058
/**
1059 24
 * For a given product, and optionally price/qty, work out the price with tax excluded, based on store settings.
1060
 *
1061
 * @since  3.0.0
1062
 * @param  WC_Product $product WC_Product object.
1063
 * @param  array      $args Optional arguments to pass product quantity and price.
1064
 * @return float
1065
 */
1066 24
function wc_get_price_excluding_tax( $product, $args = array() ) {
1067 24
	$args = wp_parse_args(
1068 24
		$args,
1069 24
		array(
1070
			'qty'   => '',
1071
			'price' => '',
1072
		)
1073
	);
1074
1075
	$price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : $product->get_price();
1076
	$qty   = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1;
1077
1078
	if ( '' === $price ) {
1079
		return '';
1080
	} elseif ( empty( $qty ) ) {
1081
		return 0.0;
1082
	}
1083
1084
	$line_price = $price * $qty;
1085
1086
	if ( $product->is_taxable() && wc_prices_include_tax() ) {
1087
		$tax_rates      = WC_Tax::get_rates( $product->get_tax_class() );
1088
		$base_tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) );
1089
		$remove_taxes   = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $line_price, $base_tax_rates, true ) : WC_Tax::calc_tax( $line_price, $tax_rates, true );
1090
		$return_price   = $line_price - array_sum( $remove_taxes ); // Unrounded since we're dealing with tax inclusive prices. Matches logic in cart-totals class. @see adjust_non_base_location_price.
1091
	} else {
1092
		$return_price = $line_price;
1093
	}
1094
1095
	return apply_filters( 'woocommerce_get_price_excluding_tax', $return_price, $qty, $product );
1096
}
1097
1098
/**
1099
 * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting.
1100
 *
1101
 * @since  3.0.0
1102
 * @param  WC_Product $product WC_Product object.
1103
 * @param  array      $args Optional arguments to pass product quantity and price.
1104
 * @return float
1105
 */
1106
function wc_get_price_to_display( $product, $args = array() ) {
1107
	$args = wp_parse_args(
1108
		$args,
1109
		array(
1110
			'qty'   => 1,
1111
			'price' => $product->get_price(),
1112
		)
1113
	);
1114
1115
	$price = $args['price'];
1116
	$qty   = $args['qty'];
1117
1118
	return 'incl' === get_option( 'woocommerce_tax_display_shop' ) ?
1119 1
		wc_get_price_including_tax(
1120
			$product,
1121
			array(
1122
				'qty'   => $qty,
1123
				'price' => $price,
1124
			)
1125
		) :
1126
		wc_get_price_excluding_tax(
1127
			$product,
1128
			array(
1129
				'qty'   => $qty,
1130
				'price' => $price,
1131
			)
1132
		);
1133
}
1134
1135
/**
1136
 * Returns the product categories in a list.
1137
 *
1138
 * @param int    $product_id Product ID.
1139
 * @param string $sep (default: ', ').
1140
 * @param string $before (default: '').
1141
 * @param string $after (default: '').
1142
 * @return string
1143
 */
1144
function wc_get_product_category_list( $product_id, $sep = ', ', $before = '', $after = '' ) {
1145
	return get_the_term_list( $product_id, 'product_cat', $before, $sep, $after );
1146
}
1147
1148
/**
1149
 * Returns the product tags in a list.
1150
 *
1151
 * @param int    $product_id Product ID.
1152
 * @param string $sep (default: ', ').
1153
 * @param string $before (default: '').
1154
 * @param string $after (default: '').
1155
 * @return string
1156
 */
1157
function wc_get_product_tag_list( $product_id, $sep = ', ', $before = '', $after = '' ) {
1158
	return get_the_term_list( $product_id, 'product_tag', $before, $sep, $after );
1159
}
1160
1161
/**
1162
 * Callback for array filter to get visible only.
1163
 *
1164
 * @since  3.0.0
1165
 * @param  WC_Product $product WC_Product object.
1166
 * @return bool
1167
 */
1168
function wc_products_array_filter_visible( $product ) {
1169
	return $product && is_a( $product, 'WC_Product' ) && $product->is_visible();
1170
}
1171
1172
/**
1173
 * Callback for array filter to get visible grouped products only.
1174
 *
1175
 * @since  3.1.0
1176
 * @param  WC_Product $product WC_Product object.
1177
 * @return bool
1178
 */
1179
function wc_products_array_filter_visible_grouped( $product ) {
1180
	return $product && is_a( $product, 'WC_Product' ) && ( 'publish' === $product->get_status() || current_user_can( 'edit_product', $product->get_id() ) );
1181
}
1182
1183
/**
1184
 * Callback for array filter to get products the user can edit only.
1185
 *
1186
 * @since  3.0.0
1187
 * @param  WC_Product $product WC_Product object.
1188
 * @return bool
1189
 */
1190
function wc_products_array_filter_editable( $product ) {
1191
	return $product && is_a( $product, 'WC_Product' ) && current_user_can( 'edit_product', $product->get_id() );
1192
}
1193
1194
/**
1195
 * Callback for array filter to get products the user can view only.
1196
 *
1197
 * @since  3.4.0
1198
 * @param  WC_Product $product WC_Product object.
1199
 * @return bool
1200
 */
1201
function wc_products_array_filter_readable( $product ) {
1202
	return $product && is_a( $product, 'WC_Product' ) && current_user_can( 'read_product', $product->get_id() );
1203
}
1204
1205
/**
1206
 * Sort an array of products by a value.
1207
 *
1208
 * @since  3.0.0
1209
 *
1210
 * @param array  $products List of products to be ordered.
1211
 * @param string $orderby Optional order criteria.
1212
 * @param string $order Ascending or descending order.
1213
 *
1214
 * @return array
1215
 */
1216
function wc_products_array_orderby( $products, $orderby = 'date', $order = 'desc' ) {
1217
	$orderby = strtolower( $orderby );
1218
	$order   = strtolower( $order );
1219
	switch ( $orderby ) {
1220
		case 'title':
1221
		case 'id':
1222
		case 'date':
1223
		case 'modified':
1224
		case 'menu_order':
1225
		case 'price':
1226
			usort( $products, 'wc_products_array_orderby_' . $orderby );
1227
			break;
1228
		default:
1229
			shuffle( $products );
1230
			break;
1231
	}
1232
	if ( 'desc' === $order ) {
1233
		$products = array_reverse( $products );
1234
	}
1235
	return $products;
1236
}
1237
1238
/**
1239
 * Sort by title.
1240
 *
1241
 * @since  3.0.0
1242
 * @param  WC_Product $a First WC_Product object.
1243
 * @param  WC_Product $b Second WC_Product object.
1244
 * @return int
1245
 */
1246
function wc_products_array_orderby_title( $a, $b ) {
1247
	return strcasecmp( $a->get_name(), $b->get_name() );
1248
}
1249
1250
/**
1251
 * Sort by id.
1252
 *
1253
 * @since  3.0.0
1254
 * @param  WC_Product $a First WC_Product object.
1255
 * @param  WC_Product $b Second WC_Product object.
1256
 * @return int
1257
 */
1258
function wc_products_array_orderby_id( $a, $b ) {
1259
	if ( $a->get_id() === $b->get_id() ) {
1260
		return 0;
1261
	}
1262
	return ( $a->get_id() < $b->get_id() ) ? -1 : 1;
1263
}
1264
1265
/**
1266
 * Sort by date.
1267
 *
1268
 * @since  3.0.0
1269
 * @param  WC_Product $a First WC_Product object.
1270
 * @param  WC_Product $b Second WC_Product object.
1271
 * @return int
1272 58
 */
1273 1
function wc_products_array_orderby_date( $a, $b ) {
1274
	if ( $a->get_date_created() === $b->get_date_created() ) {
1275
		return 0;
1276 58
	}
1277
	return ( $a->get_date_created() < $b->get_date_created() ) ? -1 : 1;
1278
}
1279
1280
/**
1281
 * Sort by modified.
1282
 *
1283
 * @since  3.0.0
1284
 * @param  WC_Product $a First WC_Product object.
1285
 * @param  WC_Product $b Second WC_Product object.
1286
 * @return int
1287
 */
1288
function wc_products_array_orderby_modified( $a, $b ) {
1289
	if ( $a->get_date_modified() === $b->get_date_modified() ) {
1290
		return 0;
1291
	}
1292
	return ( $a->get_date_modified() < $b->get_date_modified() ) ? -1 : 1;
1293
}
1294
1295
/**
1296
 * Sort by menu order.
1297
 *
1298
 * @since  3.0.0
1299
 * @param  WC_Product $a First WC_Product object.
1300
 * @param  WC_Product $b Second WC_Product object.
1301
 * @return int
1302
 */
1303
function wc_products_array_orderby_menu_order( $a, $b ) {
1304
	if ( $a->get_menu_order() === $b->get_menu_order() ) {
1305
		return 0;
1306
	}
1307
	return ( $a->get_menu_order() < $b->get_menu_order() ) ? -1 : 1;
1308
}
1309
1310
/**
1311
 * Sort by price low to high.
1312
 *
1313
 * @since  3.0.0
1314
 * @param  WC_Product $a First WC_Product object.
1315
 * @param  WC_Product $b Second WC_Product object.
1316
 * @return int
1317
 */
1318
function wc_products_array_orderby_price( $a, $b ) {
1319
	if ( $a->get_price() === $b->get_price() ) {
1320
		return 0;
1321
	}
1322
	return ( $a->get_price() < $b->get_price() ) ? -1 : 1;
1323
}
1324
1325
/**
1326
 * Queue a product for syncing at the end of the request.
1327
 *
1328
 * @param int $product_id Product ID.
1329
 */
1330
function wc_deferred_product_sync( $product_id ) {
1331
	global $wc_deferred_product_sync;
1332
1333
	if ( empty( $wc_deferred_product_sync ) ) {
1334
		$wc_deferred_product_sync = array();
1335
	}
1336
1337
	$wc_deferred_product_sync[] = $product_id;
1338
}
1339