/**
  * Returns the price in html format.
  *
  * @access public
  * @param string $price (default: '')
  * @return string
  */
 public function get_price_html($price = '')
 {
     $tax_display_mode = get_option('woocommerce_tax_display_shop');
     $child_prices = array();
     foreach ($this->get_children() as $child_id) {
         $child = wc_get_product($child_id);
         if ('' !== $child->get_price()) {
             $child_prices[] = 'incl' === $tax_display_mode ? wc_get_price_including_tax($child) : wc_get_price_excluding_tax($child);
         }
     }
     if (!empty($child_prices)) {
         $min_price = min($child_prices);
         $max_price = max($child_prices);
     } else {
         $min_price = '';
         $max_price = '';
     }
     if ('' !== $min_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 = 0 == $min_price && 0 == $max_price;
         if ($is_free) {
             $price = apply_filters('woocommerce_grouped_free_price_html', __('Free!', 'woocommerce'), $this);
         } else {
             $price = apply_filters('woocommerce_grouped_price_html', $price . wc_get_price_suffix($this), $this, $child_prices);
         }
     } else {
         $price = apply_filters('woocommerce_grouped_empty_price_html', '', $this);
     }
     return apply_filters('woocommerce_get_price_html', $price, $this);
 }
 /**
  * Get the product row subtotal.
  *
  * Gets the tax etc to avoid rounding issues.
  *
  * When on the checkout (review order), this will get the subtotal based on the customer's tax rate rather than the base rate.
  *
  * @param WC_Product $product
  * @param int $quantity
  * @return string formatted price
  */
 public function get_product_subtotal($product, $quantity)
 {
     $price = $product->get_price();
     $taxable = $product->is_taxable();
     // Taxable
     if ($taxable) {
         if ('excl' === $this->tax_display_cart) {
             $row_price = wc_get_price_excluding_tax($product, array('qty' => $quantity));
             $product_subtotal = wc_price($row_price);
             if ($this->prices_include_tax && $this->tax_total > 0) {
                 $product_subtotal .= ' <small class="tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
             }
         } else {
             $row_price = wc_get_price_including_tax($product, array('qty' => $quantity));
             $product_subtotal = wc_price($row_price);
             if (!$this->prices_include_tax && $this->tax_total > 0) {
                 $product_subtotal .= ' <small class="tax_label">' . WC()->countries->inc_tax_or_vat() . '</small>';
             }
         }
         // Non-taxable
     } else {
         $row_price = $price * $quantity;
         $product_subtotal = wc_price($row_price);
     }
     return apply_filters('woocommerce_cart_product_subtotal', $product_subtotal, $product, $quantity, $this);
 }
 /**
  * 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.
  *
  * @since  2.7.0
  * @param  WC_Product
  * @param  bool $include_taxes If taxes should be calculated or not.
  */
 private function read_price_data(&$product, $include_taxes = false)
 {
     global $wp_filter;
     /**
      * Transient name for storing prices for this product (note: Max transient length is 45)
      * @since 2.5.0 a single transient is used per product for all prices, rather than many transients per product.
      */
     $transient_name = 'wc_var_prices_' . $product->get_id();
     /**
      * 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
      */
     $price_hash = $include_taxes ? array(get_option('woocommerce_tax_display_shop', 'excl'), WC_Tax::get_rates()) : array(false);
     $filter_names = array('woocommerce_variation_prices_price', 'woocommerce_variation_prices_regular_price', 'woocommerce_variation_prices_sale_price');
     foreach ($filter_names as $filter_name) {
         if (!empty($wp_filter[$filter_name])) {
             $price_hash[$filter_name] = array();
             foreach ($wp_filter[$filter_name] as $priority => $callbacks) {
                 $price_hash[$filter_name][] = array_values(wp_list_pluck($callbacks, 'function'));
             }
         }
     }
     $price_hash[] = WC_Cache_Helper::get_transient_version('product');
     $price_hash = md5(json_encode(apply_filters('woocommerce_get_variation_prices_hash', $price_hash, $product, $include_taxes)));
     /**
      * $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])) {
         if ($include_taxes) {
             $product->set_variation_prices_including_taxes($this->prices_array[$price_hash]);
         } else {
             $product->set_variation_prices($this->prices_array[$price_hash]);
         }
         /**
          * No locally cached value? Get the data from the transient or generate it.
          */
     } else {
         // Get value of transient
         $transient_cached_prices_array = array_filter((array) json_decode(strval(get_transient($transient_name)), true));
         // If the product version has changed since the transient was last saved, reset the transient cache.
         if (empty($transient_cached_prices_array['version']) || WC_Cache_Helper::get_transient_version('product') !== $transient_cached_prices_array['version']) {
             $transient_cached_prices_array = array('version' => WC_Cache_Helper::get_transient_version('product'));
         }
         // If the prices are not stored for this hash, generate them and add to the transient.
         if (empty($transient_cached_prices_array[$price_hash])) {
             $prices = array();
             $regular_prices = array();
             $sale_prices = array();
             $variation_ids = $product->get_visible_children();
             foreach ($variation_ids as $variation_id) {
                 if ($variation = wc_get_product($variation_id)) {
                     $price = apply_filters('woocommerce_variation_prices_price', $variation->get_price('edit'), $variation, $product);
                     $regular_price = apply_filters('woocommerce_variation_prices_regular_price', $variation->get_regular_price('edit'), $variation, $product);
                     $sale_price = apply_filters('woocommerce_variation_prices_sale_price', $variation->get_sale_price('edit'), $variation, $product);
                     // Skip empty prices
                     if ('' === $price) {
                         continue;
                     }
                     // 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;
                     }
                     // If we are getting prices for display, we need to account for taxes
                     if ($include_taxes) {
                         if ('incl' === get_option('woocommerce_tax_display_shop')) {
                             $price = '' === $price ? '' : wc_get_price_including_tax($variation, array('qty' => 1, 'price' => $price));
                             $regular_price = '' === $regular_price ? '' : wc_get_price_including_tax($variation, array('qty' => 1, 'price' => $regular_price));
                             $sale_price = '' === $sale_price ? '' : wc_get_price_including_tax($variation, array('qty' => 1, 'price' => $sale_price));
                         } else {
                             $price = '' === $price ? '' : wc_get_price_excluding_tax($variation, array('qty' => 1, 'price' => $price));
                             $regular_price = '' === $regular_price ? '' : wc_get_price_excluding_tax($variation, array('qty' => 1, 'price' => $regular_price));
                             $sale_price = '' === $sale_price ? '' : wc_get_price_excluding_tax($variation, array('qty' => 1, 'price' => $sale_price));
                         }
                     }
                     $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());
                 }
             }
             asort($prices);
             asort($regular_prices);
             asort($sale_prices);
             $transient_cached_prices_array[$price_hash] = array('price' => $prices, 'regular_price' => $regular_prices, 'sale_price' => $sale_prices);
             set_transient($transient_name, json_encode($transient_cached_prices_array), DAY_IN_SECONDS * 30);
         }
         /**
          * 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.
          */
         $this->prices_array[$price_hash] = apply_filters('woocommerce_variation_prices', $transient_cached_prices_array[$price_hash], $product, $include_taxes);
         if ($include_taxes) {
             $product->set_variation_prices_including_taxes($this->prices_array[$price_hash]);
         } else {
             $product->set_variation_prices($this->prices_array[$price_hash]);
         }
     }
 }
 /**
  * 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 = '')
 {
     wc_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));
 }
/**
 * 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));
}
/**
 * Get the price suffix for a product if needed.
 * @since  2.7.0
 * @param  WC_Product  $product
 * @param  string  $price
 * @param  integer $qty
 * @return string
 */
