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:
2025-12-29 20:02:03 +01:00
parent 45a89fc693
commit 87784f467a
15 changed files with 711 additions and 108 deletions

View File

@@ -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();