Issues (942)

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.

class-wc-product-variable-data-store-cpt.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
 * File for WC Variable Product Data Store class.
4
 *
5
 * @package WooCommerce/Classes
6
 */
7
8
if ( ! defined( 'ABSPATH' ) ) {
9
	exit;
10
}
11
12
/**
13
 * WC Variable Product Data Store: Stored in CPT.
14
 *
15
 * @version 3.0.0
16
 */
17
class WC_Product_Variable_Data_Store_CPT extends WC_Product_Data_Store_CPT implements WC_Object_Data_Store_Interface, WC_Product_Variable_Data_Store_Interface {
18
19
	/**
20
	 * Cached & hashed prices array for child variations.
21
	 *
22
	 * @var array
23
	 */
24
	protected $prices_array = array();
25
26
	/**
27
	 * Read attributes from post meta.
28
	 *
29
	 * @param WC_Product $product Product object.
30
	 */
31 24
	protected function read_attributes( &$product ) {
32 24
		$meta_attributes = get_post_meta( $product->get_id(), '_product_attributes', true );
33
34 24
		if ( ! empty( $meta_attributes ) && is_array( $meta_attributes ) ) {
35 20
			$attributes   = array();
36 20
			$force_update = false;
37 20
			foreach ( $meta_attributes as $meta_attribute_key => $meta_attribute_value ) {
38 20
				$meta_value = array_merge(
39
					array(
40 20
						'name'         => '',
41
						'value'        => '',
42
						'position'     => 0,
43
						'is_visible'   => 0,
44
						'is_variation' => 0,
45
						'is_taxonomy'  => 0,
46
					),
47 20
					(array) $meta_attribute_value
48
				);
49
50
				// Maintain data integrity. 4.9 changed sanitization functions - update the values here so variations function correctly.
51 20
				if ( $meta_value['is_variation'] && strstr( $meta_value['name'], '/' ) && sanitize_title( $meta_value['name'] ) !== $meta_attribute_key ) {
52
					global $wpdb;
53
54
					$old_slug      = 'attribute_' . $meta_attribute_key;
55
					$new_slug      = 'attribute_' . sanitize_title( $meta_value['name'] );
56
					$old_meta_rows = $wpdb->get_results( $wpdb->prepare( "SELECT post_id, meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s;", $old_slug ) ); // WPCS: db call ok, cache ok.
57
58
					if ( $old_meta_rows ) {
59
						foreach ( $old_meta_rows as $old_meta_row ) {
60
							update_post_meta( $old_meta_row->post_id, $new_slug, $old_meta_row->meta_value );
61
						}
62
					}
63
64
					$force_update = true;
65
				}
66
67
				// Check if is a taxonomy attribute.
68 20 View Code Duplication
				if ( ! empty( $meta_value['is_taxonomy'] ) ) {
69 14
					if ( ! taxonomy_exists( $meta_value['name'] ) ) {
70
						continue;
71
					}
72 14
					$id      = wc_attribute_taxonomy_id_by_name( $meta_value['name'] );
73 14
					$options = wc_get_object_terms( $product->get_id(), $meta_value['name'], 'term_id' );
74
				} else {
75 6
					$id      = 0;
76 6
					$options = wc_get_text_attributes( $meta_value['value'] );
77
				}
78
79 20
				$attribute = new WC_Product_Attribute();
80 20
				$attribute->set_id( $id );
81 20
				$attribute->set_name( $meta_value['name'] );
82 20
				$attribute->set_options( $options );
83 20
				$attribute->set_position( $meta_value['position'] );
84 20
				$attribute->set_visible( $meta_value['is_visible'] );
85 20
				$attribute->set_variation( $meta_value['is_variation'] );
86 20
				$attributes[] = $attribute;
87
			}
88 20
			$product->set_attributes( $attributes );
89
90 20
			if ( $force_update ) {
91
				$this->update_attributes( $product, true );
92
			}
93
		}
94
	}
95
96
	/**
97
	 * Read product data.
98
	 *
99
	 * @param WC_Product $product Product object.
100
	 *
101
	 * @since 3.0.0
102
	 */
103 24
	protected function read_product_data( &$product ) {
104 24
		parent::read_product_data( $product );
105
106
		// Make sure data which does not apply to variables is unset.
107 24
		$product->set_regular_price( '' );
108 24
		$product->set_sale_price( '' );
109
	}
110
111
	/**
112
	 * Loads variation child IDs.
113
	 *
114
	 * @param WC_Product $product Product object.
115
	 * @param bool       $force_read True to bypass the transient.
116
	 *
117
	 * @return array
118
	 */
119 28
	public function read_children( &$product, $force_read = false ) {
120 28
		$children_transient_name = 'wc_product_children_' . $product->get_id();
121 28
		$children                = get_transient( $children_transient_name );
122
123 28
		if ( empty( $children ) || ! is_array( $children ) || ! isset( $children['all'] ) || ! isset( $children['visible'] ) || $force_read ) {
124
			$all_args = array(
125 28
				'post_parent' => $product->get_id(),
126 28
				'post_type'   => 'product_variation',
127
				'orderby'     => array(
128
					'menu_order' => 'ASC',
129
					'ID'         => 'ASC',
130
				),
131 28
				'fields'      => 'ids',
132
				'post_status' => array( 'publish', 'private' ),
133
				'numberposts' => -1, // phpcs:ignore WordPress.VIP.PostsPerPage.posts_per_page_numberposts
134
			);
135
136 28
			$visible_only_args                = $all_args;
137 28
			$visible_only_args['post_status'] = 'publish';
138
139 28 View Code Duplication
			if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
140
				$visible_only_args['tax_query'][] = array(
141
					'taxonomy' => 'product_visibility',
142
					'field'    => 'name',
143
					'terms'    => 'outofstock',
144
					'operator' => 'NOT IN',
145
				);
146
			}
147 28
			$children['all']     = get_posts( apply_filters( 'woocommerce_variable_children_args', $all_args, $product, false ) );
148 28
			$children['visible'] = get_posts( apply_filters( 'woocommerce_variable_children_args', $visible_only_args, $product, true ) );
149
150 28
			set_transient( $children_transient_name, $children, DAY_IN_SECONDS * 30 );
151
		}
152
153 28
		$children['all']     = wp_parse_id_list( (array) $children['all'] );
154 28
		$children['visible'] = wp_parse_id_list( (array) $children['visible'] );
155
156 28
		return $children;
157
	}
