/** * 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; }
/** * Modify the 'add to cart' url for pricing calculator products to simply link to * the product page, just like a variable product. This is because the * customer must supply whatever product measurements they require. * * @since 3.3 * @param string $tag the 'add to cart' button tag html * @param WC_Product $product the product * @return string the Add to Cart tag */ public function loop_add_to_cart_link($tag, $product) { if (WC_Price_Calculator_Product::pricing_calculator_enabled($product)) { // otherwise, for simple type products, the page javascript would take over and // try to do an ajax add-to-cart, when really we need the customer to visit the // product page to supply whatever input fields they require $tag = sprintf('<a href="%s" rel="nofollow" data-product_id="%s" data-product_sku="%s" class="button add_to_cart_button product_type_%s">%s</a>', get_permalink($product->id), esc_attr($product->id), esc_attr($product->get_sku()), 'variable', __('Select options', WC_Measurement_Price_Calculator::TEXT_DOMAIN)); } return $tag; }
/** * Add product page information if calculator is enabled * Example: Add a heading above the calculator section */ function sv_wc_measurement_price_calculator_heading() { // bail if MPC isn't active if (!class_exists('WC_Price_Calculator_Product')) { return; } global $product; $measurement = WC_Price_Calculator_Product::calculator_enabled($product); // if the calculator is enabled, add the heading if ($measurement) { echo '<h4>Enter Your Needed Measurements</h4>'; } }
/** * Hide the unit price from the top of the product page * for Measurement Calculator products */ function sv_wc_measurement_price_calculator_hide_unit_price() { // bail if the calculator isn't active or this isn't a product page if (!class_exists('WC_Price_Calculator_Product') || !is_product()) { return; } global $product; $measurement = WC_Price_Calculator_Product::calculator_enabled($product); // if the calculator is enabled, hide unit price if ($measurement) { echo '<style>.product form .price { display: none; }</style>'; } }
/** * Changes MPC width / length inputs to number type inputs * then adds min / max values accepted for each * * requires HTML5 support */ function sv_wc_measurement_price_calculator_input_min_max() { // bail unless we're on a product page and MPC is active if (!(is_product() && class_exists('WC_Price_Calculator_Product'))) { return; } global $product; // bail unless the calculator is enabled for this product, this is also why we hook into the footer scripts // since this isn't available immediately at page load if (!WC_Price_Calculator_Product::calculator_enabled($product)) { return; } wc_enqueue_js("\n\t\t\$('#length_needed').attr({ type: 'number', min: '.1', max: '24', step: '.1' }).addClass('input-text');\n\t\t\$('#width_needed').attr({ type: 'number', min: '.1', max: '5', step: '.1' }).addClass('input-text');\n\t"); }
/** * Add the pricing calculator and quantity input if the user can view the price * * @since 3.7.0 */ public function catalog_visibility_options_pricing_calculator_quantity_input() { global $product; // bail if the calculator is not enabled for this product if (!$product || !WC_Price_Calculator_Product::calculator_enabled($product)) { return; } // bail if current user can't view the price if (class_exists('WC_Catalog_Restrictions_Filters') && !WC_Catalog_Restrictions_Filters::instance()->user_can_view_price($product)) { return; } // render pricing calculator wc_measurement_price_calculator()->get_product_page_instance()->render_price_calculator(); // render quantity input if (!$product->is_sold_individually()) { woocommerce_quantity_input(array('min_value' => apply_filters('woocommerce_quantity_input_min', 1, $product), 'max_value' => apply_filters('woocommerce_quantity_input_max', $product->backorders_allowed() ? '' : $product->get_stock_quantity(), $product))); } }
/** * Returns an array of measurements for the given product * * @since 3.0 * @param WC_Product $product the product * @return array of WC_Price_Calculator_Measurement objects for the product */ public static function get_product_measurements($product) { if (WC_Price_Calculator_Product::pricing_calculator_enabled($product)) { $settings = new WC_Price_Calculator_Settings($product); return $settings->get_calculator_measurements(); } }
/** * Returns the price html for the given pricing rule, ie: * * -$10 / ft- $5 / ft * * $5 / ft * * -$10 / ft- Free! * * Free! * * @since 3.0 * @param array $rule the pricing rule with keys 'range_start', 'range_end', * 'price', 'regular_price' and 'sale_price' * @return string pricing rule price html */ public function get_pricing_rule_price_html($rule) { $price_html = ''; $sep = apply_filters('wc_measurement_price_calculator_pricing_label_separator', '/'); if ($rule['price'] > 0) { if ('' !== $rule['sale_price'] && '' !== $rule['regular_price']) { $price_html .= WC_Price_Calculator_Product::get_price_html_from_to($rule['regular_price'], $rule['price'], $sep . ' ' . __($this->get_pricing_label(), WC_Measurement_Price_Calculator::TEXT_DOMAIN)); } else { $price_html .= wc_price($rule['price']) . ' ' . $sep . ' ' . __($this->get_pricing_label(), WC_Measurement_Price_Calculator::TEXT_DOMAIN); } } elseif ('' === $rule['price']) { // no-op (for now) } elseif (0 == $rule['price']) { if ($rule['price'] === $rule['sale_price'] && '' !== $rule['regular_price']) { $price_html .= WC_Price_Calculator_Product::get_price_html_from_to($rule['regular_price'], __('Free!', WC_Measurement_Price_Calculator::TEXT_DOMAIN), __($this->get_pricing_label(), WC_Measurement_Price_Calculator::TEXT_DOMAIN)); } else { $price_html = __('Free!', WC_Measurement_Price_Calculator::TEXT_DOMAIN); } } return apply_filters('wc_measurement_price_calculator_get_pricing_rule_price_html', $price_html, $rule, $this); }
/** * 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/'); } }
/** * Turn the cart item data into human-readable key/value pairs for * display in the cart * * @since 3.0 * @param array $item cart item * @param array $cart_item_data the cart item data * @return array human-readable cart item data */ private function humanize_cart_item_data($item, $cart_item_data) { $new_cart_item_data = array(); // always need the actual parent product, not the useless variation product $product = isset($item['variation_id']) && $item['variation_id'] ? wc_get_product($item['product_id']) : $item['data']; $settings = new WC_Price_Calculator_Settings($product); foreach ($settings->get_calculator_measurements() as $measurement) { if (isset($cart_item_data[$measurement->get_name()])) { // if the measurement has a set of available options, get the option label for display, if we can determine it // (this way we display "1/8" rather than "0.125", etc) if (count($measurement->get_options()) > 0) { foreach ($measurement->get_options() as $value => $label) { if ($cart_item_data[$measurement->get_name()] === $value) { $cart_item_data[$measurement->get_name()] = $label; } } } $label = $measurement->get_unit_label() ? sprintf("%s (%s)", $measurement->get_label(), __($measurement->get_unit_label(), WC_Measurement_Price_Calculator::TEXT_DOMAIN)) : __($measurement->get_label(), WC_Measurement_Price_Calculator::TEXT_DOMAIN); $new_cart_item_data[$label] = $cart_item_data[$measurement->get_name()]; } } // render the total measurement if this is a derived calculator (ie "Area (sq. ft.): 10" if the calculator is Area (LxW)) if ($settings->is_calculator_type_derived() && isset($cart_item_data['_measurement_needed'])) { // get the product total measurement (ie area or volume) $product_measurement = WC_Price_Calculator_Product::get_product_measurement($product, $settings); $product_measurement->set_unit($cart_item_data['_measurement_needed_unit']); $product_measurement->set_value($cart_item_data['_measurement_needed']); $total_amount_text = apply_filters('wc_measurement_price_calculator_total_amount_text', $product_measurement->get_unit_label() ? sprintf(__('Total %s (%s)', WC_Measurement_Price_Calculator::TEXT_DOMAIN), $product_measurement->get_label(), __($product_measurement->get_unit_label(), WC_Measurement_Price_Calculator::TEXT_DOMAIN)) : sprintf(__('Total %s', WC_Measurement_Price_Calculator::TEXT_DOMAIN), $product_measurement->get_label()), $item); $new_cart_item_data[$total_amount_text] = $product_measurement->get_value(); } return $new_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; }