You've already forked wc-tier-and-package-prices
Release version 1.2.0 - Add complete variable product support
This major feature release adds full support for WooCommerce variable products with variation-level pricing configuration. ## Key Features - Each product variation can have independent tier and package pricing - AJAX-based dynamic pricing table loading on variation selection - Admin UI integrated into WooCommerce variation panels - Full backward compatibility with existing simple product functionality - WooCommerce Blocks compatibility maintained ## Implementation Highlights - Effective ID pattern throughout codebase for variation handling - Variation-specific meta boxes with field prefix support - Template system updated to support both simple and variation products - JavaScript enhancements for variation selector integration - Cart logic updated to handle variation pricing correctly ## Files Changed - Core: wc-tier-and-package-prices.php (version 1.2.0), composer.json - Cart: includes/class-wc-tpp-cart.php (effective ID logic) - Frontend: includes/class-wc-tpp-frontend.php (AJAX endpoint, variation detection) - Admin: includes/class-wc-tpp-product-meta.php (variation hooks and methods) - Templates: templates/admin/*.twig (field prefix support, table structure) - JavaScript: assets/js/*.js (variation support) - Documentation: CHANGELOG.md, README.md, CLAUDE.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,10 @@ if (!class_exists('WC_TPP_Frontend')) {
|
||||
|
||||
// Modify catalog add to cart button for restricted products
|
||||
add_filter('woocommerce_loop_add_to_cart_link', array($this, 'modify_catalog_add_to_cart_button'), 10, 2);
|
||||
|
||||
// AJAX endpoints for variation pricing
|
||||
add_action('wp_ajax_wc_tpp_get_variation_pricing', array($this, 'ajax_get_variation_pricing'));
|
||||
add_action('wp_ajax_nopriv_wc_tpp_get_variation_pricing', array($this, 'ajax_get_variation_pricing'));
|
||||
}
|
||||
|
||||
public function enqueue_scripts() {
|
||||
@@ -31,8 +35,10 @@ if (!class_exists('WC_TPP_Frontend')) {
|
||||
if (is_product()) {
|
||||
wp_enqueue_script('wc-tpp-frontend', WC_TPP_PLUGIN_URL . 'assets/js/frontend.js', array('jquery'), WC_TPP_VERSION, true);
|
||||
|
||||
// Localize script with currency settings
|
||||
// Localize script with currency settings and AJAX data
|
||||
wp_localize_script('wc-tpp-frontend', 'wcTppData', array(
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'nonce' => wp_create_nonce('wc_tpp_variation_pricing'),
|
||||
'currency_symbol' => esc_js(get_woocommerce_currency_symbol()),
|
||||
'currency_position' => esc_js(get_option('woocommerce_currency_pos', 'left')),
|
||||
'price_decimals' => absint(wc_get_price_decimals()),
|
||||
@@ -67,6 +73,11 @@ if (!class_exists('WC_TPP_Frontend')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For variable products, quantity hiding is handled per-variation via JS
|
||||
if ($product->is_type('variable')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product_id = $product->get_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';
|
||||
@@ -85,6 +96,13 @@ if (!class_exists('WC_TPP_Frontend')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For variable products, show a placeholder that will be populated by JS when variation is selected
|
||||
if ($product->is_type('variable')) {
|
||||
echo '<div class="wc-tpp-pricing-table-container" data-product-type="variable" style="display:none;"></div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// For simple products, display pricing table directly
|
||||
$product_id = $product->get_id();
|
||||
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
|
||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
||||
@@ -103,8 +121,17 @@ if (!class_exists('WC_TPP_Frontend')) {
|
||||
));
|
||||
}
|
||||
|
||||
public static function get_tier_price($product_id, $quantity) {
|
||||
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
|
||||
/**
|
||||
* Get tier price for a product or variation
|
||||
*
|
||||
* @param int $product_id Product ID (parent for simple, parent for variable)
|
||||
* @param int $quantity Quantity
|
||||
* @param int $variation_id Variation ID (0 for simple products)
|
||||
* @return float|null Tier price or null if not applicable
|
||||
*/
|
||||
public static function get_tier_price($product_id, $quantity, $variation_id = 0) {
|
||||
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||
$tiers = get_post_meta($effective_id, '_wc_tpp_tiers', true);
|
||||
|
||||
if (empty($tiers) || !is_array($tiers)) {
|
||||
return null;
|
||||
@@ -120,8 +147,17 @@ if (!class_exists('WC_TPP_Frontend')) {
|
||||
return $applicable_price;
|
||||
}
|
||||
|
||||
public static function get_package_price($product_id, $quantity) {
|
||||
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
||||
/**
|
||||
* Get package price for a product or variation
|
||||
*
|
||||
* @param int $product_id Product ID (parent for simple, parent for variable)
|
||||
* @param int $quantity Quantity
|
||||
* @param int $variation_id Variation ID (0 for simple products)
|
||||
* @return float|null Package price or null if not applicable
|
||||
*/
|
||||
public static function get_package_price($product_id, $quantity, $variation_id = 0) {
|
||||
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||
|
||||
if (empty($packages) || !is_array($packages)) {
|
||||
return null;
|
||||
@@ -164,8 +200,24 @@ if (!class_exists('WC_TPP_Frontend')) {
|
||||
|
||||
$product_id = $product->get_id();
|
||||
|
||||
// Check if product has quantity restrictions
|
||||
if (!self::has_quantity_restriction($product_id)) {
|
||||
// For variable products, check if ANY variation has restrictions
|
||||
// For simple products, check the product itself
|
||||
$has_restriction = false;
|
||||
|
||||
if ($product->is_type('variable')) {
|
||||
// Check if any variation has package restrictions
|
||||
$variations = $product->get_available_variations();
|
||||
foreach ($variations as $variation_data) {
|
||||
if (self::has_quantity_restriction($variation_data['variation_id'])) {
|
||||
$has_restriction = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$has_restriction = self::has_quantity_restriction($product_id);
|
||||
}
|
||||
|
||||
if (!$has_restriction) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
@@ -173,15 +225,72 @@ if (!class_exists('WC_TPP_Frontend')) {
|
||||
$product_url = esc_url($product->get_permalink());
|
||||
$button_text = esc_html__('View Options', 'wc-tier-package-prices');
|
||||
|
||||
// Use correct product type class
|
||||
$product_type_class = $product->is_type('variable') ? 'product_type_variable' : 'product_type_simple';
|
||||
|
||||
$new_html = sprintf(
|
||||
'<a href="%s" class="button wc-tpp-view-options product_type_simple" aria-label="%s">%s</a>',
|
||||
'<a href="%s" class="button wc-tpp-view-options %s" aria-label="%s">%s</a>',
|
||||
$product_url,
|
||||
esc_attr($product_type_class),
|
||||
esc_attr(sprintf(__('View options for %s', 'wc-tier-package-prices'), $product->get_name())),
|
||||
$button_text
|
||||
);
|
||||
|
||||
return $new_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler to get variation pricing data
|
||||
*/
|
||||
public function ajax_get_variation_pricing() {
|
||||
// Verify nonce
|
||||
check_ajax_referer('wc_tpp_variation_pricing', 'nonce');
|
||||
|
||||
$variation_id = isset($_POST['variation_id']) ? absint($_POST['variation_id']) : 0;
|
||||
|
||||
if (!$variation_id) {
|
||||
wp_send_json_error(array('message' => __('Invalid variation ID', 'wc-tier-package-prices')));
|
||||
}
|
||||
|
||||
// Get variation data
|
||||
$variation = wc_get_product($variation_id);
|
||||
|
||||
if (!$variation || !$variation->is_type('variation')) {
|
||||
wp_send_json_error(array('message' => __('Variation not found', 'wc-tier-package-prices')));
|
||||
}
|
||||
|
||||
// Get tier and package pricing
|
||||
$tiers = get_post_meta($variation_id, '_wc_tpp_tiers', true);
|
||||
$packages = get_post_meta($variation_id, '_wc_tpp_packages', true);
|
||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||
$product_restrict = get_post_meta($variation_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||
|
||||
if (empty($tiers) && empty($packages)) {
|
||||
// No pricing data for this variation
|
||||
wp_send_json_success(array(
|
||||
'has_pricing' => false,
|
||||
'html' => ''
|
||||
));
|
||||
}
|
||||
|
||||
// Render the pricing table HTML
|
||||
ob_start();
|
||||
WC_TPP_Template_Loader::get_instance()->display('frontend/pricing-table.twig', array(
|
||||
'product' => $variation,
|
||||
'tiers' => $tiers,
|
||||
'packages' => $packages,
|
||||
'restrict_to_packages' => $global_restrict || $product_restrict
|
||||
));
|
||||
$html = ob_get_clean();
|
||||
|
||||
wp_send_json_success(array(
|
||||
'has_pricing' => true,
|
||||
'html' => $html,
|
||||
'tiers' => $tiers ? $tiers : array(),
|
||||
'packages' => $packages ? $packages : array(),
|
||||
'restrict_to_packages' => $global_restrict || $product_restrict
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
new WC_TPP_Frontend();
|
||||
|
||||
Reference in New Issue
Block a user