2025-12-21 04:56:50 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Cart price calculation for tier and package pricing
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (!defined('ABSPATH')) {
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class WC_TPP_Cart {
|
|
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
|
add_action('woocommerce_before_calculate_totals', array($this, 'apply_tier_package_pricing'), 10, 1);
|
|
|
|
|
add_filter('woocommerce_cart_item_price', array($this, 'display_cart_item_price'), 10, 3);
|
|
|
|
|
add_filter('woocommerce_cart_item_subtotal', array($this, 'display_cart_item_subtotal'), 10, 3);
|
2025-12-21 15:54:04 +01:00
|
|
|
add_filter('woocommerce_add_to_cart_validation', array($this, 'validate_package_quantity'), 10, 3);
|
2025-12-21 19:19:18 +01:00
|
|
|
add_filter('woocommerce_cart_item_quantity', array($this, 'maybe_hide_cart_quantity_input'), 999, 3);
|
|
|
|
|
add_filter('woocommerce_widget_cart_item_quantity', array($this, 'maybe_hide_mini_cart_quantity_input'), 999, 3);
|
|
|
|
|
add_action('wp_head', array($this, 'add_cart_quantity_css'));
|
2025-12-21 19:33:34 +01:00
|
|
|
|
|
|
|
|
// WooCommerce Blocks support
|
|
|
|
|
add_filter('woocommerce_store_api_product_quantity_editable', array($this, 'block_quantity_editable'), 10, 2);
|
2025-12-21 04:56:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function apply_tier_package_pricing($cart) {
|
|
|
|
|
if (is_admin() && !defined('DOING_AJAX')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prevent infinite loops
|
|
|
|
|
if (did_action('woocommerce_before_calculate_totals') >= 2) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if cart object is valid
|
|
|
|
|
if (!$cart || !is_a($cart, 'WC_Cart')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
|
|
|
|
$product_id = $cart_item['product_id'];
|
|
|
|
|
$quantity = $cart_item['quantity'];
|
|
|
|
|
$product = $cart_item['data'];
|
|
|
|
|
|
|
|
|
|
// Validate product object
|
|
|
|
|
if (!$product || !is_a($product, 'WC_Product')) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for exact package match first
|
|
|
|
|
$package_price = null;
|
|
|
|
|
if (get_option('wc_tpp_enable_package_pricing') === 'yes') {
|
|
|
|
|
$package_price = WC_TPP_Frontend::get_package_price($product_id, $quantity);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($package_price !== null) {
|
|
|
|
|
// Apply package pricing (total price divided by quantity)
|
|
|
|
|
$unit_price = $package_price / $quantity;
|
|
|
|
|
$product->set_price($unit_price);
|
|
|
|
|
// Store pricing information in cart item for display
|
|
|
|
|
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_pricing_type'] = 'package';
|
|
|
|
|
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_total_price'] = $package_price;
|
|
|
|
|
} else {
|
|
|
|
|
// Apply tier pricing if no package match
|
|
|
|
|
if (get_option('wc_tpp_enable_tier_pricing') === 'yes') {
|
|
|
|
|
$tier_price = WC_TPP_Frontend::get_tier_price($product_id, $quantity);
|
|
|
|
|
if ($tier_price !== null) {
|
|
|
|
|
$product->set_price($tier_price);
|
|
|
|
|
// Store pricing information in cart item for display
|
|
|
|
|
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_pricing_type'] = 'tier';
|
|
|
|
|
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_unit_price'] = $tier_price;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function display_cart_item_price($price, $cart_item, $cart_item_key) {
|
|
|
|
|
if (isset($cart_item['wc_tpp_pricing_type'])) {
|
|
|
|
|
if ($cart_item['wc_tpp_pricing_type'] === 'package') {
|
|
|
|
|
$total_price = isset($cart_item['wc_tpp_total_price']) ? $cart_item['wc_tpp_total_price'] : $cart_item['line_total'];
|
|
|
|
|
$unit_price = $total_price / $cart_item['quantity'];
|
|
|
|
|
return wc_price($unit_price) . ' <small class="wc-tpp-notice">(' . __('Package price', 'wc-tier-package-prices') . ')</small>';
|
|
|
|
|
} elseif ($cart_item['wc_tpp_pricing_type'] === 'tier') {
|
|
|
|
|
$unit_price = isset($cart_item['wc_tpp_unit_price']) ? $cart_item['wc_tpp_unit_price'] : $cart_item['data']->get_price();
|
|
|
|
|
return wc_price($unit_price) . ' <small class="wc-tpp-notice">(' . __('Volume discount', 'wc-tier-package-prices') . ')</small>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return $price;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function display_cart_item_subtotal($subtotal, $cart_item, $cart_item_key) {
|
|
|
|
|
if (isset($cart_item['wc_tpp_pricing_type']) && $cart_item['wc_tpp_pricing_type'] === 'package') {
|
|
|
|
|
$total_price = isset($cart_item['wc_tpp_total_price']) ? $cart_item['wc_tpp_total_price'] : $cart_item['line_total'];
|
|
|
|
|
return wc_price($total_price);
|
|
|
|
|
}
|
|
|
|
|
return $subtotal;
|
|
|
|
|
}
|
2025-12-21 15:54:04 +01:00
|
|
|
|
|
|
|
|
public function validate_package_quantity($passed, $product_id, $quantity) {
|
|
|
|
|
// Check if restriction is enabled globally or for this product
|
|
|
|
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
|
|
|
|
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
|
|
|
|
|
|
|
|
|
if (!$global_restrict && !$product_restrict) {
|
|
|
|
|
return $passed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get packages for this product
|
|
|
|
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
|
|
|
|
|
|
|
|
|
if (empty($packages) || !is_array($packages)) {
|
|
|
|
|
return $passed;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if the quantity matches any package
|
|
|
|
|
$valid_quantity = false;
|
|
|
|
|
$available_quantities = array();
|
|
|
|
|
|
|
|
|
|
foreach ($packages as $package) {
|
|
|
|
|
$available_quantities[] = $package['qty'];
|
|
|
|
|
if ($quantity == $package['qty']) {
|
|
|
|
|
$valid_quantity = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!$valid_quantity) {
|
|
|
|
|
$product = wc_get_product($product_id);
|
|
|
|
|
$product_name = $product ? $product->get_name() : __('this product', 'wc-tier-package-prices');
|
|
|
|
|
|
|
|
|
|
wc_add_notice(
|
|
|
|
|
sprintf(
|
|
|
|
|
__('The quantity %1$d is not available for %2$s. Please choose from the available package sizes: %3$s', 'wc-tier-package-prices'),
|
|
|
|
|
$quantity,
|
|
|
|
|
$product_name,
|
|
|
|
|
implode(', ', $available_quantities)
|
|
|
|
|
),
|
|
|
|
|
'error'
|
|
|
|
|
);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $passed;
|
|
|
|
|
}
|
2025-12-21 17:21:40 +01:00
|
|
|
|
|
|
|
|
public function maybe_hide_cart_quantity_input($product_quantity, $cart_item_key, $cart_item) {
|
|
|
|
|
$product_id = $cart_item['product_id'];
|
|
|
|
|
|
|
|
|
|
// Check if restriction is enabled globally or for this product
|
|
|
|
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
|
|
|
|
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
|
|
|
|
|
|
|
|
|
// Get packages for this product
|
|
|
|
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
|
|
|
|
|
|
|
|
|
// If restriction is enabled and packages exist, show quantity as text only
|
|
|
|
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
2025-12-21 19:19:18 +01:00
|
|
|
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s</span>',
|
|
|
|
|
$product_id,
|
|
|
|
|
$cart_item['quantity']
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $product_quantity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function maybe_hide_mini_cart_quantity_input($product_quantity, $cart_item, $cart_item_key) {
|
|
|
|
|
$product_id = $cart_item['product_id'];
|
|
|
|
|
|
|
|
|
|
// Check if restriction is enabled globally or for this product
|
|
|
|
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
|
|
|
|
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
|
|
|
|
|
|
|
|
|
// Get packages for this product
|
|
|
|
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
|
|
|
|
|
|
|
|
|
// If restriction is enabled and packages exist, show quantity as text only
|
|
|
|
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
|
|
|
|
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s ×</span>',
|
|
|
|
|
$product_id,
|
|
|
|
|
$cart_item['quantity']
|
|
|
|
|
);
|
2025-12-21 17:21:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $product_quantity;
|
|
|
|
|
}
|
2025-12-21 19:19:18 +01:00
|
|
|
|
|
|
|
|
public function add_cart_quantity_css() {
|
|
|
|
|
// Get all cart items and check which products have restrictions
|
2025-12-21 19:49:24 +01:00
|
|
|
if (!function_exists('WC') || !WC()->cart || is_admin()) {
|
2025-12-21 19:19:18 +01:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$restricted_products = array();
|
|
|
|
|
foreach (WC()->cart->get_cart() as $cart_item) {
|
|
|
|
|
$product_id = $cart_item['product_id'];
|
|
|
|
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
|
|
|
|
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
|
|
|
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
|
|
|
|
|
|
|
|
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
|
|
|
|
$restricted_products[] = $product_id;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!empty($restricted_products)) {
|
|
|
|
|
echo '<style type="text/css">';
|
|
|
|
|
foreach ($restricted_products as $product_id) {
|
2025-12-21 19:33:34 +01:00
|
|
|
// Hide quantity inputs for restricted products in cart (classic cart)
|
2025-12-21 19:19:18 +01:00
|
|
|
echo '.cart_item .wc-tpp-restricted-qty[data-product-id="' . esc_attr($product_id) . '"] + .quantity,';
|
|
|
|
|
echo '.cart_item .wc-tpp-restricted-qty[data-product-id="' . esc_attr($product_id) . '"] ~ .quantity,';
|
|
|
|
|
echo '.woocommerce-mini-cart-item .wc-tpp-restricted-qty[data-product-id="' . esc_attr($product_id) . '"] + .quantity,';
|
|
|
|
|
echo '.woocommerce-mini-cart-item .wc-tpp-restricted-qty[data-product-id="' . esc_attr($product_id) . '"] ~ .quantity { display: none !important; }';
|
2025-12-21 19:33:34 +01:00
|
|
|
|
|
|
|
|
// Hide WooCommerce blocks quantity selector for restricted products
|
|
|
|
|
echo '.wc-block-cart-item[data-product-id="' . esc_attr($product_id) . '"] .wc-block-components-quantity-selector,';
|
|
|
|
|
echo '.wc-block-mini-cart__items .wc-block-cart-item[data-product-id="' . esc_attr($product_id) . '"] .wc-block-components-quantity-selector { display: none !important; }';
|
2025-12-21 19:19:18 +01:00
|
|
|
}
|
|
|
|
|
echo '</style>';
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-12-21 19:33:34 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Make quantity non-editable for restricted products in WooCommerce blocks
|
|
|
|
|
*
|
|
|
|
|
* @param bool $editable Whether the quantity is editable
|
|
|
|
|
* @param array $cart_item Cart item data
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public function block_quantity_editable($editable, $cart_item) {
|
|
|
|
|
$product_id = $cart_item['id'] ?? ($cart_item['product_id'] ?? 0);
|
|
|
|
|
|
|
|
|
|
if (!$product_id) {
|
|
|
|
|
return $editable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
|
|
|
|
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
|
|
|
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
|
|
|
|
|
|
|
|
|
// If restriction is enabled and packages exist, make quantity non-editable
|
|
|
|
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $editable;
|
|
|
|
|
}
|
2025-12-21 04:56:50 +01:00
|
|
|
}
|