function wc_get_price_suffix($product, $price = '', $qty = 1)
{
    if (($price_display_suffix = get_option('woocommerce_price_display_suffix')) && wc_tax_enabled()) {
        $price = '' === $price ? $product->get_price() : $price;
        $price_display_suffix = ' <small class="woocommerce-price-suffix">' . wp_kses_post($price_display_suffix) . '</small>';
        $find = array('{price_including_tax}', '{price_excluding_tax}');
        $replace = array(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);
}
 /**
  * Add a product line item to the order. This is the only line item type with
  * it's own method because it saves looking up order amounts (costs are added up for you).
  * @param  \WC_Product $product
  * @param  int $qty
  * @param  array $args
  * @return int order item ID
  * @throws WC_Data_Exception
  */
 public function add_product($product, $qty = 1, $args = array())
 {
     if ($product) {
         $default_args = array('name' => $product->get_name(), 'tax_class' => $product->get_tax_class(), 'product_id' => $product->is_type('variation') ? $product->get_parent_id() : $product->get_id(), 'variation_id' => $product->is_type('variation') ? $product->get_id() : 0, 'variation' => $product->is_type('variation') ? $product->get_attributes() : array(), 'subtotal' => wc_get_price_excluding_tax($product, array('qty' => $qty)), 'total' => wc_get_price_excluding_tax($product, array('qty' => $qty)), 'quantity' => $qty);
     } else {
         $default_args = array('quantity' => $qty);
     }
     $args = wp_parse_args($args, $default_args);
     // BW compatibility with old args
     if (isset($args['totals'])) {
         foreach ($args['totals'] as $key => $value) {
             if ('tax' === $key) {
                 $args['total_tax'] = $value;
             } elseif ('tax_data' === $key) {
                 $args['taxes'] = $value;
             } else {
                 $args[$key] = $value;
             }
         }
     }
     $item = new WC_Order_Item_Product();
     $item->set_props($args);
     $item->set_backorder_meta();
     $item->set_order_id($this->get_id());
     $item->save();
     $this->add_item($item);
     wc_do_deprecated_action('woocommerce_order_add_product', array($this->get_id(), $item->get_id(), $product, $qty, $args), '2.7', 'Use woocommerce_new_order_item action instead.');
     return $item->get_id();
 }
 /**
  * Get the suffix to display after prices > 0.
  *
  * @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_price_suffix($price = '', $qty = 1)
 {
     $html = '';
     if (($suffix = get_option('woocommerce_price_display_suffix')) && wc_tax_enabled()) {
         if ('' === $price) {
             $price = $this->get_price();
         }
         $replacements = array('{price_including_tax}' => wc_price(wc_get_price_including_tax($this, array('qty' => $qty, 'price' => $price))), '{price_excluding_tax}' => wc_price(wc_get_price_excluding_tax($this, array('qty' => $qty, 'price' => $price))));
         $html = str_replace(array_keys($replacements), array_values($replacements), ' <small class ="woocommerce-price-suffix">' . wp_kses_post($suffix) . '</small>');
     }
     return apply_filters('woocommerce_get_price_suffix', $html, $this);
 }
 /**
  * Update a line item for the order.
  *
  * Note this does not update order totals.
  *
  * @param object|int $item order item ID or item object.
  * @param WC_Product $product
  * @param array $args data to update.
  * @return int updated order item ID
  * @throws WC_Data_Exception
  */
 public function update_product($item, $product, $args)
 {
     wc_deprecated_function('WC_Order::update_product', '2.7', 'Interact with WC_Order_Item_Product class');
     if (is_numeric($item)) {
         $item = $this->get_item($item);
     }
     if (!is_object($item) || !$item->is_type('line_item')) {
         return false;
     }
     if (!$this->get_id()) {
         $this->save();
         // Order must exist
     }
     // BW compatibility with old args
     if (isset($args['totals'])) {
         foreach ($args['totals'] as $key => $value) {
             if ('tax' === $key) {
                 $args['total_tax'] = $value;
             } elseif ('tax_data' === $key) {
                 $args['taxes'] = $value;
             } else {
                 $args[$key] = $value;
             }
         }
     }
     // Handly qty if set
     if (isset($args['qty'])) {
         if ($product->backorders_require_notification() && $product->is_on_backorder($args['qty'])) {
             $item->add_meta_data(apply_filters('woocommerce_backordered_item_meta_name', __('Backordered', 'woocommerce')), $args['qty'] - max(0, $product->get_stock_quantity()), true);
         }
         $args['subtotal'] = $args['subtotal'] ? $args['subtotal'] : wc_get_price_excluding_tax($product, array('qty' => $args['qty']));
         $args['total'] = $args['total'] ? $args['total'] : wc_get_price_excluding_tax($product, array('qty' => $args['qty']));
     }
     $item->set_order_id($this->get_id());
     $item->set_props($args);
     $item->save();
     do_action('woocommerce_order_edit_product', $this->get_id(), $item->get_id(), $args, $product);
     return $item->get_id();
 }
 /**
  * Get discount amount for a cart item.
  *
  * @param  float $discounting_amount Amount the coupon is being applied to
  * @param  array|null $cart_item Cart item being discounted if applicable
  * @param  boolean $single True if discounting a single qty item, false if its the line
  * @return float Amount this coupon has discounted
  */
 public function get_discount_amount($discounting_amount, $cart_item = null, $single = false)
 {
     $discount = 0;
     $cart_item_qty = is_null($cart_item) ? 1 : $cart_item['quantity'];
     if ($this->is_type(array('percent_product', 'percent'))) {
         $discount = $this->get_amount() * ($discounting_amount / 100);
     } elseif ($this->is_type('fixed_cart') && !is_null($cart_item) && WC()->cart->subtotal_ex_tax) {
         /**
          * This is the most complex discount - we need to divide the discount between rows based on their price in.
          * proportion to the subtotal. This is so rows with different tax rates get a fair discount, and so rows.
          * with no price (free) don't get discounted.
          *
          * Get item discount by dividing item cost by subtotal to get a %.
          *
          * Uses price inc tax if prices include tax to work around https://github.com/woocommerce/woocommerce/issues/7669 and https://github.com/woocommerce/woocommerce/issues/8074.
          */
         if (wc_prices_include_tax()) {
             $discount_percent = wc_get_price_including_tax($cart_item['data']) * $cart_item_qty / WC()->cart->subtotal;
         } else {
             $discount_percent = wc_get_price_excluding_tax($cart_item['data']) * $cart_item_qty / WC()->cart->subtotal_ex_tax;
         }
         $discount = $this->get_amount() * $discount_percent / $cart_item_qty;
     } elseif ($this->is_type('fixed_product')) {
         $discount = min($this->get_amount(), $discounting_amount);
         $discount = $single ? $discount : $discount * $cart_item_qty;
     }
     $discount = min($discount, $discounting_amount);
     // Handle the limit_usage_to_x_items option
     if ($this->is_type(array('percent_product', 'fixed_product'))) {
         if ($discounting_amount) {
             if (!$this->get_limit_usage_to_x_items()) {
                 $limit_usage_qty = $cart_item_qty;
             } else {
                 $limit_usage_qty = min($this->get_limit_usage_to_x_items(), $cart_item_qty);
                 $this->set_limit_usage_to_x_items(max(0, $this->get_limit_usage_to_x_items() - $limit_usage_qty));
             }
             if ($single) {
                 $discount = $discount * $limit_usage_qty / $cart_item_qty;
             } else {
                 $discount = $discount / $cart_item_qty * $limit_usage_qty;
             }
         }
     }
     $discount = round($discount, wc_get_rounding_precision());
     return apply_filters('woocommerce_coupon_get_discount_amount', $discount, $discounting_amount, $cart_item, $single, $this);
 }