Issues (1182)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

includes/wc-product-functions.php (7 issues)

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
 * @author   WooThemes
8
 * @category Core
9
 * @package  WooCommerce/Functions
10
 * @version  2.3.0
11
 */
12
13
if ( ! defined( 'ABSPATH' ) ) {
14
	exit; // Exit if accessed directly
15
}
16
17
/**
18
 * Main function for returning products, uses the WC_Product_Factory class.
19
 *
20
 * @param mixed $the_product Post object or post ID of the product.
21
 * @param array $args (default: array()) Contains all arguments to be used to get this product.
22
 * @return WC_Product
23
 */
24 View Code Duplication
function wc_get_product( $the_product = false, $args = array() ) {
0 ignored issues
show
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...
25
	if ( ! did_action( 'woocommerce_init' ) ) {
26
		_doing_it_wrong( __FUNCTION__, __( 'wc_get_product should not be called before the woocommerce_init action.', 'woocommerce' ), '2.5' );
27
		return false;
28
	}
29
	return WC()->product_factory->get_product( $the_product, $args );
30
}
31
32
/**
33
 * Update a product's stock amount.
34
 *
35
 * @param  int $product_id
36
 * @param  int $new_stock_level
37
 */
38
function wc_update_product_stock( $product_id, $new_stock_level ) {
39
	$product = wc_get_product( $product_id );
40
41
	if ( ! metadata_exists( 'post', $product_id, '_stock' ) || $product->get_stock_quantity() !== $new_stock_level ) {
42
		$product->set_stock( $new_stock_level );
43
	}
44
}
45
46
/**
47
 * Update a product's stock status.
48
 *
49
 * @param  int $product_id
50
 * @param  int $status
51
 */
52
function wc_update_product_stock_status( $product_id, $status ) {
53
	$product = wc_get_product( $product_id );
54
	if ( $product ) {
55
		$product->set_stock_status( $status );
56
	}
57
}
58
59
/**
60
 * Returns whether or not SKUS are enabled.
61
 * @return bool
62
 */
63
function wc_product_sku_enabled() {
64
	return apply_filters( 'wc_product_sku_enabled', true );
65
}
66
67
/**
68
 * Returns whether or not product weights are enabled.
69
 * @return bool
70
 */
71
function wc_product_weight_enabled() {
72
	return apply_filters( 'wc_product_weight_enabled', true );
73
}
74
75
/**
76
 * Returns whether or not product dimensions (HxWxD) are enabled.
77
 * @return bool
78
 */
79
function wc_product_dimensions_enabled() {
80
	return apply_filters( 'wc_product_dimensions_enabled', true );
81
}
82
83
/**
84
 * Clear all transients cache for product data.
85
 *
86
 * @param int $post_id (default: 0)
87
 */
88
function wc_delete_product_transients( $post_id = 0 ) {
89
	// Core transients
90
	$transients_to_clear = array(
91
		'wc_products_onsale',
92
		'wc_featured_products',
93
		'wc_outofstock_count',
94
		'wc_low_stock_count'
95
	);
96
97
	// Transient names that include an ID
98
	$post_transient_names = array(
99
		'wc_product_children_',
100
		'wc_product_total_stock_',
101
		'wc_var_prices_',
102
		'wc_related_'
103
	);
104
105
	if ( $post_id > 0 ) {
106
		foreach( $post_transient_names as $transient ) {
107
			$transients_to_clear[] = $transient . $post_id;
108
		}
109
110
		// Does this product have a parent?
111
		if ( $parent_id = wp_get_post_parent_id( $post_id ) ) {
112
			wc_delete_product_transients( $parent_id );
113
		}
114
	}
115
116
	// Delete transients
117
	foreach( $transients_to_clear as $transient ) {
118
		delete_transient( $transient );
119
	}
120
121
	// Increments the transient version to invalidate cache
122
	WC_Cache_Helper::get_transient_version( 'product', true );
123
124
	do_action( 'woocommerce_delete_product_transients', $post_id );
125
}
126
127
/**
128
 * Function that returns an array containing the IDs of the products that are on sale.
129
 *
130
 * @since 2.0
131
 * @access public
132
 * @return array
133
 */
