/**
 * Filter the product retruned by `WC_Order::woocommerce_get_product_from_item()`
 * to re-calculate a Measurement Price Calculator product's weight based on the
 * selected measurements. This function ensures that the "Weight" calculator
 * type is handled appropriately as well.
 *
 * @param \WC_Product $product The product.
 * @param array $item The order item.
 * @param \WC_Order $order The order.
 * @return \WC_Product The filtered product
 */
function sv_wc_mpc_shipstation_get_product_from_item_weight($product, $item, $order)
{
    if (WC_Price_Calculator_Product::pricing_calculated_weight_enabled($product)) {
        $settings = new WC_Price_Calculator_Settings($product);
        if ('weight' == $settings->get_calculator_type()) {
            // Now, the weight calculator products have to be handled specially
            // since the customer is actually supplying the weight, but it will
            // be in pricing units which may not be the same as the globally
            // configured WooCommerce Weight Unit expected by other plugins and code
            if (isset($item['item_meta']['_measurement_data'][0])) {
                $measurement_data = maybe_unserialize($item['item_meta']['_measurement_data'][0]);
                if (isset($measurement_data['_measurement_needed_unit']) && isset($measurement_data['_measurement_needed'])) {
                    $supplied_weight = new WC_Price_Calculator_Measurement($measurement_data['_measurement_needed_unit'], $measurement_data['_measurement_needed']);
                    // set the product weight as supplied by the customer, in WC Weight Units
                    $product->weight = $supplied_weight->get_value(get_option('woocommerce_weight_unit'));
                }
            }
        } elseif ($product->get_weight()) {
            if (isset($item['item_meta']['_measurement_data'][0])) {
                $measurement_data = maybe_unserialize($item['item_meta']['_measurement_data'][0]);
                // record the configured weight per unit for future reference
                if (!isset($measurement_data['_weight'])) {
                    $measurement_data['_weight'] = $product->get_weight();
                }
                // calculate the product weight = unit weight * total measurement (both will be in the same pricing units so we have say lbs/sq. ft. * sq. ft. = lbs)
                $product->weight = $measurement_data['_weight'] * $measurement_data['_measurement_needed'];
            }
        }
    }
    return $product;
}
 /**
  * Gets the volume of the product, if one is defined, in woocommerce product units
  *
  * @since 3.0
  * @param WC_Product $product the product
  * @return WC_Price_Calculator_Measurement total volume measurement for the product, or null
  */
 public static function get_volume_measurement($product)
 {
     $measurement = null;
     // if a length and width are defined, use that.  We allow large and small dimensions
     //  (mm, km, mi) which don't make much sense to use as volumes, but
     //  we have no choice but to support them to some extent, so convert
     //  them to something more reasonable
     if ($product->length && $product->width && $product->height) {
         $volume = $product->length * $product->width * $product->height;
         switch (get_option('woocommerce_dimension_unit')) {
             case 'mm':
                 $volume *= 0.001;
                 // convert to ml
                 $unit = 'ml';
                 break;
             case 'km':
                 $volume *= 1000000000;
                 // convert to cu m
                 $unit = 'cu m';
                 break;
             case 'mi':
                 $volume *= 5451776000;
                 // convert to cu yd
                 $unit = 'cu. yd.';
                 break;
         }
         $unit = WC_Price_Calculator_Measurement::to_volume_unit(get_option('woocommerce_dimension_unit'));
         $measurement = new WC_Price_Calculator_Measurement($unit, $volume, 'volume', __('Volume', WC_Measurement_Price_Calculator::TEXT_DOMAIN));
         // convert to the product volume units
         $measurement->set_unit(get_option('woocommerce_volume_unit'));
     }
     // if there's an area and height, next use that
     $area = WC_Price_Calculator_Product::get_product_meta($product, 'area');
     if ($area && $product->height) {
         $area_unit = get_option('woocommerce_area_unit');
         $area_measurement = new WC_Price_Calculator_Measurement($area_unit, $area);
         $dimension_unit = get_option('woocommerce_dimension_unit');
         $dimension_measurement = new WC_Price_Calculator_Measurement($dimension_unit, $product->height);
         // determine the volume, in common units
         $dimension_measurement->set_common_unit($area_measurement->get_unit_common());
         $volume = $area_measurement->get_value_common() * $dimension_measurement->get_value_common();
         $volume_unit = WC_Price_Calculator_Measurement::to_volume_unit($area_measurement->get_unit_common());
         $measurement = new WC_Price_Calculator_Measurement($volume_unit, $volume, 'volume', __('Volume', WC_Measurement_Price_Calculator::TEXT_DOMAIN));
         // and convert to final volume units
         $measurement->set_unit(get_option('woocommerce_volume_unit'));
     }
     // finally if they overrode the length/width/height with a volume value, use that
     $volume = WC_Price_Calculator_Product::get_product_meta($product, 'volume');
     if ($volume) {
         $measurement = new WC_Price_Calculator_Measurement(get_option('woocommerce_volume_unit'), $volume, 'volume', __('Volume', WC_Measurement_Price_Calculator::TEXT_DOMAIN));
     }
     // if no measurement, just create a default empty one
     if (!$measurement) {
         $measurement = new WC_Price_Calculator_Measurement(get_option('woocommerce_volume_unit'), 0, 'volume', __('Volume', WC_Measurement_Price_Calculator::TEXT_DOMAIN));
     }
     return $measurement;
 }
 /**
  * Compare units, disregarding measurement type (length/area/volume/weight)
  * meaning that 'in' will compare as true to 'in', 'sq. in.' and 'cu. in.',
  * etc.
  *
  * @param string $unit1 first unit to compare
  * @param string $unit2 second unit to compare
  *
  * @return boolean if the units are the 'same'
  */
 public static function compare_units($unit1, $unit2)
 {
     // straight comparison
     if ($unit1 === $unit2) {
         return true;
     }
     // match found as area units (ie perhaps 'in' and 'sq. in.')  This also covers volume units
     if (WC_Price_Calculator_Measurement::to_area_unit($unit1) && WC_Price_Calculator_Measurement::to_area_unit($unit2) && WC_Price_Calculator_Measurement::to_area_unit($unit1) === WC_Price_Calculator_Measurement::to_area_unit($unit2)) {
         return true;
     }
     return false;
 }
 /**
  * Returns an array of option values for the given measurement.  This is
  * used for the pricing calculator only.
  *
  * @since 3.0
  * @param string $measurement_name the measurement name
  * @return array associative array of measurement option values to label
  */
 public function get_options($measurement_name)
 {
     $calculator_type = $this->get_calculator_type();
     $options = array();
     if ($this->is_pricing_calculator_enabled() && isset($this->settings[$calculator_type][$measurement_name]['options'])) {
         foreach ($this->settings[$calculator_type][$measurement_name]['options'] as $value) {
             if ('' !== $value) {
                 $result = WC_Price_Calculator_Measurement::convert_to_float($value);
                 $options[(string) $result] = $value;
             }
         }
     }
     return $options;
 }
 /**
  * Render the price calculator on the product page
  *
  * @since 3.0
  */
 public function render_price_calculator()
 {
     global $product, $wc_measurement_price_calculator;
     // is the calculator enabled for this product?
     if (!$product || !WC_Price_Calculator_Product::calculator_enabled($product)) {
         return;
     }
     $settings = new WC_Price_Calculator_Settings($product);
     if (WC_Price_Calculator_Product::pricing_calculator_enabled($product)) {
         // Pricing calculator with custom dimensions and a price "per unit"
         // get the product total measurement (ie Area or Volume, etc)
         $product_measurement = WC_Price_Calculator_Product::get_product_measurement($product, $settings);
         $product_measurement->set_unit($settings->get_pricing_unit());
         // get the product measurements, get a measurement, and set the product total measurement common unit based on the measurements common unit
         $measurements = $settings->get_calculator_measurements();
         list($measurement) = $measurements;
         $product_measurement->set_common_unit($measurement->get_unit_common());
         // pricing calculator enabled, get the template
         wc_get_template('single-product/price-calculator.php', array('product_measurement' => $product_measurement, 'settings' => $settings, 'measurements' => $measurements), '', $wc_measurement_price_calculator->get_plugin_path() . '/templates/');
         // need an element to contain the price for simple pricing rule products
         if ($product->is_type('simple') && $settings->pricing_rules_enabled()) {
             echo '<div class="single_variation"></div>';
         }
     } else {
         // quantity calculator.  where the quantity of product needed is based on the configured product dimensions.  This is a actually bit more complex
         // get the starting quantity, max quantity, and total product measurement in product units
         $quantity_range = WC_Price_Calculator_Product::get_quantity_range($product);
         // set the product measurement based on the minimum quantity value, and set the unit to the frontend calculator unit
         $measurements = $settings->get_calculator_measurements();
         // The product measurement will be used to create the 'amount actual' field.
         $product_measurement = WC_Price_Calculator_Product::get_product_measurement($product, $settings);
         // see whether all calculator measurements are defined in the same units (ie 'in', 'sq. in.' are considered the same)
         $measurements_unit = null;
         foreach ($measurements as $measurement) {
             if (!$measurements_unit) {
                 $measurements_unit = $measurement->get_unit();
             } else {
                 if (!WC_Price_Calculator_Measurement::compare_units($measurements_unit, $measurement->get_unit())) {
                     $measurements_unit = false;
                     break;
                 }
             }
         }
         // All calculator measurements use the same base units, so lets use those for the 'amount actual' field
         //  area/volume product measurement can have a calculator measurement defined in units of length, so it
         //  will need to be converted to units of area or volume respectively
         if ($measurements_unit) {
             switch ($product_measurement->get_type()) {
                 case 'area':
                     $measurements_unit = WC_Price_Calculator_Measurement::to_area_unit($measurements_unit);
                     break;
                 case 'volume':
                     $measurements_unit = WC_Price_Calculator_Measurement::to_volume_unit($measurements_unit);
                     break;
             }
         }
         // if the price per unit is displayed for this product, default to the pricing units for the 'amount actual' field
         if (WC_Price_Calculator_Product::pricing_per_unit_enabled($product)) {
             $measurements_unit = $settings->get_pricing_unit();
         }
         // if a measurement unit other than the default was determined, set it
         if ($measurements_unit) {
             $product_measurement->set_unit($measurements_unit);
         }
         $total_price = '';
         if ($product->is_type('simple')) {
             // If the product type is simple we can set an initial 'Amount Actual' and 'total price'
             //  we can't do this for variable products because we don't know which will be configured
             //  initially (actually I guess a default product can be configured, so maybe we can do something here)
             // not enough product physical attributes defined to get our measurement, so bail
             if (!$product_measurement->get_value()) {
                 return;
             }
             // figure out the starting measurement amount
             // multiply the starting quantity by the measurement value
             $product_measurement->set_value(round($quantity_range['min_value'] * $product_measurement->get_value(), 2));
             $total_price = wc_price($quantity_range['min_value'] * $product->get_price(), 2);
         } elseif ($product->is_type('variable')) {
             // clear the product measurement value for variable products, since we can't really know what it is ahead of time (except for when a default is set)
             $product_measurement->set_value('');
         }
         // pricing calculator enabled, get the template
         wc_get_template('single-product/quantity-calculator.php', array('calculator_type' => $settings->get_calculator_type(), 'product_measurement' => $product_measurement, 'measurements' => $measurements, 'total_price' => $total_price), '', $wc_measurement_price_calculator->get_plugin_path() . '/templates/');
     }
 }
 /**
  * Add a measurement product to the cart.  This allows for the programmatic
  * addition of measurement pricing calculator products to the cart.
  *
  * This method expects the single total measurement needed, given by
  * $measurement_needed, this would be the dimension, area, volume or weight
  * depending on the type of calculator.
  *
  * This method also expects the full set of product measurements, given by
  * $measurements.  For calculators with a single measurement like the dimension
  * calculator or simple area, this will contain the same value as
  * $measurement_needed.  For more complex calculators, like Area (w x l)
  * this is how the width and length measurements are specified.  For
  * convenience use <code>WC_Price_Calculator_Product::get_product_measurements( $product )</code>
  * to get the set of dimensions for your product, along with the correct
  * units, and set whatever values you need.
  *
  * @since 3.0
  * @param string $product_id contains the id of the product to add to the cart
  * @param WC_Price_Calculator_Measurement $measurement_needed the total
  *        measurement desired, ie 1 m, 3 sq. ft., etc
  * @param array $measurements array of WC_Price_Calculator_Measurement product
  *        measurements, ie 1 m or 1.5 ft, 1.5 ft.  Defaults to $measurement_needed
  *        for convenience for calculators with a single measurement like dimension,
  *        simple area, etc.
  * @param string $quantity contains the quantity of the item to add
  * @param int $variation_id optional variation id
  * @param array $variation optional attribute values
  * @param array $cart_item_data optional extra cart item data we want to pass into the item
  * @return bool true on success
  */
 public function add_to_cart($product_id, $measurement_needed, $measurements = array(), $quantity = 1, $variation_id = '', $variation = '', $cart_item_data = array())
 {
     // if measurements is empty just use the provided $measurement_needed (this is a shortcut for calculators with only one measurement, ie 'length', 'area', etc)
     if (empty($measurements)) {
         $measurements[] = $measurement_needed;
     }
     // build up the cart item data with the required values that would normally come in over the add to cart post request
     $cart_item_data['pricing_item_meta_data']['_measurement_needed_internal'] = $measurement_needed->get_value();
     $cart_item_data['pricing_item_meta_data']['_measurement_needed_unit_internal'] = $measurement_needed->get_unit();
     $cart_item_data['pricing_item_meta_data']['_quantity'] = $quantity;
     $product = wc_get_product($product_id);
     $settings = new WC_Price_Calculator_Settings($product);
     if (WC_Price_Calculator_Product::pricing_calculator_inventory_enabled($product)) {
         // pricing calculator product with inventory enabled, means we need to take the item quantity (ie 2) and determine the unit quantity (ie 2 * 3 ft = 6)
         $quantity *= $measurement_needed->get_value($settings->get_pricing_unit());
     }
     foreach ($measurements as $measurement) {
         $cart_item_data['pricing_item_meta_data'][$measurement->get_name()] = $measurement->get_value();
     }
     // initialize the cart_contents member if needed to avoid a warning from cart::find_product_in_cart()
     if (is_null(WC()->cart->cart_contents)) {
         WC()->cart->cart_contents = array();
     }
     return WC()->cart->add_to_cart($product_id, $quantity, $variation_id, $variation, $cart_item_data);
 }
 /**
  * Manage the order stock (whether restore or reduce) from the order admin
  * returning the true product stock change if this is for a pricing calculator
  * product/item with inventory enabled.  Ie 2 pieces of cloth at 3 ft each
  * we'd want to return 6
  *
  * @since 3.0
  * @param numeric $quantity the new quantity
  * @param string $item_id the order item identifier
  * @return numeric $quantity the measurement quantity
  */
 public function admin_manage_order_stock($quantity, $item_id)
 {
     $order_id = absint($_POST['order_id']);
     $order = wc_get_order($order_id);
     $order_items = $order->get_items();
     $product = wc_get_product($order_items[$item_id]['product_id']);
     if (WC_Price_Calculator_Product::pricing_calculator_inventory_enabled($product) && isset($order_items[$item_id]['measurement_data'])) {
         $settings = new WC_Price_Calculator_Settings($product);
         $measurement_data = maybe_unserialize($order_items[$item_id]['measurement_data']);
         $total_amount = new WC_Price_Calculator_Measurement($measurement_data['_measurement_needed_unit'], $measurement_data['_measurement_needed']);
         // this is a pricing calculator product so we want to return the
         //  quantity in terms of units, ie 2 pieces of cloth at 3 ft each = 6
         $quantity *= $total_amount->get_value($settings->get_pricing_unit());
     }
     return $quantity;
 }