You've already forked wc-tier-and-package-prices
Fixed two critical issues that prevented v1.2.5 features from working: 1. Parent Product Pricing Forms Not Visible (Critical) - Variable products were missing admin UI for parent pricing configuration - v1.2.5 implemented backend logic but forgot the admin forms - Added add_variable_parent_pricing_fields() method - Modified existing methods to skip variable products (simple only) - Parent pricing now fully functional with matching UI 2. Table Headers Not Hiding When Empty - CSS sibling selector ~ doesn't work when thead comes before tbody - Removed incorrect selector, kept only :has() pseudo-class - Added !important flag for proper specificity - Modern browser support (Chrome 105+, Firefox 121+, Safari 15.4+) Changed files: - includes/class-wc-tpp-product-meta.php - Added parent pricing forms - assets/css/admin.css - Fixed header hiding CSS - wc-tier-and-package-prices.php - Version 1.2.6 - composer.json - Version 1.2.6 - CHANGELOG.md - Detailed release notes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
512 lines
22 KiB
PHP
512 lines
22 KiB
PHP
<?php
|
|
/**
|
|
* Product meta boxes for tier and package pricing
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) {
|
|
exit;
|
|
}
|
|
|
|
if (!class_exists('WC_TPP_Product_Meta')) {
|
|
class WC_TPP_Product_Meta {
|
|
|
|
public function __construct() {
|
|
// Simple product hooks
|
|
add_action('woocommerce_product_options_pricing', array($this, 'add_tier_pricing_fields'));
|
|
add_action('woocommerce_product_options_pricing', array($this, 'add_package_pricing_fields'));
|
|
add_action('woocommerce_process_product_meta', array($this, 'save_tier_package_fields'));
|
|
|
|
// Variable product parent hooks (for default pricing)
|
|
add_action('woocommerce_product_options_pricing', array($this, 'add_variable_parent_pricing_fields'));
|
|
|
|
// Variable product variation hooks
|
|
add_action('woocommerce_variation_options_pricing', array($this, 'add_variation_pricing_fields'), 10, 3);
|
|
add_action('woocommerce_save_product_variation', array($this, 'save_variation_pricing_fields'), 10, 2);
|
|
}
|
|
|
|
/**
|
|
* Add tier and package pricing fields for variable product parents
|
|
* These serve as defaults for all variations unless overridden
|
|
*/
|
|
public function add_variable_parent_pricing_fields() {
|
|
global $post;
|
|
|
|
// Only show for variable products, not simple products
|
|
$product = wc_get_product($post->ID);
|
|
if (!$product || !$product->is_type('variable')) {
|
|
return;
|
|
}
|
|
|
|
?>
|
|
<div class="options_group wc-tpp-variable-parent-pricing">
|
|
<p class="form-field">
|
|
<strong><?php _e('Default Tier & Package Pricing for All Variations', 'wc-tier-package-prices'); ?></strong>
|
|
<span class="woocommerce-help-tip" data-tip="<?php esc_attr_e('Set default pricing for all variations. Individual variations can override these defaults with their own specific pricing.', 'wc-tier-package-prices'); ?>"></span>
|
|
</p>
|
|
<p class="description" style="margin-left: 12px; margin-bottom: 15px;">
|
|
<?php _e('Configure default tier and package pricing here. All variations will inherit these settings unless they define their own specific pricing.', 'wc-tier-package-prices'); ?>
|
|
</p>
|
|
|
|
<!-- Tier Pricing Section -->
|
|
<div class="wc-tpp-parent-tier-pricing" style="margin-left: 12px;">
|
|
<p><strong><?php _e('Tier Pricing', 'wc-tier-package-prices'); ?></strong></p>
|
|
<table class="widefat wc-tpp-tiers-table">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('Min Quantity', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="wc-tpp-tiers-container">
|
|
<?php
|
|
$tiers = get_post_meta($post->ID, '_wc_tpp_tiers', true);
|
|
if (!is_array($tiers)) {
|
|
$tiers = array();
|
|
}
|
|
|
|
foreach ($tiers as $index => $tier) {
|
|
$this->render_tier_row($index, $tier);
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
<p class="form-field">
|
|
<button type="button" class="button wc-tpp-add-tier"><?php _e('Add Tier', 'wc-tier-package-prices'); ?></button>
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Package Pricing Section -->
|
|
<div class="wc-tpp-parent-package-pricing" style="margin-left: 12px; margin-top: 20px;">
|
|
<p><strong><?php _e('Package Pricing', 'wc-tier-package-prices'); ?></strong></p>
|
|
<table class="widefat wc-tpp-packages-table">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('Quantity', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="wc-tpp-packages-container">
|
|
<?php
|
|
$packages = get_post_meta($post->ID, '_wc_tpp_packages', true);
|
|
if (!is_array($packages)) {
|
|
$packages = array();
|
|
}
|
|
|
|
foreach ($packages as $index => $package) {
|
|
$this->render_package_row($index, $package);
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
<p class="form-field">
|
|
<button type="button" class="button wc-tpp-add-package"><?php _e('Add Package', 'wc-tier-package-prices'); ?></button>
|
|
</p>
|
|
|
|
<?php
|
|
woocommerce_wp_checkbox(array(
|
|
'id' => '_wc_tpp_restrict_to_packages',
|
|
'label' => __('Restrict to Package Quantities (Default)', 'wc-tier-package-prices'),
|
|
'description' => __('Default restriction setting for all variations. Only allow quantities defined in packages above.', 'wc-tier-package-prices'),
|
|
'desc_tip' => true,
|
|
));
|
|
?>
|
|
</div>
|
|
|
|
<!-- Templates for JavaScript (shared between simple and variable parent) -->
|
|
<script type="text/html" id="wc-tpp-tier-row-template">
|
|
<?php $this->render_tier_row('{{INDEX}}', array('min_qty' => '', 'price' => '', 'label' => '')); ?>
|
|
</script>
|
|
|
|
<script type="text/html" id="wc-tpp-package-row-template">
|
|
<?php $this->render_package_row('{{INDEX}}', array('qty' => '', 'price' => '', 'label' => '')); ?>
|
|
</script>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
public function add_tier_pricing_fields() {
|
|
global $post;
|
|
|
|
// Only show for simple products (variable products use add_variable_parent_pricing_fields)
|
|
$product = wc_get_product($post->ID);
|
|
if ($product && $product->is_type('variable')) {
|
|
return;
|
|
}
|
|
|
|
?>
|
|
<div class="options_group wc-tpp-tier-pricing">
|
|
<p class="form-field">
|
|
<label><?php _e('Tier Pricing', 'wc-tier-package-prices'); ?></label>
|
|
<span class="description"><?php _e('Set quantity-based pricing tiers. Customers get discounted prices when buying in larger quantities.', 'wc-tier-package-prices'); ?></span>
|
|
</p>
|
|
|
|
<table class="widefat wc-tpp-tiers-table">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('Min Quantity', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="wc-tpp-tiers-container">
|
|
<?php
|
|
$tiers = get_post_meta($post->ID, '_wc_tpp_tiers', true);
|
|
if (!is_array($tiers)) {
|
|
$tiers = array();
|
|
}
|
|
|
|
foreach ($tiers as $index => $tier) {
|
|
$this->render_tier_row($index, $tier);
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p class="form-field">
|
|
<button type="button" class="button wc-tpp-add-tier"><?php _e('Add Tier', 'wc-tier-package-prices'); ?></button>
|
|
</p>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
public function add_package_pricing_fields() {
|
|
global $post;
|
|
|
|
// Only show for simple products (variable products use add_variable_parent_pricing_fields)
|
|
$product = wc_get_product($post->ID);
|
|
if ($product && $product->is_type('variable')) {
|
|
return;
|
|
}
|
|
|
|
?>
|
|
<div class="options_group wc-tpp-package-pricing">
|
|
<p class="form-field">
|
|
<label><?php _e('Package Pricing', 'wc-tier-package-prices'); ?></label>
|
|
<span class="description"><?php _e('Set fixed-price packages with specific quantities. For example: 10 pieces for $50, 25 pieces for $100.', 'wc-tier-package-prices'); ?></span>
|
|
</p>
|
|
|
|
<table class="widefat wc-tpp-packages-table">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('Quantity', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="wc-tpp-packages-container">
|
|
<?php
|
|
$packages = get_post_meta($post->ID, '_wc_tpp_packages', true);
|
|
if (!is_array($packages)) {
|
|
$packages = array();
|
|
}
|
|
|
|
foreach ($packages as $index => $package) {
|
|
$this->render_package_row($index, $package);
|
|
}
|
|
?>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p class="form-field">
|
|
<button type="button" class="button wc-tpp-add-package"><?php _e('Add Package', 'wc-tier-package-prices'); ?></button>
|
|
</p>
|
|
|
|
<?php
|
|
woocommerce_wp_checkbox(array(
|
|
'id' => '_wc_tpp_restrict_to_packages',
|
|
'label' => __('Restrict to Package Quantities', 'wc-tier-package-prices'),
|
|
'description' => __('Only allow quantities defined in packages above', 'wc-tier-package-prices'),
|
|
'desc_tip' => true,
|
|
));
|
|
?>
|
|
</div>
|
|
|
|
<script type="text/html" id="wc-tpp-tier-row-template">
|
|
<?php $this->render_tier_row('{{INDEX}}', array('min_qty' => '', 'price' => '', 'label' => '')); ?>
|
|
</script>
|
|
|
|
<script type="text/html" id="wc-tpp-package-row-template">
|
|
<?php $this->render_package_row('{{INDEX}}', array('qty' => '', 'price' => '', 'label' => '')); ?>
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
private function render_tier_row($index, $tier) {
|
|
WC_TPP_Template_Loader::get_instance()->display('admin/tier-row.twig', array(
|
|
'index' => $index,
|
|
'tier' => $tier
|
|
));
|
|
}
|
|
|
|
private function render_package_row($index, $package) {
|
|
WC_TPP_Template_Loader::get_instance()->display('admin/package-row.twig', array(
|
|
'index' => $index,
|
|
'package' => $package
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Add tier and package pricing fields to product variations
|
|
*
|
|
* @param int $loop Position in the loop
|
|
* @param array $variation_data Variation data
|
|
* @param WP_Post $variation Variation post object
|
|
*/
|
|
public function add_variation_pricing_fields($loop, $variation_data, $variation) {
|
|
$variation_id = $variation->ID;
|
|
|
|
// Retrieve variation-specific data
|
|
$tiers = get_post_meta($variation_id, '_wc_tpp_tiers', true);
|
|
$packages = get_post_meta($variation_id, '_wc_tpp_packages', true);
|
|
$restrict = get_post_meta($variation_id, '_wc_tpp_restrict_to_packages', true);
|
|
|
|
if (!is_array($tiers)) {
|
|
$tiers = array();
|
|
}
|
|
if (!is_array($packages)) {
|
|
$packages = array();
|
|
}
|
|
|
|
?>
|
|
<div class="form-row form-row-full wc-tpp-variation-pricing" data-variation-loop="<?php echo esc_attr($loop); ?>">
|
|
<h4><?php _e('Tier & Package Pricing', 'wc-tier-package-prices'); ?></h4>
|
|
|
|
<!-- Tier Pricing Section -->
|
|
<div class="wc-tpp-variation-tiers">
|
|
<p><strong><?php _e('Tier Pricing', 'wc-tier-package-prices'); ?></strong></p>
|
|
<table class="widefat wc-tpp-tiers-table">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('Min Quantity', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="wc-tpp-tiers-container">
|
|
<?php foreach ($tiers as $index => $tier) : ?>
|
|
<?php $this->render_variation_tier_row($loop, $index, $tier); ?>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<button type="button" class="button wc-tpp-add-tier" data-loop="<?php echo esc_attr($loop); ?>">
|
|
<?php _e('Add Tier', 'wc-tier-package-prices'); ?>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Package Pricing Section -->
|
|
<div class="wc-tpp-variation-packages" style="margin-top: 15px;">
|
|
<p><strong><?php _e('Package Pricing', 'wc-tier-package-prices'); ?></strong></p>
|
|
<table class="widefat wc-tpp-packages-table">
|
|
<thead>
|
|
<tr>
|
|
<th><?php _e('Quantity', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Price', 'wc-tier-package-prices'); ?></th>
|
|
<th><?php _e('Label (optional)', 'wc-tier-package-prices'); ?></th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody class="wc-tpp-packages-container">
|
|
<?php foreach ($packages as $index => $package) : ?>
|
|
<?php $this->render_variation_package_row($loop, $index, $package); ?>
|
|
<?php endforeach; ?>
|
|
</tbody>
|
|
</table>
|
|
<button type="button" class="button wc-tpp-add-package" data-loop="<?php echo esc_attr($loop); ?>">
|
|
<?php _e('Add Package', 'wc-tier-package-prices'); ?>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Restriction Checkbox -->
|
|
<div style="margin-top: 15px;">
|
|
<?php
|
|
woocommerce_wp_checkbox(array(
|
|
'id' => 'wc_tpp_restrict_to_packages_' . $loop,
|
|
'name' => 'wc_tpp_restrict_to_packages[' . $loop . ']',
|
|
'label' => __('Restrict to Package Quantities', 'wc-tier-package-prices'),
|
|
'description' => __('Only allow quantities defined in packages above', 'wc-tier-package-prices'),
|
|
'desc_tip' => true,
|
|
'value' => $restrict,
|
|
'cbvalue' => 'yes',
|
|
'wrapper_class' => 'form-row form-row-full'
|
|
));
|
|
?>
|
|
</div>
|
|
|
|
<!-- Templates for JavaScript -->
|
|
<script type="text/html" id="wc-tpp-variation-tier-row-template-<?php echo esc_attr($loop); ?>">
|
|
<?php $this->render_variation_tier_row($loop, '{{INDEX}}', array('min_qty' => '', 'price' => '', 'label' => '')); ?>
|
|
</script>
|
|
|
|
<script type="text/html" id="wc-tpp-variation-package-row-template-<?php echo esc_attr($loop); ?>">
|
|
<?php $this->render_variation_package_row($loop, '{{INDEX}}', array('qty' => '', 'price' => '', 'label' => '')); ?>
|
|
</script>
|
|
</div>
|
|
<?php
|
|
}
|
|
|
|
/**
|
|
* Render a tier row for variations
|
|
*
|
|
* @param int $loop Variation loop index
|
|
* @param int $index Tier index
|
|
* @param array $tier Tier data
|
|
*/
|
|
private function render_variation_tier_row($loop, $index, $tier) {
|
|
WC_TPP_Template_Loader::get_instance()->display('admin/tier-row.twig', array(
|
|
'index' => $index,
|
|
'tier' => $tier,
|
|
'field_prefix' => 'wc_tpp_tiers[' . $loop . ']'
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Render a package row for variations
|
|
*
|
|
* @param int $loop Variation loop index
|
|
* @param int $index Package index
|
|
* @param array $package Package data
|
|
*/
|
|
private function render_variation_package_row($loop, $index, $package) {
|
|
WC_TPP_Template_Loader::get_instance()->display('admin/package-row.twig', array(
|
|
'index' => $index,
|
|
'package' => $package,
|
|
'field_prefix' => 'wc_tpp_packages[' . $loop . ']'
|
|
));
|
|
}
|
|
|
|
public function save_tier_package_fields($post_id) {
|
|
// Verify nonce for security
|
|
if (!isset($_POST['woocommerce_meta_nonce']) || !wp_verify_nonce($_POST['woocommerce_meta_nonce'], 'woocommerce_save_data')) {
|
|
return;
|
|
}
|
|
|
|
// Check user permissions
|
|
if (!current_user_can('edit_post', $post_id)) {
|
|
return;
|
|
}
|
|
|
|
// Avoid auto-save
|
|
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
|
|
return;
|
|
}
|
|
|
|
// Save tier pricing
|
|
if (isset($_POST['_wc_tpp_tiers'])) {
|
|
$tiers = array();
|
|
foreach ($_POST['_wc_tpp_tiers'] as $tier) {
|
|
if (!empty($tier['min_qty']) && !empty($tier['price'])) {
|
|
$tiers[] = array(
|
|
'min_qty' => absint($tier['min_qty']),
|
|
'price' => wc_format_decimal($tier['price']),
|
|
'label' => sanitize_text_field($tier['label'] ?? '')
|
|
);
|
|
}
|
|
}
|
|
// Sort by minimum quantity
|
|
usort($tiers, function($a, $b) {
|
|
return $a['min_qty'] - $b['min_qty'];
|
|
});
|
|
update_post_meta($post_id, '_wc_tpp_tiers', $tiers);
|
|
} else {
|
|
delete_post_meta($post_id, '_wc_tpp_tiers');
|
|
}
|
|
|
|
// Save package pricing
|
|
if (isset($_POST['_wc_tpp_packages'])) {
|
|
$packages = array();
|
|
foreach ($_POST['_wc_tpp_packages'] as $package) {
|
|
if (!empty($package['qty']) && !empty($package['price'])) {
|
|
$packages[] = array(
|
|
'qty' => absint($package['qty']),
|
|
'price' => wc_format_decimal($package['price']),
|
|
'label' => sanitize_text_field($package['label'])
|
|
);
|
|
}
|
|
}
|
|
// Sort by quantity
|
|
usort($packages, function($a, $b) {
|
|
return $a['qty'] - $b['qty'];
|
|
});
|
|
update_post_meta($post_id, '_wc_tpp_packages', $packages);
|
|
} else {
|
|
delete_post_meta($post_id, '_wc_tpp_packages');
|
|
}
|
|
|
|
// Save package quantity restriction setting
|
|
$restrict_to_packages = isset($_POST['_wc_tpp_restrict_to_packages']) ? 'yes' : 'no';
|
|
update_post_meta($post_id, '_wc_tpp_restrict_to_packages', $restrict_to_packages);
|
|
}
|
|
|
|
/**
|
|
* Save tier and package pricing for variations
|
|
*
|
|
* @param int $variation_id Variation ID
|
|
* @param int $loop Position in loop
|
|
*/
|
|
public function save_variation_pricing_fields($variation_id, $loop) {
|
|
// Security check
|
|
if (!current_user_can('edit_products')) {
|
|
return;
|
|
}
|
|
|
|
// Save tier pricing for this variation
|
|
if (isset($_POST['wc_tpp_tiers'][$loop])) {
|
|
$tiers = array();
|
|
foreach ($_POST['wc_tpp_tiers'][$loop] as $tier) {
|
|
if (!empty($tier['min_qty']) && !empty($tier['price'])) {
|
|
$tiers[] = array(
|
|
'min_qty' => absint($tier['min_qty']),
|
|
'price' => wc_format_decimal($tier['price']),
|
|
'label' => sanitize_text_field($tier['label'] ?? '')
|
|
);
|
|
}
|
|
}
|
|
// Sort by minimum quantity
|
|
usort($tiers, function($a, $b) {
|
|
return $a['min_qty'] - $b['min_qty'];
|
|
});
|
|
update_post_meta($variation_id, '_wc_tpp_tiers', $tiers);
|
|
} else {
|
|
delete_post_meta($variation_id, '_wc_tpp_tiers');
|
|
}
|
|
|
|
// Save package pricing for this variation
|
|
if (isset($_POST['wc_tpp_packages'][$loop])) {
|
|
$packages = array();
|
|
foreach ($_POST['wc_tpp_packages'][$loop] as $package) {
|
|
if (!empty($package['qty']) && !empty($package['price'])) {
|
|
$packages[] = array(
|
|
'qty' => absint($package['qty']),
|
|
'price' => wc_format_decimal($package['price']),
|
|
'label' => sanitize_text_field($package['label'] ?? '')
|
|
);
|
|
}
|
|
}
|
|
// Sort by quantity
|
|
usort($packages, function($a, $b) {
|
|
return $a['qty'] - $b['qty'];
|
|
});
|
|
update_post_meta($variation_id, '_wc_tpp_packages', $packages);
|
|
} else {
|
|
delete_post_meta($variation_id, '_wc_tpp_packages');
|
|
}
|
|
|
|
// Save restriction setting for this variation
|
|
if (isset($_POST['wc_tpp_restrict_to_packages'][$loop]) && $_POST['wc_tpp_restrict_to_packages'][$loop] === 'yes') {
|
|
update_post_meta($variation_id, '_wc_tpp_restrict_to_packages', 'yes');
|
|
} else {
|
|
delete_post_meta($variation_id, '_wc_tpp_restrict_to_packages');
|
|
}
|
|
}
|
|
}
|
|
|
|
new WC_TPP_Product_Meta();
|
|
}
|