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

WC_Product_Variation_Data_Store_CPT::update_guid()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 14
ccs 0
cts 0
cp 0
crap 2
rs 9.7998
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class WC_Product_Variation_Data_Store_CPT file.
4
 *
5
 * @package WooCommerce\DataStores
6
 */
7
8
if ( ! defined( 'ABSPATH' ) ) {
9
	exit;
10
}
11
12
/**
13
 * WC Variation Product Data Store: Stored in CPT.
14
 *
15
 * @version  3.0.0
16
 */
17
class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT implements WC_Object_Data_Store_Interface {
18
19
	/**
20
	 * Callback to remove unwanted meta data.
21
	 *
22
	 * @param object $meta Meta object.
23
	 * @return bool false if excluded.
24
	 */
25 15
	protected function exclude_internal_meta_keys( $meta ) {
26 15
		return ! in_array( $meta->meta_key, $this->internal_meta_keys, true ) && 0 !== stripos( $meta->meta_key, 'attribute_' ) && 0 !== stripos( $meta->meta_key, 'wp_' );
27
	}
28
29
	/*
30
	|--------------------------------------------------------------------------
31
	| CRUD Methods
32
	|--------------------------------------------------------------------------
33
	*/
34
35
	/**
36
	 * Reads a product from the database and sets its data to the class.
37
	 *
38
	 * @since 3.0.0
39
	 * @param WC_Product $product Product object. $product Product object.
40
	 */
41 60
	public function read( &$product ) {
42 60
		$product->set_defaults();
43
44 60
		if ( ! $product->get_id() ) {
45
			return;
46
		}
47
48 60
		$post_object = get_post( $product->get_id() );
49
50 60 View Code Duplication
		if ( ! $post_object || ! in_array( $post_object->post_type, array( 'product', 'product_variation' ), true ) ) {
51
			return;
52
		}
53
54 60
		$product->set_props(
55
			array(
56 60
				'name'            => $post_object->post_title,
57 60
				'slug'            => $post_object->post_name,
58 60
				'date_created'    => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null,
59 60
				'date_modified'   => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null,
60 60
				'status'          => $post_object->post_status,
61 60
				'menu_order'      => $post_object->menu_order,
62 60
				'reviews_allowed' => 'open' === $post_object->comment_status,
63 60
				'parent_id'       => $post_object->post_parent,
64
			)
65
		);
66
67
		// The post parent is not a valid variable product so we should prevent this.
68 60
		if ( $product->get_parent_id( 'edit' ) && 'product' !== get_post_type( $product->get_parent_id( 'edit' ) ) ) {
69
			$product->set_parent_id( 0 );
70
		}
71
72 60
		$this->read_downloads( $product );
73 60
		$this->read_product_data( $product );
74 60
		$this->read_extra_data( $product );
75 60
		$product->set_attributes( wc_get_product_variation_attributes( $product->get_id() ) );
76
77
		/**
78
		 * If a variation title is not in sync with the parent e.g. saved prior to 3.0, or if the parent title has changed, detect here and update.
79
		 */
80 60
		$new_title = $this->generate_product_title( $product );
81
82 60
		if ( $post_object->post_title !== $new_title ) {
83 51
			$product->set_name( $new_title );
84 51
			$GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, array( 'post_title' => $new_title ), array( 'ID' => $product->get_id() ) );
85 51
			clean_post_cache( $product->get_id() );
86
		}
87
88
		// Set object_read true once all data is read.
89 60
		$product->set_object_read( true );
90
	}
91
92
	/**
93
	 * Create a new product.
94
	 *
95
	 * @since 3.0.0
96
	 * @param WC_Product $product Product object.
97
	 */
98 60
	public function create( &$product ) {
99 60
		if ( ! $product->get_date_created() ) {
100 60
			$product->set_date_created( current_time( 'timestamp', true ) );
101
		}
102
103 60
		$new_title = $this->generate_product_title( $product );
104
105 60
		if ( $product->get_name( 'edit' ) !== $new_title ) {
106 58
			$product->set_name( $new_title );
107
		}
108
109
		// The post parent is not a valid variable product so we should prevent this.
110 60
		if ( $product->get_parent_id( 'edit' ) && 'product' !== get_post_type( $product->get_parent_id( 'edit' ) ) ) {
111
			$product->set_parent_id( 0 );
112
		}
113
114 60
		$id = wp_insert_post(
115 60
			apply_filters(
116 60
				'woocommerce_new_product_variation_data',
117 60
				array(
118 60
					'post_type'      => 'product_variation',
119 60
					'post_status'    => $product->get_status() ? $product->get_status() : 'publish',
120 60
					'post_author'    => get_current_user_id(),
121 60
					'post_title'     => $product->get_name( 'edit' ),
122 60
					'post_content'   => '',
123 60
					'post_parent'    => $product->get_parent_id(),
124 60
					'comment_status' => 'closed',
125 60
					'ping_status'    => 'closed',
126 60
					'menu_order'     => $product->get_menu_order(),
127 60
					'post_date'      => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() ),
128 60
					'post_date_gmt'  => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getTimestamp() ),
129
					'post_name'      => $product->get_slug( 'edit' ),
130 60
				)
131
			),
