2025-12-21 04:56:50 +01:00
|
|
|
<?php
|
|
|
|
|
/**
|
|
|
|
|
* Frontend display for tier and package pricing
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if (!defined('ABSPATH')) {
|
|
|
|
|
exit;
|
|
|
|
|
}
|
|
|
|
|
|
Release version 1.1.13 - Critical class redeclaration fixes
Fixed critical class redeclaration errors affecting all plugin functionality
in version 1.1.12. All plugin component classes now properly guarded.
**CRITICAL FIXES:**
- Plugin completely non-functional in v1.1.12 (no settings, no frontend, no backend)
- Fatal errors for WC_TPP_Admin, WC_TPP_Product_Meta, WC_TPP_Frontend, WC_TPP_Cart, WC_TPP_Settings classes
- All classes now wrapped in class_exists() checks
**Files Modified:**
- includes/class-wc-tpp-admin.php
- includes/class-wc-tpp-product-meta.php
- includes/class-wc-tpp-frontend.php
- includes/class-wc-tpp-cart.php
- includes/class-wc-tpp-settings.php
**Technical Details:**
- Completes comprehensive redeclaration protection started in v1.1.9-1.1.12
- All 2 functions, 4 constants, and 6 classes now protected
- Plugin activates successfully and all features functional
- Settings page, product meta boxes, frontend display, cart integration all working
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 19:02:18 +01:00
|
|
|
if (!class_exists('WC_TPP_Frontend')) {
|
|
|
|
|
class WC_TPP_Frontend {
|
2025-12-21 04:56:50 +01:00
|
|
|
|
|
|
|
|
public function __construct() {
|
|
|
|
|
add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'));
|
|
|
|
|
add_action('woocommerce_before_add_to_cart_button', array($this, 'display_pricing_table_before'), 20);
|
|
|
|
|
add_action('woocommerce_after_add_to_cart_button', array($this, 'display_pricing_table_after'), 10);
|
|
|
|
|
add_action('woocommerce_single_product_summary', array($this, 'display_pricing_table_after_price'), 15);
|
2025-12-21 15:54:04 +01:00
|
|
|
add_action('woocommerce_before_add_to_cart_quantity', array($this, 'maybe_hide_quantity_input'));
|
2025-12-21 19:04:41 +01:00
|
|
|
|
|
|
|
|
// 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);
|
2025-12-29 20:02:03 +01:00
|
|
|
|
|
|
|
|
// 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'));
|
2025-12-21 04:56:50 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function enqueue_scripts() {
|
2025-12-21 19:04:41 +01:00
|
|
|
// Enqueue CSS on all WooCommerce pages (for catalog buttons and cart)
|
|
|
|
|
if (is_woocommerce() || is_cart() || is_checkout() || is_product()) {
|
2025-12-21 04:56:50 +01:00
|
|
|
wp_enqueue_style('wc-tpp-frontend', WC_TPP_PLUGIN_URL . 'assets/css/frontend.css', array(), WC_TPP_VERSION);
|
2025-12-21 19:04:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enqueue JS only on product pages
|
|
|
|
|
if (is_product()) {
|
2025-12-21 04:56:50 +01:00
|
|
|
wp_enqueue_script('wc-tpp-frontend', WC_TPP_PLUGIN_URL . 'assets/js/frontend.js', array('jquery'), WC_TPP_VERSION, true);
|
|
|
|
|
|
2025-12-29 20:02:03 +01:00
|
|
|
// Localize script with currency settings and AJAX data
|
2025-12-21 04:56:50 +01:00
|
|
|
wp_localize_script('wc-tpp-frontend', 'wcTppData', array(
|
2025-12-29 20:02:03 +01:00
|
|
|
'ajax_url' => admin_url('admin-ajax.php'),
|
|
|
|
|
'nonce' => wp_create_nonce('wc_tpp_variation_pricing'),
|
2025-12-21 04:56:50 +01:00
|
|
|
'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()),
|
|
|
|
|
'price_decimal_separator' => esc_js(wc_get_price_decimal_separator()),
|
|
|
|
|
'price_thousand_separator' => esc_js(wc_get_price_thousand_separator())
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function display_pricing_table_before() {
|
|
|
|
|
if (get_option('wc_tpp_display_position') === 'before_add_to_cart') {
|
|
|
|
|
$this->display_pricing_table();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function display_pricing_table_after() {
|
|
|
|
|
if (get_option('wc_tpp_display_position') === 'after_add_to_cart') {
|
|
|
|
|
$this->display_pricing_table();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public function display_pricing_table_after_price() {
|
|
|
|
|
if (get_option('wc_tpp_display_position') === 'after_price') {
|
|
|
|
|
$this->display_pricing_table();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 15:54:04 +01:00
|
|
|
public function maybe_hide_quantity_input() {
|
|
|
|
|
global $product;
|
|
|
|
|
|
|
|
|
|
if (!$product || !is_a($product, 'WC_Product')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 20:02:03 +01:00
|
|
|
// For variable products, quantity hiding is handled per-variation via JS
|
|
|
|
|
if ($product->is_type('variable')) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 15:54:04 +01:00
|
|
|
$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';
|
|
|
|
|
$packages = get_post_meta($product_id, '_wc_tpp_packages', true);
|
|
|
|
|
|
|
|
|
|
// Hide quantity input if restriction is enabled and packages exist
|
|
|
|
|
if (($global_restrict || $product_restrict) && !empty($packages)) {
|
|
|
|
|
echo '<style>.quantity { display: none !important; }</style>';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 04:56:50 +01:00
|
|
|
public function display_pricing_table() {
|
|
|
|
|
global $product;
|
|
|
|
|
|
|
|
|
|
if (!$product || !is_a($product, 'WC_Product') || get_option('wc_tpp_display_table') !== 'yes') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 20:02:03 +01:00
|
|
|
// 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
|
2025-12-21 04:56:50 +01:00
|
|
|
$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);
|
2025-12-21 15:54:04 +01:00
|
|
|
$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';
|
2025-12-21 04:56:50 +01:00
|
|
|
|
|
|
|
|
if (empty($tiers) && empty($packages)) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
WC_TPP_Template_Loader::get_instance()->display('frontend/pricing-table.twig', array(
|
|
|
|
|
'product' => $product,
|
|
|
|
|
'tiers' => $tiers,
|
2025-12-21 15:54:04 +01:00
|
|
|
'packages' => $packages,
|
|
|
|
|
'restrict_to_packages' => $global_restrict || $product_restrict
|
2025-12-21 04:56:50 +01:00
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 20:02:03 +01:00
|
|
|
/**
|
|
|
|
|
* 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);
|
2025-12-21 04:56:50 +01:00
|
|
|
|
2025-12-30 00:23:56 +01:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 04:56:50 +01:00
|
|
|
if (empty($tiers) || !is_array($tiers)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$applicable_price = null;
|
|
|
|
|
foreach ($tiers as $tier) {
|
|
|
|
|
if ($quantity >= $tier['min_qty']) {
|
|
|
|
|
$applicable_price = $tier['price'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return $applicable_price;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-29 20:02:03 +01:00
|
|
|
/**
|
|
|
|
|
* 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);
|
2025-12-21 04:56:50 +01:00
|
|
|
|
2025-12-30 00:23:56 +01:00
|
|
|
// 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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-21 04:56:50 +01:00
|
|
|
if (empty($packages) || !is_array($packages)) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach ($packages as $package) {
|
|
|
|
|
if ($quantity == $package['qty']) {
|
|
|
|
|
return $package['price'];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2025-12-21 19:04:41 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Check if a product has quantity restrictions enabled
|
|
|
|
|
*
|
|
|
|
|
* @param int $product_id Product ID
|
|
|
|
|
* @return bool
|
|
|
|
|
*/
|
|
|
|
|
public static function has_quantity_restriction($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);
|
|
|
|
|
|
|
|
|
|
return ($global_restrict || $product_restrict) && !empty($packages);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Modify catalog add to cart button for products with quantity restrictions
|
|
|
|
|
*
|
|
|
|
|
* @param string $html Add to cart button HTML
|
|
|
|
|
* @param WC_Product $product Product object
|
|
|
|
|
* @return string Modified HTML
|
|
|
|
|
*/
|
|
|
|
|
public function modify_catalog_add_to_cart_button($html, $product) {
|
|
|
|
|
if (!$product || !is_a($product, 'WC_Product')) {
|
|
|
|
|
return $html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$product_id = $product->get_id();
|
|
|
|
|
|
2025-12-29 20:02:03 +01:00
|
|
|
// 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) {
|
2025-12-21 19:04:41 +01:00
|
|
|
return $html;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Replace add to cart button with "View Options" link
|
|
|
|
|
$product_url = esc_url($product->get_permalink());
|
|
|
|
|
$button_text = esc_html__('View Options', 'wc-tier-package-prices');
|
|
|
|
|
|
2025-12-29 20:02:03 +01:00
|
|
|
// Use correct product type class
|
|
|
|
|
$product_type_class = $product->is_type('variable') ? 'product_type_variable' : 'product_type_simple';
|
|
|
|
|
|
2025-12-21 19:04:41 +01:00
|
|
|
$new_html = sprintf(
|
2025-12-29 20:02:03 +01:00
|
|
|
'<a href="%s" class="button wc-tpp-view-options %s" aria-label="%s">%s</a>',
|
2025-12-21 19:04:41 +01:00
|
|
|
$product_url,
|
2025-12-29 20:02:03 +01:00
|
|
|
esc_attr($product_type_class),
|
2025-12-21 19:04:41 +01:00
|
|
|
esc_attr(sprintf(__('View options for %s', 'wc-tier-package-prices'), $product->get_name())),
|
|
|
|
|
$button_text
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return $new_html;
|
|
|
|
|
}
|
2025-12-29 20:02:03 +01:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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
|
|
|
|
|
));
|
|
|
|
|
}
|
2025-12-21 04:56:50 +01:00
|
|
|
}
|
2025-12-22 19:32:47 +01:00
|
|
|
|
|
|
|
|
new WC_TPP_Frontend();
|
Release version 1.1.13 - Critical class redeclaration fixes
Fixed critical class redeclaration errors affecting all plugin functionality
in version 1.1.12. All plugin component classes now properly guarded.
**CRITICAL FIXES:**
- Plugin completely non-functional in v1.1.12 (no settings, no frontend, no backend)
- Fatal errors for WC_TPP_Admin, WC_TPP_Product_Meta, WC_TPP_Frontend, WC_TPP_Cart, WC_TPP_Settings classes
- All classes now wrapped in class_exists() checks
**Files Modified:**
- includes/class-wc-tpp-admin.php
- includes/class-wc-tpp-product-meta.php
- includes/class-wc-tpp-frontend.php
- includes/class-wc-tpp-cart.php
- includes/class-wc-tpp-settings.php
**Technical Details:**
- Completes comprehensive redeclaration protection started in v1.1.9-1.1.12
- All 2 functions, 4 constants, and 6 classes now protected
- Plugin activates successfully and all features functional
- Settings page, product meta boxes, frontend display, cart integration all working
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-22 19:02:18 +01:00
|
|
|
}
|