2016-11-11 14:31:15 +00:00
< ? php
2018-01-03 17:57:33 +00:00
/**
* WC_Product_Data_Store_CPT class file .
*
2020-08-05 16:36:24 +00:00
* @ package WooCommerce\Classes
2018-01-03 17:57:33 +00:00
*/
2020-02-01 06:18:47 +00:00
use Automattic\Jetpack\Constants ;
2020-10-01 08:57:12 +00:00
use Automattic\WooCommerce\Utilities\NumberUtil ;
2020-02-01 06:18:47 +00:00
2016-11-11 14:31:15 +00:00
if ( ! defined ( 'ABSPATH' ) ) {
exit ;
}
/**
* WC Product Data Store : Stored in CPT .
*
2017-03-15 16:36:53 +00:00
* @ version 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
2016-11-22 13:54:51 +00:00
class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Data_Store_Interface , WC_Product_Data_Store_Interface {
2016-11-21 23:48:49 +00:00
/**
* Data stored in meta keys , but not considered " meta " .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-21 23:48:49 +00:00
* @ var array
*/
protected $internal_meta_keys = array (
'_visibility' ,
'_sku' ,
'_price' ,
'_regular_price' ,
'_sale_price' ,
'_sale_price_dates_from' ,
'_sale_price_dates_to' ,
'total_sales' ,
'_tax_status' ,
'_tax_class' ,
'_manage_stock' ,
'_stock' ,
'_stock_status' ,
'_backorders' ,
2018-05-27 04:40:58 +00:00
'_low_stock_amount' ,
2016-11-21 23:48:49 +00:00
'_sold_individually' ,
'_weight' ,
'_length' ,
'_width' ,
'_height' ,
'_upsell_ids' ,
'_crosssell_ids' ,
'_purchase_note' ,
'_default_attributes' ,
'_product_attributes' ,
'_virtual' ,
'_downloadable' ,
2017-07-25 17:01:24 +00:00
'_download_limit' ,
'_download_expiry' ,
2016-11-21 23:48:49 +00:00
'_featured' ,
'_downloadable_files' ,
'_wc_rating_count' ,
'_wc_average_rating' ,
'_wc_review_count' ,
'_variation_description' ,
2017-03-17 20:30:38 +00:00
'_thumbnail_id' ,
'_file_paths' ,
'_product_image_gallery' ,
'_product_version' ,
2017-03-17 20:37:38 +00:00
'_wp_old_slug' ,
'_edit_last' ,
'_edit_lock' ,
2016-11-21 23:48:49 +00:00
);
2016-11-11 14:31:15 +00:00
2019-02-19 13:23:24 +00:00
/**
* Meta data which should exist in the DB , even if empty .
*
* @ since 3.6 . 0
*
* @ var array
*/
protected $must_exist_meta_keys = array (
'_tax_class' ,
);
2016-11-11 14:31:15 +00:00
/**
* If we have already saved our extra data , don ' t do automatic / default handling .
2018-01-03 17:57:33 +00:00
*
* @ var bool
2016-11-11 14:31:15 +00:00
*/
protected $extra_data_saved = false ;
2017-02-14 16:14:37 +00:00
/**
* Stores updated props .
2018-01-03 16:44:37 +00:00
*
2017-02-14 16:14:37 +00:00
* @ var array
*/
protected $updated_props = array ();
2016-11-11 14:31:15 +00:00
/*
|--------------------------------------------------------------------------
| CRUD Methods
|--------------------------------------------------------------------------
*/
/**
* Method to create a new product in the database .
2016-12-13 14:22:59 +00:00
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2016-11-11 14:31:15 +00:00
*/
public function create ( & $product ) {
2017-08-22 10:43:48 +00:00
if ( ! $product -> get_date_created ( 'edit' ) ) {
2019-12-20 17:23:05 +00:00
$product -> set_date_created ( time () );
2016-12-13 14:22:59 +00:00
}
2016-11-11 14:31:15 +00:00
2018-01-03 16:44:37 +00:00
$id = wp_insert_post (
apply_filters (
2018-11-23 14:57:51 +00:00
'woocommerce_new_product_data' ,
array (
2018-01-03 16:44:37 +00:00
'post_type' => 'product' ,
'post_status' => $product -> get_status () ? $product -> get_status () : 'publish' ,
'post_author' => get_current_user_id (),
'post_title' => $product -> get_name () ? $product -> get_name () : __ ( 'Product' , 'woocommerce' ),
'post_content' => $product -> get_description (),
'post_excerpt' => $product -> get_short_description (),
'post_parent' => $product -> get_parent_id (),
'comment_status' => $product -> get_reviews_allowed () ? 'open' : 'closed' ,
'ping_status' => 'closed' ,
'menu_order' => $product -> get_menu_order (),
2019-01-29 16:04:00 +00:00
'post_password' => $product -> get_post_password ( 'edit' ),
2018-01-03 16:44:37 +00:00
'post_date' => gmdate ( 'Y-m-d H:i:s' , $product -> get_date_created ( 'edit' ) -> getOffsetTimestamp () ),
'post_date_gmt' => gmdate ( 'Y-m-d H:i:s' , $product -> get_date_created ( 'edit' ) -> getTimestamp () ),
'post_name' => $product -> get_slug ( 'edit' ),
)
2018-11-23 14:57:51 +00:00
),
true
2018-01-03 16:44:37 +00:00
);
2016-11-11 14:31:15 +00:00
if ( $id && ! is_wp_error ( $id ) ) {
$product -> set_id ( $id );
2017-02-14 16:14:37 +00:00
2017-02-15 14:40:57 +00:00
$this -> update_post_meta ( $product , true );
$this -> update_terms ( $product , true );
$this -> update_visibility ( $product , true );
$this -> update_attributes ( $product , true );
2016-11-11 14:31:15 +00:00
$this -> update_version_and_type ( $product );
2017-02-15 14:40:57 +00:00
$this -> handle_updated_props ( $product );
2019-01-18 23:12:04 +00:00
$this -> clear_caches ( $product );
2017-02-14 16:14:37 +00:00
2016-12-19 16:40:53 +00:00
$product -> save_meta_data ();
$product -> apply_changes ();
2017-02-14 16:14:37 +00:00
2019-04-17 11:50:46 +00:00
do_action ( 'woocommerce_new_product' , $id , $product );
2016-11-11 14:31:15 +00:00
}
}
/**
* Method to read a product from the database .
2018-01-03 16:44:37 +00:00
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
* @ throws Exception If invalid product .
2016-11-11 14:31:15 +00:00
*/
public function read ( & $product ) {
$product -> set_defaults ();
2018-01-03 17:57:33 +00:00
$post_object = get_post ( $product -> get_id () );
2016-11-11 14:31:15 +00:00
2018-01-03 17:57:33 +00:00
if ( ! $product -> get_id () || ! $post_object || 'product' !== $post_object -> post_type ) {
2016-11-11 14:31:15 +00:00
throw new Exception ( __ ( 'Invalid product.' , 'woocommerce' ) );
}
2018-01-03 16:44:37 +00:00
$product -> set_props (
array (
'name' => $post_object -> post_title ,
'slug' => $post_object -> post_name ,
2020-10-22 07:21:15 +00:00
'date_created' => $this -> string_to_timestamp ( $post_object -> post_date_gmt ),
'date_modified' => $this -> string_to_timestamp ( $post_object -> post_modified_gmt ),
2018-01-03 16:44:37 +00:00
'status' => $post_object -> post_status ,
'description' => $post_object -> post_content ,
'short_description' => $post_object -> post_excerpt ,
'parent_id' => $post_object -> post_parent ,
'menu_order' => $post_object -> menu_order ,
2019-01-29 16:04:00 +00:00
'post_password' => $post_object -> post_password ,
2018-01-03 16:44:37 +00:00
'reviews_allowed' => 'open' === $post_object -> comment_status ,
)
);
2016-11-11 14:31:15 +00:00
$this -> read_attributes ( $product );
2016-11-11 15:31:00 +00:00
$this -> read_downloads ( $product );
2016-12-08 10:56:45 +00:00
$this -> read_visibility ( $product );
2016-11-11 14:31:15 +00:00
$this -> read_product_data ( $product );
2017-01-24 20:18:35 +00:00
$this -> read_extra_data ( $product );
2016-11-11 14:31:15 +00:00
$product -> set_object_read ( true );
2019-07-04 17:17:34 +00:00
do_action ( 'woocommerce_product_read' , $product -> get_id () );
2016-11-11 14:31:15 +00:00
}
/**
* Method to update a product in the database .
2017-02-11 15:26:13 +00:00
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2016-11-11 14:31:15 +00:00
*/
public function update ( & $product ) {
2017-04-05 21:39:41 +00:00
$product -> save_meta_data ();
2017-02-11 15:26:13 +00:00
$changes = $product -> get_changes ();
// Only update the post when the post data changes.
2017-03-29 12:41:23 +00:00
if ( array_intersect ( array ( 'description' , 'short_description' , 'name' , 'parent_id' , 'reviews_allowed' , 'status' , 'menu_order' , 'date_created' , 'date_modified' , 'slug' ), array_keys ( $changes ) ) ) {
2017-03-31 11:38:18 +00:00
$post_data = array (
'post_content' => $product -> get_description ( 'edit' ),
'post_excerpt' => $product -> get_short_description ( 'edit' ),
'post_title' => $product -> get_name ( 'edit' ),
'post_parent' => $product -> get_parent_id ( 'edit' ),
'comment_status' => $product -> get_reviews_allowed ( 'edit' ) ? 'open' : 'closed' ,
'post_status' => $product -> get_status ( 'edit' ) ? $product -> get_status ( 'edit' ) : 'publish' ,
'menu_order' => $product -> get_menu_order ( 'edit' ),
2019-01-29 16:04:00 +00:00
'post_password' => $product -> get_post_password ( 'edit' ),
2017-03-31 11:38:18 +00:00
'post_name' => $product -> get_slug ( 'edit' ),
2017-05-25 11:09:59 +00:00
'post_type' => 'product' ,
2017-03-31 11:38:18 +00:00
);
if ( $product -> get_date_created ( 'edit' ) ) {
$post_data [ 'post_date' ] = gmdate ( 'Y-m-d H:i:s' , $product -> get_date_created ( 'edit' ) -> getOffsetTimestamp () );
$post_data [ 'post_date_gmt' ] = gmdate ( 'Y-m-d H:i:s' , $product -> get_date_created ( 'edit' ) -> getTimestamp () );
}
if ( isset ( $changes [ 'date_modified' ] ) && $product -> get_date_modified ( 'edit' ) ) {
$post_data [ 'post_modified' ] = gmdate ( 'Y-m-d H:i:s' , $product -> get_date_modified ( 'edit' ) -> getOffsetTimestamp () );
$post_data [ 'post_modified_gmt' ] = gmdate ( 'Y-m-d H:i:s' , $product -> get_date_modified ( 'edit' ) -> getTimestamp () );
} else {
$post_data [ 'post_modified' ] = current_time ( 'mysql' );
$post_data [ 'post_modified_gmt' ] = current_time ( 'mysql' , 1 );
}
2017-04-15 20:04:41 +00:00
/**
2017-04-15 20:18:24 +00:00
* When updating this object , to prevent infinite loops , use $wpdb
* to update data , since wp_update_post spawns more calls to the
* save_post action .
2017-04-15 20:04:41 +00:00
*
2017-04-15 20:18:24 +00:00
* This ensures hooks are fired by either WP itself ( admin screen save ),
* or an update purely from CRUD .
2017-04-15 20:04:41 +00:00
*/
if ( doing_action ( 'save_post' ) ) {
2017-04-15 20:18:24 +00:00
$GLOBALS [ 'wpdb' ] -> update ( $GLOBALS [ 'wpdb' ] -> posts , $post_data , array ( 'ID' => $product -> get_id () ) );
2017-04-15 20:04:41 +00:00
clean_post_cache ( $product -> get_id () );
} else {
wp_update_post ( array_merge ( array ( 'ID' => $product -> get_id () ), $post_data ) );
}
2017-04-05 21:39:41 +00:00
$product -> read_meta_data ( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook.
2018-04-03 11:51:58 +00:00
} else { // Only update post modified time to record this save event.
$GLOBALS [ 'wpdb' ] -> update (
$GLOBALS [ 'wpdb' ] -> posts ,
array (
'post_modified' => current_time ( 'mysql' ),
'post_modified_gmt' => current_time ( 'mysql' , 1 ),
),
array (
'ID' => $product -> get_id (),
)
);
clean_post_cache ( $product -> get_id () );
2017-02-11 15:26:13 +00:00
}
2016-11-11 14:31:15 +00:00
$this -> update_post_meta ( $product );
$this -> update_terms ( $product );
2016-12-08 10:56:45 +00:00
$this -> update_visibility ( $product );
2016-11-11 14:31:15 +00:00
$this -> update_attributes ( $product );
$this -> update_version_and_type ( $product );
2017-02-15 14:40:57 +00:00
$this -> handle_updated_props ( $product );
2019-01-18 23:12:04 +00:00
$this -> clear_caches ( $product );
2017-02-14 16:14:37 +00:00
2016-12-19 16:40:53 +00:00
$product -> apply_changes ();
2017-02-14 16:14:37 +00:00
2019-04-17 11:50:46 +00:00
do_action ( 'woocommerce_update_product' , $product -> get_id (), $product );
2016-11-11 14:31:15 +00:00
}
/**
* Method to delete a product from the database .
2018-01-03 16:44:37 +00:00
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2018-01-03 16:44:37 +00:00
* @ param array $args Array of args to pass to the delete method .
2016-11-11 14:31:15 +00:00
*/
2016-11-16 12:17:00 +00:00
public function delete ( & $product , $args = array () ) {
$id = $product -> get_id ();
2016-11-11 14:31:15 +00:00
$post_type = $product -> is_type ( 'variation' ) ? 'product_variation' : 'product' ;
2018-01-03 16:44:37 +00:00
$args = wp_parse_args (
2018-11-23 14:57:51 +00:00
$args ,
array (
2018-01-03 16:44:37 +00:00
'force_delete' => false ,
)
);
2016-11-16 12:17:00 +00:00
2017-05-30 13:44:28 +00:00
if ( ! $id ) {
return ;
}
2016-11-16 12:17:00 +00:00
if ( $args [ 'force_delete' ] ) {
2018-03-29 12:52:19 +00:00
do_action ( 'woocommerce_before_delete_' . $post_type , $id );
2017-05-30 13:44:28 +00:00
wp_delete_post ( $id );
2016-11-11 14:31:15 +00:00
$product -> set_id ( 0 );
2016-12-29 22:22:12 +00:00
do_action ( 'woocommerce_delete_' . $post_type , $id );
2016-11-11 14:31:15 +00:00
} else {
2017-05-30 13:44:28 +00:00
wp_trash_post ( $id );
2016-11-11 14:31:15 +00:00
$product -> set_status ( 'trash' );
2016-12-29 22:22:12 +00:00
do_action ( 'woocommerce_trash_' . $post_type , $id );
2016-11-11 14:31:15 +00:00
}
}
/*
|--------------------------------------------------------------------------
| Additional Methods
|--------------------------------------------------------------------------
*/
/**
* Read product data . Can be overridden by child classes to load other props .
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
protected function read_product_data ( & $product ) {
2019-01-30 14:59:58 +00:00
$id = $product -> get_id ();
$post_meta_values = get_post_meta ( $id );
$meta_key_to_props = array (
'_sku' => 'sku' ,
'_regular_price' => 'regular_price' ,
'_sale_price' => 'sale_price' ,
2019-01-30 15:12:32 +00:00
'_price' => 'price' ,
2019-01-30 14:59:58 +00:00
'_sale_price_dates_from' => 'date_on_sale_from' ,
'_sale_price_dates_to' => 'date_on_sale_to' ,
'total_sales' => 'total_sales' ,
'_tax_status' => 'tax_status' ,
'_tax_class' => 'tax_class' ,
'_manage_stock' => 'manage_stock' ,
'_backorders' => 'backorders' ,
'_low_stock_amount' => 'low_stock_amount' ,
'_sold_individually' => 'sold_individually' ,
'_weight' => 'weight' ,
'_length' => 'length' ,
'_width' => 'width' ,
'_height' => 'height' ,
'_upsell_ids' => 'upsell_ids' ,
'_crosssell_ids' => 'cross_sell_ids' ,
'_purchase_note' => 'purchase_note' ,
'_default_attributes' => 'default_attributes' ,
'_virtual' => 'virtual' ,
'_downloadable' => 'downloadable' ,
'_download_limit' => 'download_limit' ,
'_download_expiry' => 'download_expiry' ,
'_thumbnail_id' => 'image_id' ,
'_stock' => 'stock_quantity' ,
'_stock_status' => 'stock_status' ,
'_wc_average_rating' => 'average_rating' ,
'_wc_rating_count' => 'rating_counts' ,
'_wc_review_count' => 'review_count' ,
'_product_image_gallery' => 'gallery_image_ids' ,
);
$set_props = array ();
foreach ( $meta_key_to_props as $meta_key => $prop ) {
2019-02-12 11:46:55 +00:00
$meta_value = isset ( $post_meta_values [ $meta_key ][ 0 ] ) ? $post_meta_values [ $meta_key ][ 0 ] : null ;
2019-01-30 14:59:58 +00:00
$set_props [ $prop ] = maybe_unserialize ( $meta_value ); // get_post_meta only unserializes single values.
}
$set_props [ 'category_ids' ] = $this -> get_term_ids ( $product , 'product_cat' );
$set_props [ 'tag_ids' ] = $this -> get_term_ids ( $product , 'product_tag' );
$set_props [ 'shipping_class_id' ] = current ( $this -> get_term_ids ( $product , 'product_shipping_class' ) );
$set_props [ 'gallery_image_ids' ] = array_filter ( explode ( ',' , $set_props [ 'gallery_image_ids' ] ) );
2016-11-11 15:31:00 +00:00
2019-01-30 14:59:58 +00:00
$product -> set_props ( $set_props );
2017-01-24 20:18:35 +00:00
}
2016-11-11 14:31:15 +00:00
2019-04-26 16:24:11 +00:00
/**
* Re - reads stock from the DB ignoring changes .
*
* @ param WC_Product $product Product object .
* @ param int | float $new_stock New stock level if already read .
*/
public function read_stock_quantity ( & $product , $new_stock = null ) {
$object_read = $product -> get_object_read ();
2019-05-02 17:41:15 +00:00
$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).
2019-04-26 16:24:11 +00:00
$product -> set_stock_quantity ( is_null ( $new_stock ) ? get_post_meta ( $product -> get_id (), '_stock' , true ) : $new_stock );
$product -> set_object_read ( $object_read );
}
2017-01-24 20:18:35 +00:00
/**
* Read extra data associated with the product , like button text or product URL for external products .
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-01-24 20:18:35 +00:00
*/
protected function read_extra_data ( & $product ) {
2016-11-11 14:31:15 +00:00
foreach ( $product -> get_extra_data_keys () as $key ) {
$function = 'set_' . $key ;
if ( is_callable ( array ( $product , $function ) ) ) {
$product -> { $function }( get_post_meta ( $product -> get_id (), '_' . $key , true ) );
}
}
}
2016-12-08 10:56:45 +00:00
/**
* Convert visibility terms to props .
* Catalog visibility valid values are 'visible' , 'catalog' , 'search' , and 'hidden' .
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-12-08 10:56:45 +00:00
*/
protected function read_visibility ( & $product ) {
$terms = get_the_terms ( $product -> get_id (), 'product_visibility' );
$term_names = is_array ( $terms ) ? wp_list_pluck ( $terms , 'name' ) : array ();
2018-01-03 17:57:33 +00:00
$featured = in_array ( 'featured' , $term_names , true );
$exclude_search = in_array ( 'exclude-from-search' , $term_names , true );
$exclude_catalog = in_array ( 'exclude-from-catalog' , $term_names , true );
2016-12-08 10:56:45 +00:00
if ( $exclude_search && $exclude_catalog ) {
$catalog_visibility = 'hidden' ;
} elseif ( $exclude_search ) {
$catalog_visibility = 'catalog' ;
} elseif ( $exclude_catalog ) {
$catalog_visibility = 'search' ;
} else {
$catalog_visibility = 'visible' ;
}
2018-01-03 16:44:37 +00:00
$product -> set_props (
array (
'featured' => $featured ,
'catalog_visibility' => $catalog_visibility ,
)
);
2016-12-08 10:56:45 +00:00
}
2016-11-11 14:31:15 +00:00
/**
* Read attributes from post meta .
*
2017-11-18 14:53:18 +00:00
* @ param WC_Product $product Product object .
2016-11-11 14:31:15 +00:00
*/
protected function read_attributes ( & $product ) {
2017-11-18 14:53:18 +00:00
$meta_attributes = get_post_meta ( $product -> get_id (), '_product_attributes' , true );
2016-11-11 14:31:15 +00:00
2017-11-18 14:53:18 +00:00
if ( ! empty ( $meta_attributes ) && is_array ( $meta_attributes ) ) {
2016-11-11 14:31:15 +00:00
$attributes = array ();
2017-11-18 14:53:18 +00:00
foreach ( $meta_attributes as $meta_attribute_key => $meta_attribute_value ) {
2018-01-03 16:44:37 +00:00
$meta_value = array_merge (
array (
'name' => '' ,
'value' => '' ,
'position' => 0 ,
'is_visible' => 0 ,
'is_variation' => 0 ,
'is_taxonomy' => 0 ,
2018-11-23 14:57:51 +00:00
),
( array ) $meta_attribute_value
2018-01-03 16:44:37 +00:00
);
2017-04-07 09:59:59 +00:00
2017-05-03 19:30:50 +00:00
// Check if is a taxonomy attribute.
2016-11-11 14:31:15 +00:00
if ( ! empty ( $meta_value [ 'is_taxonomy' ] ) ) {
if ( ! taxonomy_exists ( $meta_value [ 'name' ] ) ) {
continue ;
}
2017-05-03 19:30:50 +00:00
$id = wc_attribute_taxonomy_id_by_name ( $meta_value [ 'name' ] );
2016-11-17 19:02:05 +00:00
$options = wc_get_object_terms ( $product -> get_id (), $meta_value [ 'name' ], 'term_id' );
2016-11-11 14:31:15 +00:00
} else {
2017-11-18 14:53:18 +00:00
$id = 0 ;
2016-11-11 14:31:15 +00:00
$options = wc_get_text_attributes ( $meta_value [ 'value' ] );
}
2017-04-07 09:59:59 +00:00
2016-11-11 14:31:15 +00:00
$attribute = new WC_Product_Attribute ();
2017-05-03 19:30:50 +00:00
$attribute -> set_id ( $id );
2016-11-11 14:31:15 +00:00
$attribute -> set_name ( $meta_value [ 'name' ] );
$attribute -> set_options ( $options );
$attribute -> set_position ( $meta_value [ 'position' ] );
$attribute -> set_visible ( $meta_value [ 'is_visible' ] );
$attribute -> set_variation ( $meta_value [ 'is_variation' ] );
$attributes [] = $attribute ;
}
$product -> set_attributes ( $attributes );
}
}
2016-11-11 15:31:00 +00:00
/**
* Read downloads from post meta .
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 15:31:00 +00:00
*/
protected function read_downloads ( & $product ) {
2017-03-31 11:10:20 +00:00
$meta_values = array_filter ( ( array ) get_post_meta ( $product -> get_id (), '_downloadable_files' , true ) );
2016-11-11 15:31:00 +00:00
if ( $meta_values ) {
$downloads = array ();
foreach ( $meta_values as $key => $value ) {
2017-04-12 13:11:04 +00:00
if ( ! isset ( $value [ 'name' ], $value [ 'file' ] ) ) {
continue ;
}
2018-01-03 16:44:37 +00:00
$download = new WC_Product_Download ();
2016-11-11 15:31:00 +00:00
$download -> set_id ( $key );
$download -> set_name ( $value [ 'name' ] ? $value [ 'name' ] : wc_get_filename_from_url ( $value [ 'file' ] ) );
$download -> set_file ( apply_filters ( 'woocommerce_file_download_path' , $value [ 'file' ], $product , $key ) );
$downloads [] = $download ;
}
$product -> set_downloads ( $downloads );
}
}
2016-11-11 14:31:15 +00:00
/**
* Helper method that updates all the post meta for a product based on it ' s settings in the WC_Product class .
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
* @ param bool $force Force update . Used during create .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
2017-02-15 14:40:57 +00:00
protected function update_post_meta ( & $product , $force = false ) {
2016-11-11 14:31:15 +00:00
$meta_key_to_props = array (
'_sku' => 'sku' ,
'_regular_price' => 'regular_price' ,
'_sale_price' => 'sale_price' ,
'_sale_price_dates_from' => 'date_on_sale_from' ,
'_sale_price_dates_to' => 'date_on_sale_to' ,
'total_sales' => 'total_sales' ,
'_tax_status' => 'tax_status' ,
'_tax_class' => 'tax_class' ,
'_manage_stock' => 'manage_stock' ,
'_backorders' => 'backorders' ,
2018-05-27 04:40:58 +00:00
'_low_stock_amount' => 'low_stock_amount' ,
2016-11-11 14:31:15 +00:00
'_sold_individually' => 'sold_individually' ,
'_weight' => 'weight' ,
'_length' => 'length' ,
'_width' => 'width' ,
'_height' => 'height' ,
'_upsell_ids' => 'upsell_ids' ,
'_crosssell_ids' => 'cross_sell_ids' ,
'_purchase_note' => 'purchase_note' ,
'_default_attributes' => 'default_attributes' ,
'_virtual' => 'virtual' ,
'_downloadable' => 'downloadable' ,
'_product_image_gallery' => 'gallery_image_ids' ,
'_download_limit' => 'download_limit' ,
'_download_expiry' => 'download_expiry' ,
'_thumbnail_id' => 'image_id' ,
'_stock' => 'stock_quantity' ,
'_stock_status' => 'stock_status' ,
2016-11-11 15:31:00 +00:00
'_wc_average_rating' => 'average_rating' ,
'_wc_rating_count' => 'rating_counts' ,
'_wc_review_count' => 'review_count' ,
2016-11-11 14:31:15 +00:00
);
2017-01-23 20:20:29 +00:00
// Make sure to take extra data (like product url or text for external products) into account.
$extra_data_keys = $product -> get_extra_data_keys ();
2017-02-15 14:40:57 +00:00
2017-01-23 20:20:29 +00:00
foreach ( $extra_data_keys as $key ) {
$meta_key_to_props [ '_' . $key ] = $key ;
}
2017-02-15 14:40:57 +00:00
$props_to_update = $force ? $meta_key_to_props : $this -> get_props_to_update ( $product , $meta_key_to_props );
2017-02-11 15:26:13 +00:00
2017-01-23 20:20:29 +00:00
foreach ( $props_to_update as $meta_key => $prop ) {
2016-11-11 14:31:15 +00:00
$value = $product -> { " get_ $prop " }( 'edit' );
2018-04-10 13:43:38 +00:00
$value = is_string ( $value ) ? wp_slash ( $value ) : $value ;
2016-11-11 14:31:15 +00:00
switch ( $prop ) {
2018-01-03 16:44:37 +00:00
case 'virtual' :
case 'downloadable' :
case 'manage_stock' :
case 'sold_individually' :
2019-02-19 13:23:24 +00:00
$value = wc_bool_to_string ( $value );
2016-11-11 14:31:15 +00:00
break ;
2018-01-03 16:44:37 +00:00
case 'gallery_image_ids' :
2019-02-19 13:23:24 +00:00
$value = implode ( ',' , $value );
2016-11-11 14:31:15 +00:00
break ;
2018-01-03 16:44:37 +00:00
case 'date_on_sale_from' :
case 'date_on_sale_to' :
2019-02-19 13:23:24 +00:00
$value = $value ? $value -> getTimestamp () : '' ;
2016-11-11 14:31:15 +00:00
break ;
}
2019-02-19 13:23:24 +00:00
$updated = $this -> update_or_delete_post_meta ( $product , $meta_key , $value );
2016-11-11 14:31:15 +00:00
if ( $updated ) {
2017-02-14 16:14:37 +00:00
$this -> updated_props [] = $prop ;
2016-11-11 14:31:15 +00:00
}
}
2017-02-11 15:26:13 +00:00
// Update extra data associated with the product like button text or product URL for external products.
if ( ! $this -> extra_data_saved ) {
foreach ( $extra_data_keys as $key ) {
2019-02-19 13:23:24 +00:00
$meta_key = '_' . $key ;
$function = 'get_' . $key ;
if ( ! array_key_exists ( $meta_key , $props_to_update ) ) {
2017-02-11 15:26:13 +00:00
continue ;
}
if ( is_callable ( array ( $product , $function ) ) ) {
2019-02-19 13:23:24 +00:00
$value = $product -> { $function }( 'edit' );
$value = is_string ( $value ) ? wp_slash ( $value ) : $value ;
$updated = $this -> update_or_delete_post_meta ( $product , $meta_key , $value );
2018-04-10 13:43:38 +00:00
2019-02-19 13:23:24 +00:00
if ( $updated ) {
2017-02-14 16:14:37 +00:00
$this -> updated_props [] = $key ;
2017-02-11 15:26:13 +00:00
}
}
}
}
2017-02-15 14:40:57 +00:00
if ( $this -> update_downloads ( $product , $force ) ) {
2017-02-14 16:14:37 +00:00
$this -> updated_props [] = 'downloads' ;
2017-02-11 15:26:13 +00:00
}
}
/**
2017-02-15 14:40:57 +00:00
* Handle updated meta props after updating meta data .
2017-02-11 15:26:13 +00:00
*
2018-01-03 17:57:33 +00:00
* @ since 3.0 . 0
* @ param WC_Product $product Product Object .
2017-02-11 15:26:13 +00:00
*/
2017-02-14 16:26:40 +00:00
protected function handle_updated_props ( & $product ) {
2018-06-11 10:44:58 +00:00
$price_is_synced = $product -> is_type ( array ( 'variable' , 'grouped' ) );
if ( ! $price_is_synced ) {
if ( in_array ( 'regular_price' , $this -> updated_props , true ) || in_array ( 'sale_price' , $this -> updated_props , true ) ) {
if ( $product -> get_sale_price ( 'edit' ) >= $product -> get_regular_price ( 'edit' ) ) {
update_post_meta ( $product -> get_id (), '_sale_price' , '' );
$product -> set_sale_price ( '' );
}
2017-08-22 10:43:48 +00:00
}
2018-06-11 10:44:58 +00:00
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 ) ) {
if ( $product -> is_on_sale ( 'edit' ) ) {
update_post_meta ( $product -> get_id (), '_price' , $product -> get_sale_price ( 'edit' ) );
$product -> set_price ( $product -> get_sale_price ( 'edit' ) );
} else {
update_post_meta ( $product -> get_id (), '_price' , $product -> get_regular_price ( 'edit' ) );
$product -> set_price ( $product -> get_regular_price ( 'edit' ) );
}
2016-11-11 14:31:15 +00:00
}
}
2017-08-22 10:43:48 +00:00
if ( in_array ( 'stock_quantity' , $this -> updated_props , true ) ) {
2018-05-02 13:28:53 +00:00
if ( $product -> is_type ( 'variation' ) ) {
do_action ( 'woocommerce_variation_set_stock' , $product );
} else {
do_action ( 'woocommerce_product_set_stock' , $product );
}
2016-11-11 14:31:15 +00:00
}
2017-08-22 10:43:48 +00:00
if ( in_array ( 'stock_status' , $this -> updated_props , true ) ) {
2018-05-02 13:28:53 +00:00
if ( $product -> is_type ( 'variation' ) ) {
do_action ( 'woocommerce_variation_set_stock_status' , $product -> get_id (), $product -> get_stock_status (), $product );
} else {
do_action ( 'woocommerce_product_set_stock_status' , $product -> get_id (), $product -> get_stock_status (), $product );
}
2016-11-11 14:31:15 +00:00
}
2020-01-20 14:17:42 +00:00
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' , 'tax_status' , 'tax_class' ) ) ) {
2019-02-15 12:37:45 +00:00
$this -> update_lookup_table ( $product -> get_id (), 'wc_product_meta_lookup' );
2019-02-12 13:23:47 +00:00
}
2017-02-15 14:40:57 +00:00
// Trigger action so 3rd parties can deal with updated props.
2017-02-14 16:14:37 +00:00
do_action ( 'woocommerce_product_object_updated_props' , $product , $this -> updated_props );
2017-02-15 14:40:57 +00:00
// After handling, we can reset the props array.
$this -> updated_props = array ();
2016-11-11 14:31:15 +00:00
}
/**
* For all stored terms in all taxonomies , save them to the DB .
*
2017-11-23 14:26:43 +00:00
* @ param WC_Product $product Product object .
* @ param bool $force Force update . Used during create .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
2017-02-15 14:40:57 +00:00
protected function update_terms ( & $product , $force = false ) {
2017-02-11 15:26:13 +00:00
$changes = $product -> get_changes ();
2017-02-15 14:40:57 +00:00
if ( $force || array_key_exists ( 'category_ids' , $changes ) ) {
2017-11-23 14:26:43 +00:00
$categories = $product -> get_category_ids ( 'edit' );
2017-11-23 14:41:17 +00:00
if ( empty ( $categories ) && get_option ( 'default_product_cat' , 0 ) ) {
$categories = array ( get_option ( 'default_product_cat' , 0 ) );
2017-11-23 14:26:43 +00:00
}
wp_set_post_terms ( $product -> get_id (), $categories , 'product_cat' , false );
2017-02-11 15:26:13 +00:00
}
2017-02-15 14:40:57 +00:00
if ( $force || array_key_exists ( 'tag_ids' , $changes ) ) {
2017-02-11 15:26:13 +00:00
wp_set_post_terms ( $product -> get_id (), $product -> get_tag_ids ( 'edit' ), 'product_tag' , false );
}
2017-02-15 14:40:57 +00:00
if ( $force || array_key_exists ( 'shipping_class_id' , $changes ) ) {
2017-02-11 15:26:13 +00:00
wp_set_post_terms ( $product -> get_id (), array ( $product -> get_shipping_class_id ( 'edit' ) ), 'product_shipping_class' , false );
}
2016-11-11 14:31:15 +00:00
}
2016-12-08 10:56:45 +00:00
/**
* Update visibility terms based on props .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-05-15 11:50:52 +00:00
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2018-01-03 16:44:37 +00:00
* @ param bool $force Force update . Used during create .
2016-12-08 10:56:45 +00:00
*/
2017-02-15 14:40:57 +00:00
protected function update_visibility ( & $product , $force = false ) {
2017-02-11 15:35:20 +00:00
$changes = $product -> get_changes ();
2016-12-08 10:56:45 +00:00
2017-02-15 14:40:57 +00:00
if ( $force || array_intersect ( array ( 'featured' , 'stock_status' , 'average_rating' , 'catalog_visibility' ), array_keys ( $changes ) ) ) {
2017-02-11 15:35:20 +00:00
$terms = array ();
2016-12-08 10:56:45 +00:00
2017-02-11 15:35:20 +00:00
if ( $product -> get_featured () ) {
$terms [] = 'featured' ;
}
2016-12-08 10:56:45 +00:00
2017-02-11 15:35:20 +00:00
if ( 'outofstock' === $product -> get_stock_status () ) {
$terms [] = 'outofstock' ;
}
2020-10-01 08:57:12 +00:00
$rating = min ( 5 , NumberUtil :: round ( $product -> get_average_rating (), 0 ) );
2017-05-10 18:21:38 +00:00
if ( $rating > 0 ) {
$terms [] = 'rated-' . $rating ;
}
2016-12-08 10:56:45 +00:00
2017-02-11 15:35:20 +00:00
switch ( $product -> get_catalog_visibility () ) {
2018-01-03 16:44:37 +00:00
case 'hidden' :
2017-02-11 15:35:20 +00:00
$terms [] = 'exclude-from-search' ;
$terms [] = 'exclude-from-catalog' ;
break ;
2018-01-03 16:44:37 +00:00
case 'catalog' :
2017-02-11 15:35:20 +00:00
$terms [] = 'exclude-from-search' ;
break ;
2018-01-03 16:44:37 +00:00
case 'search' :
2017-02-11 15:35:20 +00:00
$terms [] = 'exclude-from-catalog' ;
break ;
}
if ( ! is_wp_error ( wp_set_post_terms ( $product -> get_id (), $terms , 'product_visibility' , false ) ) ) {
do_action ( 'woocommerce_product_set_visibility' , $product -> get_id (), $product -> get_catalog_visibility () );
}
2016-12-08 10:56:45 +00:00
}
}
2016-11-11 14:31:15 +00:00
/**
* Update attributes which are a mix of terms and meta data .
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
* @ param bool $force Force update . Used during create .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
2017-02-15 14:40:57 +00:00
protected function update_attributes ( & $product , $force = false ) {
2017-02-11 15:35:20 +00:00
$changes = $product -> get_changes ();
2016-11-11 14:31:15 +00:00
2017-02-15 14:40:57 +00:00
if ( $force || array_key_exists ( 'attributes' , $changes ) ) {
2017-02-11 15:35:20 +00:00
$attributes = $product -> get_attributes ();
$meta_values = array ();
2016-11-11 14:31:15 +00:00
2017-02-11 15:35:20 +00:00
if ( $attributes ) {
foreach ( $attributes as $attribute_key => $attribute ) {
$value = '' ;
2016-11-11 14:31:15 +00:00
2017-02-11 15:35:20 +00:00
if ( is_null ( $attribute ) ) {
if ( taxonomy_exists ( $attribute_key ) ) {
// Handle attributes that have been unset.
wp_set_object_terms ( $product -> get_id (), array (), $attribute_key );
2019-02-14 15:58:12 +00:00
} elseif ( taxonomy_exists ( urldecode ( $attribute_key ) ) ) {
// Handle attributes that have been unset.
wp_set_object_terms ( $product -> get_id (), array (), urldecode ( $attribute_key ) );
2017-02-11 15:35:20 +00:00
}
continue ;
2016-11-11 14:31:15 +00:00
2017-02-11 15:35:20 +00:00
} elseif ( $attribute -> is_taxonomy () ) {
2018-10-16 16:53:31 +00:00
wp_set_object_terms ( $product -> get_id (), wp_list_pluck ( ( array ) $attribute -> get_terms (), 'term_id' ), $attribute -> get_name () );
2017-02-11 15:35:20 +00:00
} else {
$value = wc_implode_text_attributes ( $attribute -> get_options () );
}
// Store in format WC uses in meta.
$meta_values [ $attribute_key ] = array (
'name' => $attribute -> get_name (),
'value' => $value ,
'position' => $attribute -> get_position (),
'is_visible' => $attribute -> get_visible () ? 1 : 0 ,
'is_variation' => $attribute -> get_variation () ? 1 : 0 ,
'is_taxonomy' => $attribute -> is_taxonomy () ? 1 : 0 ,
);
}
2016-11-11 14:31:15 +00:00
}
2019-01-08 13:28:26 +00:00
// Note, we use wp_slash to add extra level of escaping. See https://codex.wordpress.org/Function_Reference/update_post_meta#Workaround.
2019-02-19 13:23:24 +00:00
$this -> update_or_delete_post_meta ( $product , '_product_attributes' , wp_slash ( $meta_values ) );
2016-11-11 14:31:15 +00:00
}
}
2016-11-11 15:31:00 +00:00
/**
* Update downloads .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
* @ param bool $force Force update . Used during create .
2017-02-11 15:35:20 +00:00
* @ return bool If updated or not .
2016-11-11 15:31:00 +00:00
*/
2017-02-15 14:40:57 +00:00
protected function update_downloads ( & $product , $force = false ) {
2017-02-11 15:35:20 +00:00
$changes = $product -> get_changes ();
2016-11-11 15:31:00 +00:00
2017-02-15 14:40:57 +00:00
if ( $force || array_key_exists ( 'downloads' , $changes ) ) {
2017-02-11 15:35:20 +00:00
$downloads = $product -> get_downloads ();
$meta_values = array ();
if ( $downloads ) {
foreach ( $downloads as $key => $download ) {
// Store in format WC uses in meta.
$meta_values [ $key ] = $download -> get_data ();
}
2016-11-11 15:31:00 +00:00
}
2016-11-18 19:29:37 +00:00
2017-02-11 15:35:20 +00:00
if ( $product -> is_type ( 'variation' ) ) {
do_action ( 'woocommerce_process_product_file_download_paths' , $product -> get_parent_id (), $product -> get_id (), $downloads );
} else {
do_action ( 'woocommerce_process_product_file_download_paths' , $product -> get_id (), 0 , $downloads );
}
2016-11-18 19:29:37 +00:00
2019-02-19 13:23:24 +00:00
return $this -> update_or_delete_post_meta ( $product , '_downloadable_files' , wp_slash ( $meta_values ) );
2017-02-11 15:35:20 +00:00
}
return false ;
2016-11-11 15:31:00 +00:00
}
2016-11-11 14:31:15 +00:00
/**
* Make sure we store the product type and version ( to track data changes ) .
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
protected function update_version_and_type ( & $product ) {
2017-01-11 11:48:31 +00:00
$old_type = WC_Product_Factory :: get_product_type ( $product -> get_id () );
$new_type = $product -> get_type ();
wp_set_object_terms ( $product -> get_id (), $new_type , 'product_type' );
2020-02-01 06:18:47 +00:00
update_post_meta ( $product -> get_id (), '_product_version' , Constants :: get_constant ( 'WC_VERSION' ) );
2017-01-11 11:48:31 +00:00
// Action for the transition.
if ( $old_type !== $new_type ) {
2017-08-30 15:23:57 +00:00
$this -> updated_props [] = 'product_type' ;
2017-01-11 11:48:31 +00:00
do_action ( 'woocommerce_product_type_changed' , $product , $old_type , $new_type );
}
2016-11-11 14:31:15 +00:00
}
/**
* Clear any caches .
*
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
protected function clear_caches ( & $product ) {
wc_delete_product_transients ( $product -> get_id () );
2019-01-18 23:24:43 +00:00
if ( $product -> get_parent_id ( 'edit' ) ) {
wc_delete_product_transients ( $product -> get_parent_id ( 'edit' ) );
2019-11-28 13:03:57 +00:00
WC_Cache_Helper :: invalidate_cache_group ( 'product_' . $product -> get_parent_id ( 'edit' ) );
2019-01-18 23:24:43 +00:00
}
WC_Cache_Helper :: invalidate_attribute_count ( array_keys ( $product -> get_attributes () ) );
2019-11-28 13:03:57 +00:00
WC_Cache_Helper :: invalidate_cache_group ( 'product_' . $product -> get_id () );
2016-11-11 14:31:15 +00:00
}
/*
|--------------------------------------------------------------------------
| wc - product - functions . php methods
|--------------------------------------------------------------------------
*/
/**
* Returns an array of on sale products , as an array of objects with an
* ID and parent_id present . Example : $return [ 0 ] -> id , $return [ 0 ] -> parent_id .
*
* @ return array
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
public function get_on_sale_products () {
global $wpdb ;
2017-02-08 17:04:13 +00:00
2018-02-15 16:35:29 +00:00
$exclude_term_ids = array ();
$outofstock_join = '' ;
$outofstock_where = '' ;
2018-11-01 10:01:59 +00:00
$non_published_where = '' ;
2018-02-15 16:35:29 +00:00
$product_visibility_term_ids = wc_get_product_visibility_term_ids ();
2017-02-08 17:04:13 +00:00
2018-02-15 16:35:29 +00:00
if ( 'yes' === get_option ( 'woocommerce_hide_out_of_stock_items' ) && $product_visibility_term_ids [ 'outofstock' ] ) {
$exclude_term_ids [] = $product_visibility_term_ids [ 'outofstock' ];
}
if ( count ( $exclude_term_ids ) ) {
$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' ;
$outofstock_where = ' AND exclude_join.object_id IS NULL' ;
}
2020-05-06 16:14:53 +00:00
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
2018-01-03 16:44:37 +00:00
return $wpdb -> get_results (
2019-03-13 17:14:20 +00:00
"
SELECT posts . ID as id , posts . post_parent as parent_id
FROM { $wpdb -> posts } AS posts
INNER JOIN { $wpdb -> wc_product_meta_lookup } AS lookup ON posts . ID = lookup . product_id
$outofstock_join
WHERE posts . post_type IN ( 'product' , 'product_variation' )
AND posts . post_status = 'publish'
AND lookup . onsale = 1
$outofstock_where
2019-04-10 11:43:22 +00:00
AND posts . post_parent NOT IN (
2019-04-10 12:48:03 +00:00
SELECT ID FROM `$wpdb->posts` as posts
2019-04-10 11:43:22 +00:00
WHERE posts . post_type = 'product'
AND posts . post_parent = 0
AND posts . post_status != 'publish'
)
2019-03-13 17:14:20 +00:00
GROUP BY posts . ID
"
2018-01-03 16:44:37 +00:00
);
2020-05-06 16:14:53 +00:00
// phpcs:enable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
2016-11-11 14:31:15 +00:00
}
/**
* Returns a list of product IDs ( id as key => parent as value ) that are
* featured . Uses get_posts instead of wc_get_products since we want
* some extra meta queries and ALL products ( posts_per_page = - 1 ) .
*
* @ return array
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
*/
public function get_featured_product_ids () {
2016-12-08 17:01:39 +00:00
$product_visibility_term_ids = wc_get_product_visibility_term_ids ();
2018-01-03 16:44:37 +00:00
return get_posts (
array (
'post_type' => array ( 'product' , 'product_variation' ),
'posts_per_page' => - 1 ,
'post_status' => 'publish' ,
2019-01-30 15:31:48 +00:00
'tax_query' => array ( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2018-01-03 16:44:37 +00:00
'relation' => 'AND' ,
array (
'taxonomy' => 'product_visibility' ,
'field' => 'term_taxonomy_id' ,
'terms' => array ( $product_visibility_term_ids [ 'featured' ] ),
),
array (
'taxonomy' => 'product_visibility' ,
'field' => 'term_taxonomy_id' ,
'terms' => array ( $product_visibility_term_ids [ 'exclude-from-catalog' ] ),
'operator' => 'NOT IN' ,
),
2016-11-11 14:31:15 +00:00
),
2018-01-03 16:44:37 +00:00
'fields' => 'id=>parent' ,
)
);
2016-11-11 14:31:15 +00:00
}
/**
* Check if product sku is found for any other product IDs .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2018-01-03 17:57:33 +00:00
* @ param int $product_id Product ID .
* @ param string $sku Will be slashed to work around https :// core . trac . wordpress . org / ticket / 27421.
2016-11-11 14:31:15 +00:00
* @ return bool
*/
public function is_existing_sku ( $product_id , $sku ) {
global $wpdb ;
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
2019-08-12 15:45:43 +00:00
return ( bool ) $wpdb -> get_var (
2018-01-03 16:44:37 +00:00
$wpdb -> prepare (
2019-03-13 13:25:55 +00:00
"
SELECT posts . ID
FROM { $wpdb -> posts } as posts
INNER JOIN { $wpdb -> wc_product_meta_lookup } AS lookup ON posts . ID = lookup . product_id
WHERE
2019-03-13 13:30:49 +00:00
posts . post_type IN ( 'product' , 'product_variation' )
AND posts . post_status != 'trash'
AND lookup . sku = % s
2019-03-13 15:00:12 +00:00
AND lookup . product_id <> % d
2019-03-13 13:30:49 +00:00
LIMIT 1
" ,
2018-11-23 14:57:51 +00:00
wp_slash ( $sku ),
$product_id
2018-01-03 16:44:37 +00:00
)
);
2016-11-11 14:31:15 +00:00
}
/**
* Return product ID based on SKU .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-08-22 18:04:21 +00:00
* @ param string $sku Product SKU .
2016-11-11 14:31:15 +00:00
* @ return int
*/
public function get_product_id_by_sku ( $sku ) {
global $wpdb ;
2017-08-22 18:04:21 +00:00
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
2018-01-03 16:44:37 +00:00
$id = $wpdb -> get_var (
$wpdb -> prepare (
2019-03-13 13:30:49 +00:00
"
SELECT posts . ID
FROM { $wpdb -> posts } as posts
INNER JOIN { $wpdb -> wc_product_meta_lookup } AS lookup ON posts . ID = lookup . product_id
WHERE
posts . post_type IN ( 'product' , 'product_variation' )
AND posts . post_status != 'trash'
AND lookup . sku = % s
LIMIT 1
" ,
2018-01-03 17:57:33 +00:00
$sku
2018-01-03 16:44:37 +00:00
)
);
2017-08-22 18:04:21 +00:00
return ( int ) apply_filters ( 'woocommerce_get_product_id_by_sku' , $id , $sku );
2016-11-11 14:31:15 +00:00
}
/**
* Returns an array of IDs of products that have sales starting soon .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
* @ return array
*/
public function get_starting_sales () {
global $wpdb ;
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
2018-01-03 16:44:37 +00:00
return $wpdb -> get_col (
$wpdb -> prepare (
2018-01-03 17:57:33 +00:00
" SELECT postmeta.post_id FROM { $wpdb -> postmeta } as postmeta
LEFT JOIN { $wpdb -> postmeta } as postmeta_2 ON postmeta . post_id = postmeta_2 . post_id
LEFT JOIN { $wpdb -> postmeta } as postmeta_3 ON postmeta . post_id = postmeta_3 . post_id
WHERE postmeta . meta_key = '_sale_price_dates_from'
AND postmeta_2 . meta_key = '_price'
AND postmeta_3 . meta_key = '_sale_price'
AND postmeta . meta_value > 0
AND postmeta . meta_value < % s
AND postmeta_2 . meta_value != postmeta_3 . meta_value " ,
2019-12-20 17:23:05 +00:00
time ()
2018-01-03 16:44:37 +00:00
)
);
2016-11-11 14:31:15 +00:00
}
/**
* Returns an array of IDs of products that have sales which are due to end .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
* @ return array
*/
public function get_ending_sales () {
global $wpdb ;
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
2018-01-03 16:44:37 +00:00
return $wpdb -> get_col (
$wpdb -> prepare (
2018-01-03 17:57:33 +00:00
" SELECT postmeta.post_id FROM { $wpdb -> postmeta } as postmeta
LEFT JOIN { $wpdb -> postmeta } as postmeta_2 ON postmeta . post_id = postmeta_2 . post_id
LEFT JOIN { $wpdb -> postmeta } as postmeta_3 ON postmeta . post_id = postmeta_3 . post_id
WHERE postmeta . meta_key = '_sale_price_dates_to'
AND postmeta_2 . meta_key = '_price'
AND postmeta_3 . meta_key = '_regular_price'
AND postmeta . meta_value > 0
AND postmeta . meta_value < % s
AND postmeta_2 . meta_value != postmeta_3 . meta_value " ,
2019-12-20 17:23:05 +00:00
time ()
2018-01-03 16:44:37 +00:00
)
);
2016-11-11 14:31:15 +00:00
}
/**
* Find a matching ( enabled ) variation within a variable product .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
* @ param WC_Product $product Variable product .
2018-01-03 16:44:37 +00:00
* @ param array $match_attributes Array of attributes we want to try to match .
2016-11-11 14:31:15 +00:00
* @ return int Matching variation ID or 0.
*/
public function find_matching_product_variation ( $product , $match_attributes = array () ) {
2018-07-25 23:05:57 +00:00
global $wpdb ;
2016-11-11 14:31:15 +00:00
2019-01-11 16:48:05 +00:00
$meta_attribute_names = array ();
2016-11-11 14:31:15 +00:00
2019-01-11 16:48:05 +00:00
// Get attributes to match in meta.
2016-11-11 14:31:15 +00:00
foreach ( $product -> get_attributes () as $attribute ) {
if ( ! $attribute -> get_variation () ) {
continue ;
}
2019-03-15 16:25:26 +00:00
$meta_attribute_names [] = 'attribute_' . sanitize_title ( $attribute -> get_name () );
2019-01-11 16:48:05 +00:00
}
2016-11-11 14:31:15 +00:00
2019-01-11 16:48:05 +00:00
// Get the attributes of the variations.
$query = $wpdb -> prepare (
"
2019-06-07 09:56:34 +00:00
SELECT postmeta . post_id , postmeta . meta_key , postmeta . meta_value , posts . menu_order FROM { $wpdb -> postmeta } as postmeta
LEFT JOIN { $wpdb -> posts } as posts ON postmeta . post_id = posts . ID
WHERE postmeta . post_id IN (
2019-02-18 21:38:29 +00:00
SELECT ID FROM { $wpdb -> posts }
WHERE { $wpdb -> posts } . post_parent = % d
AND { $wpdb -> posts } . post_status = 'publish'
AND { $wpdb -> posts } . post_type = 'product_variation'
2019-01-11 16:48:05 +00:00
)
" ,
$product -> get_id ()
);
2019-06-07 09:56:34 +00:00
$query .= ' AND postmeta.meta_key IN ( "' . implode ( '","' , array_map ( 'esc_sql' , $meta_attribute_names ) ) . '" )' ;
2019-08-12 18:26:34 +00:00
$query .= ' ORDER BY posts.menu_order ASC, postmeta.post_id ASC;' ;
2019-01-11 16:48:05 +00:00
2019-02-18 21:45:07 +00:00
$attributes = $wpdb -> get_results ( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
2019-01-11 15:52:43 +00:00
2019-01-11 16:48:05 +00:00
if ( ! $attributes ) {
return 0 ;
2016-11-11 14:31:15 +00:00
}
2019-01-11 16:48:05 +00:00
$sorted_meta = array ();
2019-01-11 15:52:43 +00:00
2019-01-11 16:48:05 +00:00
foreach ( $attributes as $m ) {
2019-02-18 21:45:07 +00:00
$sorted_meta [ $m -> post_id ][ $m -> meta_key ] = $m -> meta_value ; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
2019-01-11 16:48:05 +00:00
}
2016-11-11 14:31:15 +00:00
2019-01-11 16:48:05 +00:00
/**
* Check each variation to find the one that matches the $match_attributes .
*
* Note : Not all meta fields will be set which is why we check existance .
*/
foreach ( $sorted_meta as $variation_id => $variation ) {
$match = true ;
2019-03-18 12:09:37 +00:00
// Loop over the variation meta keys and values i.e. what is saved to the products. Note: $attribute_value is empty when 'any' is in use.
2019-03-15 16:25:26 +00:00
foreach ( $variation as $attribute_key => $attribute_value ) {
2019-10-02 14:58:45 +00:00
$match_any_value = '' === $attribute_value ;
2019-03-18 12:09:37 +00:00
if ( ! $match_any_value && ! array_key_exists ( $attribute_key , $match_attributes ) ) {
$match = false ; // Requires a selection but no value was provide.
2019-03-15 16:25:26 +00:00
}
2019-03-18 12:09:37 +00:00
if ( array_key_exists ( $attribute_key , $match_attributes ) ) { // Value to match was provided.
if ( ! $match_any_value && $match_attributes [ $attribute_key ] !== $attribute_value ) {
$match = false ; // Provided value does not match variation.
2018-07-25 22:00:40 +00:00
}
}
}
2019-01-11 16:48:05 +00:00
if ( true === $match ) {
2019-01-11 16:52:57 +00:00
return $variation_id ;
2019-01-11 16:48:05 +00:00
}
2016-11-11 14:31:15 +00:00
}
2019-01-11 16:52:57 +00:00
if ( version_compare ( get_post_meta ( $product -> get_id (), '_product_version' , true ), '2.4.0' , '<' ) ) {
2016-11-11 14:31:15 +00:00
/**
* Pre 2.4 handling where 'slugs' were saved instead of the full text attribute .
* Fallback is here because there are cases where data will be 'synced' but the product version will remain the same .
*/
return ( array_map ( 'sanitize_title' , $match_attributes ) === $match_attributes ) ? 0 : $this -> find_matching_product_variation ( $product , array_map ( 'sanitize_title' , $match_attributes ) );
}
2019-03-15 16:25:26 +00:00
return 0 ;
2016-11-11 14:31:15 +00:00
}
2019-04-15 10:59:57 +00:00
/**
* Creates all possible combinations of variations from the attributes , without creating duplicates .
*
* @ since 3.6 . 0
2019-04-15 11:10:22 +00:00
* @ todo Add to interface in 4.0 .
2019-04-15 10:59:57 +00:00
* @ param WC_Product $product Variable product .
* @ param int $limit Limit the number of created variations .
* @ return int Number of created variations .
*/
public function create_all_product_variations ( $product , $limit = - 1 ) {
$count = 0 ;
if ( ! $product ) {
return $count ;
}
$attributes = wc_list_pluck ( array_filter ( $product -> get_attributes (), 'wc_attributes_array_filter_variation' ), 'get_slugs' );
if ( empty ( $attributes ) ) {
return $count ;
}
// Get existing variations so we don't create duplicates.
$existing_variations = array_map ( 'wc_get_product' , $product -> get_children () );
$existing_attributes = array ();
foreach ( $existing_variations as $existing_variation ) {
$existing_attributes [] = $existing_variation -> get_attributes ();
}
$possible_attributes = array_reverse ( wc_array_cartesian ( $attributes ) );
foreach ( $possible_attributes as $possible_attribute ) {
2019-04-15 11:10:22 +00:00
// Allow any order if key/values -- do not use strict mode.
if ( in_array ( $possible_attribute , $existing_attributes ) ) { // phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
2019-04-15 10:59:57 +00:00
continue ;
}
2019-12-04 19:15:16 +00:00
$variation = wc_get_product_object ( 'variation' );
2019-04-15 10:59:57 +00:00
$variation -> set_parent_id ( $product -> get_id () );
$variation -> set_attributes ( $possible_attribute );
$variation_id = $variation -> save ();
do_action ( 'product_variation_linked' , $variation_id );
$count ++ ;
if ( $limit > 0 && $count >= $limit ) {
break ;
}
}
return $count ;
}
2016-12-08 14:28:51 +00:00
/**
* Make sure all variations have a sort order set so they can be reordered correctly .
*
2018-01-03 17:57:33 +00:00
* @ param int $parent_id Product ID .
2016-12-08 14:28:51 +00:00
*/
public function sort_all_product_variations ( $parent_id ) {
global $wpdb ;
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
$ids = $wpdb -> get_col (
$wpdb -> prepare (
" 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 " ,
$parent_id
)
);
2017-06-05 20:42:58 +00:00
$index = 1 ;
2016-12-08 14:28:51 +00:00
foreach ( $ids as $id ) {
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
2017-06-05 20:42:58 +00:00
$wpdb -> update ( $wpdb -> posts , array ( 'menu_order' => ( $index ++ ) ), array ( 'ID' => absint ( $id ) ) );
2016-12-08 14:28:51 +00:00
}
}
2016-11-11 14:31:15 +00:00
/**
* Return a list of related products ( using data like categories and IDs ) .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2016-11-11 14:31:15 +00:00
* @ param array $cats_array List of categories IDs .
* @ param array $tags_array List of tags IDs .
* @ param array $exclude_ids Excluded IDs .
* @ param int $limit Limit of results .
2018-01-03 17:57:33 +00:00
* @ param int $product_id Product ID .
2016-11-11 14:31:15 +00:00
* @ return array
*/
public function get_related_products ( $cats_array , $tags_array , $exclude_ids , $limit , $product_id ) {
global $wpdb ;
2017-12-19 02:18:33 +00:00
$args = array (
'categories' => $cats_array ,
'tags' => $tags_array ,
'exclude_ids' => $exclude_ids ,
'limit' => $limit + 10 ,
);
2017-12-19 09:19:10 +00:00
$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 );
2017-12-19 02:18:33 +00:00
2018-11-23 17:10:52 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
2017-12-19 09:19:10 +00:00
return $wpdb -> get_col ( implode ( ' ' , $related_product_query ) );
2016-11-11 14:31:15 +00:00
}
/**
* Builds the related posts query .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2017-05-15 11:50:52 +00:00
*
2016-11-11 14:31:15 +00:00
* @ param array $cats_array List of categories IDs .
* @ param array $tags_array List of tags IDs .
* @ param array $exclude_ids Excluded IDs .
* @ param int $limit Limit of results .
2017-05-15 11:50:52 +00:00
*
* @ return array
2016-11-11 14:31:15 +00:00
*/
2016-11-16 12:17:00 +00:00
public function get_related_products_query ( $cats_array , $tags_array , $exclude_ids , $limit ) {
2016-11-11 14:31:15 +00:00
global $wpdb ;
2017-05-08 13:15:16 +00:00
$include_term_ids = array_merge ( $cats_array , $tags_array );
$exclude_term_ids = array ();
2016-12-08 17:01:39 +00:00
$product_visibility_term_ids = wc_get_product_visibility_term_ids ();
if ( $product_visibility_term_ids [ 'exclude-from-catalog' ] ) {
2017-05-08 13:15:16 +00:00
$exclude_term_ids [] = $product_visibility_term_ids [ 'exclude-from-catalog' ];
2016-12-08 10:56:45 +00:00
}
2016-12-08 17:01:39 +00:00
if ( 'yes' === get_option ( 'woocommerce_hide_out_of_stock_items' ) && $product_visibility_term_ids [ 'outofstock' ] ) {
2017-05-08 13:15:16 +00:00
$exclude_term_ids [] = $product_visibility_term_ids [ 'outofstock' ];
2016-11-11 14:31:15 +00:00
}
2017-05-08 13:15:16 +00:00
$query = array (
'fields' => "
SELECT DISTINCT ID FROM { $wpdb -> posts } p
" ,
'join' => '' ,
'where' => "
WHERE 1 = 1
AND p . post_status = 'publish'
AND p . post_type = 'product'
" ,
2018-01-03 16:44:37 +00:00
'limits' => '
LIMIT ' . absint( $limit ) . '
' ,
2017-05-08 13:15:16 +00:00
);
2016-11-11 14:31:15 +00:00
2017-05-08 18:08:59 +00:00
if ( count ( $exclude_term_ids ) ) {
2018-01-03 16:44:37 +00:00
$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' ;
$query [ 'where' ] .= ' AND exclude_join.object_id IS NULL' ;
2017-05-08 13:15:16 +00:00
}
2016-11-11 14:31:15 +00:00
2017-05-08 18:08:59 +00:00
if ( count ( $include_term_ids ) ) {
2018-01-03 16:44:37 +00:00
$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' ;
2016-11-11 14:31:15 +00:00
}
2017-05-08 18:08:59 +00:00
if ( count ( $exclude_ids ) ) {
2018-01-03 16:44:37 +00:00
$query [ 'where' ] .= ' AND p.ID NOT IN ( ' . implode ( ',' , array_map ( 'absint' , $exclude_ids ) ) . ' )' ;
2017-05-08 13:15:16 +00:00
}
2016-11-11 14:31:15 +00:00
return $query ;
}
2019-05-02 17:41:15 +00:00
/**
* Update a product ' s stock amount directly in the database .
*
* Updates both post meta and lookup tables . Ignores manage stock setting on the product .
*
* @ param int $product_id_with_stock Product ID .
* @ param int | float | null $stock_quantity Stock quantity .
*/
protected function set_product_stock ( $product_id_with_stock , $stock_quantity ) {
global $wpdb ;
// Generate SQL.
$sql = $wpdb -> prepare (
" UPDATE { $wpdb -> postmeta } SET meta_value = %f WHERE post_id = %d AND meta_key='_stock' " ,
$stock_quantity ,
$product_id_with_stock
);
$sql = apply_filters ( 'woocommerce_update_product_stock_query' , $sql , $product_id_with_stock , $stock_quantity , 'set' );
$wpdb -> query ( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
// Cache delete is required (not only) to set correct data for lookup table (which reads from cache).
// Sometimes I wonder if it shouldn't be part of update_lookup_table.
wp_cache_delete ( $product_id_with_stock , 'post_meta' );
2019-05-15 12:20:27 +00:00
$this -> update_lookup_table ( $product_id_with_stock , 'wc_product_meta_lookup' );
2019-05-02 17:41:15 +00:00
}
2016-11-11 14:31:15 +00:00
/**
* Update a product ' s stock amount directly .
*
* Uses queries rather than update_post_meta so we can do this in one query ( to avoid stock issues ) .
2019-05-02 17:41:15 +00:00
* Ignores manage stock setting on the product and sets quantities directly in the db : post meta and lookup tables .
* Uses locking to update the quantity . If the lock is not acquired , change is lost .
2016-11-11 14:31:15 +00:00
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0 this supports set , increase and decrease .
2019-04-26 16:23:45 +00:00
* @ param int $product_id_with_stock Product ID .
* @ param int | float | null $stock_quantity Stock quantity .
* @ param string $operation Set , increase and decrease .
* @ return int | float New stock level .
2016-11-11 14:31:15 +00:00
*/
2016-11-16 12:17:00 +00:00
public function update_product_stock ( $product_id_with_stock , $stock_quantity = null , $operation = 'set' ) {
2016-11-11 14:31:15 +00:00
global $wpdb ;
2019-04-26 16:23:45 +00:00
// Ensures a row exists to update.
2016-11-11 14:31:15 +00:00
add_post_meta ( $product_id_with_stock , '_stock' , 0 , true );
2019-04-26 16:23:45 +00:00
if ( 'set' === $operation ) {
$new_stock = wc_stock_amount ( $stock_quantity );
2020-03-31 10:09:07 +00:00
// Generate SQL.
$sql = $wpdb -> prepare (
" UPDATE { $wpdb -> postmeta } SET meta_value = %f WHERE post_id = %d AND meta_key='_stock' " ,
$new_stock ,
$product_id_with_stock
);
2019-04-26 16:23:45 +00:00
} else {
2019-05-15 13:09:16 +00:00
$current_stock = wc_stock_amount (
$wpdb -> get_var (
2019-05-02 17:41:15 +00:00
$wpdb -> prepare (
" SELECT meta_value FROM { $wpdb -> postmeta } WHERE post_id = %d AND meta_key='_stock'; " ,
$product_id_with_stock
)
2019-05-15 13:09:16 +00:00
)
);
2019-02-12 13:23:47 +00:00
2020-03-31 10:09:07 +00:00
// Calculate new value for filter below. Set multiplier to subtract or add the meta_value.
2019-05-15 13:09:16 +00:00
switch ( $operation ) {
case 'increase' :
2020-10-01 08:53:38 +00:00
$new_stock = $current_stock + wc_stock_amount ( $stock_quantity );
2020-03-31 10:09:07 +00:00
$multiplier = 1 ;
2019-05-15 13:09:16 +00:00
break ;
default :
2020-10-01 08:53:38 +00:00
$new_stock = $current_stock - wc_stock_amount ( $stock_quantity );
2020-03-31 10:09:07 +00:00
$multiplier = - 1 ;
2019-05-15 13:09:16 +00:00
break ;
2019-05-02 17:41:15 +00:00
}
2019-03-13 13:30:31 +00:00
2020-03-31 10:09:07 +00:00
// Generate SQL.
$sql = $wpdb -> prepare (
" UPDATE { $wpdb -> postmeta } SET meta_value = meta_value %+f WHERE post_id = %d AND meta_key='_stock' " ,
wc_stock_amount ( $stock_quantity ) * $multiplier , // This will either subtract or add depending on operation.
$product_id_with_stock
);
}
2019-05-15 13:09:16 +00:00
2020-03-31 10:09:07 +00:00
$sql = apply_filters ( 'woocommerce_update_product_stock_query' , $sql , $product_id_with_stock , $new_stock , $operation );
2019-05-15 13:09:16 +00:00
$wpdb -> query ( $sql ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
// Cache delete is required (not only) to set correct data for lookup table (which reads from cache).
// Sometimes I wonder if it shouldn't be part of update_lookup_table.
wp_cache_delete ( $product_id_with_stock , 'post_meta' );
$this -> update_lookup_table ( $product_id_with_stock , 'wc_product_meta_lookup' );
2019-02-12 13:23:47 +00:00
/**
* Fire an action for this direct update so it can be detected by other code .
*
* @ since 3.6
* @ param int $product_id_with_stock Product ID that was updated directly .
*/
do_action ( 'woocommerce_updated_product_stock' , $product_id_with_stock );
2019-04-26 16:23:45 +00:00
return $new_stock ;
2016-11-11 14:31:15 +00:00
}
2017-02-10 23:41:53 +00:00
/**
* Update a product ' s sale count directly .
*
* Uses queries rather than update_post_meta so we can do this in one query for performance .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0 this supports set , increase and decrease .
2018-01-03 17:57:33 +00:00
* @ param int $product_id Product ID .
* @ param int | null $quantity Quantity .
2018-01-03 16:44:37 +00:00
* @ param string $operation set , increase and decrease .
2017-02-10 23:41:53 +00:00
*/
public function update_product_sales ( $product_id , $quantity = null , $operation = 'set' ) {
global $wpdb ;
add_post_meta ( $product_id , 'total_sales' , 0 , true );
2018-01-03 17:57:33 +00:00
// Update stock in DB directly.
2017-02-10 23:41:53 +00:00
switch ( $operation ) {
2018-01-03 16:44:37 +00:00
case 'increase' :
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
$wpdb -> query (
$wpdb -> prepare (
2018-11-23 14:57:51 +00:00
" UPDATE { $wpdb -> postmeta } SET meta_value = meta_value + %f WHERE post_id = %d AND meta_key='total_sales' " ,
$quantity ,
$product_id
2018-01-03 17:57:33 +00:00
)
);
2017-02-10 23:41:53 +00:00
break ;
2018-01-03 16:44:37 +00:00
case 'decrease' :
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
$wpdb -> query (
$wpdb -> prepare (
2018-11-23 14:57:51 +00:00
" UPDATE { $wpdb -> postmeta } SET meta_value = meta_value - %f WHERE post_id = %d AND meta_key='total_sales' " ,
$quantity ,
$product_id
2018-01-03 17:57:33 +00:00
)
);
2017-02-10 23:41:53 +00:00
break ;
2018-01-03 16:44:37 +00:00
default :
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
$wpdb -> query (
$wpdb -> prepare (
2018-11-23 14:57:51 +00:00
" UPDATE { $wpdb -> postmeta } SET meta_value = %f WHERE post_id = %d AND meta_key='total_sales' " ,
$quantity ,
$product_id
2018-01-03 17:57:33 +00:00
)
);
2017-02-10 23:41:53 +00:00
break ;
}
wp_cache_delete ( $product_id , 'post_meta' );
2019-02-12 13:23:47 +00:00
2019-02-15 12:37:45 +00:00
$this -> update_lookup_table ( $product_id , 'wc_product_meta_lookup' );
2019-02-12 13:23:47 +00:00
/**
* Fire an action for this direct update so it can be detected by other code .
*
* @ since 3.6
* @ param int $product_id Product ID that was updated directly .
*/
do_action ( 'woocommerce_updated_product_sales' , $product_id );
2017-02-10 23:41:53 +00:00
}
2016-11-16 12:17:00 +00:00
/**
* Update a products average rating meta .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2019-02-12 13:23:47 +00:00
* @ todo Deprecate unused function ?
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2016-11-16 12:17:00 +00:00
*/
public function update_average_rating ( $product ) {
update_post_meta ( $product -> get_id (), '_wc_average_rating' , $product -> get_average_rating ( 'edit' ) );
2017-06-15 10:29:18 +00:00
self :: update_visibility ( $product , true );
2016-11-16 12:17:00 +00:00
}
/**
* Update a products review count meta .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2019-02-12 13:23:47 +00:00
* @ todo Deprecate unused function ?
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2016-11-16 12:17:00 +00:00
*/
public function update_review_count ( $product ) {
update_post_meta ( $product -> get_id (), '_wc_review_count' , $product -> get_review_count ( 'edit' ) );
}
/**
* Update a products rating counts .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2019-02-12 13:23:47 +00:00
* @ todo Deprecate unused function ?
2018-01-03 17:57:33 +00:00
* @ param WC_Product $product Product object .
2016-11-16 12:17:00 +00:00
*/
public function update_rating_counts ( $product ) {
update_post_meta ( $product -> get_id (), '_wc_rating_count' , $product -> get_rating_counts ( 'edit' ) );
}
2016-11-16 20:48:42 +00:00
/**
* Get shipping class ID by slug .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2018-01-03 17:57:33 +00:00
* @ param string $slug Product shipping class slug .
2016-11-16 20:48:42 +00:00
* @ return int | false
*/
public function get_shipping_class_id_by_slug ( $slug ) {
$shipping_class_term = get_term_by ( 'slug' , $slug , 'product_shipping_class' );
if ( $shipping_class_term ) {
return $shipping_class_term -> term_id ;
} else {
return false ;
}
}
2016-11-21 12:25:00 +00:00
/**
* Returns an array of products .
*
2018-01-03 17:57:33 +00:00
* @ param array $args Args to pass to WC_Product_Query () .
2017-05-15 11:50:52 +00:00
* @ return array | object
2018-01-03 17:57:33 +00:00
* @ see wc_get_products
2016-11-21 12:25:00 +00:00
*/
public function get_products ( $args = array () ) {
2017-07-14 17:01:25 +00:00
$query = new WC_Product_Query ( $args );
return $query -> get_products ();
2016-11-21 12:25:00 +00:00
}
2016-11-24 11:50:34 +00:00
/**
* Search product data for a term and return ids .
*
2019-04-09 02:33:50 +00:00
* @ param string $term Search term .
* @ param string $type Type of product .
* @ param bool $include_variations Include variations in search or not .
* @ param bool $all_statuses Should we search all statuses or limit to published .
* @ param null | int $limit Limit returned results . @ since 3.5 . 0.
* @ param null | array $include Keep specific results . @ since 3.6 . 0.
* @ param null | array $exclude Discard specific results . @ since 3.6 . 0.
2016-11-24 11:50:34 +00:00
* @ return array of ids
*/
2019-04-09 02:33:50 +00:00
public function search_products ( $term , $type = '' , $include_variations = false , $all_statuses = false , $limit = null , $include = null , $exclude = null ) {
2016-11-24 11:50:34 +00:00
global $wpdb ;
2018-12-06 17:57:40 +00:00
$custom_results = apply_filters ( 'woocommerce_product_pre_search_products' , false , $term , $type , $include_variations , $all_statuses , $limit );
if ( is_array ( $custom_results ) ) {
return $custom_results ;
}
2019-05-29 00:05:26 +00:00
$post_types = $include_variations ? array ( 'product' , 'product_variation' ) : array ( 'product' );
2020-07-29 23:19:15 +00:00
$join_query = '' ;
2019-05-29 00:05:26 +00:00
$type_where = '' ;
$status_where = '' ;
$limit_query = '' ;
2020-07-29 23:19:15 +00:00
// When searching variations we should include the parent's meta table for use in searches.
if ( $include_variations ) {
$join_query = " LEFT JOIN { $wpdb -> wc_product_meta_lookup } parent_wc_product_meta_lookup
ON posts . post_type = 'product_variation' AND parent_wc_product_meta_lookup . product_id = posts . post_parent " ;
}
2019-05-29 00:05:26 +00:00
/**
* Hook woocommerce_search_products_post_statuses .
*
* @ since 3.7 . 0
* @ param array $post_statuses List of post statuses .
*/
$post_statuses = apply_filters (
'woocommerce_search_products_post_statuses' ,
current_user_can ( 'edit_private_products' ) ? array ( 'private' , 'publish' ) : array ( 'publish' )
);
2018-02-22 18:22:55 +00:00
2018-02-27 19:45:45 +00:00
// See if search term contains OR keywords.
2019-12-24 23:00:40 +00:00
if ( stristr ( $term , ' or ' ) ) {
$term_groups = preg_split ( '/\s+or\s+/i' , $term );
2018-02-22 18:22:55 +00:00
} else {
2018-02-27 19:45:45 +00:00
$term_groups = array ( $term );
2018-02-22 18:22:55 +00:00
}
2018-02-27 19:45:45 +00:00
$search_where = '' ;
$search_queries = array ();
foreach ( $term_groups as $term_group ) {
// Parse search terms.
if ( preg_match_all ( '/".*?("|$)|((?<=[\t ",+])|^)[^\t ",+]+/' , $term_group , $matches ) ) {
$search_terms = $this -> get_valid_search_terms ( $matches [ 0 ] );
$count = count ( $search_terms );
// if the search string has only short terms or stopwords, or is 10+ terms long, match it as sentence.
if ( 9 < $count || 0 === $count ) {
$search_terms = array ( $term_group );
}
} else {
$search_terms = array ( $term_group );
}
$term_group_query = '' ;
$searchand = '' ;
2018-02-22 18:22:55 +00:00
2018-02-27 19:45:45 +00:00
foreach ( $search_terms as $search_term ) {
2020-07-29 23:19:15 +00:00
$like = '%' . $wpdb -> esc_like ( $search_term ) . '%' ;
// Variations should also search the parent's meta table for fallback fields.
if ( $include_variations ) {
$variation_query = $wpdb -> prepare ( ' OR ( wc_product_meta_lookup.sku = "" AND parent_wc_product_meta_lookup.sku LIKE %s ) ' , $like );
} else {
$variation_query = '' ;
}
$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 ) $variation_query ) " , $like , $like , $like , $like ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
2018-02-27 19:45:45 +00:00
$searchand = ' AND ' ;
}
if ( $term_group_query ) {
$search_queries [] = $term_group_query ;
}
2018-02-22 18:22:55 +00:00
}
2018-06-07 06:35:58 +00:00
if ( ! empty ( $search_queries ) ) {
2019-04-09 02:33:50 +00:00
$search_where = ' AND (' . implode ( ') OR (' , $search_queries ) . ') ' ;
}
if ( ! empty ( $include ) && is_array ( $include ) ) {
$search_where .= ' AND posts.ID IN(' . implode ( ',' , array_map ( 'absint' , $include ) ) . ') ' ;
}
if ( ! empty ( $exclude ) && is_array ( $exclude ) ) {
2019-04-09 02:48:08 +00:00
$search_where .= ' AND posts.ID NOT IN(' . implode ( ',' , array_map ( 'absint' , $exclude ) ) . ') ' ;
2018-02-22 18:22:55 +00:00
}
2016-11-24 11:50:34 +00:00
2019-03-07 14:46:01 +00:00
if ( 'virtual' === $type ) {
$type_where = ' AND ( wc_product_meta_lookup.virtual = 1 ) ' ;
} elseif ( 'downloadable' === $type ) {
$type_where = ' AND ( wc_product_meta_lookup.downloadable = 1 ) ' ;
2016-11-24 11:50:34 +00:00
}
2018-02-08 12:53:39 +00:00
if ( ! $all_statuses ) {
$status_where = " AND posts.post_status IN (' " . implode ( " ',' " , $post_statuses ) . " ') " ;
}
2018-06-09 16:23:07 +00:00
if ( $limit ) {
$limit_query = $wpdb -> prepare ( ' LIMIT %d ' , $limit );
}
2018-01-03 17:57:33 +00:00
// phpcs:ignore WordPress.VIP.DirectDatabaseQuery.DirectQuery
2018-02-08 16:36:24 +00:00
$search_results = $wpdb -> get_results (
2018-01-03 17:57:33 +00:00
// phpcs:disable
2018-02-22 18:22:55 +00:00
" SELECT DISTINCT posts.ID as product_id, posts.post_parent as parent_id FROM { $wpdb -> posts } posts
2019-03-07 14:46:01 +00:00
LEFT JOIN { $wpdb -> wc_product_meta_lookup } wc_product_meta_lookup ON posts . ID = wc_product_meta_lookup . product_id
2020-07-29 23:19:15 +00:00
$join_query
2018-02-22 18:22:55 +00:00
WHERE posts . post_type IN ( '" . implode( "' , '", $post_types ) . "' )
$search_where
$status_where
$type_where
2018-06-09 16:23:07 +00:00
ORDER BY posts . post_parent ASC , posts . post_title ASC
$limit_query
"
2018-01-03 17:57:33 +00:00
// phpcs:enable
2016-11-24 11:50:34 +00:00
);
2018-02-08 16:36:24 +00:00
$product_ids = wp_parse_id_list ( array_merge ( wp_list_pluck ( $search_results , 'product_id' ), wp_list_pluck ( $search_results , 'parent_id' ) ) );
2016-11-24 11:50:34 +00:00
if ( is_numeric ( $term ) ) {
2016-12-28 14:41:21 +00:00
$post_id = absint ( $term );
$post_type = get_post_type ( $post_id );
2017-01-03 11:24:36 +00:00
if ( 'product_variation' === $post_type && $include_variations ) {
$product_ids [] = $post_id ;
} elseif ( 'product' === $post_type ) {
$product_ids [] = $post_id ;
2016-12-28 14:41:21 +00:00
}
2017-01-03 11:24:36 +00:00
$product_ids [] = wp_get_post_parent_id ( $post_id );
2016-11-24 11:50:34 +00:00
}
return wp_parse_id_list ( $product_ids );
}
2017-02-08 19:18:39 +00:00
/**
* Get the product type based on product ID .
*
2017-03-15 16:36:53 +00:00
* @ since 3.0 . 0
2018-01-03 17:57:33 +00:00
* @ param int $product_id Product ID .
2017-02-08 19:18:39 +00:00
* @ return bool | string
*/
public function get_product_type ( $product_id ) {
2019-01-30 14:57:20 +00:00
$cache_key = WC_Cache_Helper :: get_cache_prefix ( 'product_' . $product_id ) . '_type_' . $product_id ;
2019-01-30 14:13:34 +00:00
$product_type = wp_cache_get ( $cache_key , 'products' );
if ( $product_type ) {
return $product_type ;
}
2017-02-08 19:18:39 +00:00
$post_type = get_post_type ( $product_id );
2019-01-30 14:13:34 +00:00
2017-02-08 19:18:39 +00:00
if ( 'product_variation' === $post_type ) {
2019-01-30 14:13:34 +00:00
$product_type = 'variation' ;
2017-02-08 19:18:39 +00:00
} elseif ( 'product' === $post_type ) {
2019-01-30 14:13:34 +00:00
$terms = get_the_terms ( $product_id , 'product_type' );
2020-08-21 17:07:26 +00:00
$product_type = ! empty ( $terms ) && ! is_wp_error ( $terms ) ? sanitize_title ( current ( $terms ) -> name ) : 'simple' ;
2017-02-08 19:18:39 +00:00
} else {
2019-01-30 14:13:34 +00:00
$product_type = false ;
2017-02-08 19:18:39 +00:00
}
2019-01-30 14:13:34 +00:00
wp_cache_set ( $cache_key , $product_type , 'products' );
return $product_type ;
2017-02-08 19:18:39 +00:00
}
2017-07-12 20:58:39 +00:00
2017-07-28 20:27:19 +00:00
/**
* Add ability to get products by 'reviews_allowed' in WC_Product_Query .
*
* @ since 3.2 . 0
2018-01-03 17:57:33 +00:00
* @ param string $where Where clause .
* @ param WP_Query $wp_query WP_Query instance .
* @ return string
2017-07-28 20:27:19 +00:00
*/
2017-07-25 17:01:24 +00:00
public function reviews_allowed_query_where ( $where , $wp_query ) {
global $wpdb ;
if ( isset ( $wp_query -> query_vars [ 'reviews_allowed' ] ) && is_bool ( $wp_query -> query_vars [ 'reviews_allowed' ] ) ) {
if ( $wp_query -> query_vars [ 'reviews_allowed' ] ) {
$where .= " AND $wpdb->posts .comment_status = 'open' " ;
} else {
$where .= " AND $wpdb->posts .comment_status = 'closed' " ;
}
}
return $where ;
}
2017-07-12 20:58:39 +00:00
/**
* Get valid WP_Query args from a WC_Product_Query ' s query variables .
*
* @ since 3.2 . 0
2018-01-03 17:57:33 +00:00
* @ param array $query_vars Query vars from a WC_Product_Query .
2017-07-12 20:58:39 +00:00
* @ return array
*/
protected function get_wp_query_args ( $query_vars ) {
// Map query vars to ones that get_wp_query_args or WP_Query recognize.
$key_mapping = array (
2017-07-25 17:01:24 +00:00
'status' => 'post_status' ,
'page' => 'paged' ,
'include' => 'post__in' ,
'stock_quantity' => 'stock' ,
'average_rating' => 'wc_average_rating' ,
'review_count' => 'wc_review_count' ,
2017-07-12 20:58:39 +00:00
);
foreach ( $key_mapping as $query_key => $db_key ) {
if ( isset ( $query_vars [ $query_key ] ) ) {
$query_vars [ $db_key ] = $query_vars [ $query_key ];
unset ( $query_vars [ $query_key ] );
}
}
2017-07-28 20:27:19 +00:00
// Map boolean queries that are stored as 'yes'/'no' in the DB to 'yes' or 'no'.
2017-07-24 22:05:23 +00:00
$boolean_queries = array (
'virtual' ,
'downloadable' ,
'sold_individually' ,
'manage_stock' ,
);
foreach ( $boolean_queries as $boolean_query ) {
2017-08-01 20:21:23 +00:00
if ( isset ( $query_vars [ $boolean_query ] ) && '' !== $query_vars [ $boolean_query ] ) {
2017-07-24 22:05:23 +00:00
$query_vars [ $boolean_query ] = $query_vars [ $boolean_query ] ? 'yes' : 'no' ;
}
}
2017-07-28 18:59:44 +00:00
// These queries cannot be auto-generated so we have to remove them and build them manually.
$manual_queries = array (
2017-08-01 19:52:12 +00:00
'sku' => '' ,
'featured' => '' ,
2017-07-28 18:59:44 +00:00
'visibility' => '' ,
);
foreach ( $manual_queries as $key => $manual_query ) {
if ( isset ( $query_vars [ $key ] ) ) {
$manual_queries [ $key ] = $query_vars [ $key ];
unset ( $query_vars [ $key ] );
}
2017-07-25 17:01:24 +00:00
}
2017-07-12 20:58:39 +00:00
$wp_query_args = parent :: get_wp_query_args ( $query_vars );
2017-07-14 17:10:37 +00:00
if ( ! isset ( $wp_query_args [ 'date_query' ] ) ) {
$wp_query_args [ 'date_query' ] = array ();
}
if ( ! isset ( $wp_query_args [ 'meta_query' ] ) ) {
2019-01-30 15:31:48 +00:00
$wp_query_args [ 'meta_query' ] = array (); // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
2017-07-14 17:10:37 +00:00
}
2017-07-14 16:50:20 +00:00
// Handle product types.
if ( 'variation' === $query_vars [ 'type' ] ) {
$wp_query_args [ 'post_type' ] = 'product_variation' ;
2018-01-03 17:57:33 +00:00
} elseif ( is_array ( $query_vars [ 'type' ] ) && in_array ( 'variation' , $query_vars [ 'type' ], true ) ) {
2017-07-14 16:50:20 +00:00
$wp_query_args [ 'post_type' ] = array ( 'product_variation' , 'product' );
2019-01-30 15:31:48 +00:00
$wp_query_args [ 'tax_query' ][] = array ( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query
2017-07-14 16:50:20 +00:00
'relation' => 'OR' ,
array (
'taxonomy' => 'product_type' ,
'field' => 'slug' ,
'terms' => $query_vars [ 'type' ],
),
array (
'taxonomy' => 'product_type' ,
'field' => 'id' ,
'operator' => 'NOT EXISTS' ,
),
);
} else {
$wp_query_args [ 'post_type' ] = 'product' ;
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_type' ,
'field' => 'slug' ,
'terms' => $query_vars [ 'type' ],
);
}
2017-07-28 20:27:19 +00:00
// Handle product categories.
2017-07-14 16:50:20 +00:00
if ( ! empty ( $query_vars [ 'category' ] ) ) {
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_cat' ,
'field' => 'slug' ,
2017-08-01 19:52:12 +00:00
'terms' => $query_vars [ 'category' ],
2017-07-14 16:50:20 +00:00
);
}
2017-07-28 20:27:19 +00:00
// Handle product tags.
2017-07-14 16:50:20 +00:00
if ( ! empty ( $query_vars [ 'tag' ] ) ) {
unset ( $wp_query_args [ 'tag' ] );
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_tag' ,
'field' => 'slug' ,
2017-08-01 19:52:12 +00:00
'terms' => $query_vars [ 'tag' ],
2017-07-14 16:50:20 +00:00
);
}
2017-07-28 20:27:19 +00:00
// Handle shipping classes.
2017-07-28 18:59:44 +00:00
if ( ! empty ( $query_vars [ 'shipping_class' ] ) ) {
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_shipping_class' ,
'field' => 'slug' ,
2017-08-01 19:52:12 +00:00
'terms' => $query_vars [ 'shipping_class' ],
2017-07-25 17:01:24 +00:00
);
}
2017-07-28 20:27:19 +00:00
// Handle total_sales.
2017-07-25 17:01:24 +00:00
// This query doesn't get auto-generated since the meta key doesn't have the underscore prefix.
if ( isset ( $query_vars [ 'total_sales' ] ) && '' !== $query_vars [ 'total_sales' ] ) {
$wp_query_args [ 'meta_query' ][] = array (
'key' => 'total_sales' ,
'value' => absint ( $query_vars [ 'total_sales' ] ),
'compare' => '=' ,
);
}
2017-07-28 20:27:19 +00:00
// Handle SKU.
2017-07-28 18:59:44 +00:00
if ( $manual_queries [ 'sku' ] ) {
2018-08-22 23:59:39 +00:00
// Check for existing values if wildcard is used.
if ( '*' === $manual_queries [ 'sku' ] ) {
$wp_query_args [ 'meta_query' ][] = array (
2018-08-24 03:03:09 +00:00
array (
'key' => '_sku' ,
'compare' => 'EXISTS' ,
),
array (
'key' => '_sku' ,
'value' => '' ,
'compare' => '!=' ,
),
2018-08-22 23:59:39 +00:00
);
} else {
$wp_query_args [ 'meta_query' ][] = array (
'key' => '_sku' ,
'value' => $manual_queries [ 'sku' ],
'compare' => 'LIKE' ,
);
}
2017-07-14 16:50:20 +00:00
}
2017-07-28 20:27:19 +00:00
// Handle featured.
2017-07-28 18:59:44 +00:00
if ( '' !== $manual_queries [ 'featured' ] ) {
2017-07-25 17:01:24 +00:00
$product_visibility_term_ids = wc_get_product_visibility_term_ids ();
2017-07-28 18:59:44 +00:00
if ( $manual_queries [ 'featured' ] ) {
2017-07-25 17:01:24 +00:00
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_visibility' ,
'field' => 'term_taxonomy_id' ,
'terms' => array ( $product_visibility_term_ids [ 'featured' ] ),
);
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_visibility' ,
'field' => 'term_taxonomy_id' ,
'terms' => array ( $product_visibility_term_ids [ 'exclude-from-catalog' ] ),
'operator' => 'NOT IN' ,
);
} else {
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_visibility' ,
'field' => 'term_taxonomy_id' ,
'terms' => array ( $product_visibility_term_ids [ 'featured' ] ),
'operator' => 'NOT IN' ,
);
}
}
2017-07-28 20:27:19 +00:00
// Handle visibility.
2017-07-28 18:59:44 +00:00
if ( $manual_queries [ 'visibility' ] ) {
switch ( $manual_queries [ 'visibility' ] ) {
case 'search' :
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_visibility' ,
2017-08-01 19:52:12 +00:00
'field' => 'slug' ,
'terms' => array ( 'exclude-from-search' ),
2017-07-28 18:59:44 +00:00
'operator' => 'NOT IN' ,
);
2018-01-03 16:44:37 +00:00
break ;
2017-07-28 18:59:44 +00:00
case 'catalog' :
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_visibility' ,
2017-08-01 19:52:12 +00:00
'field' => 'slug' ,
'terms' => array ( 'exclude-from-catalog' ),
2017-07-28 18:59:44 +00:00
'operator' => 'NOT IN' ,
);
2018-01-03 16:44:37 +00:00
break ;
2017-07-28 18:59:44 +00:00
case 'visible' :
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_visibility' ,
2017-08-01 19:52:12 +00:00
'field' => 'slug' ,
'terms' => array ( 'exclude-from-catalog' , 'exclude-from-search' ),
2017-07-28 18:59:44 +00:00
'operator' => 'NOT IN' ,
);
2018-01-03 16:44:37 +00:00
break ;
2017-07-28 18:59:44 +00:00
case 'hidden' :
$wp_query_args [ 'tax_query' ][] = array (
'taxonomy' => 'product_visibility' ,
2017-08-01 19:52:12 +00:00
'field' => 'slug' ,
'terms' => array ( 'exclude-from-catalog' , 'exclude-from-search' ),
2017-07-28 18:59:44 +00:00
'operator' => 'AND' ,
);
2018-01-03 16:44:37 +00:00
break ;
2017-07-28 18:59:44 +00:00
}
}
2017-07-28 20:27:19 +00:00
// Handle date queries.
2017-07-12 20:58:39 +00:00
$date_queries = array (
'date_created' => 'post_date' ,
'date_modified' => 'post_modified' ,
'date_on_sale_from' => '_sale_price_dates_from' ,
'date_on_sale_to' => '_sale_price_dates_to' ,
);
foreach ( $date_queries as $query_var_key => $db_key ) {
if ( isset ( $query_vars [ $query_var_key ] ) && '' !== $query_vars [ $query_var_key ] ) {
// Remove any existing meta queries for the same keys to prevent conflicts.
$existing_queries = wp_list_pluck ( $wp_query_args [ 'meta_query' ], 'key' , true );
foreach ( $existing_queries as $query_index => $query_contents ) {
unset ( $wp_query_args [ 'meta_query' ][ $query_index ] );
}
$wp_query_args = $this -> parse_date_for_wp_query ( $query_vars [ $query_var_key ], $db_key , $wp_query_args );
}
}
2017-07-28 20:27:19 +00:00
// Handle paginate.
2017-07-12 20:58:39 +00:00
if ( ! isset ( $query_vars [ 'paginate' ] ) || ! $query_vars [ 'paginate' ] ) {
$wp_query_args [ 'no_found_rows' ] = true ;
}
2017-07-28 20:27:19 +00:00
// Handle reviews_allowed.
2017-07-25 17:01:24 +00:00
if ( isset ( $query_vars [ 'reviews_allowed' ] ) && is_bool ( $query_vars [ 'reviews_allowed' ] ) ) {
add_filter ( 'posts_where' , array ( $this , 'reviews_allowed_query_where' ), 10 , 2 );
}
2019-08-01 18:55:32 +00:00
// Handle orderby.
if ( isset ( $query_vars [ 'orderby' ] ) && 'include' === $query_vars [ 'orderby' ] ) {
$wp_query_args [ 'orderby' ] = 'post__in' ;
}
2017-07-12 20:58:39 +00:00
return apply_filters ( 'woocommerce_product_data_store_cpt_get_products_query' , $wp_query_args , $query_vars , $this );
}
/**
* Query for Products matching specific criteria .
*
* @ since 3.2 . 0
*
2018-01-03 17:57:33 +00:00
* @ param array $query_vars Query vars from a WC_Product_Query .
2017-07-12 20:58:39 +00:00
*
* @ return array | object
*/
public function query ( $query_vars ) {
$args = $this -> get_wp_query_args ( $query_vars );
if ( ! empty ( $args [ 'errors' ] ) ) {
$query = ( object ) array (
'posts' => array (),
'found_posts' => 0 ,
'max_num_pages' => 0 ,
);
} else {
$query = new WP_Query ( $args );
}
2017-07-14 17:01:25 +00:00
if ( isset ( $query_vars [ 'return' ] ) && 'objects' === $query_vars [ 'return' ] && ! empty ( $query -> posts ) ) {
// Prime caches before grabbing objects.
2017-08-29 10:19:00 +00:00
update_post_caches ( $query -> posts , array ( 'product' , 'product_variation' ) );
2017-07-14 17:01:25 +00:00
}
2017-07-12 20:58:39 +00:00
$products = ( isset ( $query_vars [ 'return' ] ) && 'ids' === $query_vars [ 'return' ] ) ? $query -> posts : array_filter ( array_map ( 'wc_get_product' , $query -> posts ) );
if ( isset ( $query_vars [ 'paginate' ] ) && $query_vars [ 'paginate' ] ) {
return ( object ) array (
'products' => $products ,
'total' => $query -> found_posts ,
'max_num_pages' => $query -> max_num_pages ,
);
}
return $products ;
}
2019-02-12 13:23:47 +00:00
/**
* Get data to save to a lookup table .
*
* @ since 3.6 . 0
* @ param int $id ID of object to update .
* @ param string $table Lookup table name .
* @ return array
*/
2019-05-15 12:20:27 +00:00
protected function get_data_for_lookup_table ( $id , $table ) {
2019-02-15 12:37:45 +00:00
if ( 'wc_product_meta_lookup' === $table ) {
2019-02-15 13:05:46 +00:00
$price_meta = ( array ) get_post_meta ( $id , '_price' , false );
2019-05-16 08:57:42 +00:00
$manage_stock = get_post_meta ( $id , '_manage_stock' , true );
$stock = 'yes' === $manage_stock ? wc_stock_amount ( get_post_meta ( $id , '_stock' , true ) ) : null ;
2019-03-13 17:33:44 +00:00
$price = wc_format_decimal ( get_post_meta ( $id , '_price' , true ) );
$sale_price = wc_format_decimal ( get_post_meta ( $id , '_sale_price' , true ) );
2019-02-12 13:23:47 +00:00
return array (
'product_id' => absint ( $id ),
2019-03-07 13:34:28 +00:00
'sku' => get_post_meta ( $id , '_sku' , true ),
2019-03-07 14:32:20 +00:00
'virtual' => 'yes' === get_post_meta ( $id , '_virtual' , true ) ? 1 : 0 ,
'downloadable' => 'yes' === get_post_meta ( $id , '_downloadable' , true ) ? 1 : 0 ,
2019-02-12 13:23:47 +00:00
'min_price' => reset ( $price_meta ),
'max_price' => end ( $price_meta ),
2019-03-13 17:33:44 +00:00
'onsale' => $sale_price && $price === $sale_price ? 1 : 0 ,
2019-03-07 14:32:20 +00:00
'stock_quantity' => $stock ,
'stock_status' => get_post_meta ( $id , '_stock_status' , true ),
2019-03-06 14:03:44 +00:00
'rating_count' => array_sum ( ( array ) get_post_meta ( $id , '_wc_rating_count' , true ) ),
2019-02-12 13:23:47 +00:00
'average_rating' => get_post_meta ( $id , '_wc_average_rating' , true ),
'total_sales' => get_post_meta ( $id , 'total_sales' , true ),
2020-01-20 14:17:42 +00:00
'tax_status' => get_post_meta ( $id , '_tax_status' , true ),
'tax_class' => get_post_meta ( $id , '_tax_class' , true ),
2019-02-12 13:23:47 +00:00
);
}
return array ();
}
2019-03-07 15:08:22 +00:00
/**
* Get primary key name for lookup table .
*
* @ since 3.6 . 0
* @ param string $table Lookup table name .
* @ return string
*/
protected function get_primary_key_for_lookup_table ( $table ) {
if ( 'wc_product_meta_lookup' === $table ) {
return 'product_id' ;
}
return '' ;
}
2020-05-06 16:14:53 +00:00
/**
* Returns query statement for getting current `_stock` of a product .
*
* @ internal MAX function below is used to make sure result is a scalar .
* @ param int $product_id Product ID .
* @ return string | void Query statement .
*/
public function get_query_for_stock ( $product_id ) {
global $wpdb ;
return $wpdb -> prepare (
"
SELECT COALESCE ( MAX ( meta_value ), 0 ) FROM $wpdb -> postmeta as meta_table
WHERE meta_table . meta_key = '_stock'
AND meta_table . post_id = % d
" ,
$product_id
);
}
2016-11-11 14:31:15 +00:00
}