132
			true
133 60
		);
134 60
135 View Code Duplication
		if ( $id && ! is_wp_error( $id ) ) {
136 60
			$product->set_id( $id );
137 60
138 60
			$this->update_post_meta( $product, true );
139 60
			$this->update_terms( $product, true );
140 60
			$this->update_visibility( $product, true );
141
			$this->update_attributes( $product, true );
142 60
			$this->handle_updated_props( $product );
143 60
144
			$product->save_meta_data();
145 60
			$product->apply_changes();
146
147 60
			$this->update_version_and_type( $product );
148
			$this->update_guid( $product );
149 60
150
			$this->clear_caches( $product );
151
152
			do_action( 'woocommerce_new_product_variation', $id );
153
		}
154
	}
155
156
	/**
157
	 * Updates an existing product.
158
	 *
159 16
	 * @since 3.0.0
160 16
	 * @param WC_Product $product Product object.
161
	 */
162 16
	public function update( &$product ) {
163
		$product->save_meta_data();
164
165
		if ( ! $product->get_date_created() ) {
166 16
			$product->set_date_created( current_time( 'timestamp', true ) );
167
		}
168 16
169 2
		$new_title = $this->generate_product_title( $product );
170
171
		if ( $product->get_name( 'edit' ) !== $new_title ) {
172
			$product->set_name( $new_title );
173 16
		}
174
175
		// The post parent is not a valid variable product so we should prevent this.
176
		if ( $product->get_parent_id( 'edit' ) && 'product' !== get_post_type( $product->get_parent_id( 'edit' ) ) ) {
177 16
			$product->set_parent_id( 0 );
178
		}
179
180 16
		$changes = $product->get_changes();
181
182 3
		// Only update the post when the post data changes.
183 3
		if ( array_intersect( array( 'name', 'parent_id', 'status', 'menu_order', 'date_created', 'date_modified' ), array_keys( $changes ) ) ) {
184 3
			$post_data = array(
185 3
				'post_title'        => $product->get_name( 'edit' ),
186 3
				'post_parent'       => $product->get_parent_id( 'edit' ),
187 3
				'comment_status'    => 'closed',
188 3
				'post_status'       => $product->get_status( 'edit' ) ? $product->get_status( 'edit' ) : 'publish',
189 3
				'menu_order'        => $product->get_menu_order( 'edit' ),
190 3
				'post_date'         => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() ),
191 3
				'post_date_gmt'     => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getTimestamp() ),
192 3
				'post_modified'     => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getOffsetTimestamp() ) : current_time( 'mysql' ),
193
				'post_modified_gmt' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getTimestamp() ) : current_time( 'mysql', 1 ),
194
				'post_type'         => 'product_variation',
195
				'post_name'         => $product->get_slug( 'edit' ),
196
			);
197
198
			/**
199
			 * When updating this object, to prevent infinite loops, use $wpdb
200
			 * to update data, since wp_update_post spawns more calls to the
201
			 * save_post action.
202
			 *
203 3
			 * This ensures hooks are fired by either WP itself (admin screen save),
204
			 * or an update purely from CRUD.
205
			 */
206 View Code Duplication
			if ( doing_action( 'save_post' ) ) {
207 3
				$GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, $post_data, array( 'ID' => $product->get_id() ) );
208
				clean_post_cache( $product->get_id() );
209 3
			} else {
210
				wp_update_post( array_merge( array( 'ID' => $product->get_id() ), $post_data ) );
211
			}
212 13
			$product->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook.
