diff --git a/includes/abstracts/abstract-wc-legacy-product.php b/includes/abstracts/abstract-wc-legacy-product.php index 828bd44ab51..7d77a02835b 100644 --- a/includes/abstracts/abstract-wc-legacy-product.php +++ b/includes/abstracts/abstract-wc-legacy-product.php @@ -106,6 +106,57 @@ abstract class WC_Abstract_Legacy_Product extends WC_Data { wc_display_product_attributes( $this ); } + /** + * Returns the price (including tax). Uses customer tax rates. Can work for a specific $qty for more accurate taxes. + * + * @deprecated 2.7.0 Use wc_get_price_including_tax instead. + * @param int $qty + * @param string $price to calculate, left blank to just use get_price() + * @return string + */ + public function get_price_including_tax( $qty = 1, $price = '' ) { + _deprecated_function( 'WC_Product::get_price_including_tax', '2.7', 'wc_get_price_including_tax' ); + return wc_get_price_including_tax( $this, array( 'qty' => $qty, 'price' => $price ) ); + } + + /** + * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting. + * + * @deprecated 2.7.0 Use wc_get_price_to_display instead. + * @param string $price to calculate, left blank to just use get_price() + * @param integer $qty passed on to get_price_including_tax() or get_price_excluding_tax() + * @return string + */ + public function get_display_price( $price = '', $qty = 1 ) { + _deprecated_function( 'WC_Product::get_display_price', '2.7', 'wc_get_price_to_display' ); + return wc_get_price_to_display( $this, array( 'qty' => $qty, 'price' => $price ) ); + } + + /** + * Returns the price (excluding tax) - ignores tax_class filters since the price may *include* tax and thus needs subtracting. + * Uses store base tax rates. Can work for a specific $qty for more accurate taxes. + * + * @deprecated 2.7.0 Use wc_get_price_excluding_tax instead. + * @param int $qty + * @param string $price to calculate, left blank to just use get_price() + * @return string + */ + public function get_price_excluding_tax( $qty = 1, $price = '' ) { + _deprecated_function( 'WC_Product::get_price_excluding_tax', '2.7', 'wc_get_price_excluding_tax' ); + return wc_get_price_excluding_tax( $this, array( 'qty' => $qty, 'price' => $price ) ); + } + + /** + * Adjust a products price dynamically. + * + * @deprecated 2.7.0 + * @param mixed $price + */ + public function adjust_price( $price ) { + _deprecated_function( 'WC_Product::adjust_price', '2.7', 'WC_Product::set_price / WC_Product::get_price' ); + $this->data['price'] = $this->data['price'] + $price; + } + /** * Returns the availability of the product. * diff --git a/includes/abstracts/abstract-wc-product.php b/includes/abstracts/abstract-wc-product.php index f78ff4d89d0..f2aed20d3e6 100644 --- a/includes/abstracts/abstract-wc-product.php +++ b/includes/abstracts/abstract-wc-product.php @@ -37,6 +37,7 @@ class WC_Product extends WC_Abstract_Legacy_Product { 'description' => '', 'short_description' => '', 'sku' => '', + 'price' => '', // @todo save and set this 'regular_price' => '', 'sale_price' => '', 'date_on_sale_from' => '', @@ -61,8 +62,8 @@ class WC_Product extends WC_Abstract_Legacy_Product { 'attributes' => array(), 'default_attributes' => array(), 'menu_order' => 0, - 'virtual' => false, // @todo - 'downloadable' => false, // @todo + 'virtual' => false, // @todo + 'downloadable' => false, // @todo ); /** @@ -221,6 +222,15 @@ class WC_Product extends WC_Abstract_Legacy_Product { return apply_filters( 'woocommerce_get_sku', $this->data['sku'], $this ); } + /** + * Returns the product's active price. + * + * @return string price + */ + public function get_price() { + return apply_filters( 'woocommerce_get_price', $this->data['price'], $this ); + } + /** * Returns the product's regular price. * @@ -624,6 +634,15 @@ class WC_Product extends WC_Abstract_Legacy_Product { $this->data['sku'] = $sku; } + /** + * Set the product's active price. + * + * @param string $price Price. + */ + public function set_price( $price ) { + $this->data['price'] = wc_format_decimal( $price ); + } + /** * Set the product's regular price. * @@ -975,6 +994,11 @@ class WC_Product extends WC_Abstract_Legacy_Product { 'default_attributes' => get_post_meta( $id, '_default_attributes', true ), 'menu_order' => $post_object->menu_order, ) ); + if ( $this->is_on_sale() ) { + $this->set_price( $this->get_sale_price() ); + } else { + $this->set_price( $this->get_regular_price() ); + } $this->read_meta_data(); } @@ -1085,6 +1109,12 @@ class WC_Product extends WC_Abstract_Legacy_Product { update_post_meta( $id, '_purchase_note', $this->get_purchase_note() ); update_post_meta( $id, '_attributes', $this->get_attributes() ); update_post_meta( $id, '_default_attributes', $this->get_default_attributes() ); + + if ( $this->is_on_sale() ) { + update_post_meta( $id, '_price', $this->get_sale_price() ); + } else { + update_post_meta( $id, '_price', $this->get_regular_price() ); + } } /* @@ -1197,7 +1227,20 @@ class WC_Product extends WC_Abstract_Legacy_Product { * @return bool */ public function is_on_sale() { - return apply_filters( 'woocommerce_product_is_on_sale', ( $this->get_sale_price() !== $this->get_regular_price() && $this->get_sale_price() === $this->get_price() ), $this ); + if ( '' !== $this->get_sale_price() && $this->get_regular_price() > $this->get_sale_price() ) { + $onsale = true; + + if ( '' !== $this->get_date_on_sale_from() && $this->get_date_on_sale_from() > strtotime( 'NOW', current_time( 'timestamp' ) ) ) { + $onsale = false; + } + + if ( '' !== $this->get_date_on_sale_to() && $this->get_date_on_sale_to() < strtotime( 'NOW', current_time( 'timestamp' ) ) ) { + $onsale = false; + } + } else { + $onsale = false; + } + return apply_filters( 'woocommerce_product_is_on_sale', $onsale, $this ); } /** @@ -1307,6 +1350,25 @@ class WC_Product extends WC_Abstract_Legacy_Product { |-------------------------------------------------------------------------- */ + /** + * Returns the price in html format. + * @todo Should this be moved out of the classes? + * @return string + */ + public function get_price_html( $deprecated = '' ) { + if ( '' === $this->get_price() ) { + return apply_filters( 'woocommerce_empty_price_html', '', $this ); + } + + if ( $this->is_on_sale() ) { + $price = wc_format_price_range( wc_get_price_to_display( $this, array( 'price' => $this->get_regular_price() ) ), wc_get_price_to_display( $this ) ) . wc_get_price_suffix( $this ); + } else { + $price = wc_price( wc_get_price_to_display( $this ) ) . wc_get_price_suffix( $this ); + } + + return apply_filters( 'woocommerce_get_price_html', $price, $this ); + } + /** * Get product name with SKU or ID. Used within admin. * @@ -1623,159 +1685,6 @@ class WC_Product extends WC_Abstract_Legacy_Product { return apply_filters( 'woocommerce_product_file_download_path', $file_path, $this, $download_id ); } - /* - |-------------------------------------------------------------------------- - | @todo price functions - |-------------------------------------------------------------------------- - */ - - /** - * Set a products price dynamically. - * - * @param float $price Price to set. - */ - public function set_price( $price ) { - $this->price = $price; - } - - /** - * Adjust a products price dynamically. - * - * @param mixed $price - */ - public function adjust_price( $price ) { - $this->price = $this->price + $price; - } - - /** - * Returns the product's active price. - * - * @return string price - */ - public function get_price() { - return apply_filters( 'woocommerce_get_price', $this->price, $this ); - } - - /** - * Returns the price (including tax). Uses customer tax rates. Can work for a specific $qty for more accurate taxes. - * - * @param int $qty - * @param string $price to calculate, left blank to just use get_price() - * @return string - */ - public function get_price_including_tax( $qty = 1, $price = '' ) { - - if ( '' === $price ) { - $price = $this->get_price(); - } - - if ( $this->is_taxable() ) { - - if ( get_option( 'woocommerce_prices_include_tax' ) === 'no' ) { - - $tax_rates = WC_Tax::get_rates( $this->get_tax_class() ); - $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, false ); - $tax_amount = WC_Tax::get_tax_total( $taxes ); - $price = round( $price * $qty + $tax_amount, wc_get_price_decimals() ); - - } else { - - $tax_rates = WC_Tax::get_rates( $this->get_tax_class() ); - $base_tax_rates = WC_Tax::get_base_tax_rates( $this->tax_class ); - - if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) { - - $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true ); - $base_tax_amount = array_sum( $base_taxes ); - $price = round( $price * $qty - $base_tax_amount, wc_get_price_decimals() ); - - /** - * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. - * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. - * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. - */ - } elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { - - $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true ); - $modded_taxes = WC_Tax::calc_tax( ( $price * $qty ) - array_sum( $base_taxes ), $tax_rates, false ); - $price = round( ( $price * $qty ) - array_sum( $base_taxes ) + array_sum( $modded_taxes ), wc_get_price_decimals() ); - - } else { - - $price = $price * $qty; - - } - } - } else { - $price = $price * $qty; - } - - return apply_filters( 'woocommerce_get_price_including_tax', $price, $qty, $this ); - } - - /** - * Returns the price (excluding tax) - ignores tax_class filters since the price may *include* tax and thus needs subtracting. - * Uses store base tax rates. Can work for a specific $qty for more accurate taxes. - * - * @param int $qty - * @param string $price to calculate, left blank to just use get_price() - * @return string - */ - public function get_price_excluding_tax( $qty = 1, $price = '' ) { - - if ( '' === $price ) { - $price = $this->get_price(); - } - - if ( $this->is_taxable() && 'yes' === get_option( 'woocommerce_prices_include_tax' ) ) { - $tax_rates = WC_Tax::get_base_tax_rates( $this->tax_class ); - $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, true ); - $price = WC_Tax::round( $price * $qty - array_sum( $taxes ) ); - } else { - $price = $price * $qty; - } - - return apply_filters( 'woocommerce_get_price_excluding_tax', $price, $qty, $this ); - } - - /** - * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting. - * - * @param string $price to calculate, left blank to just use get_price() - * @param integer $qty passed on to get_price_including_tax() or get_price_excluding_tax() - * @return string - */ - public function get_display_price( $price = '', $qty = 1 ) { - - if ( '' === $price ) { - $price = $this->get_price(); - } - - $tax_display_mode = get_option( 'woocommerce_tax_display_shop' ); - $display_price = ( 'incl' === $tax_display_mode ) ? $this->get_price_including_tax( $qty, $price ) : $this->get_price_excluding_tax( $qty, $price ); - - return $display_price; - } - - /** - * Returns the price in html format. - * - * @return string - */ - public function get_price_html( $deprecated = '' ) { - if ( '' === $this->get_price() ) { - return apply_filters( 'woocommerce_empty_price_html', '', $this ); - } - - if ( $this->is_on_sale() ) { - $price = wc_format_price_range( $this->get_display_price( $this->get_regular_price() ), $this->get_display_price() ) . wc_get_price_suffix( $this ); - } else { - $price = wc_price( $this->get_display_price() ) . wc_get_price_suffix( $this ); - } - - return apply_filters( 'woocommerce_get_price_html', $price, $this ); - } - /* |-------------------------------------------------------------------------- | @todo taxonomy functions diff --git a/includes/wc-product-functions.php b/includes/wc-product-functions.php index 8d4f7cde9dc..65ed5210015 100644 --- a/includes/wc-product-functions.php +++ b/includes/wc-product-functions.php @@ -864,3 +864,98 @@ function wc_get_related_products_query( $cats_array, $tags_array, $exclude_ids, return $query; } + +/** + * For a given product, and optionally price/qty, work out the price with tax included, based on store settings. + * @since 2.7.0 + * @param WC_Product $product + * @param array $args + * @return float + */ +function wc_get_price_including_tax( $product, $args = array() ) { + $args = wp_parse_args( $args, array( + 'qty' => 1, + 'price' => $product->get_price(), + ) ); + + $price = $args['price']; + $qty = $args['qty']; + + if ( ! $product->is_taxable() ) { + $price = $price * $qty; + } elseif ( wc_prices_include_tax() ) { + $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); + $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, false ); + $tax_amount = WC_Tax::get_tax_total( $taxes ); + $price = round( $price * $qty + $tax_amount, wc_get_price_decimals() ); + } else { + $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); + $base_tax_rates = WC_Tax::get_base_tax_rates( $product->tax_class ); + + if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) { + $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true ); + $base_tax_amount = array_sum( $base_taxes ); + $price = round( $price * $qty - $base_tax_amount, wc_get_price_decimals() ); + + /** + * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. + * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. + * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. + */ + } elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { + $base_taxes = WC_Tax::calc_tax( $price * $qty, $base_tax_rates, true ); + $modded_taxes = WC_Tax::calc_tax( ( $price * $qty ) - array_sum( $base_taxes ), $tax_rates, false ); + $price = round( ( $price * $qty ) - array_sum( $base_taxes ) + array_sum( $modded_taxes ), wc_get_price_decimals() ); + + } else { + $price = $price * $qty; + } + } + return apply_filters( 'woocommerce_get_price_including_tax', $price, $qty, $product ); +} + +/** + * For a given product, and optionally price/qty, work out the price with tax excluded, based on store settings. + * @since 2.7.0 + * @param WC_Product $product + * @param array $args + * @return float + */ +function wc_get_price_excluding_tax( $product, $args = array() ) { + $args = wp_parse_args( $args, array( + 'qty' => 1, + 'price' => $product->get_price(), + ) ); + + $price = $args['price']; + $qty = $args['qty']; + + if ( $product->is_taxable() && wc_prices_include_tax() ) { + $tax_rates = WC_Tax::get_base_tax_rates( $product->tax_class ); + $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, true ); + $price = WC_Tax::round( $price * $qty - array_sum( $taxes ) ); + } else { + $price = $price * $qty; + } + + return apply_filters( 'woocommerce_get_price_excluding_tax', $price, $qty, $product ); +} + +/** + * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting. + * @since 2.7.0 + * @param WC_Product $product + * @param array $args + * @return float + */ +function wc_get_price_to_display( $product, $args = array() ) { + $args = wp_parse_args( $args, array( + 'qty' => 1, + 'price' => $product->get_price(), + ) ); + + $price = $args['price']; + $qty = $args['qty']; + + return 'incl' === get_option( 'woocommerce_tax_display_shop' ) ? wc_get_price_including_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) : wc_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => $price ) ); +} diff --git a/includes/wc-template-functions.php b/includes/wc-template-functions.php index 1908030238f..67a97907fca 100644 --- a/includes/wc-template-functions.php +++ b/includes/wc-template-functions.php @@ -2500,14 +2500,13 @@ function wc_get_price_suffix( $product, $price = '', $qty = 1 ) { ); $replace = array( - wc_price( $product->get_price_including_tax( $qty, $price ) ), - wc_price( $product->get_price_excluding_tax( $qty, $price ) ), + wc_price( wc_get_price_including_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) ), + wc_price( wc_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) ), ); $price_display_suffix = str_replace( $find, $replace, $price_display_suffix ); } else { $price_display_suffix = ''; } - return apply_filters( 'woocommerce_get_price_suffix', $price_display_suffix, $product ); }