134
function wc_get_product_ids_on_sale() {
135
	global $wpdb;
136
137
	// Load from cache
138
	$product_ids_on_sale = get_transient( 'wc_products_onsale' );
139
140
	// Valid cache found
141
	if ( false !== $product_ids_on_sale ) {
142
		return $product_ids_on_sale;
143
	}
144
145
	$on_sale_posts = $wpdb->get_results( "
146
		SELECT post.ID, post.post_parent FROM `$wpdb->posts` AS post
147
		LEFT JOIN `$wpdb->postmeta` AS meta ON post.ID = meta.post_id
148
		LEFT JOIN `$wpdb->postmeta` AS meta2 ON post.ID = meta2.post_id
149
		WHERE post.post_type IN ( 'product', 'product_variation' )
150
			AND post.post_status = 'publish'
151
			AND meta.meta_key = '_sale_price'
152
			AND meta2.meta_key = '_price'
153
			AND CAST( meta.meta_value AS DECIMAL ) >= 0
154
			AND CAST( meta.meta_value AS CHAR ) != ''
155
			AND CAST( meta.meta_value AS DECIMAL ) = CAST( meta2.meta_value AS DECIMAL )
156
		GROUP BY post.ID;
157
	" );
158
159
	$product_ids_on_sale = array_unique( array_map( 'absint', array_merge( wp_list_pluck( $on_sale_posts, 'ID' ), array_diff( wp_list_pluck( $on_sale_posts, 'post_parent' ), array( 0 ) ) ) ) );
160
161
	set_transient( 'wc_products_onsale', $product_ids_on_sale, DAY_IN_SECONDS * 30 );
162
163
	return $product_ids_on_sale;
164
}
165
166
/**
167
 * Function that returns an array containing the IDs of the featured products.
168
 *
169
 * @since 2.1
170
 * @access public
171
 * @return array
172
 */
173
function wc_get_featured_product_ids() {
174
175
	// Load from cache
176
	$featured_product_ids = get_transient( 'wc_featured_products' );
177
178
	// Valid cache found
179
	if ( false !== $featured_product_ids )
180
		return $featured_product_ids;
181
182
	$featured = get_posts( array(
183
		'post_type'      => array( 'product', 'product_variation' ),
184
		'posts_per_page' => -1,
185
		'post_status'    => 'publish',
186
		'meta_query'     => array(
187
			array(
188
				'key' 		=> '_visibility',
189
				'value' 	=> array('catalog', 'visible'),
190
				'compare' 	=> 'IN'
191
			),
192
			array(
193
				'key' 	=> '_featured',
194
				'value' => 'yes'
195
			)
196
		),
197
		'fields' => 'id=>parent'
198
	) );
199
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
 * Filter to allow product_cat in the permalinks for products.
211
 *
212
 * @param  string  $permalink The existing permalink URL.
213
 * @param  WP_Post $post
214
 * @return string
215
 */
216
function wc_product_post_type_link( $permalink, $post ) {
217
	// Abort if post is not a product.
218
	if ( $post->post_type !== 'product' ) {
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
		usort( $terms, '_usort_terms_by_ID' ); // order by ID
232
233
		$category_object = apply_filters( 'wc_product_post_type_link_product_cat', $terms[0], $terms, $post );
234
		$category_object = get_term( $category_object, 'product_cat' );
235
		$product_cat     = $category_object->slug;
236
237
		if ( $category_object->parent ) {
238
			$ancestors = get_ancestors( $category_object->term_id, 'product_cat' );
239
			foreach ( $ancestors as $ancestor ) {
240
				$ancestor_object = get_term( $ancestor, 'product_cat' );
241
				$product_cat     = $ancestor_object->slug . '/' . $product_cat;
242
			}
243
		}
244
	} else {
245
		// If no terms are assigned to this post, use a string instead (can't leave the placeholder there)
246
		$product_cat = _x( 'uncategorized', 'slug', 'woocommerce' );
247
	}
248
249
	$find = array(
250
		'%year%',
251
		'%monthnum%',
252
		'%day%',
253
		'%hour%',
254
		'%minute%',
255
		'%second%',
256
		'%post_id%',
257
		'%category%',
258
		'%product_cat%'
259
	);
260
261
	$replace = array(
262
		date_i18n( 'Y', strtotime( $post->post_date ) ),
263
		date_i18n( 'm', strtotime( $post->post_date ) ),
264
		date_i18n( 'd', strtotime( $post->post_date ) ),
265
		date_i18n( 'H', strtotime( $post->post_date ) ),
266
		date_i18n( 'i', strtotime( $post->post_date ) ),
267
		date_i18n( 's', strtotime( $post->post_date ) ),
268
		$post->ID,
269
		$product_cat,
270
		$product_cat
271
	);
272
273
	$permalink = str_replace( $find, $replace, $permalink );
274
275
	return $permalink;
276
}
277
add_filter( 'post_type_link', 'wc_product_post_type_link', 10, 2 );
278
279
280
/**
281
 * Get the placeholder image URL for products etc.
282
 *
283
 * @access public
284
 * @return string
285
 */
286
function wc_placeholder_img_src() {
287
	return apply_filters( 'woocommerce_placeholder_img_src', WC()->plugin_url() . '/assets/images/placeholder.png' );
288
}
289
290
/**
291
 * Get the placeholder image.
292
 *
293
 * @access public
294
 * @return string
295
 */
296
function wc_placeholder_img( $size = 'shop_thumbnail' ) {
297
	$dimensions = wc_get_image_size( $size );
298
299
	return apply_filters('woocommerce_placeholder_img', '<img src="' . wc_placeholder_img_src() . '" alt="' . esc_attr__( 'Placeholder', 'woocommerce' ) . '" width="' . esc_attr( $dimensions['width'] ) . '" class="woocommerce-placeholder wp-post-image" height="' . esc_attr( $dimensions['height'] ) . '" />', $size, $dimensions );
300
}
301
302
/**
303
 * Variation Formatting.
304
 *
305
 * Gets a formatted version of variation data or item meta.
306
 *
307
 * @access public
308
 * @param string $variation
309
 * @param bool $flat (default: false)
310
 * @return string
311
 */
312
function wc_get_formatted_variation( $variation, $flat = false ) {
313
	$return = '';
314
	if ( is_array( $variation ) ) {
315
316
		if ( ! $flat ) {
317
			$return = '<dl class="variation">';
318
		}
319
320
		$variation_list = array();
321
322
		foreach ( $variation as $name => $value ) {
323
			if ( ! $value ) {
324
				continue;
325
			}
326
327
			// If this is a term slug, get the term's nice name
328
			if ( taxonomy_exists( esc_attr( str_replace( 'attribute_', '', $name ) ) ) ) {
329
				$term = get_term_by( 'slug', $value, esc_attr( str_replace( 'attribute_', '', $name ) ) );
330
				if ( ! is_wp_error( $term ) && ! empty( $term->name ) ) {
331
					$value = $term->name;
332
				}
333
			} else {
334
				$value = ucwords( str_replace( '-', ' ', $value ) );
335
			}
336
337
			if ( $flat ) {
338
				$variation_list[] = wc_attribute_label( str_replace( 'attribute_', '', $name ) ) . ': ' . rawurldecode( $value );
339
			} else {
340
				$variation_list[] = '<dt>' . wc_attribute_label( str_replace( 'attribute_', '', $name ) ) . ':</dt><dd>' . rawurldecode( $value ) . '</dd>';
341
			}
342
		}
343
344
		if ( $flat ) {
345
			$return .= implode( ', ', $variation_list );
346
		} else {
347
			$return .= implode( '', $variation_list );
348
		}
349
350
		if ( ! $flat ) {
351
			$return .= '</dl>';
352
		}
353
	}
354
	return $return;
355
}
356
357
/**
358
 * Function which handles the start and end of scheduled sales via cron.
359
 *
360
 * @access public
361
 */
362
function wc_scheduled_sales() {
363
	global $wpdb;
364
365
	// Sales which are due to start
366
	$product_ids = $wpdb->get_col( $wpdb->prepare( "
367
		SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
368
		LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id
369
		LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id
370
		WHERE postmeta.meta_key = '_sale_price_dates_from'
371
		AND postmeta_2.meta_key = '_price'
372
		AND postmeta_3.meta_key = '_sale_price'
373
		AND postmeta.meta_value > 0
374
		AND postmeta.meta_value < %s
375
		AND postmeta_2.meta_value != postmeta_3.meta_value
376
	", current_time( 'timestamp' ) ) );
377
378
	if ( $product_ids ) {
379 View Code Duplication
		foreach ( $product_ids as $product_id ) {
0 ignored issues
show
This code seems to be duplicated across 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...
380
			$sale_price = get_post_meta( $product_id, '_sale_price', true );
381
382
			if ( $sale_price ) {
383
				update_post_meta( $product_id, '_price', $sale_price );
384
			} else {
385
				// No sale price!
386
				update_post_meta( $product_id, '_sale_price_dates_from', '' );
387
				update_post_meta( $product_id, '_sale_price_dates_to', '' );
388
			}
389
390
			$parent = wp_get_post_parent_id( $product_id );
391
392
			// Sync parent
393
			if ( $parent ) {
394
				// Clear prices transient for variable products.
395
				delete_transient( 'wc_var_prices_' . $parent );
396
397
				// Grouped products need syncing via a function
398
				$this_product = wc_get_product( $product_id );
399
400
				if ( $this_product->is_type( 'simple' ) ) {
401
					$this_product->grouped_product_sync();
402
				}
403
			}
404
		}
405
406
		delete_transient( 'wc_products_onsale' );
407
	}
408
409
	// Sales which are due to end
410
	$product_ids = $wpdb->get_col( $wpdb->prepare( "
411
		SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta
412
		LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id
413
		LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id
414
		WHERE postmeta.meta_key = '_sale_price_dates_to'
415
		AND postmeta_2.meta_key = '_price'
416
		AND postmeta_3.meta_key = '_regular_price'
417
		AND postmeta.meta_value > 0
418
		AND postmeta.meta_value < %s
419
		AND postmeta_2.meta_value != postmeta_3.meta_value
420
	", current_time( 'timestamp' ) ) );
421
422
	if ( $product_ids ) {
423 View Code Duplication
		foreach ( $product_ids as $product_id ) {
0 ignored issues
show
This code seems to be duplicated across 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...
424
			$regular_price = get_post_meta( $product_id, '_regular_price', true );
425
426
			update_post_meta( $product_id, '_price', $regular_price );
427
			update_post_meta( $product_id, '_sale_price', '' );
428
			update_post_meta( $product_id, '_sale_price_dates_from', '' );
429
			update_post_meta( $product_id, '_sale_price_dates_to', '' );
430
431
			$parent = wp_get_post_parent_id( $product_id );
432
433
			// Sync parent
434
			if ( $parent ) {
435
				// Grouped products need syncing via a function
436
				$this_product = wc_get_product( $product_id );
437
				if ( $this_product->is_type( 'simple' ) ) {
438
					$this_product->grouped_product_sync();
439
				}
440
			}
441
		}
442
443
		WC_Cache_Helper::get_transient_version( 'product', true );
444
		delete_transient( 'wc_products_onsale' );
445
	}
446
}
447
add_action( 'woocommerce_scheduled_sales', 'wc_scheduled_sales' );
448
449
/**
450
 * Get attachment image attributes.
451
 *
452
 * @access public
453
 * @param array $attr
454
 * @return array
455
 */
456
function wc_get_attachment_image_attributes( $attr ) {
457
	if ( strstr( $attr['src'], 'woocommerce_uploads/' ) ) {
458
		$attr['src'] = wc_placeholder_img_src();
459
	}
460
461
	return $attr;
462
}
463
add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attributes' );
464
465
466
/**
467
 * Prepare attachment for JavaScript.
468
 *
469
 * @access public
470
 * @param array $response
471
 * @return array
472
 */
473
function wc_prepare_attachment_for_js( $response ) {
474
475
	if ( isset( $response['url'] ) && strstr( $response['url'], 'woocommerce_uploads/' ) ) {
476
		$response['full']['url'] = wc_placeholder_img_src();
477
		if ( isset( $response['sizes'] ) ) {
478
			foreach( $response['sizes'] as $size => $value ) {
479
				$response['sizes'][ $size ]['url'] = wc_placeholder_img_src();
480
			}
481
		}
482
	}
483
484
	return $response;
485
}
486
add_filter( 'wp_prepare_attachment_for_js', 'wc_prepare_attachment_for_js' );
487
488
/**
489
 * Track product views.
490
 */
491
function wc_track_product_view() {
492
	if ( ! is_singular( 'product' ) || ! is_active_widget( false, false, 'woocommerce_recently_viewed_products', true ) ) {
493
		return;
494
	}
495
496
	global $post;
497
498
	if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) )
499
		$viewed_products = array();
500
	else
501
		$viewed_products = (array) explode( '|', $_COOKIE['woocommerce_recently_viewed'] );
502
503
	if ( ! in_array( $post->ID, $viewed_products ) ) {
504
		$viewed_products[] = $post->ID;
505
	}
506
507
	if ( sizeof( $viewed_products ) > 15 ) {
508
		array_shift( $viewed_products );
509
	}
510
511
	// Store for session only
512
	wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) );
513
}
514
515
add_action( 'template_redirect', 'wc_track_product_view', 20 );
516
517
/**
518
 * Get product types.
519
 *
520
 * @since 2.2
521
 * @return array
522
 */
523
function wc_get_product_types() {
524
	return (array) apply_filters( 'product_type_selector', array(
525
		'simple'   => __( 'Simple product', 'woocommerce' ),
526
		'grouped'  => __( 'Grouped product', 'woocommerce' ),
527
		'external' => __( 'External/Affiliate product', 'woocommerce' ),
528
		'variable' => __( 'Variable product', 'woocommerce' )
529
	) );
530
}
531
532
/**
533
 * Check if product sku is unique.
534
 *
535
 * @since 2.2
536
 * @param int $product_id
537
 * @param string $sku Will be slashed to work around https://core.trac.wordpress.org/ticket/27421
538
 * @return bool
539
 */
540
function wc_product_has_unique_sku( $product_id, $sku ) {
541
	global $wpdb;
542
543
	$sku_found = $wpdb->get_var( $wpdb->prepare( "
544
		SELECT $wpdb->posts.ID
545
		FROM $wpdb->posts
546
		LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )
547
		WHERE $wpdb->posts.post_type IN ( 'product', 'product_variation' )
548
		AND $wpdb->posts.post_status = 'publish'
549
		AND $wpdb->postmeta.meta_key = '_sku' AND $wpdb->postmeta.meta_value = '%s'
550
		AND $wpdb->postmeta.post_id <> %d LIMIT 1
551
	 ", wp_slash( $sku ), $product_id ) );
552
553
	if ( apply_filters( 'wc_product_has_unique_sku', $sku_found, $product_id, $sku ) ) {
554
		return false;
555
	} else {
556
		return true;
557
	}
558
}
559
560
/**
561
 * Get product ID by SKU.
562
 *
563
 * @since  2.3.0
564
 * @param  string $sku
565
 * @return int
566
 */
567
function wc_get_product_id_by_sku( $sku ) {
568
	global $wpdb;
569
570
	$product_id = $wpdb->get_var( $wpdb->prepare( "
571
		SELECT posts.ID
572
		FROM $wpdb->posts AS posts
573
		LEFT JOIN $wpdb->postmeta AS postmeta ON ( posts.ID = postmeta.post_id )
574
		WHERE posts.post_type IN ( 'product', 'product_variation' )
575
		AND postmeta.meta_key = '_sku' AND postmeta.meta_value = '%s'
576
		LIMIT 1
577
	 ", $sku ) );
578
579
	return ( $product_id ) ? intval( $product_id ) : 0;
580
}
581
582
/**
583
 * Save product price.
584
 *
585
 * This is a private function (internal use ONLY) used until a data manipulation api is built.
586
 *
587
 * @since 2.4.0
588
 * @todo  look into Data manipulation API
589
 *
590
 * @param int $product_id
591
 * @param float $regular_price
592
 * @param float $sale_price
593
 * @param string $date_from
594
 * @param string $date_to
595
 */
596
function _wc_save_product_price( $product_id, $regular_price, $sale_price = '', $date_from = '', $date_to = '' ) {
597
	$product_id  = absint( $product_id );
598
	$regular_price = wc_format_decimal( $regular_price );
599
	$sale_price    = $sale_price === '' ? '' : wc_format_decimal( $sale_price );
600
	$date_from     = wc_clean( $date_from );
601
	$date_to       = wc_clean( $date_to );
602
603
	update_post_meta( $product_id, '_regular_price', $regular_price );
604
	update_post_meta( $product_id, '_sale_price', $sale_price );
605
606
	// Save Dates
607
	update_post_meta( $product_id, '_sale_price_dates_from', $date_from ? strtotime( $date_from ) : '' );
608
	update_post_meta( $product_id, '_sale_price_dates_to', $date_to ? strtotime( $date_to ) : '' );
609
610
	if ( $date_to && ! $date_from ) {
611
		update_post_meta( $product_id, '_sale_price_dates_from', strtotime( 'NOW', current_time( 'timestamp' ) ) );
612
	}
613
614
	// Update price if on sale
615 View Code Duplication
	if ( '' !== $sale_price && '' === $date_to && '' === $date_from ) {
0 ignored issues
show
This code seems to be duplicated across 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...
616
		update_post_meta( $product_id, '_price', $sale_price );
617
	} else {
618
		update_post_meta( $product_id, '_price', $regular_price );
619
	}
620
621 View Code Duplication
	if ( '' !== $sale_price && $date_from && strtotime( $date_from ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
0 ignored issues
show
This code seems to be duplicated across 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...
622
		update_post_meta( $product_id, '_price', $sale_price );
623
	}
624
625 View Code Duplication
	if ( $date_to && strtotime( $date_to ) < strtotime( 'NOW', current_time( 'timestamp' ) ) ) {
0 ignored issues
show
This code seems to be duplicated across 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
		update_post_meta( $product_id, '_price', $regular_price );
627
		update_post_meta( $product_id, '_sale_price_dates_from', '' );
628
		update_post_meta( $product_id, '_sale_price_dates_to', '' );
629
	}
630
}
631
632
/**
633
 * Get attibutes/data for an individual variation from the database and maintain it's integrity.
634
 * @since  2.4.0
635
 * @param  int $variation_id
636
 * @return array
637
 */
638
function wc_get_product_variation_attributes( $variation_id ) {
639
	// Build variation data from meta
640
	$all_meta                = get_post_meta( $variation_id );
641
	$parent_id               = wp_get_post_parent_id( $variation_id );
642
	$parent_attributes       = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) );
643
	$found_parent_attributes = array();
644
	$variation_attributes    = array();
645
646
	// Compare to parent variable product attributes and ensure they match
647
	foreach ( $parent_attributes as $attribute_name => $options ) {
648
		if ( ! empty( $options['is_variation'] ) ) {
649
			$attribute                 = 'attribute_' . sanitize_title( $attribute_name );
650
			$found_parent_attributes[] = $attribute;
651
			if ( ! array_key_exists( $attribute, $variation_attributes ) ) {
652
				$variation_attributes[ $attribute ] = ''; // Add it - 'any' will be asumed
653
			}
654
		}
655
	}
656
657
	// Get the variation attributes from meta
658
	foreach ( $all_meta as $name => $value ) {
659
		// Only look at valid attribute meta, and also compare variation level attributes and remove any which do not exist at parent level
660
		if ( 0 !== strpos( $name, 'attribute_' ) || ! in_array( $name, $found_parent_attributes ) ) {
661
			unset( $variation_attributes[ $name ] );
662
			continue;
663
		}
664
		/**
665
		 * Pre 2.4 handling where 'slugs' were saved instead of the full text attribute.
666
		 * Attempt to get full version of the text attribute from the parent.
667
		 */
668
		if ( sanitize_title( $value[0] ) === $value[0] && version_compare( get_post_meta( $parent_id, '_product_version', true ), '2.4.0', '<' ) ) {
669 View Code Duplication
			foreach ( $parent_attributes as $attribute ) {
0 ignored issues
show
This code seems to be duplicated across 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...
670
				if ( $name !== 'attribute_' . sanitize_title( $attribute['name'] ) ) {
671
					continue;
672
				}
673
				$text_attributes = wc_get_text_attributes( $attribute['value'] );
674
675
				foreach ( $text_attributes as $text_attribute ) {
676
					if ( sanitize_title( $text_attribute ) === $value[0] ) {
677
						$value[0] = $text_attribute;
678
						break;
679
					}
680
				}
681
			}
682
		}
683
684
		$variation_attributes[ $name ] = $value[0];
685
	}
686
687
	return $variation_attributes;
688
}
689
690
/**
691
 * Get all product cats for a product by ID, including hierarchy
692
 * @since  2.5.0
693
 * @param  int $product_id
694
 * @return array
695
 */
696
function wc_get_product_cat_ids( $product_id ) {
697
	$product_cats = wp_get_post_terms( $product_id, 'product_cat', array( "fields" => "ids" ) );
698
699
	foreach ( $product_cats as $product_cat ) {
700
		$product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) );
701
	}
702
703
	return $product_cats;
704
}
705
706
/**
707
 * Gets data about an attachment, such as alt text and captions.
708
 * @since 2.6.0
709
 * @param object|bool $product
710
 * @return array
711
 */
712
function wc_get_product_attachment_props( $attachment_id, $product = false ) {
713
	$props = array(
714
		'title'   => '',
715
		'caption' => '',
716
		'url'     => '',
717
		'alt'     => '',
718
	);
719
	if ( $attachment_id ) {
720
		$attachment       = get_post( $attachment_id );
721
		$props['title']   = trim( strip_tags( $attachment->post_title ) );
722
		$props['caption'] = trim( strip_tags( $attachment->post_excerpt ) );
723
		$props['url']     = wp_get_attachment_url( $attachment_id );
724
		$props['alt']     = trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) );
725
726
		// Alt text fallbacks
727
		$props['alt']     = empty( $props['alt'] ) ? $props['caption'] : $props['alt'];
728
		$props['alt']     = empty( $props['alt'] ) ? trim( strip_tags( $attachment->post_title ) ) : $props['alt'];
729
		$props['alt']     = empty( $props['alt'] ) && $product ? trim( strip_tags( get_the_title( $product->ID ) ) ) : $props['alt'];
730
	}
731
	return $props;
732
}
733