213 13
214 View Code Duplication
		} else { // Only update post modified time to record this save event.
215 13
			$GLOBALS['wpdb']->update(
216 13
				$GLOBALS['wpdb']->posts,
217
				array(
218
					'post_modified'     => current_time( 'mysql' ),
219 13
					'post_modified_gmt' => current_time( 'mysql', 1 ),
220
				),
221
				array(
222 13
					'ID' => $product->get_id(),
223
				)
224
			);
225 16
			clean_post_cache( $product->get_id() );
226 16
		}
227 16
228 16
		$this->update_post_meta( $product );
229 16
		$this->update_terms( $product );
230
		$this->update_visibility( $product, true );
231 16
		$this->update_attributes( $product );
232
		$this->handle_updated_props( $product );
233 16
234
		$product->apply_changes();
235 16
236
		$this->update_version_and_type( $product );
237 16
238
		$this->clear_caches( $product );
239
240
		do_action( 'woocommerce_update_product_variation', $product->get_id() );
241
	}
242
243
	/*
244
	|--------------------------------------------------------------------------
245
	| Additional Methods
246
	|--------------------------------------------------------------------------
247
	*/
248
249
	/**
250
	 * Generates a title with attribute information for a variation.
251
	 * Products will get a title of the form "Name - Value, Value" or just "Name".
252
	 *
253
	 * @since 3.0.0
254 60
	 * @param WC_Product $product Product object.
255 60
	 * @return string
256
	 */
257
	protected function generate_product_title( $product ) {
258 60
		$attributes = (array) $product->get_attributes();
259
260
		// Do not include attributes if the product has 3+ attributes.
261
		$should_include_attributes = count( $attributes ) < 3;
262 60
263 4
		// Do not include attributes if an attribute name has 2+ words and the
264 4
		// product has multiple attributes.
265
		if ( $should_include_attributes && 1 < count( $attributes ) ) {
266 4
			foreach ( $attributes as $name => $value ) {
267
				if ( false !== strpos( $name, '-' ) ) {
268
					$should_include_attributes = false;
269
					break;
270
				}
271 60
			}
272 60
		}
273 60
274 60
		$should_include_attributes = apply_filters( 'woocommerce_product_variation_title_include_attributes', $should_include_attributes, $product );
275
		$separator                 = apply_filters( 'woocommerce_product_variation_title_attributes_separator', ' - ', $product );
276 60
		$title_base                = get_post_field( 'post_title', $product->get_parent_id() );
277
		$title_suffix              = $should_include_attributes ? wc_get_formatted_variation( $product, true, false ) : '';
278
279
		return apply_filters( 'woocommerce_product_variation_title', $title_suffix ? $title_base . $separator . $title_suffix : $title_base, $product, $title_base, $title_suffix );
280
	}
281
282
	/**
283
	 * Make sure we store the product version (to track data changes).
284
	 *
285 60
	 * @param WC_Product $product Product object.
286 60
	 * @since 3.0.0
287 60
	 */
288
	protected function update_version_and_type( &$product ) {
289
		wp_set_object_terms( $product->get_id(), '', 'product_type' );
290
		update_post_meta( $product->get_id(), '_product_version', WC_VERSION );
291
	}
292
293
	/**
294
	 * Read post data.
295
	 *
296
	 * @since 3.0.0
297 60
	 * @param WC_Product $product Product object.
298 60
	 * @throws WC_Data_Exception If WC_Product::set_tax_status() is called with an invalid tax status.
299
	 */