158
159
	/**
160
	 * Loads an array of attributes used for variations, as well as their possible values.
161
	 *
162
	 * @param WC_Product $product Product object.
163
	 *
164
	 * @return array
165
	 */
166 4
	public function read_variation_attributes( &$product ) {
167
		global $wpdb;
168
169 4
		$variation_attributes = array();
170 4
		$attributes           = $product->get_attributes();
171 4
		$child_ids            = $product->get_children();
172 4
		$cache_key            = WC_Cache_Helper::get_cache_prefix( 'product_' . $product->get_id() ) . 'product_variation_attributes_' . $product->get_id();
173 4
		$cache_group          = 'products';
174 4
		$cached_data          = wp_cache_get( $cache_key, $cache_group );
175
176 4
		if ( false !== $cached_data ) {
177
			return $cached_data;
178
		}
179
180 4
		if ( ! empty( $attributes ) ) {
181 4
			foreach ( $attributes as $attribute ) {
182 4
				if ( empty( $attribute['is_variation'] ) ) {
183
					continue;
184
				}
185
186
				// Get possible values for this attribute, for only visible variations.
187 4
				if ( ! empty( $child_ids ) ) {
188 4
					$format     = array_fill( 0, count( $child_ids ), '%d' );
189 4
					$query_in   = '(' . implode( ',', $format ) . ')';
190 4
					$query_args = array( 'attribute_name' => wc_variation_attribute_name( $attribute['name'] ) ) + $child_ids;
191 4
					$values     = array_unique(
192 4
						$wpdb->get_col(
193 4
							$wpdb->prepare( // wpcs: PreparedSQLPlaceholders replacement count ok.
194 4
								"SELECT meta_value FROM {$wpdb->postmeta} WHERE meta_key = %s AND post_id IN {$query_in}", // @codingStandardsIgnoreLine.
195
								$query_args
196
							)
197
						)
198
					);
199
				} else {
200
					$values = array();
201
				}
202
203
				// Empty value indicates that all options for given attribute are available.
204 4
				if ( in_array( null, $values, true ) || in_array( '', $values, true ) || empty( $values ) ) {
205
					$values = $attribute['is_taxonomy'] ? wc_get_object_terms( $product->get_id(), $attribute['name'], 'slug' ) : wc_get_text_attributes( $attribute['value'] );
206
					// Get custom attributes (non taxonomy) as defined.
207 4
				} elseif ( ! $attribute['is_taxonomy'] ) {
208 1
					$text_attributes          = wc_get_text_attributes( $attribute['value'] );
209 1
					$assigned_text_attributes = $values;
210 1
					$values                   = array();
211
212
					// Pre 2.4 handling where 'slugs' were saved instead of the full text attribute.
213 1
					if ( version_compare( get_post_meta( $product->get_id(), '_product_version', true ), '2.4.0', '<' ) ) {
214
						$assigned_text_attributes = array_map( 'sanitize_title', $assigned_text_attributes );
215
						foreach ( $text_attributes as $text_attribute ) {
216
							if ( in_array( sanitize_title( $text_attribute ), $assigned_text_attributes, true ) ) {
217
								$values[] = $text_attribute;
218
							}
219
						}
220
					} else {
221 1
						foreach ( $text_attributes as $text_attribute ) {
222 1
							if ( in_array( $text_attribute, $assigned_text_attributes, true ) ) {
223 1
								$values[] = $text_attribute;
224
							}
225
						}
226
					}
227
				}
228 4
				$variation_attributes[ $attribute['name'] ] = array_unique( $values );
229
			}
230
		}
231
232 4
		wp_cache_set( $cache_key, $variation_attributes, $cache_group );
233
234 4
		return $variation_attributes;
235
	}
