1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* WC_Product_Data_Store_CPT class file. |
4
|
|
|
* |
5
|
|
|
* @package WooCommerce/Classes |
6
|
|
|
*/ |
7
|
|
|
|
8
|
|
|
if ( ! defined( 'ABSPATH' ) ) { |
9
|
|
|
exit; |
10
|
|
|
} |
11
|
|
|
|
12
|
|
|
/** |
13
|
|
|
* WC Product Data Store: Stored in CPT. |
14
|
|
|
* |
15
|
|
|
* @version 3.0.0 |
16
|
|
|
*/ |
17
|
|
|
class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Data_Store_Interface, WC_Product_Data_Store_Interface { |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* Data stored in meta keys, but not considered "meta". |
21
|
|
|
* |
22
|
|
|
* @since 3.0.0 |
23
|
|
|
* @var array |
24
|
|
|
*/ |
25
|
|
|
protected $internal_meta_keys = array( |
26
|
|
|
'_visibility', |
27
|
|
|
'_sku', |
28
|
|
|
'_price', |
29
|
|
|
'_regular_price', |
30
|
|
|
'_sale_price', |
31
|
|
|
'_sale_price_dates_from', |
32
|
|
|
'_sale_price_dates_to', |
33
|
|
|
'total_sales', |
34
|
|
|
'_tax_status', |
35
|
|
|
'_tax_class', |
36
|
|
|
'_manage_stock', |
37
|
|
|
'_stock', |
38
|
|
|
'_stock_status', |
39
|
|
|
'_backorders', |
40
|
|
|
'_low_stock_amount', |
41
|
|
|
'_sold_individually', |
42
|
|
|
'_weight', |
43
|
|
|
'_length', |
44
|
|
|
'_width', |
45
|
|
|
'_height', |
46
|
|
|
'_upsell_ids', |
47
|
|
|
'_crosssell_ids', |
48
|
|
|
'_purchase_note', |
49
|
|
|
'_default_attributes', |
50
|
|
|
'_product_attributes', |
51
|
|
|
'_virtual', |
52
|
|
|
'_downloadable', |
53
|
|
|
'_download_limit', |
54
|
|
|
'_download_expiry', |
55
|
|
|
'_featured', |
56
|
|
|
'_downloadable_files', |
57
|
|
|
'_wc_rating_count', |
58
|
|
|
'_wc_average_rating', |
59
|
|
|
'_wc_review_count', |
60
|
|
|
'_variation_description', |
61
|
|
|
'_thumbnail_id', |
62
|
|
|
'_file_paths', |
63
|
|
|
'_product_image_gallery', |
64
|
|
|
'_product_version', |
65
|
|
|
'_wp_old_slug', |
66
|
|
|
'_edit_last', |
67
|
|
|
'_edit_lock', |
68
|
|
|
); |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Meta data which should exist in the DB, even if empty. |
72
|
|
|
* |
73
|
|
|
* @since 3.6.0 |
74
|
|
|
* |
75
|
|
|
* @var array |
76
|
|
|
*/ |
77
|
|
|
protected $must_exist_meta_keys = array( |
78
|
|
|
'_tax_class', |
79
|
|
|
); |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* If we have already saved our extra data, don't do automatic / default handling. |
83
|
|
|
* |
84
|
|
|
* @var bool |
85
|
|
|
*/ |
86
|
|
|
protected $extra_data_saved = false; |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Stores updated props. |
90
|
|
|
* |
91
|
|
|
* @var array |
92
|
|
|
*/ |
93
|
|
|
protected $updated_props = array(); |
94
|
|
|
|
95
|
|
|
/* |
96
|
|
|
|-------------------------------------------------------------------------- |
97
|
|
|
| CRUD Methods |
98
|
|
|
|-------------------------------------------------------------------------- |
99
|
|
|
*/ |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Method to create a new product in the database. |
103
|
|
|
* |
104
|
|
|
* @param WC_Product $product Product object. |
105
|
|
|
*/ |
106
|
398 |
|
public function create( &$product ) { |
107
|
398 |
|
if ( ! $product->get_date_created( 'edit' ) ) { |
108
|
398 |
|
$product->set_date_created( current_time( 'timestamp', true ) ); |
109
|
|
|
} |
110
|
|
|
|
111
|
398 |
|
$id = wp_insert_post( |
112
|
398 |
|
apply_filters( |
113
|
398 |
|
'woocommerce_new_product_data', |
114
|
|
|
array( |
115
|
398 |
|
'post_type' => 'product', |
116
|
398 |
|
'post_status' => $product->get_status() ? $product->get_status() : 'publish', |
117
|
398 |
|
'post_author' => get_current_user_id(), |
118
|
398 |
|
'post_title' => $product->get_name() ? $product->get_name() : __( 'Product', 'woocommerce' ), |
119
|
398 |
|
'post_content' => $product->get_description(), |
120
|
398 |
|
'post_excerpt' => $product->get_short_description(), |
121
|
398 |
|
'post_parent' => $product->get_parent_id(), |
122
|
398 |
|
'comment_status' => $product->get_reviews_allowed() ? 'open' : 'closed', |
123
|
398 |
|
'ping_status' => 'closed', |
124
|
398 |
|
'menu_order' => $product->get_menu_order(), |
125
|
398 |
|
'post_password' => $product->get_post_password( 'edit' ), |
126
|
398 |
|
'post_date' => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() ), |
127
|
398 |
|
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getTimestamp() ), |
128
|
398 |
|
'post_name' => $product->get_slug( 'edit' ), |
129
|
|
|
) |
130
|
|
|
), |
131
|
398 |
|
true |
132
|
|
|
); |
133
|
|
|
|
134
|
398 |
View Code Duplication |
if ( $id && ! is_wp_error( $id ) ) { |
135
|
398 |
|
$product->set_id( $id ); |
136
|
|
|
|
137
|
398 |
|
$this->update_post_meta( $product, true ); |
138
|
398 |
|
$this->update_terms( $product, true ); |
139
|
398 |
|
$this->update_visibility( $product, true ); |
140
|
398 |
|
$this->update_attributes( $product, true ); |
141
|
398 |
|
$this->update_version_and_type( $product ); |
142
|
398 |
|
$this->handle_updated_props( $product ); |
143
|
398 |
|
$this->clear_caches( $product ); |
144
|
|
|
|
145
|
398 |
|
$product->save_meta_data(); |
146
|
398 |
|
$product->apply_changes(); |
147
|
|
|
|
148
|
398 |
|
do_action( 'woocommerce_new_product', $id ); |
149
|
|
|
} |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Method to read a product from the database. |
154
|
|
|
* |
155
|
|
|
* @param WC_Product $product Product object. |
156
|
|
|
* @throws Exception If invalid product. |
157
|
|
|
*/ |
158
|
370 |
|
public function read( &$product ) { |
159
|
370 |
|
$product->set_defaults(); |
160
|
370 |
|
$post_object = get_post( $product->get_id() ); |
161
|
|
|
|
162
|
370 |
View Code Duplication |
if ( ! $product->get_id() || ! $post_object || 'product' !== $post_object->post_type ) { |
163
|
4 |
|
throw new Exception( __( 'Invalid product.', 'woocommerce' ) ); |
164
|
|
|
} |
165
|
|
|
|
166
|
368 |
|
$product->set_props( |
167
|
|
|
array( |
168
|
368 |
|
'name' => $post_object->post_title, |
169
|
368 |
|
'slug' => $post_object->post_name, |
170
|
368 |
|
'date_created' => 0 < $post_object->post_date_gmt ? wc_string_to_timestamp( $post_object->post_date_gmt ) : null, |
171
|
368 |
|
'date_modified' => 0 < $post_object->post_modified_gmt ? wc_string_to_timestamp( $post_object->post_modified_gmt ) : null, |
172
|
368 |
|
'status' => $post_object->post_status, |
173
|
368 |
|
'description' => $post_object->post_content, |
174
|
368 |
|
'short_description' => $post_object->post_excerpt, |
175
|
368 |
|
'parent_id' => $post_object->post_parent, |
176
|
368 |
|
'menu_order' => $post_object->menu_order, |
177
|
368 |
|
'post_password' => $post_object->post_password, |
178
|
368 |
|
'reviews_allowed' => 'open' === $post_object->comment_status, |
179
|
|
|
) |
180
|
|
|
); |
181
|
|
|
|
182
|
368 |
|
$this->read_attributes( $product ); |
183
|
368 |
|
$this->read_downloads( $product ); |
184
|
368 |
|
$this->read_visibility( $product ); |
185
|
368 |
|
$this->read_product_data( $product ); |
186
|
368 |
|
$this->read_extra_data( $product ); |
187
|
368 |
|
$product->set_object_read( true ); |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
/** |
191
|
|
|
* Method to update a product in the database. |
192
|
|
|
* |
193
|
|
|
* @param WC_Product $product Product object. |
194
|
|
|
*/ |
195
|
109 |
|
public function update( &$product ) { |
196
|
109 |
|
$product->save_meta_data(); |
197
|
109 |
|
$changes = $product->get_changes(); |
198
|
|
|
|
199
|
|
|
// Only update the post when the post data changes. |
200
|
109 |
|
if ( array_intersect( array( 'description', 'short_description', 'name', 'parent_id', 'reviews_allowed', 'status', 'menu_order', 'date_created', 'date_modified', 'slug' ), array_keys( $changes ) ) ) { |
201
|
|
|
$post_data = array( |
202
|
12 |
|
'post_content' => $product->get_description( 'edit' ), |
203
|
12 |
|
'post_excerpt' => $product->get_short_description( 'edit' ), |
204
|
12 |
|
'post_title' => $product->get_name( 'edit' ), |
205
|
12 |
|
'post_parent' => $product->get_parent_id( 'edit' ), |
206
|
12 |
|
'comment_status' => $product->get_reviews_allowed( 'edit' ) ? 'open' : 'closed', |
207
|
12 |
|
'post_status' => $product->get_status( 'edit' ) ? $product->get_status( 'edit' ) : 'publish', |
208
|
12 |
|
'menu_order' => $product->get_menu_order( 'edit' ), |
209
|
12 |
|
'post_password' => $product->get_post_password( 'edit' ), |
210
|
12 |
|
'post_name' => $product->get_slug( 'edit' ), |
211
|
12 |
|
'post_type' => 'product', |
212
|
|
|
); |
213
|
12 |
|
if ( $product->get_date_created( 'edit' ) ) { |
214
|
12 |
|
$post_data['post_date'] = gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() ); |
215
|
12 |
|
$post_data['post_date_gmt'] = gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getTimestamp() ); |
216
|
|
|
} |
217
|
12 |
|
if ( isset( $changes['date_modified'] ) && $product->get_date_modified( 'edit' ) ) { |
218
|
|
|
$post_data['post_modified'] = gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getOffsetTimestamp() ); |
219
|
|
|
$post_data['post_modified_gmt'] = gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getTimestamp() ); |
220
|
|
|
} else { |
221
|
12 |
|
$post_data['post_modified'] = current_time( 'mysql' ); |
222
|
12 |
|
$post_data['post_modified_gmt'] = current_time( 'mysql', 1 ); |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
/** |
226
|
|
|
* When updating this object, to prevent infinite loops, use $wpdb |
227
|
|
|
* to update data, since wp_update_post spawns more calls to the |
228
|
|
|
* save_post action. |
229
|
|
|
* |
230
|
|
|
* This ensures hooks are fired by either WP itself (admin screen save), |
231
|
|
|
* or an update purely from CRUD. |
232
|
|
|
*/ |
233
|
12 |
View Code Duplication |
if ( doing_action( 'save_post' ) ) { |
234
|
|
|
$GLOBALS['wpdb']->update( $GLOBALS['wpdb']->posts, $post_data, array( 'ID' => $product->get_id() ) ); |
235
|
|
|
clean_post_cache( $product->get_id() ); |
236
|
|
|
} else { |
237
|
12 |
|
wp_update_post( array_merge( array( 'ID' => $product->get_id() ), $post_data ) ); |
238
|
|
|
} |
239
|
12 |
|
$product->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook. |
240
|
|
|
|
241
|
|
View Code Duplication |
} else { // Only update post modified time to record this save event. |
242
|
101 |
|
$GLOBALS['wpdb']->update( |
243
|
101 |
|
$GLOBALS['wpdb']->posts, |
244
|
|
|
array( |
245
|
101 |
|
'post_modified' => current_time( 'mysql' ), |
246
|
101 |
|
'post_modified_gmt' => current_time( 'mysql', 1 ), |
247
|
|
|
), |
248
|
|
|
array( |
249
|
101 |
|
'ID' => $product->get_id(), |
250
|
|
|
) |
251
|
|
|
); |
252
|
101 |
|
clean_post_cache( $product->get_id() ); |
253
|
|
|
} |
254
|
|
|
|
255
|
109 |
|
$this->update_post_meta( $product ); |
256
|
109 |
|
$this->update_terms( $product ); |
257
|
109 |
|
$this->update_visibility( $product ); |
258
|
109 |
|
$this->update_attributes( $product ); |
259
|
109 |
|
$this->update_version_and_type( $product ); |
260
|
109 |
|
$this->handle_updated_props( $product ); |
261
|
109 |
|
$this->clear_caches( $product ); |
262
|
|
|
|
263
|
109 |
|
$product->apply_changes(); |
264
|
|
|
|
265
|
109 |
|
do_action( 'woocommerce_update_product', $product->get_id() ); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Method to delete a product from the database. |
270
|
|
|
* |
271
|
|
|
* @param WC_Product $product Product object. |
272
|
|
|
* @param array $args Array of args to pass to the delete method. |
273
|
|
|
*/ |
274
|
29 |
|
public function delete( &$product, $args = array() ) { |
275
|
29 |
|
$id = $product->get_id(); |
276
|
29 |
|
$post_type = $product->is_type( 'variation' ) ? 'product_variation' : 'product'; |
277
|
|
|
|
278
|
29 |
|
$args = wp_parse_args( |
279
|
29 |
|
$args, |
280
|
|
|
array( |
281
|
29 |
|
'force_delete' => false, |
282
|
|
|
) |
283
|
|
|
); |
284
|
|
|
|
285
|
29 |
|
if ( ! $id ) { |
286
|
|
|
return; |
287
|
|
|
} |
288
|
|
|
|
289
|
29 |
|
if ( $args['force_delete'] ) { |
290
|
27 |
|
do_action( 'woocommerce_before_delete_' . $post_type, $id ); |
291
|
27 |
|
wp_delete_post( $id ); |
292
|
27 |
|
$product->set_id( 0 ); |
293
|
27 |
|
do_action( 'woocommerce_delete_' . $post_type, $id ); |
294
|
|
|
} else { |
295
|
2 |
|
wp_trash_post( $id ); |
296
|
2 |
|
$product->set_status( 'trash' ); |
297
|
2 |
|
do_action( 'woocommerce_trash_' . $post_type, $id ); |
298
|
|
|
} |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/* |
302
|
|
|
|-------------------------------------------------------------------------- |
303
|
|
|
| Additional Methods |
304
|
|
|
|-------------------------------------------------------------------------- |
305
|
|
|
*/ |
306
|
|
|
|
307
|
|
|
/** |
308
|
|
|
* Read product data. Can be overridden by child classes to load other props. |
309
|
|
|
* |
310
|
|
|
* @param WC_Product $product Product object. |
311
|
|
|
* @since 3.0.0 |
312
|
|
|
*/ |
313
|
368 |
|
protected function read_product_data( &$product ) { |
314
|
368 |
|
$id = $product->get_id(); |
315
|
368 |
|
$post_meta_values = get_post_meta( $id ); |
316
|
|
|
$meta_key_to_props = array( |
317
|
368 |
|
'_sku' => 'sku', |
318
|
|
|
'_regular_price' => 'regular_price', |
319
|
|
|
'_sale_price' => 'sale_price', |
320
|
|
|
'_price' => 'price', |
321
|
|
|
'_sale_price_dates_from' => 'date_on_sale_from', |
322
|
|
|
'_sale_price_dates_to' => 'date_on_sale_to', |
323
|
|
|
'total_sales' => 'total_sales', |
324
|
|
|
'_tax_status' => 'tax_status', |
325
|
|
|
'_tax_class' => 'tax_class', |
326
|
|
|
'_manage_stock' => 'manage_stock', |
327
|
|
|
'_backorders' => 'backorders', |
328
|
|
|
'_low_stock_amount' => 'low_stock_amount', |
329
|
|
|
'_sold_individually' => 'sold_individually', |
330
|
|
|
'_weight' => 'weight', |
331
|
|
|
'_length' => 'length', |
332
|
|
|
'_width' => 'width', |
333
|
|
|
'_height' => 'height', |
334
|
|
|
'_upsell_ids' => 'upsell_ids', |
335
|
|
|
'_crosssell_ids' => 'cross_sell_ids', |
336
|
|
|
'_purchase_note' => 'purchase_note', |
337
|
|
|
'_default_attributes' => 'default_attributes', |
338
|
|
|
'_virtual' => 'virtual', |
339
|
|
|
'_downloadable' => 'downloadable', |
340
|
|
|
'_download_limit' => 'download_limit', |
341
|
|
|
'_download_expiry' => 'download_expiry', |
342
|
|
|
'_thumbnail_id' => 'image_id', |
343
|
|
|
'_stock' => 'stock_quantity', |
344
|
|
|
'_stock_status' => 'stock_status', |
345
|
|
|
'_wc_average_rating' => 'average_rating', |
346
|
|
|
'_wc_rating_count' => 'rating_counts', |
347
|
|
|
'_wc_review_count' => 'review_count', |
348
|
|
|
'_product_image_gallery' => 'gallery_image_ids', |
349
|
|
|
); |
350
|
|
|
|
351
|
368 |
|
$set_props = array(); |
352
|
|
|
|
353
|
368 |
|
foreach ( $meta_key_to_props as $meta_key => $prop ) { |
354
|
368 |
|
$meta_value = isset( $post_meta_values[ $meta_key ][0] ) ? $post_meta_values[ $meta_key ][0] : null; |
355
|
368 |
|
$set_props[ $prop ] = maybe_unserialize( $meta_value ); // get_post_meta only unserializes single values. |
356
|
|
|
} |
357
|
|
|
|
358
|
368 |
|
$set_props['category_ids'] = $this->get_term_ids( $product, 'product_cat' ); |
359
|
368 |
|
$set_props['tag_ids'] = $this->get_term_ids( $product, 'product_tag' ); |
360
|
368 |
|
$set_props['shipping_class_id'] = current( $this->get_term_ids( $product, 'product_shipping_class' ) ); |
361
|
368 |
|
$set_props['gallery_image_ids'] = array_filter( explode( ',', $set_props['gallery_image_ids'] ) ); |
362
|
|
|
|
363
|
368 |
|
$product->set_props( $set_props ); |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Re-reads stock from the DB ignoring changes. |
368
|
|
|
* |
369
|
|
|
* @param WC_Product $product Product object. |
370
|
|
|
* @param int|float $new_stock New stock level if already read. |
371
|
|
|
*/ |
372
|
2 |
|
public function read_stock_quantity( &$product, $new_stock = null ) { |
373
|
2 |
|
$object_read = $product->get_object_read(); |
374
|
2 |
|
$product->set_object_read( false ); // This makes update of qty go directly to data- instead of changes-array of the product object (which is needed as the data should hold status of the object as it was read from the db). |
375
|
2 |
|
$product->set_stock_quantity( is_null( $new_stock ) ? get_post_meta( $product->get_id(), '_stock', true ) : $new_stock ); |
376
|
2 |
|
$product->set_object_read( $object_read ); |
377
|
|
|
} |
378
|
|
|
|
379
|
|
|
/** |
380
|
|
|
* Read extra data associated with the product, like button text or product URL for external products. |
381
|
|
|
* |
382
|
|
|
* @param WC_Product $product Product object. |
383
|
|
|
* @since 3.0.0 |
384
|
|
|
*/ |
385
|
374 |
View Code Duplication |
protected function read_extra_data( &$product ) { |
|
|
|
|
386
|
374 |
|
foreach ( $product->get_extra_data_keys() as $key ) { |
387
|
22 |
|
$function = 'set_' . $key; |
388
|
22 |
|
if ( is_callable( array( $product, $function ) ) ) { |
389
|
22 |
|
$product->{$function}( get_post_meta( $product->get_id(), '_' . $key, true ) ); |
390
|
|
|
} |
391
|
|
|
} |
392
|
|
|
} |
393
|
|
|
|
394
|
|
|
/** |
395
|
|
|
* Convert visibility terms to props. |
396
|
|
|
* Catalog visibility valid values are 'visible', 'catalog', 'search', and 'hidden'. |
397
|
|
|
* |
398
|
|
|
* @param WC_Product $product Product object. |
399
|
|
|
* @since 3.0.0 |
400
|
|
|
*/ |
401
|
368 |
|
protected function read_visibility( &$product ) { |
402
|
368 |
|
$terms = get_the_terms( $product->get_id(), 'product_visibility' ); |
403
|
368 |
|
$term_names = is_array( $terms ) ? wp_list_pluck( $terms, 'name' ) : array(); |
404
|
368 |
|
$featured = in_array( 'featured', $term_names, true ); |
405
|
368 |
|
$exclude_search = in_array( 'exclude-from-search', $term_names, true ); |
406
|
368 |
|
$exclude_catalog = in_array( 'exclude-from-catalog', $term_names, true ); |
407
|
|
|
|
408
|
368 |
View Code Duplication |
if ( $exclude_search && $exclude_catalog ) { |
409
|
1 |
|
$catalog_visibility = 'hidden'; |
410
|
368 |
|
} elseif ( $exclude_search ) { |
411
|
1 |
|
$catalog_visibility = 'catalog'; |
412
|
368 |
|
} elseif ( $exclude_catalog ) { |
413
|
2 |
|
$catalog_visibility = 'search'; |
414
|
|
|
} else { |
415
|
367 |
|
$catalog_visibility = 'visible'; |
416
|
|
|
} |
417
|
|
|
|
418
|
368 |
|
$product->set_props( |
419
|
|
|
array( |
420
|
368 |
|
'featured' => $featured, |
421
|
368 |
|
'catalog_visibility' => $catalog_visibility, |
422
|
|
|
) |
423
|
|
|
); |
424
|
|
|
} |
425
|
|
|
|
426
|
|
|
/** |
427
|
|
|
* Read attributes from post meta. |
428
|
|
|
* |
429
|
|
|
* @param WC_Product $product Product object. |
430
|
|
|
*/ |
431
|
327 |
|
protected function read_attributes( &$product ) { |
432
|
327 |
|
$meta_attributes = get_post_meta( $product->get_id(), '_product_attributes', true ); |
433
|
|
|
|
434
|
327 |
|
if ( ! empty( $meta_attributes ) && is_array( $meta_attributes ) ) { |
435
|
3 |
|
$attributes = array(); |
436
|
3 |
|
foreach ( $meta_attributes as $meta_attribute_key => $meta_attribute_value ) { |
437
|
3 |
|
$meta_value = array_merge( |
438
|
|
|
array( |
439
|
3 |
|
'name' => '', |
440
|
|
|
'value' => '', |
441
|
|
|
'position' => 0, |
442
|
|
|
'is_visible' => 0, |
443
|
|
|
'is_variation' => 0, |
444
|
|
|
'is_taxonomy' => 0, |
445
|
|
|
), |
446
|
3 |
|
(array) $meta_attribute_value |
447
|
|
|
); |
448
|
|
|
|
449
|
|
|
// Check if is a taxonomy attribute. |
450
|
3 |
View Code Duplication |
if ( ! empty( $meta_value['is_taxonomy'] ) ) { |
451
|
|
|
if ( ! taxonomy_exists( $meta_value['name'] ) ) { |
452
|
|
|
continue; |
453
|
|
|
} |
454
|
|
|
$id = wc_attribute_taxonomy_id_by_name( $meta_value['name'] ); |
455
|
|
|
$options = wc_get_object_terms( $product->get_id(), $meta_value['name'], 'term_id' ); |
456
|
|
|
} else { |
457
|
3 |
|
$id = 0; |
458
|
3 |
|
$options = wc_get_text_attributes( $meta_value['value'] ); |
459
|
|
|
} |
460
|
|
|
|
461
|
3 |
|
$attribute = new WC_Product_Attribute(); |
462
|
3 |
|
$attribute->set_id( $id ); |
463
|
3 |
|
$attribute->set_name( $meta_value['name'] ); |
464
|
3 |
|
$attribute->set_options( $options ); |
465
|
3 |
|
$attribute->set_position( $meta_value['position'] ); |
466
|
3 |
|
$attribute->set_visible( $meta_value['is_visible'] ); |
|
|
|
|
467
|
3 |
|
$attribute->set_variation( $meta_value['is_variation'] ); |
|
|
|
|
468
|
3 |
|
$attributes[] = $attribute; |
469
|
|
|
} |
470
|
3 |
|
$product->set_attributes( $attributes ); |
471
|
|
|
} |
472
|
|
|
} |
473
|
|
|
|
474
|
|
|
/** |
475
|
|
|
* Read downloads from post meta. |
476
|
|
|
* |
477
|
|
|
* @param WC_Product $product Product object. |
478
|
|
|
* @since 3.0.0 |
479
|
|
|
*/ |
480
|
374 |
|
protected function read_downloads( &$product ) { |
481
|
374 |
|
$meta_values = array_filter( (array) get_post_meta( $product->get_id(), '_downloadable_files', true ) ); |
482
|
|
|
|
483
|
374 |
|
if ( $meta_values ) { |
|
|
|
|
484
|
3 |
|
$downloads = array(); |
485
|
3 |
|
foreach ( $meta_values as $key => $value ) { |
486
|
3 |
|
if ( ! isset( $value['name'], $value['file'] ) ) { |
487
|
|
|
continue; |
488
|
|
|
} |
489
|
3 |
|
$download = new WC_Product_Download(); |
490
|
3 |
|
$download->set_id( $key ); |
491
|
3 |
|
$download->set_name( $value['name'] ? $value['name'] : wc_get_filename_from_url( $value['file'] ) ); |
492
|
3 |
|
$download->set_file( apply_filters( 'woocommerce_file_download_path', $value['file'], $product, $key ) ); |
493
|
3 |
|
$downloads[] = $download; |
494
|
|
|
} |
495
|
3 |
|
$product->set_downloads( $downloads ); |
496
|
|
|
} |
497
|
|
|
} |
498
|
|
|
|
499
|
|
|
/** |
500
|
|
|
* Helper method that updates all the post meta for a product based on it's settings in the WC_Product class. |
501
|
|
|
* |
502
|
|
|
* @param WC_Product $product Product object. |
503
|
|
|
* @param bool $force Force update. Used during create. |
504
|
|
|
* @since 3.0.0 |
505
|
|
|
*/ |
506
|
399 |
|
protected function update_post_meta( &$product, $force = false ) { |
507
|
|
|
$meta_key_to_props = array( |
508
|
399 |
|
'_sku' => 'sku', |
509
|
|
|
'_regular_price' => 'regular_price', |
510
|
|
|
'_sale_price' => 'sale_price', |
511
|
|
|
'_sale_price_dates_from' => 'date_on_sale_from', |
512
|
|
|
'_sale_price_dates_to' => 'date_on_sale_to', |
513
|
|
|
'total_sales' => 'total_sales', |
514
|
|
|
'_tax_status' => 'tax_status', |
515
|
|
|
'_tax_class' => 'tax_class', |
516
|
|
|
'_manage_stock' => 'manage_stock', |
517
|
|
|
'_backorders' => 'backorders', |
518
|
|
|
'_low_stock_amount' => 'low_stock_amount', |
519
|
|
|
'_sold_individually' => 'sold_individually', |
520
|
|
|
'_weight' => 'weight', |
521
|
|
|
'_length' => 'length', |
522
|
|
|
'_width' => 'width', |
523
|
|
|
'_height' => 'height', |
524
|
|
|
'_upsell_ids' => 'upsell_ids', |
525
|
|
|
'_crosssell_ids' => 'cross_sell_ids', |
526
|
|
|
'_purchase_note' => 'purchase_note', |
527
|
|
|
'_default_attributes' => 'default_attributes', |
528
|
|
|
'_virtual' => 'virtual', |
529
|
|
|
'_downloadable' => 'downloadable', |
530
|
|
|
'_product_image_gallery' => 'gallery_image_ids', |
531
|
|
|
'_download_limit' => 'download_limit', |
532
|
|
|
'_download_expiry' => 'download_expiry', |
533
|
|
|
'_thumbnail_id' => 'image_id', |
534
|
|
|
'_stock' => 'stock_quantity', |
535
|
|
|
'_stock_status' => 'stock_status', |
536
|
|
|
'_wc_average_rating' => 'average_rating', |
537
|
|
|
'_wc_rating_count' => 'rating_counts', |
538
|
|
|
'_wc_review_count' => 'review_count', |
539
|
|
|
); |
540
|
|
|
|
541
|
|
|
// Make sure to take extra data (like product url or text for external products) into account. |
542
|
399 |
|
$extra_data_keys = $product->get_extra_data_keys(); |
543
|
|
|
|
544
|
399 |
|
foreach ( $extra_data_keys as $key ) { |
545
|
23 |
|
$meta_key_to_props[ '_' . $key ] = $key; |
546
|
|
|
} |
547
|
|
|
|
548
|
399 |
|
$props_to_update = $force ? $meta_key_to_props : $this->get_props_to_update( $product, $meta_key_to_props ); |
549
|
|
|
|
550
|
399 |
|
foreach ( $props_to_update as $meta_key => $prop ) { |
551
|
399 |
|
$value = $product->{"get_$prop"}( 'edit' ); |
552
|
399 |
|
$value = is_string( $value ) ? wp_slash( $value ) : $value; |
553
|
399 |
|
switch ( $prop ) { |
554
|
|
|
case 'virtual': |
555
|
|
|
case 'downloadable': |
556
|
|
|
case 'manage_stock': |
557
|
|
|
case 'sold_individually': |
558
|
399 |
|
$value = wc_bool_to_string( $value ); |
559
|
399 |
|
break; |
560
|
|
|
case 'gallery_image_ids': |
561
|
399 |
|
$value = implode( ',', $value ); |
562
|
399 |
|
break; |
563
|
|
|
case 'date_on_sale_from': |
564
|
|
|
case 'date_on_sale_to': |
565
|
399 |
|
$value = $value ? $value->getTimestamp() : ''; |
566
|
399 |
|
break; |
567
|
|
|
} |
568
|
|
|
|
569
|
399 |
|
$updated = $this->update_or_delete_post_meta( $product, $meta_key, $value ); |
570
|
|
|
|
571
|
399 |
|
if ( $updated ) { |
572
|
399 |
|
$this->updated_props[] = $prop; |
573
|
|
|
} |
574
|
|
|
} |
575
|
|
|
|
576
|
|
|
// Update extra data associated with the product like button text or product URL for external products. |
577
|
399 |
|
if ( ! $this->extra_data_saved ) { |
578
|
399 |
|
foreach ( $extra_data_keys as $key ) { |
579
|
23 |
|
$meta_key = '_' . $key; |
580
|
23 |
|
$function = 'get_' . $key; |
581
|
23 |
|
if ( ! array_key_exists( $meta_key, $props_to_update ) ) { |
582
|
1 |
|
continue; |
583
|
|
|
} |
584
|
23 |
|
if ( is_callable( array( $product, $function ) ) ) { |
585
|
23 |
|
$value = $product->{$function}( 'edit' ); |
586
|
23 |
|
$value = is_string( $value ) ? wp_slash( $value ) : $value; |
587
|
23 |
|
$updated = $this->update_or_delete_post_meta( $product, $meta_key, $value ); |
588
|
|
|
|
589
|
23 |
|
if ( $updated ) { |
590
|
|
|
$this->updated_props[] = $key; |
591
|
|
|
} |
592
|
|
|
} |
593
|
|
|
} |
594
|
|
|
} |
595
|
|
|
|
596
|
399 |
|
if ( $this->update_downloads( $product, $force ) ) { |
597
|
3 |
|
$this->updated_props[] = 'downloads'; |
598
|
|
|
} |
599
|
|
|
} |
600
|
|
|
|
601
|
|
|
/** |
602
|
|
|
* Handle updated meta props after updating meta data. |
603
|
|
|
* |
604
|
|
|
* @since 3.0.0 |
605
|
|
|
* @param WC_Product $product Product Object. |
606
|
|
|
*/ |
607
|
399 |
|
protected function handle_updated_props( &$product ) { |
608
|
399 |
|
$price_is_synced = $product->is_type( array( 'variable', 'grouped' ) ); |
609
|
|
|
|
610
|
399 |
|
if ( ! $price_is_synced ) { |
611
|
396 |
|
if ( in_array( 'regular_price', $this->updated_props, true ) || in_array( 'sale_price', $this->updated_props, true ) ) { |
612
|
355 |
|
if ( $product->get_sale_price( 'edit' ) >= $product->get_regular_price( 'edit' ) ) { |
613
|
|
|
update_post_meta( $product->get_id(), '_sale_price', '' ); |
614
|
|
|
$product->set_sale_price( '' ); |
615
|
|
|
} |
616
|
|
|
} |
617
|
|
|
|
618
|
396 |
|
if ( in_array( 'date_on_sale_from', $this->updated_props, true ) || in_array( 'date_on_sale_to', $this->updated_props, true ) || in_array( 'regular_price', $this->updated_props, true ) || in_array( 'sale_price', $this->updated_props, true ) || in_array( 'product_type', $this->updated_props, true ) ) { |
619
|
356 |
|
if ( $product->is_on_sale( 'edit' ) ) { |
620
|
16 |
|
update_post_meta( $product->get_id(), '_price', $product->get_sale_price( 'edit' ) ); |
621
|
16 |
|
$product->set_price( $product->get_sale_price( 'edit' ) ); |
622
|
|
|
} else { |
623
|
355 |
|
update_post_meta( $product->get_id(), '_price', $product->get_regular_price( 'edit' ) ); |
624
|
355 |
|
$product->set_price( $product->get_regular_price( 'edit' ) ); |
625
|
|
|
} |
626
|
|
|
} |
627
|
|
|
} |
628
|
|
|
|
629
|
399 |
|
if ( in_array( 'stock_quantity', $this->updated_props, true ) ) { |
630
|
399 |
|
if ( $product->is_type( 'variation' ) ) { |
631
|
63 |
|
do_action( 'woocommerce_variation_set_stock', $product ); |
632
|
|
|
} else { |
633
|
398 |
|
do_action( 'woocommerce_product_set_stock', $product ); |
634
|
|
|
} |
635
|
|
|
} |
636
|
|
|
|
637
|
399 |
|
if ( in_array( 'stock_status', $this->updated_props, true ) ) { |
638
|
399 |
|
if ( $product->is_type( 'variation' ) ) { |
639
|
63 |
|
do_action( 'woocommerce_variation_set_stock_status', $product->get_id(), $product->get_stock_status(), $product ); |
640
|
|
|
} else { |
641
|
398 |
|
do_action( 'woocommerce_product_set_stock_status', $product->get_id(), $product->get_stock_status(), $product ); |
642
|
|
|
} |
643
|
|
|
} |
644
|
|
|
|
645
|
399 |
|
if ( array_intersect( $this->updated_props, array( 'sku', 'regular_price', 'sale_price', 'date_on_sale_from', 'date_on_sale_to', 'total_sales', 'average_rating', 'stock_quantity', 'stock_status', 'manage_stock', 'downloadable', 'virtual' ) ) ) { |
646
|
399 |
|
$this->update_lookup_table( $product->get_id(), 'wc_product_meta_lookup' ); |
647
|
|
|
} |
648
|
|
|
|
649
|
|
|
// Trigger action so 3rd parties can deal with updated props. |
650
|
399 |
|
do_action( 'woocommerce_product_object_updated_props', $product, $this->updated_props ); |
651
|
|
|
|
652
|
|
|
// After handling, we can reset the props array. |
653
|
399 |
|
$this->updated_props = array(); |
654
|
|
|
} |
655
|
|
|
|
656
|
|
|
/** |
657
|
|
|
* For all stored terms in all taxonomies, save them to the DB. |
658
|
|
|
* |
659
|
|
|
* @param WC_Product $product Product object. |
660
|
|
|
* @param bool $force Force update. Used during create. |
661
|
|
|
* @since 3.0.0 |
662
|
|
|
*/ |
663
|
398 |
|
protected function update_terms( &$product, $force = false ) { |
664
|
398 |
|
$changes = $product->get_changes(); |
665
|
|
|
|
666
|
398 |
|
if ( $force || array_key_exists( 'category_ids', $changes ) ) { |
667
|
398 |
|
$categories = $product->get_category_ids( 'edit' ); |
668
|
|
|
|
669
|
398 |
|
if ( empty( $categories ) && get_option( 'default_product_cat', 0 ) ) { |
670
|
389 |
|
$categories = array( get_option( 'default_product_cat', 0 ) ); |
671
|
|
|
} |
672
|
|
|
|
673
|
398 |
|
wp_set_post_terms( $product->get_id(), $categories, 'product_cat', false ); |
674
|
|
|
} |
675
|
398 |
|
if ( $force || array_key_exists( 'tag_ids', $changes ) ) { |
676
|
398 |
|
wp_set_post_terms( $product->get_id(), $product->get_tag_ids( 'edit' ), 'product_tag', false ); |
677
|
|
|
} |
678
|
398 |
View Code Duplication |
if ( $force || array_key_exists( 'shipping_class_id', $changes ) ) { |
679
|
398 |
|
wp_set_post_terms( $product->get_id(), array( $product->get_shipping_class_id( 'edit' ) ), 'product_shipping_class', false ); |
680
|
|
|
} |
681
|
|
|
} |
682
|
|
|
|
683
|
|
|
/** |
684
|
|
|
* Update visibility terms based on props. |
685
|
|
|
* |
686
|
|
|
* @since 3.0.0 |
687
|
|
|
* |
688
|
|
|
* @param WC_Product $product Product object. |
689
|
|
|
* @param bool $force Force update. Used during create. |
690
|
|
|
*/ |
691
|
398 |
|
protected function update_visibility( &$product, $force = false ) { |
692
|
398 |
|
$changes = $product->get_changes(); |
693
|
|
|
|
694
|
398 |
|
if ( $force || array_intersect( array( 'featured', 'stock_status', 'average_rating', 'catalog_visibility' ), array_keys( $changes ) ) ) { |
695
|
398 |
|
$terms = array(); |
696
|
|
|
|
697
|
398 |
|
if ( $product->get_featured() ) { |
698
|
4 |
|
$terms[] = 'featured'; |
699
|
|
|
} |
700
|
|
|
|
701
|
398 |
|
if ( 'outofstock' === $product->get_stock_status() ) { |
702
|
68 |
|
$terms[] = 'outofstock'; |
703
|
|
|
} |
704
|
|
|
|
705
|
398 |
|
$rating = min( 5, round( $product->get_average_rating(), 0 ) ); |
706
|
|
|
|
707
|
398 |
|
if ( $rating > 0 ) { |
708
|
1 |
|
$terms[] = 'rated-' . $rating; |
709
|
|
|
} |
710
|
|
|
|
711
|
398 |
|
switch ( $product->get_catalog_visibility() ) { |
712
|
|
|
case 'hidden': |
713
|
2 |
|
$terms[] = 'exclude-from-search'; |
714
|
2 |
|
$terms[] = 'exclude-from-catalog'; |
715
|
2 |
|
break; |
716
|
|
|
case 'catalog': |
717
|
1 |
|
$terms[] = 'exclude-from-search'; |
718
|
1 |
|
break; |
719
|
|
|
case 'search': |
720
|
3 |
|
$terms[] = 'exclude-from-catalog'; |
721
|
3 |
|
break; |
722
|
|
|
} |
723
|
|
|
|
724
|
398 |
|
if ( ! is_wp_error( wp_set_post_terms( $product->get_id(), $terms, 'product_visibility', false ) ) ) { |
725
|
398 |
|
do_action( 'woocommerce_product_set_visibility', $product->get_id(), $product->get_catalog_visibility() ); |
726
|
|
|
} |
727
|
|
|
} |
728
|
|
|
} |
729
|
|
|
|
730
|
|
|
/** |
731
|
|
|
* Update attributes which are a mix of terms and meta data. |
732
|
|
|
* |
733
|
|
|
* @param WC_Product $product Product object. |
734
|
|
|
* @param bool $force Force update. Used during create. |
735
|
|
|
* @since 3.0.0 |
736
|
|
|
*/ |
737
|
398 |
|
protected function update_attributes( &$product, $force = false ) { |
738
|
398 |
|
$changes = $product->get_changes(); |
739
|
|
|
|
740
|
398 |
|
if ( $force || array_key_exists( 'attributes', $changes ) ) { |
741
|
398 |
|
$attributes = $product->get_attributes(); |
742
|
398 |
|
$meta_values = array(); |
743
|
|
|
|
744
|
398 |
|
if ( $attributes ) { |
|
|
|
|
745
|
56 |
|
foreach ( $attributes as $attribute_key => $attribute ) { |
746
|
56 |
|
$value = ''; |
747
|
|
|
|
748
|
56 |
|
if ( is_null( $attribute ) ) { |
749
|
|
|
if ( taxonomy_exists( $attribute_key ) ) { |
750
|
|
|
// Handle attributes that have been unset. |
751
|
|
|
wp_set_object_terms( $product->get_id(), array(), $attribute_key ); |
752
|
|
|
} elseif ( taxonomy_exists( urldecode( $attribute_key ) ) ) { |
753
|
|
|
// Handle attributes that have been unset. |
754
|
|
|
wp_set_object_terms( $product->get_id(), array(), urldecode( $attribute_key ) ); |
755
|
|
|
} |
756
|
|
|
continue; |
757
|
|
|
|
758
|
56 |
|
} elseif ( $attribute->is_taxonomy() ) { |
759
|
46 |
|
wp_set_object_terms( $product->get_id(), wp_list_pluck( (array) $attribute->get_terms(), 'term_id' ), $attribute->get_name() ); |
760
|
|
|
} else { |
761
|
13 |
|
$value = wc_implode_text_attributes( $attribute->get_options() ); |
762
|
|
|
} |
763
|
|
|
|
764
|
|
|
// Store in format WC uses in meta. |
765
|
56 |
|
$meta_values[ $attribute_key ] = array( |
766
|
56 |
|
'name' => $attribute->get_name(), |
767
|
56 |
|
'value' => $value, |
768
|
56 |
|
'position' => $attribute->get_position(), |
769
|
56 |
|
'is_visible' => $attribute->get_visible() ? 1 : 0, |
770
|
56 |
|
'is_variation' => $attribute->get_variation() ? 1 : 0, |
771
|
56 |
|
'is_taxonomy' => $attribute->is_taxonomy() ? 1 : 0, |
772
|
|
|
); |
773
|
|
|
} |
774
|
|
|
} |
775
|
|
|
// Note, we use wp_slash to add extra level of escaping. See https://codex.wordpress.org/Function_Reference/update_post_meta#Workaround. |
776
|
398 |
|
$this->update_or_delete_post_meta( $product, '_product_attributes', wp_slash( $meta_values ) ); |
777
|
|
|
} |
778
|
|
|
} |
779
|
|
|
|
780
|
|
|
/** |
781
|
|
|
* Update downloads. |
782
|
|
|
* |
783
|
|
|
* @since 3.0.0 |
784
|
|
|
* @param WC_Product $product Product object. |
785
|
|
|
* @param bool $force Force update. Used during create. |
786
|
|
|
* @return bool If updated or not. |
787
|
|
|
*/ |
788
|
399 |
|
protected function update_downloads( &$product, $force = false ) { |
789
|
399 |
|
$changes = $product->get_changes(); |
790
|
|
|
|
791
|
399 |
|
if ( $force || array_key_exists( 'downloads', $changes ) ) { |
792
|
399 |
|
$downloads = $product->get_downloads(); |
793
|
399 |
|
$meta_values = array(); |
794
|
|
|
|
795
|
399 |
|
if ( $downloads ) { |
|
|
|
|
796
|
3 |
|
foreach ( $downloads as $key => $download ) { |
797
|
|
|
// Store in format WC uses in meta. |
798
|
3 |
|
$meta_values[ $key ] = $download->get_data(); |
799
|
|
|
} |
800
|
|
|
} |
801
|
|
|
|
802
|
399 |
|
if ( $product->is_type( 'variation' ) ) { |
803
|
63 |
|
do_action( 'woocommerce_process_product_file_download_paths', $product->get_parent_id(), $product->get_id(), $downloads ); |
804
|
|
|
} else { |
805
|
398 |
|
do_action( 'woocommerce_process_product_file_download_paths', $product->get_id(), 0, $downloads ); |
806
|
|
|
} |
807
|
|
|
|
808
|
399 |
|
return $this->update_or_delete_post_meta( $product, '_downloadable_files', wp_slash( $meta_values ) ); |
809
|
|
|
} |
810
|
119 |
|
return false; |
811
|
|
|
} |
812
|
|
|
|
813
|
|
|
/** |
814
|
|
|
* Make sure we store the product type and version (to track data changes). |
815
|
|
|
* |
816
|
|
|
* @param WC_Product $product Product object. |
817
|
|
|
* @since 3.0.0 |
818
|
|
|
*/ |
819
|
398 |
|
protected function update_version_and_type( &$product ) { |
820
|
398 |
|
$old_type = WC_Product_Factory::get_product_type( $product->get_id() ); |
821
|
398 |
|
$new_type = $product->get_type(); |
822
|
|
|
|
823
|
398 |
|
wp_set_object_terms( $product->get_id(), $new_type, 'product_type' ); |
824
|
398 |
|
update_post_meta( $product->get_id(), '_product_version', WC_VERSION ); |
825
|
|
|
|
826
|
|
|
// Action for the transition. |
827
|
398 |
|
if ( $old_type !== $new_type ) { |
828
|
76 |
|
$this->updated_props[] = 'product_type'; |
829
|
76 |
|
do_action( 'woocommerce_product_type_changed', $product, $old_type, $new_type ); |
830
|
|
|
} |
831
|
|
|
} |
832
|
|
|
|
833
|
|
|
/** |
834
|
|
|
* Clear any caches. |
835
|
|
|
* |
836
|
|
|
* @param WC_Product $product Product object. |
837
|
|
|
* @since 3.0.0 |
838
|
|
|
*/ |
839
|
399 |
|
protected function clear_caches( &$product ) { |
840
|
399 |
|
wc_delete_product_transients( $product->get_id() ); |
841
|
399 |
|
if ( $product->get_parent_id( 'edit' ) ) { |
842
|
61 |
|
wc_delete_product_transients( $product->get_parent_id( 'edit' ) ); |
843
|
61 |
|
WC_Cache_Helper::incr_cache_prefix( 'product_' . $product->get_parent_id( 'edit' ) ); |
844
|
|
|
} |
845
|
399 |
|
WC_Cache_Helper::invalidate_attribute_count( array_keys( $product->get_attributes() ) ); |
846
|
399 |
|
WC_Cache_Helper::incr_cache_prefix( 'product_' . $product->get_id() ); |
847
|
|
|
} |
848
|
|
|
|
849
|
|
|
/* |
850
|
|
|
|-------------------------------------------------------------------------- |
851
|
|
|
| wc-product-functions.php methods |
852
|
|
|
|-------------------------------------------------------------------------- |
853
|
|
|
*/ |
854
|
|
|
|
855
|
|
|
/** |
856
|
|
|
* Returns an array of on sale products, as an array of objects with an |
857
|
|
|
* ID and parent_id present. Example: $return[0]->id, $return[0]->parent_id. |
858
|
|
|
* |
859
|
|
|
* @return array |
860
|
|
|
* @since 3.0.0 |
861
|
|
|
*/ |
862
|
4 |
|
public function get_on_sale_products() { |
863
|
|
|
global $wpdb; |
864
|
|
|
|
865
|
4 |
|
$exclude_term_ids = array(); |
866
|
4 |
|
$outofstock_join = ''; |
867
|
4 |
|
$outofstock_where = ''; |
868
|
4 |
|
$non_published_where = ''; |
|
|
|
|
869
|
4 |
|
$product_visibility_term_ids = wc_get_product_visibility_term_ids(); |
870
|
|
|
|
871
|
4 |
View Code Duplication |
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && $product_visibility_term_ids['outofstock'] ) { |
872
|
|
|
$exclude_term_ids[] = $product_visibility_term_ids['outofstock']; |
873
|
|
|
} |
874
|
|
|
|
875
|
4 |
|
if ( count( $exclude_term_ids ) ) { |
876
|
|
|
$outofstock_join = " LEFT JOIN ( SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN ( " . implode( ',', array_map( 'absint', $exclude_term_ids ) ) . ' ) ) AS exclude_join ON exclude_join.object_id = id'; |
877
|
|
|
$outofstock_where = ' AND exclude_join.object_id IS NULL'; |
878
|
|
|
} |
879
|
|
|
|
880
|
4 |
|
return $wpdb->get_results( |
|
|
|
|
881
|
|
|
// phpcs:disable WordPress.DB.PreparedSQL.NotPrepared |
882
|
|
|
" |
883
|
|
|
SELECT posts.ID as id, posts.post_parent as parent_id |
884
|
4 |
|
FROM {$wpdb->posts} AS posts |
885
|
4 |
|
INNER JOIN {$wpdb->wc_product_meta_lookup} AS lookup ON posts.ID = lookup.product_id |
886
|
4 |
|
$outofstock_join |
887
|
|
|
WHERE posts.post_type IN ( 'product', 'product_variation' ) |
888
|
|
|
AND posts.post_status = 'publish' |
889
|
|
|
AND lookup.onsale = 1 |
890
|
4 |
|
$outofstock_where |
891
|
|
|
AND posts.post_parent NOT IN ( |
892
|
4 |
|
SELECT ID FROM `$wpdb->posts` as posts |
893
|
|
|
WHERE posts.post_type = 'product' |
894
|
|
|
AND posts.post_parent = 0 |
895
|
|
|
AND posts.post_status != 'publish' |
896
|
|
|
) |
897
|
|
|
GROUP BY posts.ID |
898
|
|
|
" |
899
|
|
|
// phpcs:enable WordPress.DB.PreparedSQL.NotPrepared |
900
|
|
|
); |
901
|
|
|
} |
902
|
|
|
|
903
|
|
|
/** |
904
|
|
|
* Returns a list of product IDs ( id as key => parent as value) that are |
905
|
|
|
* featured. Uses get_posts instead of wc_get_products since we want |
906
|
|
|
* some extra meta queries and ALL products (posts_per_page = -1). |
907
|
|
|
* |
908
|
|
|
* @return array |
909
|
|
|
* @since 3.0.0 |
910
|
|
|
*/ |
911
|
2 |
|
public function get_featured_product_ids() { |
912
|
2 |
|
$product_visibility_term_ids = wc_get_product_visibility_term_ids(); |
913
|
|
|
|
914
|
2 |
|
return get_posts( |
915
|
|
|
array( |
916
|
2 |
|
'post_type' => array( 'product', 'product_variation' ), |
917
|
|
|
'posts_per_page' => -1, |
|
|
|
|
918
|
2 |
|
'post_status' => 'publish', |
919
|
|
|
'tax_query' => array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query |
|
|
|
|
920
|
2 |
|
'relation' => 'AND', |
921
|
|
|
array( |
922
|
2 |
|
'taxonomy' => 'product_visibility', |
923
|
2 |
|
'field' => 'term_taxonomy_id', |
924
|
2 |
|
'terms' => array( $product_visibility_term_ids['featured'] ), |
925
|
|
|
), |
926
|
|
|
array( |
927
|
2 |
|
'taxonomy' => 'product_visibility', |
928
|
2 |
|
'field' => 'term_taxonomy_id', |
929
|
2 |
|
'terms' => array( $product_visibility_term_ids['exclude-from-catalog'] ), |
930
|
2 |
|
'operator' => 'NOT IN', |
931
|
|
|
), |
932
|
|
|
), |
933
|
2 |
|
'fields' => 'id=>parent', |
934
|
|
|
) |
935
|
|
|
); |
936
|
|
|
} |
937
|
|
|
|
938
|
|
|
/** |
939
|
|
|
* Check if product sku is found for any other product IDs. |
940
|
|
|
* |
941
|
|
|
* @since 3.0.0 |
942
|
|
|
* @param int $product_id Product ID. |
943
|
|
|
* @param string $sku Will be slashed to work around https://core.trac.wordpress.org/ticket/27421. |
944
|
|
|
* @return bool |
945
|
|
|
*/ |
946
|
341 |
View Code Duplication |
public function is_existing_sku( $product_id, $sku ) { |
|
|
|
|
947
|
|
|
global $wpdb; |
948
|
|
|
|
949
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
950
|
341 |
|
return $wpdb->get_var( |
|
|
|
|
951
|
341 |
|
$wpdb->prepare( |
952
|
341 |
|
" |
953
|
|
|
SELECT posts.ID |
954
|
341 |
|
FROM {$wpdb->posts} as posts |
955
|
341 |
|
INNER JOIN {$wpdb->wc_product_meta_lookup} AS lookup ON posts.ID = lookup.product_id |
956
|
|
|
WHERE |
957
|
|
|
posts.post_type IN ( 'product', 'product_variation' ) |
958
|
|
|
AND posts.post_status != 'trash' |
959
|
|
|
AND lookup.sku = %s |
960
|
|
|
AND lookup.product_id <> %d |
961
|
|
|
LIMIT 1 |
962
|
|
|
", |
963
|
341 |
|
wp_slash( $sku ), |
964
|
|
|
$product_id |
965
|
|
|
) |
966
|
|
|
); |
967
|
|
|
} |
968
|
|
|
|
969
|
|
|
/** |
970
|
|
|
* Return product ID based on SKU. |
971
|
|
|
* |
972
|
|
|
* @since 3.0.0 |
973
|
|
|
* @param string $sku Product SKU. |
974
|
|
|
* @return int |
975
|
|
|
*/ |
976
|
94 |
View Code Duplication |
public function get_product_id_by_sku( $sku ) { |
|
|
|
|
977
|
|
|
global $wpdb; |
978
|
|
|
|
979
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
980
|
94 |
|
$id = $wpdb->get_var( |
|
|
|
|
981
|
94 |
|
$wpdb->prepare( |
982
|
94 |
|
" |
983
|
|
|
SELECT posts.ID |
984
|
94 |
|
FROM {$wpdb->posts} as posts |
985
|
94 |
|
INNER JOIN {$wpdb->wc_product_meta_lookup} AS lookup ON posts.ID = lookup.product_id |
986
|
|
|
WHERE |
987
|
|
|
posts.post_type IN ( 'product', 'product_variation' ) |
988
|
|
|
AND posts.post_status != 'trash' |
989
|
|
|
AND lookup.sku = %s |
990
|
|
|
LIMIT 1 |
991
|
|
|
", |
992
|
|
|
$sku |
993
|
|
|
) |
994
|
|
|
); |
995
|
|
|
|
996
|
94 |
|
return (int) apply_filters( 'woocommerce_get_product_id_by_sku', $id, $sku ); |
997
|
|
|
} |
998
|
|
|
|
999
|
|
|
/** |
1000
|
|
|
* Returns an array of IDs of products that have sales starting soon. |
1001
|
|
|
* |
1002
|
|
|
* @since 3.0.0 |
1003
|
|
|
* @return array |
1004
|
|
|
*/ |
1005
|
|
|
public function get_starting_sales() { |
1006
|
|
|
global $wpdb; |
1007
|
|
|
|
1008
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
1009
|
|
|
return $wpdb->get_col( |
|
|
|
|
1010
|
|
|
$wpdb->prepare( |
1011
|
|
|
"SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta |
1012
|
|
|
LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id |
1013
|
|
|
LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id |
1014
|
|
|
WHERE postmeta.meta_key = '_sale_price_dates_from' |
1015
|
|
|
AND postmeta_2.meta_key = '_price' |
1016
|
|
|
AND postmeta_3.meta_key = '_sale_price' |
1017
|
|
|
AND postmeta.meta_value > 0 |
1018
|
|
|
AND postmeta.meta_value < %s |
1019
|
|
|
AND postmeta_2.meta_value != postmeta_3.meta_value", |
1020
|
|
|
current_time( 'timestamp', true ) |
1021
|
|
|
) |
1022
|
|
|
); |
1023
|
|
|
} |
1024
|
|
|
|
1025
|
|
|
/** |
1026
|
|
|
* Returns an array of IDs of products that have sales which are due to end. |
1027
|
|
|
* |
1028
|
|
|
* @since 3.0.0 |
1029
|
|
|
* @return array |
1030
|
|
|
*/ |
1031
|
|
|
public function get_ending_sales() { |
1032
|
|
|
global $wpdb; |
1033
|
|
|
|
1034
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
1035
|
|
|
return $wpdb->get_col( |
|
|
|
|
1036
|
|
|
$wpdb->prepare( |
1037
|
|
|
"SELECT postmeta.post_id FROM {$wpdb->postmeta} as postmeta |
1038
|
|
|
LEFT JOIN {$wpdb->postmeta} as postmeta_2 ON postmeta.post_id = postmeta_2.post_id |
1039
|
|
|
LEFT JOIN {$wpdb->postmeta} as postmeta_3 ON postmeta.post_id = postmeta_3.post_id |
1040
|
|
|
WHERE postmeta.meta_key = '_sale_price_dates_to' |
1041
|
|
|
AND postmeta_2.meta_key = '_price' |
1042
|
|
|
AND postmeta_3.meta_key = '_regular_price' |
1043
|
|
|
AND postmeta.meta_value > 0 |
1044
|
|
|
AND postmeta.meta_value < %s |
1045
|
|
|
AND postmeta_2.meta_value != postmeta_3.meta_value", |
1046
|
|
|
current_time( 'timestamp', true ) |
1047
|
|
|
) |
1048
|
|
|
); |
1049
|
|
|
} |
1050
|
|
|
|
1051
|
|
|
/** |
1052
|
|
|
* Find a matching (enabled) variation within a variable product. |
1053
|
|
|
* |
1054
|
|
|
* @since 3.0.0 |
1055
|
|
|
* @param WC_Product $product Variable product. |
1056
|
|
|
* @param array $match_attributes Array of attributes we want to try to match. |
1057
|
|
|
* @return int Matching variation ID or 0. |
1058
|
|
|
*/ |
1059
|
|
|
public function find_matching_product_variation( $product, $match_attributes = array() ) { |
1060
|
|
|
global $wpdb; |
1061
|
|
|
|
1062
|
|
|
$meta_attribute_names = array(); |
1063
|
|
|
|
1064
|
|
|
// Get attributes to match in meta. |
1065
|
|
|
foreach ( $product->get_attributes() as $attribute ) { |
1066
|
|
|
if ( ! $attribute->get_variation() ) { |
1067
|
|
|
continue; |
1068
|
|
|
} |
1069
|
|
|
|
1070
|
|
|
$attribute_field_name = 'attribute_' . sanitize_title( $attribute->get_name() ); |
1071
|
|
|
|
1072
|
|
|
if ( ! isset( $match_attributes[ $attribute_field_name ] ) ) { |
1073
|
|
|
return 0; |
1074
|
|
|
} |
1075
|
|
|
|
1076
|
|
|
$meta_attribute_names[] = $attribute_field_name; |
1077
|
|
|
} |
1078
|
|
|
|
1079
|
|
|
// Get the attributes of the variations. |
1080
|
|
|
$query = $wpdb->prepare( |
1081
|
|
|
" |
1082
|
|
|
SELECT post_id, meta_key, meta_value FROM {$wpdb->postmeta} |
1083
|
|
|
WHERE post_id IN ( |
1084
|
|
|
SELECT ID FROM {$wpdb->posts} |
1085
|
|
|
WHERE {$wpdb->posts}.post_parent = %d |
1086
|
|
|
AND {$wpdb->posts}.post_status = 'publish' |
1087
|
|
|
AND {$wpdb->posts}.post_type = 'product_variation' |
1088
|
|
|
ORDER BY menu_order ASC, ID ASC |
1089
|
|
|
) |
1090
|
|
|
", |
1091
|
|
|
$product->get_id() |
1092
|
|
|
); |
1093
|
|
|
|
1094
|
|
|
$query .= ' AND meta_key IN ( "' . implode( '","', array_map( 'esc_sql', $meta_attribute_names ) ) . '" );'; |
1095
|
|
|
|
1096
|
|
|
$attributes = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared |
|
|
|
|
1097
|
|
|
|
1098
|
|
|
if ( ! $attributes ) { |
1099
|
|
|
return 0; |
1100
|
|
|
} |
1101
|
|
|
|
1102
|
|
|
$sorted_meta = array(); |
1103
|
|
|
|
1104
|
|
|
foreach ( $attributes as $m ) { |
1105
|
|
|
$sorted_meta[ $m->post_id ][ $m->meta_key ] = $m->meta_value; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key |
|
|
|
|
1106
|
|
|
} |
1107
|
|
|
|
1108
|
|
|
/** |
1109
|
|
|
* Check each variation to find the one that matches the $match_attributes. |
1110
|
|
|
* |
1111
|
|
|
* Note: Not all meta fields will be set which is why we check existance. |
1112
|
|
|
*/ |
1113
|
|
|
foreach ( $sorted_meta as $variation_id => $variation ) { |
1114
|
|
|
$match = true; |
1115
|
|
|
|
1116
|
|
|
foreach ( $match_attributes as $attribute_key => $attribute_value ) { |
1117
|
|
|
if ( array_key_exists( $attribute_key, $variation ) ) { |
1118
|
|
|
if ( $variation[ $attribute_key ] !== $attribute_value && ! empty( $variation[ $attribute_key ] ) ) { |
1119
|
|
|
$match = false; |
1120
|
|
|
} |
1121
|
|
|
} |
1122
|
|
|
} |
1123
|
|
|
|
1124
|
|
|
if ( true === $match ) { |
1125
|
|
|
return $variation_id; |
1126
|
|
|
} |
1127
|
|
|
} |
1128
|
|
|
|
1129
|
|
|
if ( version_compare( get_post_meta( $product->get_id(), '_product_version', true ), '2.4.0', '<' ) ) { |
1130
|
|
|
/** |
1131
|
|
|
* Pre 2.4 handling where 'slugs' were saved instead of the full text attribute. |
1132
|
|
|
* Fallback is here because there are cases where data will be 'synced' but the product version will remain the same. |
1133
|
|
|
*/ |
1134
|
|
|
return ( array_map( 'sanitize_title', $match_attributes ) === $match_attributes ) ? 0 : $this->find_matching_product_variation( $product, array_map( 'sanitize_title', $match_attributes ) ); |
1135
|
|
|
} |
1136
|
|
|
} |
1137
|
|
|
|
1138
|
|
|
/** |
1139
|
|
|
* Creates all possible combinations of variations from the attributes, without creating duplicates. |
1140
|
|
|
* |
1141
|
|
|
* @since 3.6.0 |
1142
|
|
|
* @todo Add to interface in 4.0. |
1143
|
|
|
* @param WC_Product $product Variable product. |
1144
|
|
|
* @param int $limit Limit the number of created variations. |
1145
|
|
|
* @return int Number of created variations. |
1146
|
|
|
*/ |
1147
|
2 |
|
public function create_all_product_variations( $product, $limit = -1 ) { |
1148
|
2 |
|
$count = 0; |
1149
|
|
|
|
1150
|
2 |
|
if ( ! $product ) { |
1151
|
|
|
return $count; |
1152
|
|
|
} |
1153
|
|
|
|
1154
|
2 |
|
$attributes = wc_list_pluck( array_filter( $product->get_attributes(), 'wc_attributes_array_filter_variation' ), 'get_slugs' ); |
1155
|
|
|
|
1156
|
2 |
|
if ( empty( $attributes ) ) { |
1157
|
|
|
return $count; |
1158
|
|
|
} |
1159
|
|
|
|
1160
|
|
|
// Get existing variations so we don't create duplicates. |
1161
|
2 |
|
$existing_variations = array_map( 'wc_get_product', $product->get_children() ); |
1162
|
2 |
|
$existing_attributes = array(); |
1163
|
|
|
|
1164
|
2 |
|
foreach ( $existing_variations as $existing_variation ) { |
1165
|
2 |
|
$existing_attributes[] = $existing_variation->get_attributes(); |
1166
|
|
|
} |
1167
|
|
|
|
1168
|
2 |
|
$possible_attributes = array_reverse( wc_array_cartesian( $attributes ) ); |
1169
|
|
|
|
1170
|
2 |
|
foreach ( $possible_attributes as $possible_attribute ) { |
1171
|
|
|
// Allow any order if key/values -- do not use strict mode. |
1172
|
2 |
|
if ( in_array( $possible_attribute, $existing_attributes ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict |
1173
|
2 |
|
continue; |
1174
|
|
|
} |
1175
|
2 |
|
$variation = new WC_Product_Variation(); |
1176
|
2 |
|
$variation->set_parent_id( $product->get_id() ); |
1177
|
2 |
|
$variation->set_attributes( $possible_attribute ); |
1178
|
2 |
|
$variation_id = $variation->save(); |
1179
|
|
|
|
1180
|
2 |
|
do_action( 'product_variation_linked', $variation_id ); |
1181
|
|
|
|
1182
|
2 |
|
$count ++; |
1183
|
|
|
|
1184
|
2 |
|
if ( $limit > 0 && $count >= $limit ) { |
1185
|
1 |
|
break; |
1186
|
|
|
} |
1187
|
|
|
} |
1188
|
|
|
|
1189
|
2 |
|
return $count; |
1190
|
|
|
} |
1191
|
|
|
|
1192
|
|
|
/** |
1193
|
|
|
* Make sure all variations have a sort order set so they can be reordered correctly. |
1194
|
|
|
* |
1195
|
|
|
* @param int $parent_id Product ID. |
1196
|
|
|
*/ |
1197
|
|
|
public function sort_all_product_variations( $parent_id ) { |
1198
|
|
|
global $wpdb; |
1199
|
|
|
|
1200
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
1201
|
|
|
$ids = $wpdb->get_col( |
|
|
|
|
1202
|
|
|
$wpdb->prepare( |
1203
|
|
|
"SELECT ID FROM {$wpdb->posts} WHERE post_type = 'product_variation' AND post_parent = %d AND post_status = 'publish' ORDER BY menu_order ASC, ID ASC", |
1204
|
|
|
$parent_id |
1205
|
|
|
) |
1206
|
|
|
); |
1207
|
|
|
$index = 1; |
1208
|
|
|
|
1209
|
|
|
foreach ( $ids as $id ) { |
1210
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
1211
|
|
|
$wpdb->update( $wpdb->posts, array( 'menu_order' => ( $index++ ) ), array( 'ID' => absint( $id ) ) ); |
|
|
|
|
1212
|
|
|
} |
1213
|
|
|
} |
1214
|
|
|
|
1215
|
|
|
/** |
1216
|
|
|
* Return a list of related products (using data like categories and IDs). |
1217
|
|
|
* |
1218
|
|
|
* @since 3.0.0 |
1219
|
|
|
* @param array $cats_array List of categories IDs. |
1220
|
|
|
* @param array $tags_array List of tags IDs. |
1221
|
|
|
* @param array $exclude_ids Excluded IDs. |
1222
|
|
|
* @param int $limit Limit of results. |
1223
|
|
|
* @param int $product_id Product ID. |
1224
|
|
|
* @return array |
1225
|
|
|
*/ |
1226
|
27 |
|
public function get_related_products( $cats_array, $tags_array, $exclude_ids, $limit, $product_id ) { |
1227
|
|
|
global $wpdb; |
1228
|
|
|
|
1229
|
|
|
$args = array( |
1230
|
27 |
|
'categories' => $cats_array, |
1231
|
27 |
|
'tags' => $tags_array, |
1232
|
27 |
|
'exclude_ids' => $exclude_ids, |
1233
|
27 |
|
'limit' => $limit + 10, |
1234
|
|
|
); |
1235
|
|
|
|
1236
|
27 |
|
$related_product_query = (array) apply_filters( 'woocommerce_product_related_posts_query', $this->get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit + 10 ), $product_id, $args ); |
1237
|
|
|
|
1238
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared |
1239
|
27 |
|
return $wpdb->get_col( implode( ' ', $related_product_query ) ); |
|
|
|
|
1240
|
|
|
} |
1241
|
|
|
|
1242
|
|
|
/** |
1243
|
|
|
* Builds the related posts query. |
1244
|
|
|
* |
1245
|
|
|
* @since 3.0.0 |
1246
|
|
|
* |
1247
|
|
|
* @param array $cats_array List of categories IDs. |
1248
|
|
|
* @param array $tags_array List of tags IDs. |
1249
|
|
|
* @param array $exclude_ids Excluded IDs. |
1250
|
|
|
* @param int $limit Limit of results. |
1251
|
|
|
* |
1252
|
|
|
* @return array |
1253
|
|
|
*/ |
1254
|
27 |
|
public function get_related_products_query( $cats_array, $tags_array, $exclude_ids, $limit ) { |
1255
|
|
|
global $wpdb; |
1256
|
|
|
|
1257
|
27 |
|
$include_term_ids = array_merge( $cats_array, $tags_array ); |
1258
|
27 |
|
$exclude_term_ids = array(); |
1259
|
27 |
|
$product_visibility_term_ids = wc_get_product_visibility_term_ids(); |
1260
|
|
|
|
1261
|
27 |
|
if ( $product_visibility_term_ids['exclude-from-catalog'] ) { |
1262
|
27 |
|
$exclude_term_ids[] = $product_visibility_term_ids['exclude-from-catalog']; |
1263
|
|
|
} |
1264
|
|
|
|
1265
|
27 |
View Code Duplication |
if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && $product_visibility_term_ids['outofstock'] ) { |
1266
|
|
|
$exclude_term_ids[] = $product_visibility_term_ids['outofstock']; |
1267
|
|
|
} |
1268
|
|
|
|
1269
|
|
|
$query = array( |
1270
|
|
|
'fields' => " |
1271
|
27 |
|
SELECT DISTINCT ID FROM {$wpdb->posts} p |
1272
|
|
|
", |
1273
|
27 |
|
'join' => '', |
1274
|
27 |
|
'where' => " |
1275
|
|
|
WHERE 1=1 |
1276
|
|
|
AND p.post_status = 'publish' |
1277
|
|
|
AND p.post_type = 'product' |
1278
|
|
|
|
1279
|
|
|
", |
1280
|
|
|
'limits' => ' |
|
|
|
|
1281
|
27 |
|
LIMIT ' . absint( $limit ) . ' |
1282
|
|
|
', |
1283
|
|
|
); |
1284
|
|
|
|
1285
|
27 |
View Code Duplication |
if ( count( $exclude_term_ids ) ) { |
1286
|
27 |
|
$query['join'] .= " LEFT JOIN ( SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN ( " . implode( ',', array_map( 'absint', $exclude_term_ids ) ) . ' ) ) AS exclude_join ON exclude_join.object_id = p.ID'; |
1287
|
27 |
|
$query['where'] .= ' AND exclude_join.object_id IS NULL'; |
1288
|
|
|
} |
1289
|
|
|
|
1290
|
27 |
View Code Duplication |
if ( count( $include_term_ids ) ) { |
1291
|
27 |
|
$query['join'] .= " INNER JOIN ( SELECT object_id FROM {$wpdb->term_relationships} INNER JOIN {$wpdb->term_taxonomy} using( term_taxonomy_id ) WHERE term_id IN ( " . implode( ',', array_map( 'absint', $include_term_ids ) ) . ' ) ) AS include_join ON include_join.object_id = p.ID'; |
1292
|
|
|
} |
1293
|
|
|
|
1294
|
27 |
|
if ( count( $exclude_ids ) ) { |
1295
|
27 |
|
$query['where'] .= ' AND p.ID NOT IN ( ' . implode( ',', array_map( 'absint', $exclude_ids ) ) . ' )'; |
1296
|
|
|
} |
1297
|
|
|
|
1298
|
27 |
|
return $query; |
1299
|
|
|
} |
1300
|
|
|
|
1301
|
|
|
/** |
1302
|
|
|
* Update a product's stock amount directly in the database. |
1303
|
|
|
* |
1304
|
|
|
* Updates both post meta and lookup tables. Ignores manage stock setting on the product. |
1305
|
|
|
* |
1306
|
|
|
* @param int $product_id_with_stock Product ID. |
1307
|
|
|
* @param int|float|null $stock_quantity Stock quantity. |
1308
|
|
|
*/ |
1309
|
|
|
protected function set_product_stock( $product_id_with_stock, $stock_quantity ) { |
1310
|
|
|
global $wpdb; |
1311
|
|
|
|
1312
|
|
|
// Generate SQL. |
1313
|
|
|
$sql = $wpdb->prepare( |
1314
|
|
|
"UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'", |
1315
|
|
|
$stock_quantity, |
1316
|
|
|
$product_id_with_stock |
1317
|
|
|
); |
1318
|
|
|
|
1319
|
|
|
$sql = apply_filters( 'woocommerce_update_product_stock_query', $sql, $product_id_with_stock, $stock_quantity, 'set' ); |
1320
|
|
|
|
1321
|
|
|
$wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared |
|
|
|
|
1322
|
|
|
|
1323
|
|
|
// Cache delete is required (not only) to set correct data for lookup table (which reads from cache). |
1324
|
|
|
// Sometimes I wonder if it shouldn't be part of update_lookup_table. |
1325
|
|
|
wp_cache_delete( $product_id_with_stock, 'post_meta' ); |
1326
|
|
|
|
1327
|
|
|
$this->update_lookup_table( $product_id_with_stock, 'wc_product_meta_lookup' ); |
1328
|
|
|
} |
1329
|
|
|
|
1330
|
|
|
/** |
1331
|
|
|
* Update a product's stock amount directly. |
1332
|
|
|
* |
1333
|
|
|
* Uses queries rather than update_post_meta so we can do this in one query (to avoid stock issues). |
1334
|
|
|
* Ignores manage stock setting on the product and sets quantities directly in the db: post meta and lookup tables. |
1335
|
|
|
* Uses locking to update the quantity. If the lock is not acquired, change is lost. |
1336
|
|
|
* |
1337
|
|
|
* @since 3.0.0 this supports set, increase and decrease. |
1338
|
|
|
* @param int $product_id_with_stock Product ID. |
1339
|
|
|
* @param int|float|null $stock_quantity Stock quantity. |
1340
|
|
|
* @param string $operation Set, increase and decrease. |
1341
|
|
|
* @return int|float New stock level. |
1342
|
|
|
*/ |
1343
|
2 |
|
public function update_product_stock( $product_id_with_stock, $stock_quantity = null, $operation = 'set' ) { |
1344
|
|
|
global $wpdb; |
1345
|
|
|
|
1346
|
|
|
// Ensures a row exists to update. |
1347
|
2 |
|
add_post_meta( $product_id_with_stock, '_stock', 0, true ); |
1348
|
|
|
|
1349
|
2 |
|
if ( 'set' === $operation ) { |
1350
|
2 |
|
$new_stock = wc_stock_amount( $stock_quantity ); |
1351
|
|
|
} else { |
1352
|
|
|
// @todo: potential race condition. |
1353
|
|
|
// Read current stock level and lock the row. If the lock can't be acquired, don't wait. |
1354
|
1 |
|
$current_stock = wc_stock_amount( |
1355
|
1 |
|
$wpdb->get_var( |
|
|
|
|
1356
|
1 |
|
$wpdb->prepare( |
1357
|
1 |
|
"SELECT meta_value FROM {$wpdb->postmeta} WHERE post_id = %d AND meta_key='_stock';", |
1358
|
|
|
$product_id_with_stock |
1359
|
|
|
) |
1360
|
|
|
) |
1361
|
|
|
); |
1362
|
|
|
|
1363
|
|
|
// Calculate new value. |
1364
|
1 |
|
switch ( $operation ) { |
1365
|
|
|
case 'increase': |
1366
|
1 |
|
$new_stock = $current_stock + wc_stock_amount( $stock_quantity ); |
1367
|
1 |
|
break; |
1368
|
|
|
default: |
1369
|
1 |
|
$new_stock = $current_stock - wc_stock_amount( $stock_quantity ); |
1370
|
1 |
|
break; |
1371
|
|
|
} |
1372
|
|
|
} |
1373
|
|
|
|
1374
|
|
|
// Generate SQL. |
1375
|
2 |
|
$sql = $wpdb->prepare( |
1376
|
2 |
|
"UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='_stock'", |
1377
|
|
|
$new_stock, |
1378
|
|
|
$product_id_with_stock |
1379
|
|
|
); |
1380
|
|
|
|
1381
|
2 |
|
$sql = apply_filters( 'woocommerce_update_product_stock_query', $sql, $product_id_with_stock, $stock_quantity, 'set' ); |
1382
|
|
|
|
1383
|
2 |
|
$wpdb->query( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared |
|
|
|
|
1384
|
|
|
|
1385
|
|
|
// Cache delete is required (not only) to set correct data for lookup table (which reads from cache). |
1386
|
|
|
// Sometimes I wonder if it shouldn't be part of update_lookup_table. |
1387
|
2 |
|
wp_cache_delete( $product_id_with_stock, 'post_meta' ); |
1388
|
|
|
|
1389
|
2 |
|
$this->update_lookup_table( $product_id_with_stock, 'wc_product_meta_lookup' ); |
1390
|
|
|
|
1391
|
|
|
/** |
1392
|
|
|
* Fire an action for this direct update so it can be detected by other code. |
1393
|
|
|
* |
1394
|
|
|
* @since 3.6 |
1395
|
|
|
* @param int $product_id_with_stock Product ID that was updated directly. |
1396
|
|
|
*/ |
1397
|
2 |
|
do_action( 'woocommerce_updated_product_stock', $product_id_with_stock ); |
1398
|
|
|
|
1399
|
2 |
|
return $new_stock; |
1400
|
|
|
} |
1401
|
|
|
|
1402
|
|
|
/** |
1403
|
|
|
* Update a product's sale count directly. |
1404
|
|
|
* |
1405
|
|
|
* Uses queries rather than update_post_meta so we can do this in one query for performance. |
1406
|
|
|
* |
1407
|
|
|
* @since 3.0.0 this supports set, increase and decrease. |
1408
|
|
|
* @param int $product_id Product ID. |
1409
|
|
|
* @param int|null $quantity Quantity. |
1410
|
|
|
* @param string $operation set, increase and decrease. |
1411
|
|
|
*/ |
1412
|
13 |
|
public function update_product_sales( $product_id, $quantity = null, $operation = 'set' ) { |
1413
|
|
|
global $wpdb; |
1414
|
13 |
|
add_post_meta( $product_id, 'total_sales', 0, true ); |
1415
|
|
|
|
1416
|
|
|
// Update stock in DB directly. |
1417
|
13 |
|
switch ( $operation ) { |
1418
|
|
|
case 'increase': |
1419
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
1420
|
13 |
|
$wpdb->query( |
|
|
|
|
1421
|
13 |
|
$wpdb->prepare( |
1422
|
13 |
|
"UPDATE {$wpdb->postmeta} SET meta_value = meta_value + %f WHERE post_id = %d AND meta_key='total_sales'", |
1423
|
|
|
$quantity, |
1424
|
|
|
$product_id |
1425
|
|
|
) |
1426
|
|
|
); |
1427
|
13 |
|
break; |
1428
|
|
|
case 'decrease': |
1429
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
1430
|
|
|
$wpdb->query( |
|
|
|
|
1431
|
|
|
$wpdb->prepare( |
1432
|
|
|
"UPDATE {$wpdb->postmeta} SET meta_value = meta_value - %f WHERE post_id = %d AND meta_key='total_sales'", |
1433
|
|
|
$quantity, |
1434
|
|
|
$product_id |
1435
|
|
|
) |
1436
|
|
|
); |
1437
|
|
|
break; |
1438
|
|
|
default: |
1439
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
1440
|
|
|
$wpdb->query( |
|
|
|
|
1441
|
|
|
$wpdb->prepare( |
1442
|
|
|
"UPDATE {$wpdb->postmeta} SET meta_value = %f WHERE post_id = %d AND meta_key='total_sales'", |
1443
|
|
|
$quantity, |
1444
|
|
|
$product_id |
1445
|
|
|
) |
1446
|
|
|
); |
1447
|
|
|
break; |
1448
|
|
|
} |
1449
|
|
|
|
1450
|
13 |
|
wp_cache_delete( $product_id, 'post_meta' ); |
1451
|
|
|
|
1452
|
13 |
|
$this->update_lookup_table( $product_id, 'wc_product_meta_lookup' ); |
1453
|
|
|
|
1454
|
|
|
/** |
1455
|
|
|
* Fire an action for this direct update so it can be detected by other code. |
1456
|
|
|
* |
1457
|
|
|
* @since 3.6 |
1458
|
|
|
* @param int $product_id Product ID that was updated directly. |
1459
|
|
|
*/ |
1460
|
13 |
|
do_action( 'woocommerce_updated_product_sales', $product_id ); |
1461
|
|
|
} |
1462
|
|
|
|
1463
|
|
|
/** |
1464
|
|
|
* Update a products average rating meta. |
1465
|
|
|
* |
1466
|
|
|
* @since 3.0.0 |
1467
|
|
|
* @todo Deprecate unused function? |
1468
|
|
|
* @param WC_Product $product Product object. |
1469
|
|
|
*/ |
1470
|
|
|
public function update_average_rating( $product ) { |
1471
|
|
|
update_post_meta( $product->get_id(), '_wc_average_rating', $product->get_average_rating( 'edit' ) ); |
1472
|
|
|
self::update_visibility( $product, true ); |
1473
|
|
|
} |
1474
|
|
|
|
1475
|
|
|
/** |
1476
|
|
|
* Update a products review count meta. |
1477
|
|
|
* |
1478
|
|
|
* @since 3.0.0 |
1479
|
|
|
* @todo Deprecate unused function? |
1480
|
|
|
* @param WC_Product $product Product object. |
1481
|
|
|
*/ |
1482
|
|
|
public function update_review_count( $product ) { |
1483
|
|
|
update_post_meta( $product->get_id(), '_wc_review_count', $product->get_review_count( 'edit' ) ); |
1484
|
|
|
} |
1485
|
|
|
|
1486
|
|
|
/** |
1487
|
|
|
* Update a products rating counts. |
1488
|
|
|
* |
1489
|
|
|
* @since 3.0.0 |
1490
|
|
|
* @todo Deprecate unused function? |
1491
|
|
|
* @param WC_Product $product Product object. |
1492
|
|
|
*/ |
1493
|
|
|
public function update_rating_counts( $product ) { |
1494
|
|
|
update_post_meta( $product->get_id(), '_wc_rating_count', $product->get_rating_counts( 'edit' ) ); |
1495
|
|
|
} |
1496
|
|
|
|
1497
|
|
|
/** |
1498
|
|
|
* Get shipping class ID by slug. |
1499
|
|
|
* |
1500
|
|
|
* @since 3.0.0 |
1501
|
|
|
* @param string $slug Product shipping class slug. |
1502
|
|
|
* @return int|false |
1503
|
|
|
*/ |
1504
|
2 |
|
public function get_shipping_class_id_by_slug( $slug ) { |
1505
|
2 |
|
$shipping_class_term = get_term_by( 'slug', $slug, 'product_shipping_class' ); |
1506
|
2 |
|
if ( $shipping_class_term ) { |
1507
|
2 |
|
return $shipping_class_term->term_id; |
1508
|
|
|
} else { |
1509
|
|
|
return false; |
1510
|
|
|
} |
1511
|
|
|
} |
1512
|
|
|
|
1513
|
|
|
/** |
1514
|
|
|
* Returns an array of products. |
1515
|
|
|
* |
1516
|
|
|
* @param array $args Args to pass to WC_Product_Query(). |
1517
|
|
|
* @return array|object |
1518
|
|
|
* @see wc_get_products |
1519
|
|
|
*/ |
1520
|
|
|
public function get_products( $args = array() ) { |
1521
|
|
|
$query = new WC_Product_Query( $args ); |
1522
|
|
|
return $query->get_products(); |
1523
|
|
|
} |
1524
|
|
|
|
1525
|
|
|
/** |
1526
|
|
|
* Search product data for a term and return ids. |
1527
|
|
|
* |
1528
|
|
|
* @param string $term Search term. |
1529
|
|
|
* @param string $type Type of product. |
1530
|
|
|
* @param bool $include_variations Include variations in search or not. |
1531
|
|
|
* @param bool $all_statuses Should we search all statuses or limit to published. |
1532
|
|
|
* @param null|int $limit Limit returned results. @since 3.5.0. |
1533
|
|
|
* @param null|array $include Keep specific results. @since 3.6.0. |
1534
|
|
|
* @param null|array $exclude Discard specific results. @since 3.6.0. |
1535
|
|
|
* @return array of ids |
1536
|
|
|
*/ |
1537
|
1 |
|
public function search_products( $term, $type = '', $include_variations = false, $all_statuses = false, $limit = null, $include = null, $exclude = null ) { |
1538
|
|
|
global $wpdb; |
1539
|
|
|
|
1540
|
1 |
|
$custom_results = apply_filters( 'woocommerce_product_pre_search_products', false, $term, $type, $include_variations, $all_statuses, $limit ); |
1541
|
|
|
|
1542
|
1 |
|
if ( is_array( $custom_results ) ) { |
1543
|
|
|
return $custom_results; |
1544
|
|
|
} |
1545
|
|
|
|
1546
|
1 |
|
$post_types = $include_variations ? array( 'product', 'product_variation' ) : array( 'product' ); |
1547
|
1 |
|
$post_statuses = current_user_can( 'edit_private_products' ) ? array( 'private', 'publish' ) : array( 'publish' ); |
1548
|
1 |
|
$type_where = ''; |
1549
|
1 |
|
$status_where = ''; |
1550
|
1 |
|
$limit_query = ''; |
1551
|
1 |
|
$term = wc_strtolower( $term ); |
1552
|
|
|
|
1553
|
|
|
// See if search term contains OR keywords. |
1554
|
1 |
|
if ( strstr( $term, ' or ' ) ) { |
1555
|
1 |
|
$term_groups = explode( ' or ', $term ); |
1556
|
|
|
} else { |
1557
|
1 |
|
$term_groups = array( $term ); |
1558
|
|
|
} |
1559
|
|
|
|
1560
|
1 |
|
$search_where = ''; |
1561
|
1 |
|
$search_queries = array(); |
1562
|
|
|
|
1563
|
1 |
|
foreach ( $term_groups as $term_group ) { |
1564
|
|
|
// Parse search terms. |
1565
|
1 |
|
if ( preg_match_all( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/', $term_group, $matches ) ) { |
1566
|
1 |
|
$search_terms = $this->get_valid_search_terms( $matches[0] ); |
1567
|
1 |
|
$count = count( $search_terms ); |
1568
|
|
|
|
1569
|
|
|
// if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence. |
1570
|
1 |
|
if ( 9 < $count || 0 === $count ) { |
1571
|
1 |
|
$search_terms = array( $term_group ); |
1572
|
|
|
} |
1573
|
|
|
} else { |
1574
|
|
|
$search_terms = array( $term_group ); |
1575
|
|
|
} |
1576
|
|
|
|
1577
|
1 |
|
$term_group_query = ''; |
1578
|
1 |
|
$searchand = ''; |
1579
|
|
|
|
1580
|
1 |
|
foreach ( $search_terms as $search_term ) { |
1581
|
1 |
|
$like = '%' . $wpdb->esc_like( $search_term ) . '%'; |
1582
|
1 |
|
$term_group_query .= $wpdb->prepare( " {$searchand} ( ( posts.post_title LIKE %s) OR ( posts.post_excerpt LIKE %s) OR ( posts.post_content LIKE %s ) OR ( wc_product_meta_lookup.sku LIKE %s ) )", $like, $like, $like, $like ); // @codingStandardsIgnoreLine. |
1583
|
1 |
|
$searchand = ' AND '; |
1584
|
|
|
} |
1585
|
|
|
|
1586
|
1 |
|
if ( $term_group_query ) { |
1587
|
1 |
|
$search_queries[] = $term_group_query; |
1588
|
|
|
} |
1589
|
|
|
} |
1590
|
|
|
|
1591
|
1 |
|
if ( ! empty( $search_queries ) ) { |
1592
|
1 |
|
$search_where = ' AND (' . implode( ') OR (', $search_queries ) . ') '; |
1593
|
|
|
} |
1594
|
|
|
|
1595
|
1 |
View Code Duplication |
if ( ! empty( $include ) && is_array( $include ) ) { |
1596
|
1 |
|
$search_where .= ' AND posts.ID IN(' . implode( ',', array_map( 'absint', $include ) ) . ') '; |
1597
|
|
|
} |
1598
|
|
|
|
1599
|
1 |
View Code Duplication |
if ( ! empty( $exclude ) && is_array( $exclude ) ) { |
1600
|
1 |
|
$search_where .= ' AND posts.ID NOT IN(' . implode( ',', array_map( 'absint', $exclude ) ) . ') '; |
1601
|
|
|
} |
1602
|
|
|
|
1603
|
1 |
|
if ( 'virtual' === $type ) { |
1604
|
|
|
$type_where = ' AND ( wc_product_meta_lookup.virtual = 1 ) '; |
1605
|
1 |
|
} elseif ( 'downloadable' === $type ) { |
1606
|
|
|
$type_where = ' AND ( wc_product_meta_lookup.downloadable = 1 ) '; |
1607
|
|
|
} |
1608
|
|
|
|
1609
|
1 |
|
if ( ! $all_statuses ) { |
1610
|
|
|
$status_where = " AND posts.post_status IN ('" . implode( "','", $post_statuses ) . "') "; |
1611
|
|
|
} |
1612
|
|
|
|
1613
|
1 |
|
if ( $limit ) { |
|
|
|
|
1614
|
1 |
|
$limit_query = $wpdb->prepare( ' LIMIT %d ', $limit ); |
1615
|
|
|
} |
1616
|
|
|
|
1617
|
|
|
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery |
1618
|
1 |
|
$search_results = $wpdb->get_results( |
|
|
|
|
1619
|
|
|
// phpcs:disable |
1620
|
1 |
|
"SELECT DISTINCT posts.ID as product_id, posts.post_parent as parent_id FROM {$wpdb->posts} posts |
1621
|
1 |
|
LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON posts.ID = wc_product_meta_lookup.product_id |
1622
|
1 |
|
WHERE posts.post_type IN ('" . implode( "','", $post_types ) . "') |
1623
|
1 |
|
$search_where |
1624
|
1 |
|
$status_where |
1625
|
1 |
|
$type_where |
1626
|
|
|
ORDER BY posts.post_parent ASC, posts.post_title ASC |
1627
|
1 |
|
$limit_query |
1628
|
|
|
" |
1629
|
|
|
// phpcs:enable |
1630
|
|
|
); |
1631
|
|
|
|
1632
|
1 |
|
$product_ids = wp_parse_id_list( array_merge( wp_list_pluck( $search_results, 'product_id' ), wp_list_pluck( $search_results, 'parent_id' ) ) ); |
1633
|
|
|
|
1634
|
1 |
|
if ( is_numeric( $term ) ) { |
1635
|
|
|
$post_id = absint( $term ); |
1636
|
|
|
$post_type = get_post_type( $post_id ); |
1637
|
|
|
|
1638
|
|
|
if ( 'product_variation' === $post_type && $include_variations ) { |
1639
|
|
|
$product_ids[] = $post_id; |
1640
|
|
|
} elseif ( 'product' === $post_type ) { |
1641
|
|
|
$product_ids[] = $post_id; |
1642
|
|
|
} |
1643
|
|
|
|
1644
|
|
|
$product_ids[] = wp_get_post_parent_id( $post_id ); |
1645
|
|
|
} |
1646
|
|
|
|
1647
|
1 |
|
return wp_parse_id_list( $product_ids ); |
1648
|
|
|
} |
1649
|
|
|
|
1650
|
|
|
/** |
1651
|
|
|
* Get the product type based on product ID. |
1652
|
|
|
* |
1653
|
|
|
* @since 3.0.0 |
1654
|
|
|
* @param int $product_id Product ID. |
1655
|
|
|
* @return bool|string |
1656
|
|
|
*/ |
1657
|
401 |
|
public function get_product_type( $product_id ) { |
1658
|
401 |
|
$cache_key = WC_Cache_Helper::get_cache_prefix( 'product_' . $product_id ) . '_type_' . $product_id; |
1659
|
401 |
|
$product_type = wp_cache_get( $cache_key, 'products' ); |
1660
|
|
|
|
1661
|
401 |
|
if ( $product_type ) { |
1662
|
231 |
|
return $product_type; |
1663
|
|
|
} |
1664
|
|
|
|
1665
|
401 |
|
$post_type = get_post_type( $product_id ); |
1666
|
|
|
|
1667
|
401 |
|
if ( 'product_variation' === $post_type ) { |
1668
|
63 |
|
$product_type = 'variation'; |
1669
|
400 |
|
} elseif ( 'product' === $post_type ) { |
1670
|
398 |
|
$terms = get_the_terms( $product_id, 'product_type' ); |
1671
|
398 |
|
$product_type = ! empty( $terms ) ? sanitize_title( current( $terms )->name ) : 'simple'; |
1672
|
|
|
} else { |
1673
|
2 |
|
$product_type = false; |
1674
|
|
|
} |
1675
|
|
|
|
1676
|
401 |
|
wp_cache_set( $cache_key, $product_type, 'products' ); |
1677
|
|
|
|
1678
|
401 |
|
return $product_type; |
1679
|
|
|
} |
1680
|
|
|
|
1681
|
|
|
/** |
1682
|
|
|
* Add ability to get products by 'reviews_allowed' in WC_Product_Query. |
1683
|
|
|
* |
1684
|
|
|
* @since 3.2.0 |
1685
|
|
|
* @param string $where Where clause. |
1686
|
|
|
* @param WP_Query $wp_query WP_Query instance. |
1687
|
|
|
* @return string |
1688
|
|
|
*/ |
1689
|
1 |
|
public function reviews_allowed_query_where( $where, $wp_query ) { |
1690
|
|
|
global $wpdb; |
1691
|
|
|
|
1692
|
1 |
|
if ( isset( $wp_query->query_vars['reviews_allowed'] ) && is_bool( $wp_query->query_vars['reviews_allowed'] ) ) { |
1693
|
1 |
|
if ( $wp_query->query_vars['reviews_allowed'] ) { |
1694
|
1 |
|
$where .= " AND $wpdb->posts.comment_status = 'open'"; |
1695
|
|
|
} else { |
1696
|
1 |
|
$where .= " AND $wpdb->posts.comment_status = 'closed'"; |
1697
|
|
|
} |
1698
|
|
|
} |
1699
|
|
|
|
1700
|
1 |
|
return $where; |
1701
|
|
|
} |
1702
|
|
|
|
1703
|
|
|
/** |
1704
|
|
|
* Get valid WP_Query args from a WC_Product_Query's query variables. |
1705
|
|
|
* |
1706
|
|
|
* @since 3.2.0 |
1707
|
|
|
* @param array $query_vars Query vars from a WC_Product_Query. |
1708
|
|
|
* @return array |
1709
|
|
|
*/ |
1710
|
14 |
|
protected function get_wp_query_args( $query_vars ) { |
1711
|
|
|
|
1712
|
|
|
// Map query vars to ones that get_wp_query_args or WP_Query recognize. |
1713
|
|
|
$key_mapping = array( |
1714
|
14 |
|
'status' => 'post_status', |
1715
|
|
|
'page' => 'paged', |
1716
|
|
|
'include' => 'post__in', |
1717
|
|
|
'stock_quantity' => 'stock', |
1718
|
|
|
'average_rating' => 'wc_average_rating', |
1719
|
|
|
'review_count' => 'wc_review_count', |
1720
|
|
|
); |
1721
|
14 |
View Code Duplication |
foreach ( $key_mapping as $query_key => $db_key ) { |
1722
|
14 |
|
if ( isset( $query_vars[ $query_key ] ) ) { |
1723
|
14 |
|
$query_vars[ $db_key ] = $query_vars[ $query_key ]; |
1724
|
14 |
|
unset( $query_vars[ $query_key ] ); |
1725
|
|
|
} |
1726
|
|
|
} |
1727
|
|
|
|
1728
|
|
|
// Map boolean queries that are stored as 'yes'/'no' in the DB to 'yes' or 'no'. |
1729
|
|
|
$boolean_queries = array( |
1730
|
14 |
|
'virtual', |
1731
|
|
|
'downloadable', |
1732
|
|
|
'sold_individually', |
1733
|
|
|
'manage_stock', |
1734
|
|
|
); |
1735
|
14 |
|
foreach ( $boolean_queries as $boolean_query ) { |
1736
|
14 |
|
if ( isset( $query_vars[ $boolean_query ] ) && '' !== $query_vars[ $boolean_query ] ) { |
1737
|
1 |
|
$query_vars[ $boolean_query ] = $query_vars[ $boolean_query ] ? 'yes' : 'no'; |
1738
|
|
|
} |
1739
|
|
|
} |
1740
|
|
|
|
1741
|
|
|
// These queries cannot be auto-generated so we have to remove them and build them manually. |
1742
|
|
|
$manual_queries = array( |
1743
|
14 |
|
'sku' => '', |
1744
|
|
|
'featured' => '', |
1745
|
|
|
'visibility' => '', |
1746
|
|
|
); |
1747
|
14 |
|
foreach ( $manual_queries as $key => $manual_query ) { |
1748
|
14 |
|
if ( isset( $query_vars[ $key ] ) ) { |
1749
|
14 |
|
$manual_queries[ $key ] = $query_vars[ $key ]; |
1750
|
14 |
|
unset( $query_vars[ $key ] ); |
1751
|
|
|
} |
1752
|
|
|
} |
1753
|
|
|
|
1754
|
14 |
|
$wp_query_args = parent::get_wp_query_args( $query_vars ); |
1755
|
|
|
|
1756
|
14 |
|
if ( ! isset( $wp_query_args['date_query'] ) ) { |
1757
|
14 |
|
$wp_query_args['date_query'] = array(); |
1758
|
|
|
} |
1759
|
14 |
|
if ( ! isset( $wp_query_args['meta_query'] ) ) { |
1760
|
|
|
$wp_query_args['meta_query'] = array(); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query |
|
|
|
|
1761
|
|
|
} |
1762
|
|
|
|
1763
|
|
|
// Handle product types. |
1764
|
14 |
|
if ( 'variation' === $query_vars['type'] ) { |
1765
|
1 |
|
$wp_query_args['post_type'] = 'product_variation'; |
1766
|
14 |
|
} elseif ( is_array( $query_vars['type'] ) && in_array( 'variation', $query_vars['type'], true ) ) { |
1767
|
1 |
|
$wp_query_args['post_type'] = array( 'product_variation', 'product' ); |
1768
|
1 |
|
$wp_query_args['tax_query'][] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query |
1769
|
1 |
|
'relation' => 'OR', |
1770
|
|
|
array( |
1771
|
1 |
|
'taxonomy' => 'product_type', |
1772
|
1 |
|
'field' => 'slug', |
1773
|
1 |
|
'terms' => $query_vars['type'], |
1774
|
|
|
), |
1775
|
|
|
array( |
1776
|
|
|
'taxonomy' => 'product_type', |
1777
|
|
|
'field' => 'id', |
1778
|
|
|
'operator' => 'NOT EXISTS', |
1779
|
|
|
), |
1780
|
|
|
); |
1781
|
|
View Code Duplication |
} else { |
1782
|
13 |
|
$wp_query_args['post_type'] = 'product'; |
1783
|
13 |
|
$wp_query_args['tax_query'][] = array( |
1784
|
13 |
|
'taxonomy' => 'product_type', |
1785
|
13 |
|
'field' => 'slug', |
1786
|
13 |
|
'terms' => $query_vars['type'], |
1787
|
|
|
); |
1788
|
|
|
} |
1789
|
|
|
|
1790
|
|
|
// Handle product categories. |
1791
|
14 |
View Code Duplication |
if ( ! empty( $query_vars['category'] ) ) { |
1792
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1793
|
1 |
|
'taxonomy' => 'product_cat', |
1794
|
1 |
|
'field' => 'slug', |
1795
|
1 |
|
'terms' => $query_vars['category'], |
1796
|
|
|
); |
1797
|
|
|
} |
1798
|
|
|
|
1799
|
|
|
// Handle product tags. |
1800
|
14 |
View Code Duplication |
if ( ! empty( $query_vars['tag'] ) ) { |
1801
|
1 |
|
unset( $wp_query_args['tag'] ); |
1802
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1803
|
1 |
|
'taxonomy' => 'product_tag', |
1804
|
1 |
|
'field' => 'slug', |
1805
|
1 |
|
'terms' => $query_vars['tag'], |
1806
|
|
|
); |
1807
|
|
|
} |
1808
|
|
|
|
1809
|
|
|
// Handle shipping classes. |
1810
|
14 |
View Code Duplication |
if ( ! empty( $query_vars['shipping_class'] ) ) { |
1811
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1812
|
1 |
|
'taxonomy' => 'product_shipping_class', |
1813
|
1 |
|
'field' => 'slug', |
1814
|
1 |
|
'terms' => $query_vars['shipping_class'], |
1815
|
|
|
); |
1816
|
|
|
} |
1817
|
|
|
|
1818
|
|
|
// Handle total_sales. |
1819
|
|
|
// This query doesn't get auto-generated since the meta key doesn't have the underscore prefix. |
1820
|
14 |
|
if ( isset( $query_vars['total_sales'] ) && '' !== $query_vars['total_sales'] ) { |
1821
|
1 |
|
$wp_query_args['meta_query'][] = array( |
1822
|
1 |
|
'key' => 'total_sales', |
1823
|
1 |
|
'value' => absint( $query_vars['total_sales'] ), |
1824
|
1 |
|
'compare' => '=', |
1825
|
|
|
); |
1826
|
|
|
} |
1827
|
|
|
|
1828
|
|
|
// Handle SKU. |
1829
|
14 |
|
if ( $manual_queries['sku'] ) { |
1830
|
|
|
// Check for existing values if wildcard is used. |
1831
|
2 |
|
if ( '*' === $manual_queries['sku'] ) { |
1832
|
1 |
|
$wp_query_args['meta_query'][] = array( |
1833
|
|
|
array( |
1834
|
1 |
|
'key' => '_sku', |
1835
|
|
|
'compare' => 'EXISTS', |
1836
|
|
|
), |
1837
|
|
|
array( |
1838
|
|
|
'key' => '_sku', |
1839
|
|
|
'value' => '', |
1840
|
|
|
'compare' => '!=', |
1841
|
|
|
), |
1842
|
|
|
); |
1843
|
|
|
} else { |
1844
|
2 |
|
$wp_query_args['meta_query'][] = array( |
1845
|
2 |
|
'key' => '_sku', |
1846
|
2 |
|
'value' => $manual_queries['sku'], |
1847
|
2 |
|
'compare' => 'LIKE', |
1848
|
|
|
); |
1849
|
|
|
} |
1850
|
|
|
} |
1851
|
|
|
|
1852
|
|
|
// Handle featured. |
1853
|
14 |
|
if ( '' !== $manual_queries['featured'] ) { |
1854
|
1 |
|
$product_visibility_term_ids = wc_get_product_visibility_term_ids(); |
1855
|
1 |
|
if ( $manual_queries['featured'] ) { |
1856
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1857
|
1 |
|
'taxonomy' => 'product_visibility', |
1858
|
1 |
|
'field' => 'term_taxonomy_id', |
1859
|
1 |
|
'terms' => array( $product_visibility_term_ids['featured'] ), |
1860
|
|
|
); |
1861
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1862
|
1 |
|
'taxonomy' => 'product_visibility', |
1863
|
1 |
|
'field' => 'term_taxonomy_id', |
1864
|
1 |
|
'terms' => array( $product_visibility_term_ids['exclude-from-catalog'] ), |
1865
|
1 |
|
'operator' => 'NOT IN', |
1866
|
|
|
); |
1867
|
|
|
} else { |
1868
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1869
|
1 |
|
'taxonomy' => 'product_visibility', |
1870
|
1 |
|
'field' => 'term_taxonomy_id', |
1871
|
1 |
|
'terms' => array( $product_visibility_term_ids['featured'] ), |
1872
|
1 |
|
'operator' => 'NOT IN', |
1873
|
|
|
); |
1874
|
|
|
} |
1875
|
|
|
} |
1876
|
|
|
|
1877
|
|
|
// Handle visibility. |
1878
|
14 |
|
if ( $manual_queries['visibility'] ) { |
1879
|
1 |
|
switch ( $manual_queries['visibility'] ) { |
1880
|
|
View Code Duplication |
case 'search': |
1881
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1882
|
|
|
'taxonomy' => 'product_visibility', |
1883
|
|
|
'field' => 'slug', |
1884
|
|
|
'terms' => array( 'exclude-from-search' ), |
1885
|
|
|
'operator' => 'NOT IN', |
1886
|
|
|
); |
1887
|
1 |
|
break; |
1888
|
|
View Code Duplication |
case 'catalog': |
1889
|
|
|
$wp_query_args['tax_query'][] = array( |
1890
|
|
|
'taxonomy' => 'product_visibility', |
1891
|
|
|
'field' => 'slug', |
1892
|
|
|
'terms' => array( 'exclude-from-catalog' ), |
1893
|
|
|
'operator' => 'NOT IN', |
1894
|
|
|
); |
1895
|
|
|
break; |
1896
|
|
View Code Duplication |
case 'visible': |
1897
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1898
|
|
|
'taxonomy' => 'product_visibility', |
1899
|
|
|
'field' => 'slug', |
1900
|
|
|
'terms' => array( 'exclude-from-catalog', 'exclude-from-search' ), |
1901
|
|
|
'operator' => 'NOT IN', |
1902
|
|
|
); |
1903
|
1 |
|
break; |
1904
|
|
View Code Duplication |
case 'hidden': |
1905
|
1 |
|
$wp_query_args['tax_query'][] = array( |
1906
|
|
|
'taxonomy' => 'product_visibility', |
1907
|
|
|
'field' => 'slug', |
1908
|
|
|
'terms' => array( 'exclude-from-catalog', 'exclude-from-search' ), |
1909
|
|
|
'operator' => 'AND', |
1910
|
|
|
); |
1911
|
1 |
|
break; |
1912
|
|
|
} |
1913
|
|
|
} |
1914
|
|
|
|
1915
|
|
|
// Handle date queries. |
1916
|
|
|
$date_queries = array( |
1917
|
14 |
|
'date_created' => 'post_date', |
1918
|
|
|
'date_modified' => 'post_modified', |
1919
|
|
|
'date_on_sale_from' => '_sale_price_dates_from', |
1920
|
|
|
'date_on_sale_to' => '_sale_price_dates_to', |
1921
|
|
|
); |
1922
|
14 |
|
foreach ( $date_queries as $query_var_key => $db_key ) { |
1923
|
14 |
|
if ( isset( $query_vars[ $query_var_key ] ) && '' !== $query_vars[ $query_var_key ] ) { |
1924
|
|
|
|
1925
|
|
|
// Remove any existing meta queries for the same keys to prevent conflicts. |
1926
|
1 |
|
$existing_queries = wp_list_pluck( $wp_query_args['meta_query'], 'key', true ); |
1927
|
1 |
|
foreach ( $existing_queries as $query_index => $query_contents ) { |
1928
|
|
|
unset( $wp_query_args['meta_query'][ $query_index ] ); |
1929
|
|
|
} |
1930
|
|
|
|
1931
|
1 |
|
$wp_query_args = $this->parse_date_for_wp_query( $query_vars[ $query_var_key ], $db_key, $wp_query_args ); |
1932
|
|
|
} |
1933
|
|
|
} |
1934
|
|
|
|
1935
|
|
|
// Handle paginate. |
1936
|
14 |
View Code Duplication |
if ( ! isset( $query_vars['paginate'] ) || ! $query_vars['paginate'] ) { |
1937
|
13 |
|
$wp_query_args['no_found_rows'] = true; |
1938
|
|
|
} |
1939
|
|
|
|
1940
|
|
|
// Handle reviews_allowed. |
1941
|
14 |
|
if ( isset( $query_vars['reviews_allowed'] ) && is_bool( $query_vars['reviews_allowed'] ) ) { |
1942
|
1 |
|
add_filter( 'posts_where', array( $this, 'reviews_allowed_query_where' ), 10, 2 ); |
1943
|
|
|
} |
1944
|
|
|
|
1945
|
14 |
|
return apply_filters( 'woocommerce_product_data_store_cpt_get_products_query', $wp_query_args, $query_vars, $this ); |
1946
|
|
|
} |
1947
|
|
|
|
1948
|
|
|
/** |
1949
|
|
|
* Query for Products matching specific criteria. |
1950
|
|
|
* |
1951
|
|
|
* @since 3.2.0 |
1952
|
|
|
* |
1953
|
|
|
* @param array $query_vars Query vars from a WC_Product_Query. |
1954
|
|
|
* |
1955
|
|
|
* @return array|object |
1956
|
|
|
*/ |
1957
|
14 |
|
public function query( $query_vars ) { |
1958
|
14 |
|
$args = $this->get_wp_query_args( $query_vars ); |
1959
|
|
|
|
1960
|
14 |
View Code Duplication |
if ( ! empty( $args['errors'] ) ) { |
1961
|
|
|
$query = (object) array( |
1962
|
|
|
'posts' => array(), |
1963
|
|
|
'found_posts' => 0, |
1964
|
|
|
'max_num_pages' => 0, |
1965
|
|
|
); |
1966
|
|
|
} else { |
1967
|
14 |
|
$query = new WP_Query( $args ); |
1968
|
|
|
} |
1969
|
|
|
|
1970
|
14 |
|
if ( isset( $query_vars['return'] ) && 'objects' === $query_vars['return'] && ! empty( $query->posts ) ) { |
1971
|
|
|
// Prime caches before grabbing objects. |
1972
|
4 |
|
update_post_caches( $query->posts, array( 'product', 'product_variation' ) ); |
1973
|
|
|
} |
1974
|
|
|
|
1975
|
14 |
|
$products = ( isset( $query_vars['return'] ) && 'ids' === $query_vars['return'] ) ? $query->posts : array_filter( array_map( 'wc_get_product', $query->posts ) ); |
1976
|
|
|
|
1977
|
14 |
View Code Duplication |
if ( isset( $query_vars['paginate'] ) && $query_vars['paginate'] ) { |
1978
|
|
|
return (object) array( |
1979
|
2 |
|
'products' => $products, |
1980
|
2 |
|
'total' => $query->found_posts, |
1981
|
2 |
|
'max_num_pages' => $query->max_num_pages, |
1982
|
|
|
); |
1983
|
|
|
} |
1984
|
|
|
|
1985
|
13 |
|
return $products; |
1986
|
|
|
} |
1987
|
|
|
|
1988
|
|
|
/** |
1989
|
|
|
* Get data to save to a lookup table. |
1990
|
|
|
* |
1991
|
|
|
* @since 3.6.0 |
1992
|
|
|
* @param int $id ID of object to update. |
1993
|
|
|
* @param string $table Lookup table name. |
1994
|
|
|
* @return array |
1995
|
|
|
*/ |
1996
|
399 |
|
protected function get_data_for_lookup_table( $id, $table ) { |
1997
|
399 |
|
if ( 'wc_product_meta_lookup' === $table ) { |
1998
|
399 |
|
$price_meta = (array) get_post_meta( $id, '_price', false ); |
1999
|
399 |
|
$manage_stock = get_post_meta( $id, '_manage_stock', true ); |
2000
|
399 |
|
$stock = 'yes' === $manage_stock ? wc_stock_amount( get_post_meta( $id, '_stock', true ) ) : null; |
2001
|
399 |
|
$price = wc_format_decimal( get_post_meta( $id, '_price', true ) ); |
2002
|
399 |
|
$sale_price = wc_format_decimal( get_post_meta( $id, '_sale_price', true ) ); |
2003
|
|
|
return array( |
2004
|
399 |
|
'product_id' => absint( $id ), |
2005
|
399 |
|
'sku' => get_post_meta( $id, '_sku', true ), |
2006
|
399 |
|
'virtual' => 'yes' === get_post_meta( $id, '_virtual', true ) ? 1 : 0, |
2007
|
399 |
|
'downloadable' => 'yes' === get_post_meta( $id, '_downloadable', true ) ? 1 : 0, |
2008
|
399 |
|
'min_price' => reset( $price_meta ), |
2009
|
399 |
|
'max_price' => end( $price_meta ), |
2010
|
399 |
|
'onsale' => $sale_price && $price === $sale_price ? 1 : 0, |
2011
|
399 |
|
'stock_quantity' => $stock, |
2012
|
399 |
|
'stock_status' => get_post_meta( $id, '_stock_status', true ), |
2013
|
399 |
|
'rating_count' => array_sum( (array) get_post_meta( $id, '_wc_rating_count', true ) ), |
2014
|
399 |
|
'average_rating' => get_post_meta( $id, '_wc_average_rating', true ), |
2015
|
399 |
|
'total_sales' => get_post_meta( $id, 'total_sales', true ), |
2016
|
|
|
); |
2017
|
|
|
} |
2018
|
|
|
return array(); |
2019
|
|
|
} |
2020
|
|
|
|
2021
|
|
|
/** |
2022
|
|
|
* Get primary key name for lookup table. |
2023
|
|
|
* |
2024
|
|
|
* @since 3.6.0 |
2025
|
|
|
* @param string $table Lookup table name. |
2026
|
|
|
* @return string |
2027
|
|
|
*/ |
2028
|
10 |
|
protected function get_primary_key_for_lookup_table( $table ) { |
2029
|
10 |
|
if ( 'wc_product_meta_lookup' === $table ) { |
2030
|
10 |
|
return 'product_id'; |
2031
|
|
|
} |
2032
|
|
|
return ''; |
2033
|
|
|
} |
2034
|
|
|
} |
2035
|
|
|
|
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.