300 60
	protected function read_product_data( &$product ) {
301
		$id = $product->get_id();
302 60
303 60
		$product->set_props(
304 60
			array(
305 60
				'description'       => get_post_meta( $id, '_variation_description', true ),
306 60
				'regular_price'     => get_post_meta( $id, '_regular_price', true ),
307 60
				'sale_price'        => get_post_meta( $id, '_sale_price', true ),
308 60
				'date_on_sale_from' => get_post_meta( $id, '_sale_price_dates_from', true ),
309 60
				'date_on_sale_to'   => get_post_meta( $id, '_sale_price_dates_to', true ),
310 60
				'manage_stock'      => get_post_meta( $id, '_manage_stock', true ),
311 60
				'stock_status'      => get_post_meta( $id, '_stock_status', true ),
312 60
				'shipping_class_id' => current( $this->get_term_ids( $id, 'product_shipping_class' ) ),
313 60
				'virtual'           => get_post_meta( $id, '_virtual', true ),
314 60
				'downloadable'      => get_post_meta( $id, '_downloadable', true ),
315 60
				'gallery_image_ids' => array_filter( explode( ',', get_post_meta( $id, '_product_image_gallery', true ) ) ),
316 60
				'download_limit'    => get_post_meta( $id, '_download_limit', true ),
317 60
				'download_expiry'   => get_post_meta( $id, '_download_expiry', true ),
318 60
				'image_id'          => get_post_thumbnail_id( $id ),
319 60
				'backorders'        => get_post_meta( $id, '_backorders', true ),
320 60
				'sku'               => get_post_meta( $id, '_sku', true ),
321 60
				'stock_quantity'    => get_post_meta( $id, '_stock', true ),
322 60
				'weight'            => get_post_meta( $id, '_weight', true ),
323 60
				'length'            => get_post_meta( $id, '_length', true ),
324
				'width'             => get_post_meta( $id, '_width', true ),
325
				'height'            => get_post_meta( $id, '_height', true ),
326
				'tax_class'         => ! metadata_exists( 'post', $id, '_tax_class' ) ? 'parent' : get_post_meta( $id, '_tax_class', true ),
327 60
			)
328 6
		);
329
330 60
		if ( $product->is_on_sale( 'edit' ) ) {
331
			$product->set_price( $product->get_sale_price( 'edit' ) );
332
		} else {
333 60
			$product->set_price( $product->get_regular_price( 'edit' ) );
334 60
		}
335 60
336 60
		$parent_object   = get_post( $product->get_parent_id() );
337 60
		$terms           = get_the_terms( $product->get_parent_id(), 'product_visibility' );
338
		$term_names      = is_array( $terms ) ? wp_list_pluck( $terms, 'name' ) : array();
339 60
		$exclude_search  = in_array( 'exclude-from-search', $term_names, true );
340
		$exclude_catalog = in_array( 'exclude-from-catalog', $term_names, true );
341 60
342 View Code Duplication
		if ( $exclude_search && $exclude_catalog ) {
343 60
			$catalog_visibility = 'hidden';
344
		} elseif ( $exclude_search ) {
345
			$catalog_visibility = 'catalog';
346 60
		} elseif ( $exclude_catalog ) {
347
			$catalog_visibility = 'search';
348
		} else {
349 60
			$catalog_visibility = 'visible';
350
		}
351 60
352 60
		$product->set_parent_data(
353 60
			array(
354 60
				'title'              => $parent_object ? $parent_object->post_title : '',
355 60
				'status'             => $parent_object ? $parent_object->post_status : '',
356 60
				'sku'                => get_post_meta( $product->get_parent_id(), '_sku', true ),
357 60
				'manage_stock'       => get_post_meta( $product->get_parent_id(), '_manage_stock', true ),
358 60
				'backorders'         => get_post_meta( $product->get_parent_id(), '_backorders', true ),
359 60
				'low_stock_amount'   => get_post_meta( $product->get_parent_id(), '_low_stock_amount', true ),
360 60
				'stock_quantity'     => wc_stock_amount( get_post_meta( $product->get_parent_id(), '_stock', true ) ),
361 60
				'weight'             => get_post_meta( $product->get_parent_id(), '_weight', true ),
362 60
				'length'             => get_post_meta( $product->get_parent_id(), '_length', true ),
363 60
				'width'              => get_post_meta( $product->get_parent_id(), '_width', true ),
364 60
				'height'             => get_post_meta( $product->get_parent_id(), '_height', true ),
365 60
				'tax_class'          => get_post_meta( $product->get_parent_id(), '_tax_class', true ),
366 60
				'shipping_class_id'  => absint( current( $this->get_term_ids( $product->get_parent_id(), 'product_shipping_class' ) ) ),
367
				'image_id'           => get_post_thumbnail_id( $product->get_parent_id() ),
368
				'purchase_note'      => get_post_meta( $product->get_parent_id(), '_purchase_note', true ),
369
				'catalog_visibility' => $catalog_visibility,
370
			)
371 60
		);
372 60
373 60
		// Pull data from the parent when there is no user-facing way to set props.
374
		$product->set_sold_individually( get_post_meta( $product->get_parent_id(), '_sold_individually', true ) );
375
		$product->set_tax_status( get_post_meta( $product->get_parent_id(), '_tax_status', true ) );
376
		$product->set_cross_sell_ids( get_post_meta( $product->get_parent_id(), '_crosssell_ids', true ) );
377
	}
378
379
	/**
380
	 * For all stored terms in all taxonomies, save them to the DB.
381
	 *
382
	 * @since 3.0.0
383 60
	 * @param WC_Product $product Product object.
384 60
	 * @param bool       $force Force update. Used during create.
385
	 */