236
237
	/**
238
	 * Get an array of all sale and regular prices from all variations. This is used for example when displaying the price range at variable product level or seeing if the variable product is on sale.
239
	 *
240
	 * Can be filtered by plugins which modify costs, but otherwise will include the raw meta costs unlike get_price() which runs costs through the woocommerce_get_price filter.
241
	 * This is to ensure modified prices are not cached, unless intended.
242
	 *
243
	 * @param WC_Product $product Product object.
244
	 * @param bool       $for_display If true, prices will be adapted for display based on the `woocommerce_tax_display_shop` setting (including or excluding taxes).
245
	 *
246
	 * @return array of prices
247
	 * @since  3.0.0
248
	 */
249 8
	public function read_price_data( &$product, $for_display = false ) {
250
251
		/**
252
		 * Transient name for storing prices for this product (note: Max transient length is 45)
253
		 *
254
		 * @since 2.5.0 a single transient is used per product for all prices, rather than many transients per product.
255
		 */
256 8
		$transient_name    = 'wc_var_prices_' . $product->get_id();
257 8
		$transient_version = WC_Cache_Helper::get_transient_version( 'product' );
258 8
		$price_hash        = $this->get_price_hash( $product, $for_display );
259
260
		// Check if prices array is stale.
261 8
		if ( ! isset( $this->prices_array['version'] ) || $this->prices_array['version'] !== $transient_version ) {
262 8
			$this->prices_array = array(
263 8
				'version' => $transient_version,
264
			);
265
		}
266
267
		/**
268
		 * $this->prices_array is an array of values which may have been modified from what is stored in transients - this may not match $transient_cached_prices_array.
269
		 * If the value has already been generated, we don't need to grab the values again so just return them. They are already filtered.
270
		 */
271 8
		if ( empty( $this->prices_array[ $price_hash ] ) ) {
272 8
			$transient_cached_prices_array = array_filter( (array) json_decode( strval( get_transient( $transient_name ) ), true ) );
273
274
			// If the product version has changed since the transient was last saved, reset the transient cache.
275 8
			if ( ! isset( $transient_cached_prices_array['version'] ) || $transient_version !== $transient_cached_prices_array['version'] ) {
276
				$transient_cached_prices_array = array(
277 8
					'version' => $transient_version,
278
				);
279
			}
280
281
			// If the prices are not stored for this hash, generate them and add to the transient.
282 8
			if ( empty( $transient_cached_prices_array[ $price_hash ] ) ) {
283
				$prices_array = array(
284 8
					'price'         => array(),
285
					'regular_price' => array(),
286
					'sale_price'    => array(),
287
				);
288
289 8
				$variation_ids = $product->get_visible_children();
290
291 8
				if ( is_callable( '_prime_post_caches' ) ) {
292 8
					_prime_post_caches( $variation_ids );
293
				}
294
295 8
				foreach ( $variation_ids as $variation_id ) {
296 7
					$variation = wc_get_product( $variation_id );
297
298 7
					if ( $variation ) {
299 7
						$price         = apply_filters( 'woocommerce_variation_prices_price', $variation->get_price( 'edit' ), $variation, $product );
300 7
						$regular_price = apply_filters( 'woocommerce_variation_prices_regular_price', $variation->get_regular_price( 'edit' ), $variation, $product );
301 7
						$sale_price    = apply_filters( 'woocommerce_variation_prices_sale_price', $variation->get_sale_price( 'edit' ), $variation, $product );
302
303
						// Skip empty prices.
304 7
						if ( '' === $price ) {
305
							continue;
306
						}
307
308
						// If sale price does not equal price, the product is not yet on sale.
309 7
						if ( $sale_price === $regular_price || $sale_price !== $price ) {
310 7
							$sale_price = $regular_price;
311
						}
312
313
						// If we are getting prices for display, we need to account for taxes.
314 7
						if ( $for_display ) {
315
							if ( 'incl' === get_option( 'woocommerce_tax_display_shop' ) ) {
316
								$price         = '' === $price ? '' : wc_get_price_including_tax(
317
									$variation,
318
									array(
319
										'qty'   => 1,
320
										'price' => $price,
321
									)
322
								);
323
								$regular_price = '' === $regular_price ? '' : wc_get_price_including_tax(
324
									$variation,
325
									array(
326
										'qty'   => 1,
327
										'price' => $regular_price,
328
									)
329
								);
330
								$sale_price    = '' === $sale_price ? '' : wc_get_price_including_tax(
331
									$variation,
332
									array(
333
										'qty'   => 1,
334
										'price' => $sale_price,
335
									)
336
								);
337
							} else {
338
								$price         = '' === $price ? '' : wc_get_price_excluding_tax(
339
									$variation,
340
									array(
341
										'qty'   => 1,
342
										'price' => $price,
343
									)
344
								);
345
								$regular_price = '' === $regular_price ? '' : wc_get_price_excluding_tax(
346
									$variation,
347
									array(
348
										'qty'   => 1,
349
										'price' => $regular_price,
350
									)
351
								);
352
								$sale_price    = '' === $sale_price ? '' : wc_get_price_excluding_tax(
353
									$variation,
354
									array(
355
										'qty'   => 1,
356
										'price' => $sale_price,
357
									)
358
								);
359
							}
360
						}
361
362 7
						$prices_array['price'][ $variation_id ]         = wc_format_decimal( $price, wc_get_price_decimals() );
363 7
						$prices_array['regular_price'][ $variation_id ] = wc_format_decimal( $regular_price, wc_get_price_decimals() );
364 7
						$prices_array['sale_price'][ $variation_id ]    = wc_format_decimal( $sale_price . '.00', wc_get_price_decimals() );
365
366 7
						$prices_array = apply_filters( 'woocommerce_variation_prices_array', $prices_array, $variation, $for_display );
367
					}
368
				}
369
370
				// Add all pricing data to the transient array.
371 8
				foreach ( $prices_array as $key => $values ) {
372 8
					$transient_cached_prices_array[ $price_hash ][ $key ] = $values;
373
				}
374
375 8
				set_transient( $transient_name, wp_json_encode( $transient_cached_prices_array ), DAY_IN_SECONDS * 30 );
376
			}
377
378
			/**
379
			 * Give plugins one last chance to filter the variation prices array which has been generated and store locally to the class.
380
			 * This value may differ from the transient cache. It is filtered once before storing locally.
381
			 */
382 8
			$this->prices_array[ $price_hash ] = apply_filters( 'woocommerce_variation_prices', $transient_cached_prices_array[ $price_hash ], $product, $for_display );
383
		}
384 8
		return $this->prices_array[ $price_hash ];
385
	}
