From 9f50d41b9789b5ee9f6ff724e07a5c34fc08f543 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 25 Mar 2015 13:35:49 +0000 Subject: [PATCH 1/7] New approach to getting min/max variation prices using transients New method will get all prices and store in array format. Cached based on user location (tax) and product transient hash. Fixes #6504 --- includes/class-wc-product-variable.php | 156 +++++++++++++------------ 1 file changed, 82 insertions(+), 74 deletions(-) diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index a08ddb8d8da..9a138a4f2a3 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -195,20 +195,13 @@ class WC_Product_Variable extends WC_Product { /** * Returns whether or not the product is on sale. - * - * @access public * @return bool */ public function is_on_sale() { $is_on_sale = false; - if ( $this->has_child() ) { - foreach ( $this->get_children( true ) as $child_id ) { - $price = get_post_meta( $child_id, '_price', true ); - $sale_price = get_post_meta( $child_id, '_sale_price', true ); - if ( $sale_price !== "" && $sale_price >= 0 && $sale_price == $price ) { - $is_on_sale = true; - } - } + $prices = $this->get_variation_prices(); + if ( $prices['regular_price'] !== $prices['price'] ) { + $is_on_sale = true; } return apply_filters( 'woocommerce_product_is_on_sale', $is_on_sale, $this ); } @@ -220,19 +213,8 @@ class WC_Product_Variable extends WC_Product { * @return string */ public function get_variation_regular_price( $min_or_max = 'min', $display = false ) { - $variation_id = get_post_meta( $this->id, '_' . $min_or_max . '_regular_price_variation_id', true ); - - if ( ! $variation_id ) { - $price = false; - } else { - $price = get_post_meta( $variation_id, '_regular_price', true ); - - if ( $display && ( $variation = $this->get_child( $variation_id ) ) ) { - $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); - $price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $price ) : $variation->get_price_excluding_tax( 1, $price ); - } - } - + $prices = $this->get_variation_prices( $display ); + $price = 'min' === $min_or_max ? current( $prices['regular_price'] ) : end( $prices['regular_price'] ); return apply_filters( 'woocommerce_get_variation_regular_price', $price, $this, $min_or_max, $display ); } @@ -243,20 +225,8 @@ class WC_Product_Variable extends WC_Product { * @return string */ public function get_variation_sale_price( $min_or_max = 'min', $display = false ) { - $variation_id = get_post_meta( $this->id, '_' . $min_or_max . '_sale_price_variation_id', true ); - - if ( ! $variation_id ) { - $price = false; - } else { - $price = get_post_meta( $variation_id, '_sale_price', true ); - - if ( $display ) { - $variation = $this->get_child( $variation_id ); - $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); - $price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $price ) : $variation->get_price_excluding_tax( 1, $price ); - } - } - + $prices = $this->get_variation_prices( $display ); + $price = 'min' === $min_or_max ? current( $prices['sale_price'] ) : end( $prices['sale_price'] ); return apply_filters( 'woocommerce_get_variation_sale_price', $price, $this, $min_or_max, $display ); } @@ -267,22 +237,67 @@ class WC_Product_Variable extends WC_Product { * @return string */ public function get_variation_price( $min_or_max = 'min', $display = false ) { - $variation_id = get_post_meta( $this->id, '_' . $min_or_max . '_price_variation_id', true ); + $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 ); + } - if ( $display ) { - $variation = $this->get_child( $variation_id ); + /** + * Get an array of all sale and regular prices from all variations. + * @param bool Are prices for display? If so, taxes will be calculated. + * @return array() + */ + public function get_variation_prices( $display = false ) { + $cache_key = 'var_prices_' . md5( apply_filters( 'woocommerce_get_variation_prices_hash', ( $display ? json_encode( WC_Tax::get_rates() ) : '' ) . WC_Cache_Helper::get_transient_version( 'product' ), $this, $display ) ); - if ( $variation ) { - $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); - $price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax() : $variation->get_price_excluding_tax(); - } else { - $price = ''; + if ( false === ( $prices_array = get_transient( $cache_key ) ) ) { + $prices = array(); + $regular_prices = array(); + $sale_prices = array(); + $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); + $hide_out_of_stock = 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ); + + foreach ( $this->get_children() as $variation_id ) { + $price = get_post_meta( $variation_id, '_price', true ); + $regular_price = get_post_meta( $variation_id, '_regular_price', true ); + $sale_price = get_post_meta( $variation_id, '_sale_price', true ); + $stock = get_post_meta( $variation_id, '_stock', true ); + + // Skip hidden and non priced variations + if ( '' === $price || ( $hide_out_of_stock && '' !== $stock && $stock <= get_option( 'woocommerce_notify_no_stock_amount' ) ) ) { + continue; + } + + // If sale price does not equal price, the product is not yet on sale + if ( $price != $sale_price ) { + $sale_price = $regular_price; + } + + // If we are getting prices for display, we need to account for taxes + if ( $display && ( $variation = $this->get_child( $variation_id ) ) ) { + $price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $price ) : $variation->get_price_excluding_tax( 1, $price ); + $sale_price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $sale_price ) : $variation->get_price_excluding_tax( 1, $sale_price ); + } + + $prices[] = $price; + $regular_prices[] = $regular_price; + $sale_prices[] = $sale_price; } - } else { - $price = get_post_meta( $variation_id, '_price', true ); + + sort( $prices ); + sort( $regular_prices ); + sort( $sale_prices ); + + $prices_array = array( + 'price' => $prices, + 'regular_price' => $regular_prices, + 'sale_price' => $sale_prices + ); + + set_transient( $cache_key, $prices_array, DAY_IN_SECONDS * 30 ); } - return apply_filters( 'woocommerce_get_variation_price', $price, $this, $min_or_max, $display ); + return $prices_array; } /** @@ -293,37 +308,24 @@ class WC_Product_Variable extends WC_Product { * @return string */ public function get_price_html( $price = '' ) { - - // Ensure variation prices are synced with variations - if ( $this->get_variation_regular_price( 'min' ) === false || $this->get_variation_price( 'min' ) === false || $this->get_variation_price( 'min' ) === '' || $this->get_price() === '' ) { - $this->variable_product_sync( $this->id ); - } - - // Get the price if ( $this->get_price() === '' ) { - $price = apply_filters( 'woocommerce_variable_empty_price_html', '', $this ); - } else { - - // Main price - $prices = array( $this->get_variation_price( 'min', true ), $this->get_variation_price( 'max', true ) ); - $price = $prices[0] !== $prices[1] ? sprintf( _x( '%1$s–%2$s', 'Price range: from-to', 'woocommerce' ), wc_price( $prices[0] ), wc_price( $prices[1] ) ) : wc_price( $prices[0] ); - - // Sale - $prices = array( $this->get_variation_regular_price( 'min', true ), $this->get_variation_regular_price( 'max', true ) ); - sort( $prices ); - $saleprice = $prices[0] !== $prices[1] ? sprintf( _x( '%1$s–%2$s', 'Price range: from-to', 'woocommerce' ), wc_price( $prices[0] ), wc_price( $prices[1] ) ) : wc_price( $prices[0] ); + $prices = $this->get_variation_prices( true ); + $min_price = current( $prices['price'] ); + $max_price = end( $prices['price'] ); + $min_sale_price = current( $prices['sale_price'] ); + $max_sale_price = end( $prices['sale_price'] ); + $free = $min_sale_price == 0 && $max_sale_price == 0; + $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 ); + $saleprice = $min_sale_price !== $max_sale_price ? sprintf( _x( '%1$s–%2$s', 'Price range: from-to', 'woocommerce' ), wc_price( $min_sale_price ), wc_price( $max_sale_price ) ) : wc_price( $min_sale_price ); if ( $price !== $saleprice ) { $price = apply_filters( 'woocommerce_variable_sale_price_html', $this->get_price_html_from_to( $saleprice, $price ) . $this->get_price_suffix(), $this ); + } elseif ( $free ) { + $price = apply_filters( 'woocommerce_variable_free_price_html', __( 'Free!', 'woocommerce' ), $this ); } else { - if ( $prices[0] == 0 && $prices[1] == 0 ) { - $price = __( 'Free!', 'woocommerce' ); - $price = apply_filters( 'woocommerce_variable_free_price_html', $price, $this ); - } else { - $price = apply_filters( 'woocommerce_variable_price_html', $price . $this->get_price_suffix(), $this ); - } + $price = apply_filters( 'woocommerce_variable_price_html', $price . $this->get_price_suffix(), $this ); } } @@ -507,7 +509,7 @@ class WC_Product_Variable extends WC_Product { self::sync( $product_id ); // Re-load prices - $this->price = get_post_meta( $product_id, '_price', true ); + $this->price = get_post_meta( $product_id, '_price', true ); foreach ( array( 'price', 'regular_price', 'sale_price' ) as $price_type ) { $min_variation_id_key = "min_{$price_type}_variation_id"; @@ -575,7 +577,6 @@ class WC_Product_Variable extends WC_Product { // 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 ) { @@ -588,6 +589,13 @@ class WC_Product_Variable extends WC_Product { update_post_meta( $product_id, $meta_key, ( true === $all_variations_yes ) ? 'yes' : 'no' ); } + // Were multiple tax classes defined? + if ( sizeof( $found_tax_classes ) > 1 ) { + update_post_meta( $product_id, '_has_multiple_variation_tax_rates', true ); + } else { + delete_post_meta( $product_id, '_has_multiple_variation_tax_rates' ); + } + // Main active prices $min_price = null; $max_price = null; From 0ab0a121569b576cd49097f77af6e0f10ff95aeb Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 25 Mar 2015 13:37:11 +0000 Subject: [PATCH 2/7] _has_multiple_variation_tax_rates no longer needed --- includes/class-wc-product-variable.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index 9a138a4f2a3..0526b1f084c 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -589,13 +589,6 @@ class WC_Product_Variable extends WC_Product { update_post_meta( $product_id, $meta_key, ( true === $all_variations_yes ) ? 'yes' : 'no' ); } - // Were multiple tax classes defined? - if ( sizeof( $found_tax_classes ) > 1 ) { - update_post_meta( $product_id, '_has_multiple_variation_tax_rates', true ); - } else { - delete_post_meta( $product_id, '_has_multiple_variation_tax_rates' ); - } - // Main active prices $min_price = null; $max_price = null; From 99fcb77262e0d40f5888901c7099fe1d21f92eb2 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 25 Mar 2015 14:52:33 +0000 Subject: [PATCH 3/7] woocommerce_get_variation_prices_hash array --- includes/class-wc-product-variable.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index 0526b1f084c..b2a25c75ad5 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -248,7 +248,10 @@ class WC_Product_Variable extends WC_Product { * @return array() */ public function get_variation_prices( $display = false ) { - $cache_key = 'var_prices_' . md5( apply_filters( 'woocommerce_get_variation_prices_hash', ( $display ? json_encode( WC_Tax::get_rates() ) : '' ) . WC_Cache_Helper::get_transient_version( 'product' ), $this, $display ) ); + $cache_key = 'var_prices_' . md5( json_encode( apply_filters( 'woocommerce_get_variation_prices_hash', array( + $display ? WC_Tax::get_rates() : '', + WC_Cache_Helper::get_transient_version( 'product' ) + ), $this, $display ) ) ); if ( false === ( $prices_array = get_transient( $cache_key ) ) ) { $prices = array(); From 01c13a52cd3f5d74c665599fdb9809d1df76ad7a Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 25 Mar 2015 15:23:28 +0000 Subject: [PATCH 4/7] Fix price/sale price listings --- includes/class-wc-product-variable.php | 28 +++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index b2a25c75ad5..a53cd0e4bee 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -278,8 +278,9 @@ class WC_Product_Variable extends WC_Product { // If we are getting prices for display, we need to account for taxes if ( $display && ( $variation = $this->get_child( $variation_id ) ) ) { - $price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $price ) : $variation->get_price_excluding_tax( 1, $price ); - $sale_price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $sale_price ) : $variation->get_price_excluding_tax( 1, $sale_price ); + $price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $price ) : $variation->get_price_excluding_tax( 1, $price ); + $regular_price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $regular_price ) : $variation->get_price_excluding_tax( 1, $regular_price ); + $sale_price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $sale_price ) : $variation->get_price_excluding_tax( 1, $sale_price ); } $prices[] = $price; @@ -314,24 +315,23 @@ class WC_Product_Variable extends WC_Product { if ( $this->get_price() === '' ) { $price = apply_filters( 'woocommerce_variable_empty_price_html', '', $this ); } else { - $prices = $this->get_variation_prices( true ); - $min_price = current( $prices['price'] ); - $max_price = end( $prices['price'] ); - $min_sale_price = current( $prices['sale_price'] ); - $max_sale_price = end( $prices['sale_price'] ); - $free = $min_sale_price == 0 && $max_sale_price == 0; - $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 ); - $saleprice = $min_sale_price !== $max_sale_price ? sprintf( _x( '%1$s–%2$s', 'Price range: from-to', 'woocommerce' ), wc_price( $min_sale_price ), wc_price( $max_sale_price ) ) : wc_price( $min_sale_price ); + $prices = $this->get_variation_prices( true ); + $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 ); + $is_free = $min_price == 0 && $max_price == 0; - if ( $price !== $saleprice ) { - $price = apply_filters( 'woocommerce_variable_sale_price_html', $this->get_price_html_from_to( $saleprice, $price ) . $this->get_price_suffix(), $this ); - } elseif ( $free ) { + 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 ) { $price = apply_filters( 'woocommerce_variable_free_price_html', __( 'Free!', 'woocommerce' ), $this ); } else { $price = apply_filters( 'woocommerce_variable_price_html', $price . $this->get_price_suffix(), $this ); } } - return apply_filters( 'woocommerce_get_price_html', $price, $this ); } From c76a08aad27105a84ef0faa7b7485ecd5986572d Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 25 Mar 2015 15:48:51 +0000 Subject: [PATCH 5/7] woocommerce_variation_prices filter --- includes/class-wc-product-variable.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index a53cd0e4bee..4a4935fb681 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -301,7 +301,7 @@ class WC_Product_Variable extends WC_Product { set_transient( $cache_key, $prices_array, DAY_IN_SECONDS * 30 ); } - return $prices_array; + return apply_filters( 'woocommerce_variation_prices', $prices_array, $this, $display ); } /** From 8cb2ca86d79b7b8745c481ea160d69a0fd162d62 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 25 Mar 2015 16:23:19 +0000 Subject: [PATCH 6/7] Store variation ID in transient --- includes/class-wc-product-variable.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index 4a4935fb681..b1eba5588d7 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -283,14 +283,14 @@ class WC_Product_Variable extends WC_Product { $sale_price = $tax_display_mode == 'incl' ? $variation->get_price_including_tax( 1, $sale_price ) : $variation->get_price_excluding_tax( 1, $sale_price ); } - $prices[] = $price; - $regular_prices[] = $regular_price; - $sale_prices[] = $sale_price; + $prices[ $variation_id ] = $price; + $regular_prices[ $variation_id ] = $regular_price; + $sale_prices[ $variation_id ] = $sale_price; } - sort( $prices ); - sort( $regular_prices ); - sort( $sale_prices ); + asort( $prices ); + asort( $regular_prices ); + asort( $sale_prices ); $prices_array = array( 'price' => $prices, From 302fca886c2258a86119199168710ffe6597c1f9 Mon Sep 17 00:00:00 2001 From: Mike Jolley Date: Wed, 25 Mar 2015 16:27:11 +0000 Subject: [PATCH 7/7] ID needs to be part of transient --- includes/class-wc-product-variable.php | 1 + 1 file changed, 1 insertion(+) diff --git a/includes/class-wc-product-variable.php b/includes/class-wc-product-variable.php index b1eba5588d7..32df8aedaa5 100644 --- a/includes/class-wc-product-variable.php +++ b/includes/class-wc-product-variable.php @@ -249,6 +249,7 @@ class WC_Product_Variable extends WC_Product { */ public function get_variation_prices( $display = false ) { $cache_key = 'var_prices_' . md5( json_encode( apply_filters( 'woocommerce_get_variation_prices_hash', array( + $this->id, $display ? WC_Tax::get_rates() : '', WC_Cache_Helper::get_transient_version( 'product' ) ), $this, $display ) ) );