You've already forked wc-tier-and-package-prices
Release version 1.2.5 - Parent product default pricing and UI enhancements
Added: - Parent product default pricing for variable products - set tier/package pricing once on parent, variations inherit unless overridden - Hide empty table headers in admin until pricing rules are defined Technical: - Added parent fallback logic to get_tier_price() and get_package_price() methods - Created helper methods get_packages_with_fallback() and is_restriction_enabled() in cart class - Updated all cart methods to support parent product defaults - Added CSS :has() selectors to hide table headers when tbody is empty - Fixed cart pricing calls to pass correct product ID for fallback resolution 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
31
CHANGELOG.md
31
CHANGELOG.md
@@ -5,6 +5,37 @@ All notable changes to WooCommerce Tier and Package Prices will be documented in
|
|||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.2.5] - 2025-12-30
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- **Parent Product Default Pricing**: Variable products can now define tier and package pricing at the parent product level that applies as defaults to all variations. Individual variations can override these defaults with their own specific pricing. This makes it much easier to set up pricing for products with many variations - set defaults once on the parent, then only customize the variations that need different pricing.
|
||||||
|
|
||||||
|
- **Hide Empty Table Headers**: Table headers for tier and package pricing in the admin area now automatically hide when no pricing rules are defined. This creates a cleaner interface when starting to configure a product, showing only the helpful empty state message and "Add" button.
|
||||||
|
|
||||||
|
### Technical Details
|
||||||
|
|
||||||
|
**Parent Fallback Implementation**:
|
||||||
|
- Modified `WC_TPP_Frontend::get_tier_price()` and `WC_TPP_Frontend::get_package_price()` to fall back to parent product pricing when variation doesn't have its own pricing
|
||||||
|
- Updated `WC_TPP_Cart` to use helper methods `get_packages_with_fallback()` and `is_restriction_enabled()` for consistent parent fallback behavior
|
||||||
|
- All cart validation, quantity restriction, and display methods now support parent product defaults
|
||||||
|
- Fixed cart pricing calls to pass parent `$product_id` instead of `$effective_id` for proper fallback resolution
|
||||||
|
|
||||||
|
**CSS Enhancement**:
|
||||||
|
- Added `:has()` pseudo-class selectors to hide table headers when tbody is empty
|
||||||
|
- Leverages existing empty state message styling for consistent UX
|
||||||
|
|
||||||
|
**Backward Compatibility**:
|
||||||
|
- 100% backward compatible - existing products continue working as before
|
||||||
|
- No database migrations required
|
||||||
|
- Variations with specific pricing take precedence over parent defaults
|
||||||
|
|
||||||
|
### Changed Files
|
||||||
|
|
||||||
|
- `includes/class-wc-tpp-frontend.php` - Added parent fallback logic to `get_tier_price()` and `get_package_price()` methods
|
||||||
|
- `includes/class-wc-tpp-cart.php` - Added helper methods `get_packages_with_fallback()` and `is_restriction_enabled()`; updated all cart methods to support parent fallback; fixed pricing calls to use correct product ID
|
||||||
|
- `assets/css/admin.css` - Added CSS rules to hide table headers when no pricing rules exist
|
||||||
|
|
||||||
## [1.2.4] - 2025-12-30
|
## [1.2.4] - 2025-12-30
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# WooCommerce Tier and Package Prices - AI Context Document
|
# WooCommerce Tier and Package Prices - AI Context Document
|
||||||
|
|
||||||
**Last Updated:** 2025-12-30
|
**Last Updated:** 2025-12-30
|
||||||
**Current Version:** 1.2.4
|
**Current Version:** 1.2.5
|
||||||
**Author:** Marco Graetsch
|
**Author:** Marco Graetsch
|
||||||
**Project Status:** Production-ready WordPress plugin
|
**Project Status:** Production-ready WordPress plugin
|
||||||
|
|
||||||
@@ -765,10 +765,6 @@ Roadmap for the upcoming development.
|
|||||||
|
|
||||||
2. Make it possible to define tier or package prices on variable products in the parent product as a default for that product and all variants of it unless a variant has its own tier or package prices.
|
2. Make it possible to define tier or package prices on variable products in the parent product as a default for that product and all variants of it unless a variant has its own tier or package prices.
|
||||||
|
|
||||||
##### New Features
|
|
||||||
|
|
||||||
1. Create different, selectable templates for tierprices and packages to use in the frontend. Make the new templates selectable globally on the settings-page, not per product.
|
|
||||||
|
|
||||||
### When Debugging Cart Issues
|
### When Debugging Cart Issues
|
||||||
|
|
||||||
1. Check `includes/class-wc-tpp-cart.php` first
|
1. Check `includes/class-wc-tpp-cart.php` first
|
||||||
|
|||||||
@@ -111,6 +111,18 @@
|
|||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hide table headers when there are no pricing rules */
|
||||||
|
.wc-tpp-tiers-container:empty ~ thead,
|
||||||
|
.wc-tpp-packages-container:empty ~ thead {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Alternative approach: hide thead when tbody is empty (more reliable) */
|
||||||
|
.wc-tpp-tiers-table:has(tbody.wc-tpp-tiers-container:empty) thead,
|
||||||
|
.wc-tpp-packages-table:has(tbody.wc-tpp-packages-container:empty) thead {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* Checkbox styling improvements */
|
/* Checkbox styling improvements */
|
||||||
#_wc_tpp_restrict_to_packages,
|
#_wc_tpp_restrict_to_packages,
|
||||||
input[id^="wc_tpp_restrict_to_packages_"] {
|
input[id^="wc_tpp_restrict_to_packages_"] {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "magdev/wc-tier-package-prices",
|
"name": "magdev/wc-tier-package-prices",
|
||||||
"description": "WooCommerce plugin for tier pricing and package prices with Twig templates",
|
"description": "WooCommerce plugin for tier pricing and package prices with Twig templates",
|
||||||
"version": "1.2.4",
|
"version": "1.2.5",
|
||||||
"type": "wordpress-plugin",
|
"type": "wordpress-plugin",
|
||||||
"license": "GPL-2.0-or-later",
|
"license": "GPL-2.0-or-later",
|
||||||
"authors": [
|
"authors": [
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for exact package match first (use effective ID for variations)
|
// Check for exact package match first (pass product_id for parent fallback support)
|
||||||
$package_price = null;
|
$package_price = null;
|
||||||
if (get_option('wc_tpp_enable_package_pricing') === 'yes') {
|
if (get_option('wc_tpp_enable_package_pricing') === 'yes') {
|
||||||
$package_price = WC_TPP_Frontend::get_package_price($effective_id, $quantity, $variation_id);
|
$package_price = WC_TPP_Frontend::get_package_price($product_id, $quantity, $variation_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($package_price !== null) {
|
if ($package_price !== null) {
|
||||||
@@ -64,9 +64,9 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_pricing_type'] = 'package';
|
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;
|
WC()->cart->cart_contents[$cart_item_key]['wc_tpp_total_price'] = $package_price;
|
||||||
} else {
|
} else {
|
||||||
// Apply tier pricing if no package match (use effective ID for variations)
|
// Apply tier pricing if no package match (pass product_id for parent fallback support)
|
||||||
if (get_option('wc_tpp_enable_tier_pricing') === 'yes') {
|
if (get_option('wc_tpp_enable_tier_pricing') === 'yes') {
|
||||||
$tier_price = WC_TPP_Frontend::get_tier_price($effective_id, $quantity, $variation_id);
|
$tier_price = WC_TPP_Frontend::get_tier_price($product_id, $quantity, $variation_id);
|
||||||
if ($tier_price !== null) {
|
if ($tier_price !== null) {
|
||||||
$product->set_price($tier_price);
|
$product->set_price($tier_price);
|
||||||
// Store pricing information in cart item for display
|
// Store pricing information in cart item for display
|
||||||
@@ -103,20 +103,16 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
public function validate_package_quantity($passed, $product_id, $quantity) {
|
public function validate_package_quantity($passed, $product_id, $quantity) {
|
||||||
// Check for variation ID in request (for variable products)
|
// Check for variation ID in request (for variable products)
|
||||||
$variation_id = isset($_REQUEST['variation_id']) ? absint($_REQUEST['variation_id']) : 0;
|
$variation_id = isset($_REQUEST['variation_id']) ? absint($_REQUEST['variation_id']) : 0;
|
||||||
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
|
||||||
|
|
||||||
// Check if restriction is enabled globally or for this product/variation
|
// Check if restriction is enabled (with parent fallback for variations)
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
if (!$this->is_restriction_enabled($product_id, $variation_id)) {
|
||||||
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
|
||||||
|
|
||||||
if (!$global_restrict && !$product_restrict) {
|
|
||||||
return $passed;
|
return $passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get packages for this product/variation
|
// Get packages for this product/variation (with parent fallback)
|
||||||
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
$packages = $this->get_packages_with_fallback($product_id, $variation_id);
|
||||||
|
|
||||||
if (empty($packages) || !is_array($packages)) {
|
if (!$packages) {
|
||||||
return $passed;
|
return $passed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,15 +152,8 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
||||||
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
|
||||||
// Check if restriction is enabled globally or for this product/variation
|
// Check if restriction is enabled (with parent fallback) and packages exist
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
if ($this->is_restriction_enabled($product_id, $variation_id) && $this->get_packages_with_fallback($product_id, $variation_id)) {
|
||||||
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
|
||||||
|
|
||||||
// Get packages for this product/variation
|
|
||||||
$packages = get_post_meta($effective_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>',
|
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s</span>',
|
||||||
$effective_id,
|
$effective_id,
|
||||||
$cart_item['quantity']
|
$cart_item['quantity']
|
||||||
@@ -179,15 +168,8 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
||||||
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
|
||||||
// Check if restriction is enabled globally or for this product/variation
|
// Check if restriction is enabled (with parent fallback) and packages exist
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
if ($this->is_restriction_enabled($product_id, $variation_id) && $this->get_packages_with_fallback($product_id, $variation_id)) {
|
||||||
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
|
||||||
|
|
||||||
// Get packages for this product/variation
|
|
||||||
$packages = get_post_meta($effective_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>',
|
return sprintf('<span class="wc-tpp-cart-quantity wc-tpp-restricted-qty" data-product-id="%d">%s ×</span>',
|
||||||
$effective_id,
|
$effective_id,
|
||||||
$cart_item['quantity']
|
$cart_item['quantity']
|
||||||
@@ -209,11 +191,8 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
$variation_id = isset($cart_item['variation_id']) ? absint($cart_item['variation_id']) : 0;
|
||||||
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
// Check if restriction is enabled (with parent fallback) and packages exist
|
||||||
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
if ($this->is_restriction_enabled($product_id, $variation_id) && $this->get_packages_with_fallback($product_id, $variation_id)) {
|
||||||
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
|
||||||
|
|
||||||
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
|
||||||
$restricted_products[] = $effective_id;
|
$restricted_products[] = $effective_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -254,20 +233,61 @@ if (!class_exists('WC_TPP_Cart')) {
|
|||||||
return $editable;
|
return $editable;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For variations, use the variation ID directly (get_id() returns variation ID for WC_Product_Variation)
|
// For variations, get parent product ID and variation ID
|
||||||
$effective_id = $product_id;
|
$variation_id = 0;
|
||||||
|
$parent_id = $product_id;
|
||||||
|
|
||||||
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
if ($product->is_type('variation')) {
|
||||||
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
$variation_id = $product_id;
|
||||||
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
$parent_id = $product->get_parent_id();
|
||||||
|
}
|
||||||
|
|
||||||
// If restriction is enabled and packages exist, make quantity non-editable
|
// Check if restriction is enabled (with parent fallback) and packages exist
|
||||||
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
if ($this->is_restriction_enabled($parent_id, $variation_id) && $this->get_packages_with_fallback($parent_id, $variation_id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $editable;
|
return $editable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get packages with parent fallback for variations
|
||||||
|
*
|
||||||
|
* @param int $product_id Parent product ID
|
||||||
|
* @param int $variation_id Variation ID (0 for simple products)
|
||||||
|
* @return array|false Packages array or false if none found
|
||||||
|
*/
|
||||||
|
private function get_packages_with_fallback($product_id, $variation_id = 0) {
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||||
|
|
||||||
|
// Fall back to parent pricing if variation doesn't have its own pricing
|
||||||
|
if ((empty($packages) || !is_array($packages)) && $variation_id > 0) {
|
||||||
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (!empty($packages) && is_array($packages)) ? $packages : false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if restriction is enabled for a product/variation with parent fallback
|
||||||
|
*
|
||||||
|
* @param int $product_id Parent product ID
|
||||||
|
* @param int $variation_id Variation ID (0 for simple products)
|
||||||
|
* @return bool Whether restriction is enabled
|
||||||
|
*/
|
||||||
|
private function is_restriction_enabled($product_id, $variation_id = 0) {
|
||||||
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
|
$global_restrict = get_option('wc_tpp_restrict_package_quantities', 'no') === 'yes';
|
||||||
|
$product_restrict = get_post_meta($effective_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
|
|
||||||
|
// Fall back to parent restriction setting if variation doesn't have its own
|
||||||
|
if (!$product_restrict && $variation_id > 0) {
|
||||||
|
$product_restrict = get_post_meta($product_id, '_wc_tpp_restrict_to_packages', true) === 'yes';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $global_restrict || $product_restrict;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new WC_TPP_Cart();
|
new WC_TPP_Cart();
|
||||||
|
|||||||
@@ -133,6 +133,11 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
$tiers = get_post_meta($effective_id, '_wc_tpp_tiers', true);
|
$tiers = get_post_meta($effective_id, '_wc_tpp_tiers', true);
|
||||||
|
|
||||||
|
// Fall back to parent pricing if variation doesn't have its own pricing
|
||||||
|
if ((empty($tiers) || !is_array($tiers)) && $variation_id > 0) {
|
||||||
|
$tiers = get_post_meta($product_id, '_wc_tpp_tiers', true);
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($tiers) || !is_array($tiers)) {
|
if (empty($tiers) || !is_array($tiers)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -159,6 +164,11 @@ if (!class_exists('WC_TPP_Frontend')) {
|
|||||||
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
$effective_id = $variation_id > 0 ? $variation_id : $product_id;
|
||||||
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
$packages = get_post_meta($effective_id, '_wc_tpp_packages', true);
|
||||||
|
|
||||||
|
// Fall back to parent pricing if variation doesn't have its own pricing
|
||||||
|
if ((empty($packages) || !is_array($packages)) && $variation_id > 0) {
|
||||||
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
||||||
|
}
|
||||||
|
|
||||||
if (empty($packages) || !is_array($packages)) {
|
if (empty($packages) || !is_array($packages)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Plugin Name: WooCommerce Tier and Package Prices
|
* Plugin Name: WooCommerce Tier and Package Prices
|
||||||
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices
|
* Plugin URI: https://src.bundespruefstelle.ch/magdev/wc-tier-package-prices
|
||||||
* Description: Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices
|
* Description: Add tier pricing and package prices to WooCommerce products with configurable quantities at fixed prices
|
||||||
* Version: 1.2.4
|
* Version: 1.2.5
|
||||||
* Author: Marco Graetsch
|
* Author: Marco Graetsch
|
||||||
* Author URI: https://src.bundespruefstelle.ch/magdev
|
* Author URI: https://src.bundespruefstelle.ch/magdev
|
||||||
* Text Domain: wc-tier-package-prices
|
* Text Domain: wc-tier-package-prices
|
||||||
@@ -23,7 +23,7 @@ if (!defined('ABSPATH')) {
|
|||||||
|
|
||||||
// Define plugin constants
|
// Define plugin constants
|
||||||
if (!defined('WC_TPP_VERSION')) {
|
if (!defined('WC_TPP_VERSION')) {
|
||||||
define('WC_TPP_VERSION', '1.2.4');
|
define('WC_TPP_VERSION', '1.2.5');
|
||||||
}
|
}
|
||||||
if (!defined('WC_TPP_PLUGIN_DIR')) {
|
if (!defined('WC_TPP_PLUGIN_DIR')) {
|
||||||
define('WC_TPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
define('WC_TPP_PLUGIN_DIR', plugin_dir_path(__FILE__));
|
||||||
|
|||||||
Reference in New Issue
Block a user