386
387
	/**
388
	 * Create unique cache key based on the tax location (affects displayed/cached prices), product version and active price filters.
389
	 * DEVELOPERS should filter this hash if offering conditional pricing to keep it unique.
390
	 *
391
	 * @param WC_Product $product Product object.
392
	 * @param bool       $for_display If taxes should be calculated or not.
393
	 *
394
	 * @since  3.0.0
395
	 * @return string
396
	 */
397 8
	protected function get_price_hash( &$product, $for_display = false ) {
398
		global $wp_filter;
399
400 8
		$price_hash   = $for_display && wc_tax_enabled() ? array( get_option( 'woocommerce_tax_display_shop', 'excl' ), WC_Tax::get_rates() ) : array( false );
401 8
		$filter_names = array( 'woocommerce_variation_prices_price', 'woocommerce_variation_prices_regular_price', 'woocommerce_variation_prices_sale_price' );
402
403 8
		foreach ( $filter_names as $filter_name ) {
404 8
			if ( ! empty( $wp_filter[ $filter_name ] ) ) {
405
				$price_hash[ $filter_name ] = array();
406
407
				foreach ( $wp_filter[ $filter_name ] as $priority => $callbacks ) {
408
					$price_hash[ $filter_name ][] = array_values( wp_list_pluck( $callbacks, 'function' ) );
409
				}
410
			}
411
		}
412
413 8
		return md5( wp_json_encode( apply_filters( 'woocommerce_get_variation_prices_hash', $price_hash, $product, $for_display ) ) );
414
	}