386 60
	protected function update_terms( &$product, $force = false ) {
387 60
		$changes = $product->get_changes();
388
389 View Code Duplication
		if ( $force || array_key_exists( 'shipping_class_id', $changes ) ) {
390
			wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false );
391
		}
392
	}
393
394
	/**
395
	 * Update visibility terms based on props.
396
	 *
397
	 * @since 3.0.0
398
	 *
399 60
	 * @param WC_Product $product Product object.
400 60
	 * @param bool       $force Force update. Used during create.
401
	 */
402 60
	protected function update_visibility( &$product, $force = false ) {
403 60
		$changes = $product->get_changes();
404
405 60
		if ( $force || array_intersect( array( 'stock_status' ), array_keys( $changes ) ) ) {
406 3
			$terms = array();
407
408
			if ( 'outofstock' === $product->get_stock_status() ) {
409 60
				$terms[] = 'outofstock';
410
			}
411
412
			wp_set_post_terms( $product->get_id(), $terms, 'product_visibility', false );
413
		}
414
	}
415
416
	/**
417
	 * Update attribute meta values.
418
	 *
419
	 * @since 3.0.0
420 60
	 * @param WC_Product $product Product object.
421 60
	 * @param bool       $force Force update. Used during create.
422
	 */
423 60
	protected function update_attributes( &$product, $force = false ) {
424
		$changes = $product->get_changes();
425 60
426 60
		if ( $force || array_key_exists( 'attributes', $changes ) ) {
427 60
			global $wpdb;
428 52
			$attributes             = $product->get_attributes();
429 52
			$updated_attribute_keys = array();
430
			foreach ( $attributes as $key => $value ) {
431
				update_post_meta( $product->get_id(), 'attribute_' . $key, $value );
432
				$updated_attribute_keys[] = 'attribute_' . $key;
433 60
			}
434 60
435 60
			// Remove old taxonomies attributes so data is kept up to date - first get attribute key names.
436 60
			$delete_attribute_keys = $wpdb->get_col(
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
437 60
				$wpdb->prepare(
438
					// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.PreparedSQLPlaceholders.QuotedDynamicPlaceholderGeneration
439
					"SELECT meta_key FROM {$wpdb->postmeta} WHERE meta_key LIKE %s AND meta_key NOT IN ( '" . implode( "','", array_map( 'esc_sql', $updated_attribute_keys ) ) . "' ) AND post_id = %d",
440
					$wpdb->esc_like( 'attribute_' ) . '%',
441 60
					$product->get_id()
442
				)
443
			);
444
445
			foreach ( $delete_attribute_keys as $key ) {
446
				delete_post_meta( $product->get_id(), $key );
447
			}
448
		}
449
	}
450
451
	/**
452
	 * Helper method that updates all the post meta for a product based on it's settings in the WC_Product class.
453
	 *
454 60
	 * @since 3.0.0
455
	 * @param WC_Product $product Product object.
456 60
	 * @param bool       $force Force update. Used during create.
457
	 */
458 View Code Duplication
	public function update_post_meta( &$product, $force = false ) {
0 ignored issues
show
Duplication introduced by
This method 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...
459 60
		$meta_key_to_props = array(
460
			'_variation_description' => 'description',
461 60
		);
462 60
463 60
		$props_to_update = $force ? $meta_key_to_props : $this->get_props_to_update( $product, $meta_key_to_props );
464 60
465 60
		foreach ( $props_to_update as $meta_key => $prop ) {
466
			$value   = $product->{"get_$prop"}( 'edit' );
467
			$updated = update_post_meta( $product->get_id(), $meta_key, $value );
468
			if ( $updated ) {
469 60
				$this->updated_props[] = $prop;
470
			}
471
		}
472
473
		parent::update_post_meta( $product, $force );
474
	}
475
476
	/**
477
	 * Update product variation guid.
478
	 *
479
	 * @param WC_Product_Variation $product Product variation object.
480
	 *
481
	 * @since 3.6.0
482
	 */
483
	protected function update_guid( $product ) {
484
		global $wpdb;
485
486
		$guid = home_url(
487
			add_query_arg(
488
				array(
489
					'post_type' => 'product_variation',
490
					'p'         => $product->get_id(),
491
				),
492
				''
493
			)
494
		);
495
		$wpdb->update( $wpdb->posts, array( 'guid' => $guid ), array( 'ID' => $product->get_id() ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
496
	}
497
}
498