2012-11-21 18:07:45 +00:00
< ? php
2014-09-20 18:57:58 +00:00
2014-08-13 14:03:30 +00:00
if ( ! defined ( 'ABSPATH' ) ) {
2014-09-20 18:57:58 +00:00
exit ; // Exit if accessed directly
2014-08-13 14:03:30 +00:00
}
2013-02-20 17:14:46 +00:00
2012-11-21 18:07:45 +00:00
/**
2015-11-03 13:31:20 +00:00
* Variable Product Class .
2012-11-21 18:07:45 +00:00
*
* The WooCommerce product class handles individual product data .
*
* @ class WC_Product_Variable
2012-12-03 19:19:58 +00:00
* @ version 2.0 . 0
2012-11-21 18:07:45 +00:00
* @ package WooCommerce / Classes / Products
2013-02-20 17:14:46 +00:00
* @ category Class
2012-11-21 18:07:45 +00:00
* @ author WooThemes
*/
class WC_Product_Variable extends WC_Product {
2012-11-27 16:22:47 +00:00
2012-12-14 21:52:46 +00:00
/** @public array Array of child products/posts/variations. */
2015-06-08 12:18:23 +00:00
public $children = null ;
2012-11-21 18:07:45 +00:00
2015-09-14 14:09:57 +00:00
/** @private array Array of variation prices. */
2015-10-09 11:49:39 +00:00
private $prices_array = array ();
2015-09-14 14:09:57 +00:00
2012-11-21 18:07:45 +00:00
/**
2015-11-03 13:31:20 +00:00
* Constructor .
2012-11-27 16:22:47 +00:00
*
2012-11-21 18:07:45 +00:00
* @ param mixed $product
*/
2012-12-19 23:04:25 +00:00
public function __construct ( $product ) {
2012-12-20 11:54:38 +00:00
$this -> product_type = 'variable' ;
2012-11-22 10:22:18 +00:00
parent :: __construct ( $product );
2012-11-21 18:07:45 +00:00
}
2013-09-25 11:35:06 +00:00
/**
2015-11-03 13:31:20 +00:00
* Get the add to cart button text .
2013-09-25 11:35:06 +00:00
*
* @ access public
* @ return string
*/
public function add_to_cart_text () {
2016-06-30 18:16:26 +00:00
$text = $this -> is_purchasable () && $this -> is_in_stock () ? __ ( 'Select options' , 'woocommerce' ) : __ ( 'Read more' , 'woocommerce' );
2016-06-07 13:34:27 +00:00
return apply_filters ( 'woocommerce_product_add_to_cart_text' , $text , $this );
2013-09-25 11:35:06 +00:00
}
2012-12-28 09:59:20 +00:00
/**
* Set stock level of the product .
*
* @ param mixed $amount ( default : null )
2014-05-10 20:45:14 +00:00
* @ param string $mode can be set , add , or subtract
2012-12-28 09:59:20 +00:00
* @ return int Stock
*/
2014-06-24 12:03:25 +00:00
public function set_stock ( $amount = null , $mode = 'set' ) {
2013-08-13 15:56:09 +00:00
$this -> total_stock = '' ;
2015-06-10 12:41:29 +00:00
delete_transient ( 'wc_product_total_stock_' . $this -> id . WC_Cache_Helper :: get_transient_version ( 'product' ) );
2014-05-10 20:45:14 +00:00
return parent :: set_stock ( $amount , $mode );
2012-12-28 09:59:20 +00:00
}
2014-06-24 12:03:25 +00:00
/**
2015-11-03 13:31:20 +00:00
* Performed after a stock level change at product level .
2014-06-24 12:03:25 +00:00
*/
2015-07-27 18:04:08 +00:00
public function check_stock_status () {
2014-06-24 12:03:25 +00:00
$set_child_stock_status = '' ;
if ( ! $this -> backorders_allowed () && $this -> get_stock_quantity () <= get_option ( 'woocommerce_notify_no_stock_amount' ) ) {
$set_child_stock_status = 'outofstock' ;
} elseif ( $this -> backorders_allowed () || $this -> get_stock_quantity () > get_option ( 'woocommerce_notify_no_stock_amount' ) ) {
$set_child_stock_status = 'instock' ;
}
if ( $set_child_stock_status ) {
foreach ( $this -> get_children () as $child_id ) {
if ( 'yes' !== get_post_meta ( $child_id , '_manage_stock' , true ) ) {
wc_update_product_stock_status ( $child_id , $set_child_stock_status );
}
}
// Children statuses changed, so sync self
self :: sync_stock_status ( $this -> id );
}
}
/**
2015-07-16 19:55:48 +00:00
* Set stock status .
2014-06-24 12:03:25 +00:00
*/
public function set_stock_status ( $status ) {
2014-08-13 14:03:30 +00:00
$status = 'outofstock' === $status ? 'outofstock' : 'instock' ;
2014-06-24 12:03:25 +00:00
if ( update_post_meta ( $this -> id , '_stock_status' , $status ) ) {
do_action ( 'woocommerce_product_set_stock_status' , $this -> id , $status );
}
}
2012-11-21 18:07:45 +00:00
/**
2015-06-08 12:18:23 +00:00
* Return a products child ids .
2014-05-10 20:45:14 +00:00
*
2014-03-19 10:06:13 +00:00
* @ param boolean $visible_only Only return variations which are not hidden
* @ return array of children ids
2012-11-21 18:07:45 +00:00
*/
2014-03-19 10:06:13 +00:00
public function get_children ( $visible_only = false ) {
2015-06-09 09:32:38 +00:00
$key = $visible_only ? 'visible' : 'all' ;
2015-07-09 15:02:26 +00:00
$transient_name = 'wc_product_children_' . $this -> id ;
2015-06-09 09:32:38 +00:00
// Get value of transient
2015-06-08 12:18:23 +00:00
if ( ! is_array ( $this -> children ) ) {
2014-08-12 11:06:18 +00:00
$this -> children = get_transient ( $transient_name );
2015-06-09 09:32:38 +00:00
}
2012-11-21 18:07:45 +00:00
2015-06-09 09:32:38 +00:00
// Get value from DB
if ( empty ( $this -> children ) || ! is_array ( $this -> children ) || ! isset ( $this -> children [ $key ] ) ) {
$args = array (
'post_parent' => $this -> id ,
'post_type' => 'product_variation' ,
'orderby' => 'menu_order' ,
'order' => 'ASC' ,
'fields' => 'ids' ,
'post_status' => 'publish' ,
2016-08-27 01:46:45 +00:00
'numberposts' => - 1 ,
2015-06-09 09:32:38 +00:00
);
2015-08-13 22:35:41 +00:00
2015-06-09 09:32:38 +00:00
if ( $visible_only ) {
2015-08-11 09:33:47 +00:00
if ( 'yes' === get_option ( 'woocommerce_hide_out_of_stock_items' ) ) {
$args [ 'meta_query' ][] = array (
2015-06-09 09:32:38 +00:00
'key' => '_stock_status' ,
'value' => 'instock' ,
'compare' => '=' ,
2015-08-11 09:33:47 +00:00
);
}
2014-03-19 10:06:13 +00:00
}
2015-08-13 21:42:16 +00:00
2015-08-13 22:35:41 +00:00
$args = apply_filters ( 'woocommerce_variable_children_args' , $args , $this , $visible_only );
$this -> children [ $key ] = get_posts ( $args );
2015-06-09 09:32:38 +00:00
set_transient ( $transient_name , $this -> children , DAY_IN_SECONDS * 30 );
2012-12-05 17:57:53 +00:00
}
2012-11-21 18:07:45 +00:00
2015-06-09 09:32:38 +00:00
return apply_filters ( 'woocommerce_get_children' , $this -> children [ $key ], $this , $visible_only );
2012-11-21 18:07:45 +00:00
}
/**
2016-01-06 15:15:00 +00:00
* Get child product .
2012-11-21 18:07:45 +00:00
*
* @ access public
* @ param mixed $child_id
2016-06-06 17:57:24 +00:00
* @ return WC_Product_Variation
2012-11-21 18:07:45 +00:00
*/
2012-12-14 21:52:46 +00:00
public function get_child ( $child_id ) {
2014-10-07 09:49:54 +00:00
return wc_get_product ( $child_id , array (
2012-11-29 16:48:40 +00:00
'parent_id' => $this -> id ,
2016-08-27 01:46:45 +00:00
'parent' => $this ,
2014-08-13 14:03:30 +00:00
) );
2012-11-21 18:07:45 +00:00
}
/**
* Returns whether or not the product has any child product .
*
* @ access public
* @ return bool
*/
2012-12-14 21:52:46 +00:00
public function has_child () {
2012-11-21 18:07:45 +00:00
return sizeof ( $this -> get_children () ) ? true : false ;
}
/**
* Returns whether or not the product is on sale .
* @ return bool
*/
2012-12-14 21:52:46 +00:00
public function is_on_sale () {
2015-01-22 16:05:59 +00:00
$is_on_sale = false ;
2015-03-25 13:35:49 +00:00
$prices = $this -> get_variation_prices ();
2016-02-11 15:22:49 +00:00
2015-08-10 14:36:40 +00:00
if ( $prices [ 'regular_price' ] !== $prices [ 'sale_price' ] && $prices [ 'sale_price' ] === $prices [ 'price' ] ) {
2015-03-25 13:35:49 +00:00
$is_on_sale = true ;
2012-11-29 16:48:40 +00:00
}
2015-01-22 13:02:43 +00:00
return apply_filters ( 'woocommerce_product_is_on_sale' , $is_on_sale , $this );
2012-11-21 18:07:45 +00:00
}
2013-07-23 10:28:59 +00:00
/**
* Get the min or max variation regular price .
* @ param string $min_or_max - min or max
2013-11-08 15:53:52 +00:00
* @ param boolean $display Whether the value is going to be displayed
2013-07-23 10:28:59 +00:00
* @ return string
*/
2013-11-08 15:53:52 +00:00
public function get_variation_regular_price ( $min_or_max = 'min' , $display = false ) {
2015-03-25 13:35:49 +00:00
$prices = $this -> get_variation_prices ( $display );
$price = 'min' === $min_or_max ? current ( $prices [ 'regular_price' ] ) : end ( $prices [ 'regular_price' ] );
2013-12-07 09:11:23 +00:00
return apply_filters ( 'woocommerce_get_variation_regular_price' , $price , $this , $min_or_max , $display );
2013-07-23 10:28:59 +00:00
}
/**
* Get the min or max variation sale price .
* @ param string $min_or_max - min or max
2013-11-08 15:53:52 +00:00
* @ param boolean $display Whether the value is going to be displayed
2013-07-23 10:28:59 +00:00
* @ return string
*/
2013-11-08 15:53:52 +00:00
public function get_variation_sale_price ( $min_or_max = 'min' , $display = false ) {
2015-03-25 13:35:49 +00:00
$prices = $this -> get_variation_prices ( $display );
$price = 'min' === $min_or_max ? current ( $prices [ 'sale_price' ] ) : end ( $prices [ 'sale_price' ] );
2013-12-07 09:11:23 +00:00
return apply_filters ( 'woocommerce_get_variation_sale_price' , $price , $this , $min_or_max , $display );
2013-07-23 10:28:59 +00:00
}
/**
* Get the min or max variation ( active ) price .
* @ param string $min_or_max - min or max
2013-11-08 15:53:52 +00:00
* @ param boolean $display Whether the value is going to be displayed
2013-07-23 10:28:59 +00:00
* @ return string
*/
2013-11-08 15:53:52 +00:00
public function get_variation_price ( $min_or_max = 'min' , $display = false ) {
2015-03-25 13:35:49 +00:00
$prices = $this -> get_variation_prices ( $display );
$price = 'min' === $min_or_max ? current ( $prices [ 'price' ] ) : end ( $prices [ 'price' ] );
return apply_filters ( 'woocommerce_get_variation_price' , $price , $this , $min_or_max , $display );
}
2014-05-10 20:45:14 +00:00
2015-03-25 13:35:49 +00:00
/**
2015-09-09 21:29:54 +00:00
* Get an array of all sale and regular prices from all variations . This is used for example when displaying the price range at variable product level or seeing if the variable product is on sale .
*
* Can be filtered by plugins which modify costs , but otherwise will include the raw meta costs unlike get_price () which runs costs through the woocommerce_get_price filter .
* This is to ensure modified prices are not cached , unless intended .
*
* @ param bool $display Are prices for display ? If so , taxes will be calculated .
* @ return array () Array of RAW prices , regular prices , and sale prices with keys set to variation ID .
2015-03-25 13:35:49 +00:00
*/
public function get_variation_prices ( $display = false ) {
2015-09-07 16:13:41 +00:00
global $wp_filter ;
2015-09-14 14:09:57 +00:00
2015-08-26 10:00:48 +00:00
/**
2015-11-13 20:01:42 +00:00
* Transient name for storing prices for this product ( note : Max transient length is 45 )
2015-10-09 11:49:39 +00:00
* @ since 2.5 . 0 a single transient is used per product for all prices , rather than many transients per product .
*/
2015-11-13 20:01:42 +00:00
$transient_name = 'wc_var_prices_' . $this -> id ;
2015-10-09 11:49:39 +00:00
/**
* Create unique cache key based on the tax location ( affects displayed / cached prices ), product version and active price filters .
* DEVELOPERS should filter this hash if offering conditonal pricing to keep it unique .
* @ var string
2015-08-26 10:00:48 +00:00
*/
2015-10-09 11:49:39 +00:00
if ( $display ) {
2015-12-31 16:21:12 +00:00
$price_hash = array ( get_option ( 'woocommerce_tax_display_shop' , 'excl' ), WC_Tax :: get_rates () );
2015-10-09 11:49:39 +00:00
} else {
$price_hash = array ( false );
}
2015-08-26 10:00:48 +00:00
2015-11-27 13:00:08 +00:00
$filter_names = array ( 'woocommerce_variation_prices_price' , 'woocommerce_variation_prices_regular_price' , 'woocommerce_variation_prices_sale_price' );
foreach ( $filter_names as $filter_name ) {
2015-11-17 08:27:44 +00:00
if ( ! empty ( $wp_filter [ $filter_name ] ) ) {
2015-12-31 16:21:12 +00:00
$price_hash [ $filter_name ] = array ();
foreach ( $wp_filter [ $filter_name ] as $priority => $callbacks ) {
$price_hash [ $filter_name ][] = array_values ( wp_list_pluck ( $callbacks , 'function' ) );
}
2015-09-07 16:13:41 +00:00
}
}
2015-03-25 13:35:49 +00:00
2016-07-25 12:33:35 +00:00
$price_hash [] = WC_Cache_Helper :: get_transient_version ( 'product' );
$price_hash = md5 ( json_encode ( apply_filters ( 'woocommerce_get_variation_prices_hash' , $price_hash , $this , $display ) ) );
2015-10-09 11:49:39 +00:00
2016-08-31 16:52:03 +00:00
/**
* $this -> prices_array is an array of values which may have been modified from what is stored in transients - this may not match $transient_cached_prices_array .
* If the value has already been generated , we don ' t need to grab the values again so just return them . They are already filtered .
*/
if ( ! empty ( $this -> prices_array [ $price_hash ] ) ) {
return $this -> prices_array [ $price_hash ];
2015-10-09 11:49:39 +00:00
2016-08-31 16:52:03 +00:00
/**
* No locally cached value ? Get the data from the transient or generate it .
*/
} else {
2016-01-13 15:07:47 +00:00
// Get value of transient
2016-08-31 16:52:03 +00:00
$transient_cached_prices_array = array_filter ( ( array ) json_decode ( strval ( get_transient ( $transient_name ) ), true ) );
2015-09-14 14:09:57 +00:00
2016-08-31 16:52:03 +00:00
// If the product version has changed since the transient was last saved, reset the transient cache.
2016-09-07 22:32:24 +00:00
if ( empty ( $transient_cached_prices_array [ 'version' ] ) || WC_Cache_Helper :: get_transient_version ( 'product' ) !== $transient_cached_prices_array [ 'version' ] ) {
2016-08-31 16:52:03 +00:00
$transient_cached_prices_array = array ( 'version' => WC_Cache_Helper :: get_transient_version ( 'product' ) );
2016-01-13 15:07:47 +00:00
}
2015-12-31 16:31:20 +00:00
2016-08-31 16:52:03 +00:00
// If the prices are not stored for this hash, generate them and add to the transient.
if ( empty ( $transient_cached_prices_array [ $price_hash ] ) ) {
2016-01-13 15:07:47 +00:00
$prices = array ();
$regular_prices = array ();
$sale_prices = array ();
$variation_ids = $this -> get_children ( true );
foreach ( $variation_ids as $variation_id ) {
if ( $variation = $this -> get_child ( $variation_id ) ) {
$price = apply_filters ( 'woocommerce_variation_prices_price' , $variation -> price , $variation , $this );
$regular_price = apply_filters ( 'woocommerce_variation_prices_regular_price' , $variation -> regular_price , $variation , $this );
$sale_price = apply_filters ( 'woocommerce_variation_prices_sale_price' , $variation -> sale_price , $variation , $this );
2016-05-05 15:05:40 +00:00
// Skip empty prices
if ( '' === $price ) {
continue ;
}
2016-01-13 15:07:47 +00:00
// If sale price does not equal price, the product is not yet on sale
if ( $sale_price === $regular_price || $sale_price !== $price ) {
$sale_price = $regular_price ;
}
2013-12-03 14:54:09 +00:00
2016-01-13 15:07:47 +00:00
// If we are getting prices for display, we need to account for taxes
if ( $display ) {
if ( 'incl' === get_option ( 'woocommerce_tax_display_shop' ) ) {
$price = '' === $price ? '' : $variation -> get_price_including_tax ( 1 , $price );
$regular_price = '' === $regular_price ? '' : $variation -> get_price_including_tax ( 1 , $regular_price );
$sale_price = '' === $sale_price ? '' : $variation -> get_price_including_tax ( 1 , $sale_price );
} else {
$price = '' === $price ? '' : $variation -> get_price_excluding_tax ( 1 , $price );
$regular_price = '' === $regular_price ? '' : $variation -> get_price_excluding_tax ( 1 , $regular_price );
$sale_price = '' === $sale_price ? '' : $variation -> get_price_excluding_tax ( 1 , $sale_price );
}
2015-09-14 14:09:57 +00:00
}
2015-03-25 13:35:49 +00:00
2016-02-11 15:22:49 +00:00
$prices [ $variation_id ] = wc_format_decimal ( $price , wc_get_price_decimals () );
$regular_prices [ $variation_id ] = wc_format_decimal ( $regular_price , wc_get_price_decimals () );
$sale_prices [ $variation_id ] = wc_format_decimal ( $sale_price . '.00' , wc_get_price_decimals () );
2016-01-13 15:07:47 +00:00
}
2015-03-25 13:35:49 +00:00
}
2016-01-13 15:07:47 +00:00
asort ( $prices );
asort ( $regular_prices );
asort ( $sale_prices );
2015-03-25 13:35:49 +00:00
2016-08-31 16:52:03 +00:00
$transient_cached_prices_array [ $price_hash ] = array (
2016-01-13 15:07:47 +00:00
'price' => $prices ,
'regular_price' => $regular_prices ,
2016-02-11 15:22:49 +00:00
'sale_price' => $sale_prices ,
2016-01-13 15:07:47 +00:00
);
2016-08-31 16:52:03 +00:00
set_transient ( $transient_name , json_encode ( $transient_cached_prices_array ), DAY_IN_SECONDS * 30 );
2016-01-13 15:07:47 +00:00
}
2015-03-25 13:35:49 +00:00
2016-01-13 15:07:47 +00:00
/**
2016-08-31 16:52:03 +00:00
* Give plugins one last chance to filter the variation prices array which has been generated and store locally to the class .
* This value may differ from the transient cache . It is filtered once before storing locally .
2016-01-13 15:07:47 +00:00
*/
2016-08-31 16:52:03 +00:00
return $this -> prices_array [ $price_hash ] = apply_filters ( 'woocommerce_variation_prices' , $transient_cached_prices_array [ $price_hash ], $this , $display );
2013-11-08 15:53:52 +00:00
}
2013-07-23 10:28:59 +00:00
}
2012-11-21 18:07:45 +00:00
/**
* Returns the price in html format .
*
* @ access public
* @ param string $price ( default : '' )
* @ return string
*/
2012-12-14 21:52:46 +00:00
public function get_price_html ( $price = '' ) {
2015-08-11 09:33:47 +00:00
$prices = $this -> get_variation_prices ( true );
// No variations, or no active variation prices
if ( $this -> get_price () === '' || empty ( $prices [ 'price' ] ) ) {
2013-09-19 15:31:54 +00:00
$price = apply_filters ( 'woocommerce_variable_empty_price_html' , '' , $this );
2013-12-04 12:08:11 +00:00
} else {
2015-03-25 15:23:28 +00:00
$min_price = current ( $prices [ 'price' ] );
$max_price = end ( $prices [ 'price' ] );
$price = $min_price !== $max_price ? sprintf ( _x ( '%1$s–%2$s' , 'Price range: from-to' , 'woocommerce' ), wc_price ( $min_price ), wc_price ( $max_price ) ) : wc_price ( $min_price );
2016-09-07 22:32:24 +00:00
$is_free = 0 == $min_price && 0 == $max_price ;
2015-03-25 15:23:28 +00:00
if ( $this -> is_on_sale () ) {
$min_regular_price = current ( $prices [ 'regular_price' ] );
$max_regular_price = end ( $prices [ 'regular_price' ] );
$regular_price = $min_regular_price !== $max_regular_price ? sprintf ( _x ( '%1$s–%2$s' , 'Price range: from-to' , 'woocommerce' ), wc_price ( $min_regular_price ), wc_price ( $max_regular_price ) ) : wc_price ( $min_regular_price );
$price = apply_filters ( 'woocommerce_variable_sale_price_html' , $this -> get_price_html_from_to ( $regular_price , $price ) . $this -> get_price_suffix (), $this );
} elseif ( $is_free ) {
2015-03-25 13:35:49 +00:00
$price = apply_filters ( 'woocommerce_variable_free_price_html' , __ ( 'Free!' , 'woocommerce' ), $this );
2012-11-21 18:07:45 +00:00
} else {
2015-03-25 13:35:49 +00:00
$price = apply_filters ( 'woocommerce_variable_price_html' , $price . $this -> get_price_suffix (), $this );
2012-11-21 18:07:45 +00:00
}
}
return apply_filters ( 'woocommerce_get_price_html' , $price , $this );
}
2015-03-27 15:15:40 +00:00
/**
* Return an array of attributes used for variations , as well as their possible values .
*
* @ return array of attributes and their available values
*/
public function get_variation_attributes () {
2016-02-15 15:53:43 +00:00
global $wpdb ;
2012-11-21 18:07:45 +00:00
2016-02-15 15:53:43 +00:00
$variation_attributes = array ();
$attributes = $this -> get_attributes ();
$child_ids = $this -> get_children ( true );
2013-10-10 15:34:44 +00:00
2016-02-15 15:53:43 +00:00
if ( ! empty ( $child_ids ) ) {
foreach ( $attributes as $attribute ) {
if ( empty ( $attribute [ 'is_variation' ] ) ) {
continue ;
2015-03-27 15:15:40 +00:00
}
2015-06-11 14:42:18 +00:00
2016-02-15 15:53:43 +00:00
// Get possible values for this attribute, for only visible variations.
$values = array_unique ( $wpdb -> get_col ( $wpdb -> prepare (
" SELECT meta_value FROM { $wpdb -> postmeta } WHERE meta_key = %s AND post_id IN ( " . implode ( ',' , array_map ( 'esc_sql' , $child_ids ) ) . " ) " ,
wc_variation_attribute_name ( $attribute [ 'name' ] )
) ) );
// empty value indicates that all options for given attribute are available
2016-10-14 11:09:24 +00:00
if ( in_array ( '' , $values ) || empty ( $values ) ) {
2016-02-15 15:53:43 +00:00
$values = $attribute [ 'is_taxonomy' ] ? wp_get_post_terms ( $this -> id , $attribute [ 'name' ], array ( 'fields' => 'slugs' ) ) : wc_get_text_attributes ( $attribute [ 'value' ] );
// Get custom attributes (non taxonomy) as defined
} elseif ( ! $attribute [ 'is_taxonomy' ] ) {
$text_attributes = wc_get_text_attributes ( $attribute [ 'value' ] );
$assigned_text_attributes = $values ;
$values = array ();
// Pre 2.4 handling where 'slugs' were saved instead of the full text attribute
if ( version_compare ( get_post_meta ( $this -> id , '_product_version' , true ), '2.4.0' , '<' ) ) {
$assigned_text_attributes = array_map ( 'sanitize_title' , $assigned_text_attributes );
foreach ( $text_attributes as $text_attribute ) {
if ( in_array ( sanitize_title ( $text_attribute ), $assigned_text_attributes ) ) {
$values [] = $text_attribute ;
}
2015-06-11 13:43:02 +00:00
}
2016-02-15 15:53:43 +00:00
} else {
foreach ( $text_attributes as $text_attribute ) {
if ( in_array ( $text_attribute , $assigned_text_attributes ) ) {
$values [] = $text_attribute ;
}
2015-06-11 13:43:02 +00:00
}
}
2015-03-27 15:15:40 +00:00
}
2012-11-21 18:07:45 +00:00
2016-02-15 15:53:43 +00:00
$variation_attributes [ $attribute [ 'name' ] ] = array_unique ( $values );
}
2015-03-27 15:15:40 +00:00
}
2012-11-21 18:07:45 +00:00
2015-03-27 15:15:40 +00:00
return $variation_attributes ;
}
2012-11-21 18:07:45 +00:00
2015-03-27 15:15:40 +00:00
/**
* If set , get the default attributes for a variable product .
*
* @ access public
* @ return array
*/
public function get_variation_default_attributes () {
$default = isset ( $this -> default_attributes ) ? $this -> default_attributes : '' ;
2015-10-05 13:39:08 +00:00
return apply_filters ( 'woocommerce_product_default_attributes' , array_filter ( ( array ) maybe_unserialize ( $default ) ), $this );
2015-03-27 15:15:40 +00:00
}
2012-11-21 18:07:45 +00:00
2015-10-02 01:45:11 +00:00
/**
2015-11-03 13:31:20 +00:00
* Check if variable product has default attributes set .
2015-10-02 01:45:11 +00:00
*
* @ access public
* @ return bool
*/
public function has_default_attributes () {
2015-10-05 13:39:08 +00:00
if ( ! $this -> get_variation_default_attributes () ) {
2015-10-02 01:45:11 +00:00
return true ;
}
return false ;
}
2015-10-05 13:39:08 +00:00
2015-07-27 15:42:22 +00:00
/**
* If set , get the default attributes for a variable product .
*
* @ param string $attribute_name
* @ return string
*/
public function get_variation_default_attribute ( $attribute_name ) {
$defaults = $this -> get_variation_default_attributes ();
$attribute_name = sanitize_title ( $attribute_name );
return isset ( $defaults [ $attribute_name ] ) ? $defaults [ $attribute_name ] : '' ;
}
2015-07-09 14:56:20 +00:00
/**
2015-11-03 13:31:20 +00:00
* Match a variation to a given set of attributes using a WP_Query .
2015-07-09 14:56:20 +00:00
* @ since 2.4 . 0
* @ param $match_attributes
* @ return int Variation ID which matched , 0 is no match was found
*/
public function get_matching_variation ( $match_attributes = array () ) {
2016-03-08 11:33:04 +00:00
global $wpdb ;
2015-07-09 14:56:20 +00:00
$query_args = array (
'post_parent' => $this -> id ,
'post_type' => 'product_variation' ,
'orderby' => 'menu_order' ,
'order' => 'ASC' ,
'fields' => 'ids' ,
'post_status' => 'publish' ,
'numberposts' => 1 ,
2016-08-27 01:46:45 +00:00
'meta_query' => array (),
2015-07-09 14:56:20 +00:00
);
foreach ( $this -> get_attributes () as $attribute ) {
if ( ! $attribute [ 'is_variation' ] ) {
continue ;
}
$attribute_field_name = 'attribute_' . sanitize_title ( $attribute [ 'name' ] );
2015-08-24 12:32:20 +00:00
if ( ! isset ( $match_attributes [ $attribute_field_name ] ) ) {
2015-07-09 14:56:20 +00:00
return 0 ;
}
2015-08-23 20:47:04 +00:00
$value = wc_clean ( $match_attributes [ $attribute_field_name ] );
2015-07-09 14:56:20 +00:00
$query_args [ 'meta_query' ][] = array (
2016-02-04 18:25:06 +00:00
'relation' => 'OR' ,
array (
'key' => $attribute_field_name ,
'value' => array ( '' , $value ),
2016-08-27 01:46:45 +00:00
'compare' => 'IN' ,
2016-02-04 18:25:06 +00:00
),
array (
'key' => $attribute_field_name ,
2016-08-27 01:46:45 +00:00
'compare' => 'NOT EXISTS' ,
2016-02-04 18:25:06 +00:00
)
2015-07-09 14:56:20 +00:00
);
2016-02-04 18:25:06 +00:00
2015-07-09 14:56:20 +00:00
}
2016-03-08 11:33:04 +00:00
// Allow large queries in case user has many variations
$wpdb -> query ( 'SET SESSION SQL_BIG_SELECTS=1' );
2016-03-30 10:15:34 +00:00
2015-07-09 14:56:20 +00:00
$matches = get_posts ( $query_args );
if ( $matches && ! is_wp_error ( $matches ) ) {
return current ( $matches );
2015-09-14 15:50:58 +00:00
/**
* Pre 2.4 handling where 'slugs' were saved instead of the full text attribute .
2015-11-03 13:31:20 +00:00
* Fallback is here because there are cases where data will be 'synced' but the product version will remain the same . @ see WC_Product_Variable :: sync_attributes .
2015-09-14 15:50:58 +00:00
*/
} elseif ( version_compare ( get_post_meta ( $this -> id , '_product_version' , true ), '2.4.0' , '<' ) ) {
2016-09-07 22:32:24 +00:00
return ( array_map ( 'sanitize_title' , $match_attributes ) === $match_attributes ) ? 0 : $this -> get_matching_variation ( array_map ( 'sanitize_title' , $match_attributes ) );
2015-09-14 15:50:58 +00:00
2015-07-09 14:56:20 +00:00
} else {
return 0 ;
}
}
2015-03-27 15:15:40 +00:00
/**
* Get an array of available variations for the current product .
* @ return array
*/
public function get_available_variations () {
$available_variations = array ();
2012-11-21 18:07:45 +00:00
foreach ( $this -> get_children () as $child_id ) {
$variation = $this -> get_child ( $child_id );
2014-09-26 16:22:43 +00:00
// Hide out of stock variations if 'Hide out of stock items from the catalog' is checked
2014-06-24 12:03:25 +00:00
if ( empty ( $variation -> variation_id ) || ( 'yes' === get_option ( 'woocommerce_hide_out_of_stock_items' ) && ! $variation -> is_in_stock () ) ) {
continue ;
}
2014-09-20 18:57:58 +00:00
2014-09-26 16:22:43 +00:00
// Filter 'woocommerce_hide_invisible_variations' to optionally hide invisible variations (disabled variations and variations with empty price)
2015-04-29 09:16:58 +00:00
if ( apply_filters ( 'woocommerce_hide_invisible_variations' , false , $this -> id , $variation ) && ! $variation -> variation_is_visible () ) {
2014-09-26 16:22:43 +00:00
continue ;
}
2015-07-09 14:56:20 +00:00
$available_variations [] = $this -> get_available_variation ( $variation );
}
2012-11-21 18:07:45 +00:00
2015-07-09 14:56:20 +00:00
return $available_variations ;
}
2012-11-21 18:07:45 +00:00
2015-07-09 14:56:20 +00:00
/**
2016-04-20 11:35:06 +00:00
* Returns an array of data for a variation . Used in the add to cart form .
2015-07-09 14:56:20 +00:00
* @ since 2.4 . 0
2015-11-05 16:05:03 +00:00
* @ param WC_Product $variation Variation product object or ID
2015-07-09 14:56:20 +00:00
* @ return array
*/
public function get_available_variation ( $variation ) {
if ( is_numeric ( $variation ) ) {
$variation = $this -> get_child ( $variation );
}
2014-06-24 12:03:25 +00:00
2015-07-09 14:56:20 +00:00
if ( has_post_thumbnail ( $variation -> get_variation_id () ) ) {
2016-10-13 14:25:42 +00:00
$attachment_id = get_post_thumbnail_id ( $variation -> get_variation_id () );
$attachment = wp_get_attachment_image_src ( $attachment_id , 'shop_single' );
$attachment_thumb = wp_get_attachment_image_src ( $attachment_id , 'thumbnail' );
$full_attachment = wp_get_attachment_image_src ( $attachment_id , 'full' );
$attachment_object = get_post ( $attachment_id );
$image = $attachment ? current ( $attachment ) : '' ;
$image_thumbnail_src = $attachment_thumb ? current ( $attachment_thumb ) : '' ;
$image_link = $full_attachment ? current ( $full_attachment ) : '' ;
$image_title = get_the_title ( $attachment_id );
$image_alt = trim ( strip_tags ( get_post_meta ( $attachment_id , '_wp_attachment_image_alt' , true ) ) );
$image_caption = $attachment_object -> post_excerpt ;
$image_srcset = function_exists ( 'wp_get_attachment_image_srcset' ) ? wp_get_attachment_image_srcset ( $attachment_id , 'shop_single' ) : false ;
$image_sizes = function_exists ( 'wp_get_attachment_image_sizes' ) ? wp_get_attachment_image_sizes ( $attachment_id , 'shop_single' ) : false ;
2016-03-30 10:15:34 +00:00
if ( empty ( $image_alt ) ) {
$image_alt = $image_title ;
}
2015-07-09 14:56:20 +00:00
} else {
2016-10-13 23:29:50 +00:00
$image = $image_link = $image_title = $image_alt = $image_srcset = $image_sizes = $image_caption = $attachment = $image_thumbnail_src = $full_attachment = '' ;
2012-11-21 18:07:45 +00:00
}
2015-07-09 14:56:20 +00:00
$availability = $variation -> get_availability ();
$availability_html = empty ( $availability [ 'availability' ] ) ? '' : '<p class="stock ' . esc_attr ( $availability [ 'class' ] ) . '">' . wp_kses_post ( $availability [ 'availability' ] ) . '</p>' ;
$availability_html = apply_filters ( 'woocommerce_stock_html' , $availability_html , $availability [ 'availability' ], $variation );
return apply_filters ( 'woocommerce_available_variation' , array (
2016-03-30 10:15:34 +00:00
'variation_id' => $variation -> variation_id ,
'variation_is_visible' => $variation -> variation_is_visible (),
'variation_is_active' => $variation -> variation_is_active (),
'is_purchasable' => $variation -> is_purchasable (),
'display_price' => $variation -> get_display_price (),
'display_regular_price' => $variation -> get_display_price ( $variation -> get_regular_price () ),
'attributes' => $variation -> get_variation_attributes (),
'image_src' => $image ,
2016-10-13 14:25:42 +00:00
'image_h' => $attachment ? $attachment [ 1 ] : '' ,
'image_w' => $attachment ? $attachment [ 2 ] : '' ,
'image_thumbnail_src' => $image_thumbnail_src ,
2016-03-30 10:15:34 +00:00
'image_link' => $image_link ,
2016-10-13 14:25:42 +00:00
'image_link_h' => $full_attachment ? $full_attachment [ 1 ] : '' ,
'image_link_w' => $full_attachment ? $full_attachment [ 2 ] : '' ,
2016-03-30 10:15:34 +00:00
'image_title' => $image_title ,
'image_alt' => $image_alt ,
'image_caption' => $image_caption ,
'image_srcset' => $image_srcset ? $image_srcset : '' ,
'image_sizes' => $image_sizes ? $image_sizes : '' ,
'price_html' => apply_filters ( 'woocommerce_show_variation_price' , $variation -> get_price () === " " || $this -> get_variation_price ( 'min' ) !== $this -> get_variation_price ( 'max' ), $this , $variation ) ? '<span class="price">' . $variation -> get_price_html () . '</span>' : '' ,
'availability_html' => $availability_html ,
'sku' => $variation -> get_sku (),
2016-09-02 01:51:31 +00:00
'weight' => $variation -> get_weight () ? $variation -> get_weight () . ' ' . esc_attr ( get_option ( 'woocommerce_weight_unit' ) ) : '' ,
2016-03-30 10:15:34 +00:00
'dimensions' => $variation -> get_dimensions (),
'min_qty' => 1 ,
'max_qty' => $variation -> backorders_allowed () ? '' : $variation -> get_stock_quantity (),
'backorders_allowed' => $variation -> backorders_allowed (),
'is_in_stock' => $variation -> is_in_stock (),
2016-08-27 02:46:40 +00:00
'is_downloadable' => $variation -> is_downloadable (),
2016-03-30 10:15:34 +00:00
'is_virtual' => $variation -> is_virtual (),
'is_sold_individually' => $variation -> is_sold_individually () ? 'yes' : 'no' ,
'variation_description' => $variation -> get_variation_description (),
2015-07-09 14:56:20 +00:00
), $this , $variation );
2015-03-27 15:15:40 +00:00
}
2012-11-21 18:07:45 +00:00
/**
2013-03-03 17:07:31 +00:00
* Sync variable product prices with the children lowest / highest prices .
2012-11-21 18:07:45 +00:00
*/
2013-08-02 00:35:54 +00:00
public function variable_product_sync ( $product_id = '' ) {
2014-10-14 13:21:23 +00:00
if ( empty ( $product_id ) ) {
2013-08-02 00:54:24 +00:00
$product_id = $this -> id ;
2014-10-14 13:21:23 +00:00
}
2013-08-02 00:54:24 +00:00
2013-10-11 14:56:13 +00:00
// Sync prices with children
2013-08-02 00:35:54 +00:00
self :: sync ( $product_id );
2013-10-11 14:56:13 +00:00
// Re-load prices
2015-03-25 13:35:49 +00:00
$this -> price = get_post_meta ( $product_id , '_price' , true );
2014-03-19 10:51:07 +00:00
foreach ( array ( 'price' , 'regular_price' , 'sale_price' ) as $price_type ) {
$min_variation_id_key = " min_ { $price_type } _variation_id " ;
$max_variation_id_key = " max_ { $price_type } _variation_id " ;
$min_price_key = " _min_variation_ { $price_type } " ;
$max_price_key = " _max_variation_ { $price_type } " ;
$this -> $min_variation_id_key = get_post_meta ( $product_id , '_' . $min_variation_id_key , true );
$this -> $max_variation_id_key = get_post_meta ( $product_id , '_' . $max_variation_id_key , true );
$this -> $min_price_key = get_post_meta ( $product_id , '_' . $min_price_key , true );
$this -> $max_price_key = get_post_meta ( $product_id , '_' . $max_price_key , true );
}
2013-08-02 00:35:54 +00:00
}
2014-06-24 12:03:25 +00:00
/**
2015-11-03 13:31:20 +00:00
* Sync variable product stock status with children .
2014-06-24 12:03:25 +00:00
* @ param int $product_id
*/
public static function sync_stock_status ( $product_id ) {
$children = get_posts ( array (
'post_parent' => $product_id ,
2016-08-27 06:07:42 +00:00
'posts_per_page' => - 1 ,
2014-06-24 12:03:25 +00:00
'post_type' => 'product_variation' ,
'fields' => 'ids' ,
2016-08-27 01:46:45 +00:00
'post_status' => 'publish' ,
2014-06-24 12:03:25 +00:00
) );
$stock_status = 'outofstock' ;
2014-09-20 18:57:58 +00:00
2014-06-24 12:03:25 +00:00
foreach ( $children as $child_id ) {
$child_stock_status = get_post_meta ( $child_id , '_stock_status' , true );
$child_stock_status = $child_stock_status ? $child_stock_status : 'instock' ;
if ( 'instock' === $child_stock_status ) {
$stock_status = 'instock' ;
break ;
}
}
wc_update_product_stock_status ( $product_id , $stock_status );
}
2015-07-28 15:20:51 +00:00
/**
2015-11-03 13:31:20 +00:00
* Sync the variable product ' s attributes with the variations .
2015-07-28 15:20:51 +00:00
*/
public static function sync_attributes ( $product_id , $children = false ) {
if ( ! $children ) {
$children = get_posts ( array (
'post_parent' => $product_id ,
2016-08-27 06:07:42 +00:00
'posts_per_page' => - 1 ,
2015-07-28 15:20:51 +00:00
'post_type' => 'product_variation' ,
'fields' => 'ids' ,
2016-08-27 01:46:45 +00:00
'post_status' => 'any' ,
2015-07-28 15:20:51 +00:00
) );
}
/**
* Pre 2.4 handling where 'slugs' were saved instead of the full text attribute .
* Attempt to get full version of the text attribute from the parent and UPDATE meta .
*/
if ( version_compare ( get_post_meta ( $product_id , '_product_version' , true ), '2.4.0' , '<' ) ) {
$parent_attributes = array_filter ( ( array ) get_post_meta ( $product_id , '_product_attributes' , true ) );
foreach ( $children as $child_id ) {
$all_meta = get_post_meta ( $child_id );
foreach ( $all_meta as $name => $value ) {
if ( 0 !== strpos ( $name , 'attribute_' ) ) {
continue ;
}
if ( sanitize_title ( $value [ 0 ] ) === $value [ 0 ] ) {
foreach ( $parent_attributes as $attribute ) {
2016-09-07 22:32:24 +00:00
if ( 'attribute_' . sanitize_title ( $attribute [ 'name' ] ) !== $name ) {
2015-07-28 15:20:51 +00:00
continue ;
}
$text_attributes = wc_get_text_attributes ( $attribute [ 'value' ] );
foreach ( $text_attributes as $text_attribute ) {
if ( sanitize_title ( $text_attribute ) === $value [ 0 ] ) {
update_post_meta ( $child_id , $name , $text_attribute );
break ;
}
}
}
}
}
}
}
}
2016-08-03 15:07:34 +00:00
/**
* Does a child have a weight set ?
* @ since 2.7 . 0
* @ return boolean
*/
public function child_has_weight () {
2016-08-04 10:12:14 +00:00
return ( bool ) get_post_meta ( $this -> id , '_child_has_weight' , true );
2016-08-03 15:07:34 +00:00
}
/**
* Does a child have dimensions set ?
* @ since 2.7 . 0
* @ return boolean
*/
public function child_has_dimensions () {
2016-08-04 10:12:14 +00:00
return ( bool ) get_post_meta ( $this -> id , '_child_has_dimensions' , true );
2016-08-03 15:07:34 +00:00
}
/**
* Returns whether or not we are showing dimensions on the product page .
*
* @ return bool
*/
public function enable_dimensions_display () {
return apply_filters ( 'wc_product_enable_dimensions_display' , true ) && ( $this -> has_dimensions () || $this -> has_weight () || $this -> child_has_weight () || $this -> child_has_dimensions () );
}
2013-08-02 00:35:54 +00:00
/**
2015-11-03 13:31:20 +00:00
* Sync the variable product with it ' s children .
2013-08-02 00:35:54 +00:00
*/
public static function sync ( $product_id ) {
2013-11-08 15:53:52 +00:00
global $wpdb ;
2012-11-21 18:07:45 +00:00
$children = get_posts ( array (
2013-07-30 11:15:09 +00:00
'post_parent' => $product_id ,
2016-08-27 06:07:42 +00:00
'posts_per_page' => - 1 ,
2012-11-21 18:07:45 +00:00
'post_type' => 'product_variation' ,
'fields' => 'ids' ,
2016-08-27 01:46:45 +00:00
'post_status' => 'publish' ,
2013-11-08 15:53:52 +00:00
) );
2012-11-21 18:07:45 +00:00
2015-12-03 10:14:00 +00:00
// No published variations - product won't be purchasable.
2015-12-23 13:08:45 +00:00
if ( ! $children ) {
update_post_meta ( $product_id , '_price' , '' );
2016-08-03 15:07:34 +00:00
delete_post_meta ( $product_id , '_child_has_weight' );
delete_post_meta ( $product_id , '_child_has_dimensions' );
2015-12-23 13:08:45 +00:00
delete_transient ( 'wc_products_onsale' );
if ( is_admin () && 'publish' === get_post_status ( $product_id ) ) {
2015-12-03 10:14:00 +00:00
WC_Admin_Meta_Boxes :: add_error ( __ ( 'This variable product has no active variations. Add or enable variations to allow this product to be purchased.' , 'woocommerce' ) );
2014-01-08 13:38:31 +00:00
}
2014-05-10 20:45:14 +00:00
2013-11-08 15:53:52 +00:00
// Loop the variations
} else {
2015-01-16 01:06:09 +00:00
// Set the variable product to be virtual/downloadable if all children are virtual/downloadable
foreach ( array ( '_downloadable' , '_virtual' ) as $meta_key ) {
$all_variations_yes = true ;
foreach ( $children as $child_id ) {
if ( 'yes' != get_post_meta ( $child_id , $meta_key , true ) ) {
$all_variations_yes = false ;
break ;
}
}
update_post_meta ( $product_id , $meta_key , ( true === $all_variations_yes ) ? 'yes' : 'no' );
}
2014-03-19 10:51:07 +00:00
// Main active prices
$min_price = null ;
$max_price = null ;
$min_price_id = null ;
$max_price_id = null ;
2014-05-10 20:45:14 +00:00
2014-03-19 10:51:07 +00:00
// Regular prices
$min_regular_price = null ;
$max_regular_price = null ;
$min_regular_price_id = null ;
$max_regular_price_id = null ;
2014-05-10 20:45:14 +00:00
2014-03-19 10:51:07 +00:00
// Sale prices
$min_sale_price = null ;
$max_sale_price = null ;
$min_sale_price_id = null ;
$max_sale_price_id = null ;
foreach ( array ( 'price' , 'regular_price' , 'sale_price' ) as $price_type ) {
foreach ( $children as $child_id ) {
$child_price = get_post_meta ( $child_id , '_' . $price_type , true );
// Skip non-priced variations
2016-09-07 22:32:24 +00:00
if ( '' === $child_price ) {
2014-03-19 10:51:07 +00:00
continue ;
}
2012-11-27 16:22:47 +00:00
2014-03-19 10:51:07 +00:00
// Skip hidden variations
if ( 'yes' === get_option ( 'woocommerce_hide_out_of_stock_items' ) ) {
$stock = get_post_meta ( $child_id , '_stock' , true );
2016-09-07 22:32:24 +00:00
if ( '' !== $stock && $stock <= get_option ( 'woocommerce_notify_no_stock_amount' ) ) {
2014-03-19 10:51:07 +00:00
continue ;
}
}
2014-03-04 14:44:24 +00:00
2014-03-19 10:51:07 +00:00
// Find min price
if ( is_null ( $ { " min_ { $price_type } " } ) || $child_price < $ { " min_ { $price_type } " } ) {
$ { " min_ { $price_type } " } = $child_price ;
$ { " min_ { $price_type } _id " } = $child_id ;
}
2014-03-04 14:44:24 +00:00
2014-03-19 10:51:07 +00:00
// Find max price
if ( $child_price > $ { " max_ { $price_type } " } ) {
$ { " max_ { $price_type } " } = $child_price ;
$ { " max_ { $price_type } _id " } = $child_id ;
2014-03-06 12:07:28 +00:00
}
2014-03-04 14:44:24 +00:00
}
2013-04-04 09:11:12 +00:00
2014-03-19 10:51:07 +00:00
// Store prices
update_post_meta ( $product_id , '_min_variation_' . $price_type , $ { " min_ { $price_type } " } );
update_post_meta ( $product_id , '_max_variation_' . $price_type , $ { " max_ { $price_type } " } );
2012-11-27 16:22:47 +00:00
2014-03-19 10:51:07 +00:00
// Store ids
update_post_meta ( $product_id , '_min_' . $price_type . '_variation_id' , $ { " min_ { $price_type } _id " } );
update_post_meta ( $product_id , '_max_' . $price_type . '_variation_id' , $ { " max_ { $price_type } _id " } );
2013-03-06 13:25:37 +00:00
}
2012-11-21 18:07:45 +00:00
2016-02-09 20:16:08 +00:00
// Sync _price meta
delete_post_meta ( $product_id , '_price' );
add_post_meta ( $product_id , '_price' , $min_price , false );
add_post_meta ( $product_id , '_price' , $max_price , false );
2014-06-19 10:40:33 +00:00
delete_transient ( 'wc_products_onsale' );
2014-09-20 18:57:58 +00:00
2016-08-03 15:07:34 +00:00
// Sync weights
foreach ( $children as $child_id ) {
if ( get_post_meta ( $child_id , '_weight' , true ) ) {
update_post_meta ( $product_id , '_child_has_weight' , true );
break ;
}
}
// Sync dimensions
foreach ( $children as $child_id ) {
if ( get_post_meta ( $child_id , '_height' , true ) || get_post_meta ( $child_id , '_width' , true ) || get_post_meta ( $child_id , '_length' , true ) ) {
update_post_meta ( $product_id , '_child_has_dimensions' , true );
break ;
}
}
2015-07-28 15:20:51 +00:00
// Sync attributes
self :: sync_attributes ( $product_id , $children );
2013-11-08 15:53:52 +00:00
do_action ( 'woocommerce_variable_product_sync' , $product_id , $children );
2012-12-19 20:42:25 +00:00
}
2012-11-21 18:07:45 +00:00
}
2014-03-07 08:29:01 +00:00
}