415
416
	/**
417
	 * Does a child have a weight set?
418
	 *
419
	 * @param WC_Product $product Product object.
420
	 *
421
	 * @since  3.0.0
422
	 * @return boolean
423
	 */
424 View Code Duplication
	public function child_has_weight( $product ) {
425
		global $wpdb;
426
		$children = $product->get_visible_children();
427
		if ( ! $children ) {
428
			return false;
429
		}
430
431
		$format   = array_fill( 0, count( $children ), '%d' );
432
		$query_in = '(' . implode( ',', $format ) . ')';
433
434
		return null !== $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_weight' AND meta_value > 0 AND post_id IN {$query_in}", $children ) ); // @codingStandardsIgnoreLine.
435
	}
436
437
	/**
438
	 * Does a child have dimensions set?
439
	 *
440
	 * @param WC_Product $product Product object.
441
	 *
442
	 * @since  3.0.0
443
	 * @return boolean
444
	 */
445 2 View Code Duplication
	public function child_has_dimensions( $product ) {
446
		global $wpdb;
447 2
		$children = $product->get_visible_children();
448 2
		if ( ! $children ) {
449
			return false;
450
		}
451
452 2
		$format   = array_fill( 0, count( $children ), '%d' );
453 2
		$query_in = '(' . implode( ',', $format ) . ')';
454
455 2
		return null !== $wpdb->get_var( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key IN ( '_length', '_width', '_height' ) AND meta_value > 0 AND post_id IN {$query_in}", $children ) ); // @codingStandardsIgnoreLine.
456
	}
457
458
	/**
459
	 * Is a child in stock?
460
	 *
461
	 * @param WC_Product $product Product object.
462
	 *
463
	 * @since  3.0.0
464
	 * @return boolean
465
	 */
466 29
	public function child_is_in_stock( $product ) {
467 29
		return $this->child_has_stock_status( $product, 'instock' );
468
	}
469
470
	/**
471
	 * Does a child have a stock status?
472
	 *
473
	 * @param WC_Product $product Product object.
474
	 * @param string     $status 'instock', 'outofstock', or 'onbackorder'.
475
	 *
476
	 * @since  3.3.0
477
	 * @return boolean
478
	 */
479 29
	public function child_has_stock_status( $product, $status ) {
480
		global $wpdb;
481
482 29
		$children = $product->get_children();
483
484 29
		if ( $children ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $children 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...
485 5
			$format     = array_fill( 0, count( $children ), '%d' );
486 5
			$query_in   = '(' . implode( ',', $format ) . ')';
487 5
			$query_args = array( 'stock_status' => $status ) + $children;
488
			// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared
489 5
			if ( get_option( 'woocommerce_product_lookup_table_is_generating' ) ) {
490
				$query = "SELECT COUNT( post_id ) FROM {$wpdb->postmeta} WHERE meta_key = '_stock_status' AND meta_value = %s AND post_id IN {$query_in}";
491
			} else {
492 5
				$query = "SELECT COUNT( product_id ) FROM {$wpdb->wc_product_meta_lookup} WHERE stock_status = %s AND product_id IN {$query_in}";
493
			}
494 5
			$children_with_status = $wpdb->get_var(
495 5
				$wpdb->prepare(
496 5
					$query,
497
					$query_args
498
				)
499
			);
500
			// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared
501
		} else {
502 29
			$children_with_status = 0;
503
		}
504
505 29
		return (bool) $children_with_status;
506
	}
507
508
	/**
509
	 * Syncs all variation names if the parent name is changed.
510
	 *
511
	 * @param WC_Product $product Product object.
512
	 * @param string     $previous_name Variation previous name.
513
	 * @param string     $new_name Variation new name.
514
	 *
515
	 * @since 3.0.0
516
	 */
517 29
	public function sync_variation_names( &$product, $previous_name = '', $new_name = '' ) {
518 29
		if ( $new_name !== $previous_name ) {
519
			global $wpdb;
520
521 20
			$wpdb->query(
522 20
				$wpdb->prepare(
523 20
					"UPDATE {$wpdb->posts}
524
					SET post_title = REPLACE( post_title, %s, %s )
525
					WHERE post_type = 'product_variation'
526
					AND post_parent = %d",
527 20
					$previous_name ? $previous_name : 'AUTO-DRAFT',
528
					$new_name,
529 20
					$product->get_id()
530
				)
531
			);
532
		}
533
	}
534
535
	/**
536
	 * Stock managed at the parent level - update children being managed by this product.
537
	 * This sync function syncs downwards (from parent to child) when the variable product is saved.
538
	 *
539
	 * @param WC_Product $product Product object.
540
	 *
541
	 * @since 3.0.0
542
	 */
543 29
	public function sync_managed_variation_stock_status( &$product ) {
544
		global $wpdb;
545
546 29
		if ( $product->get_manage_stock() ) {
547 1
			$children = $product->get_children();
548 1
			$changed  = false;
549
550 1
			if ( $children ) {
551
				$status           = $product->get_stock_status();
552
				$format           = array_fill( 0, count( $children ), '%d' );
553
				$query_in         = '(' . implode( ',', $format ) . ')';
554
				$managed_children = array_unique( $wpdb->get_col( $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_manage_stock' AND meta_value != 'yes' AND post_id IN {$query_in}", $children ) ) ); // @codingStandardsIgnoreLine.
555
				foreach ( $managed_children as $managed_child ) {
556
					if ( update_post_meta( $managed_child, '_stock_status', $status ) ) {
557
						$this->update_lookup_table( $managed_child, 'wc_product_meta_lookup' );
558
						$changed = true;
559
					}
560
				}
561
			}
562
563 1
			if ( $changed ) {
564
				$children = $this->read_children( $product, true );
565
				$product->set_children( $children['all'] );
566
				$product->set_visible_children( $children['visible'] );
567
			}
568
		}
569
	}
570
571
	/**
572
	 * Sync variable product prices with children.
573
	 *
574
	 * @param WC_Product $product Product object.
575
	 *
576
	 * @since 3.0.0
577
	 */
578 1
	public function sync_price( &$product ) {
579
		global $wpdb;
580
581 1
		$children = $product->get_visible_children();
582 1
		if ( $children ) {
583
			$format   = array_fill( 0, count( $children ), '%d' );
584
			$query_in = '(' . implode( ',', $format ) . ')';
585
			$prices   = array_unique( $wpdb->get_col( $wpdb->prepare( "SELECT meta_value FROM $wpdb->postmeta WHERE meta_key = '_price' AND post_id IN {$query_in}", $children ) ) ); // @codingStandardsIgnoreLine.
586
		} else {
587 1
			$prices = array();
588
		}
589
590 1
		delete_post_meta( $product->get_id(), '_price' );
591 1
		delete_post_meta( $product->get_id(), '_sale_price' );
592 1
		delete_post_meta( $product->get_id(), '_regular_price' );
593
594 1
		if ( $prices ) {
595
			sort( $prices, SORT_NUMERIC );
596
			// To allow sorting and filtering by multiple values, we have no choice but to store child prices in this manner.
597
			foreach ( $prices as $price ) {
598
				if ( is_null( $price ) || '' === $price ) {
599
					continue;
600
				}
601
				add_post_meta( $product->get_id(), '_price', $price, false );
602
			}
603
		}
604
605 1
		$this->update_lookup_table( $product->get_id(), 'wc_product_meta_lookup' );
606
607
		/**
608
		 * Fire an action for this direct update so it can be detected by other code.
609
		 *
610
		 * @since 3.6
611
		 * @param int $product_id Product ID that was updated directly.
612
		 */
613 1
		do_action( 'woocommerce_updated_product_price', $product->get_id() );
614
	}
615
616
	/**
617
	 * Sync variable product stock status with children.
618
	 * Change does not persist unless saved by caller.
619
	 *
620
	 * @param WC_Product $product Product object.
621
	 *
622
	 * @since 3.0.0
623
	 */
624 29
	public function sync_stock_status( &$product ) {
625 29
		if ( $product->child_is_in_stock() ) {
626 5
			$product->set_stock_status( 'instock' );
627 29
		} elseif ( $product->child_is_on_backorder() ) {
628 1
			$product->set_stock_status( 'onbackorder' );
629
		} else {
630 29
			$product->set_stock_status( 'outofstock' );
631
		}
632
	}
633
634
	/**
635
	 * Delete variations of a product.
636
	 *
637
	 * @param int  $product_id Product ID.
638
	 * @param bool $force_delete False to trash.
639
	 *
640
	 * @since 3.0.0
641
	 */
642 3
	public function delete_variations( $product_id, $force_delete = false ) {
643 3
		if ( ! is_numeric( $product_id ) || 0 >= $product_id ) {
644
			return;
645
		}
646
647 3
		$variation_ids = wp_parse_id_list(
648 3
			get_posts(
649
				array(
650 3
					'post_parent' => $product_id,
651 3
					'post_type'   => 'product_variation',
652 3
					'fields'      => 'ids',
653
					'post_status' => array( 'any', 'trash', 'auto-draft' ),
654
					'numberposts' => -1, // phpcs:ignore WordPress.VIP.PostsPerPage.posts_per_page_numberposts
655
				)
656
			)
657
		);
658
659 3
		if ( ! empty( $variation_ids ) ) {
660
			foreach ( $variation_ids as $variation_id ) {
661
				if ( $force_delete ) {
662
					do_action( 'woocommerce_before_delete_product_variation', $variation_id );
663
					wp_delete_post( $variation_id, true );
664
					do_action( 'woocommerce_delete_product_variation', $variation_id );
665
				} else {
666
					wp_trash_post( $variation_id );
667
					do_action( 'woocommerce_trash_product_variation', $variation_id );
668
				}
669
			}
670
		}
671
672 3
		delete_transient( 'wc_product_children_' . $product_id );
673
	}
674
675
	/**
676
	 * Untrash variations.
677
	 *
678
	 * @param int $product_id Product ID.
679
	 */
680
	public function untrash_variations( $product_id ) {
681
		$variation_ids = wp_parse_id_list(
682
			get_posts(
683
				array(
684
					'post_parent' => $product_id,
685
					'post_type'   => 'product_variation',
686
					'fields'      => 'ids',
687
					'post_status' => 'trash',
688
					'numberposts' => -1, // phpcs:ignore WordPress.VIP.PostsPerPage.posts_per_page_numberposts
689
				)
690
			)
691
		);
692
693
		if ( ! empty( $variation_ids ) ) {
694
			foreach ( $variation_ids as $variation_id ) {
695
				wp_untrash_post( $variation_id );
696
			}
697
		}
698
699
		delete_transient( 'wc_product_children_' . $product_id );
700
	}
701
}
702