Changeset 3465867
- Timestamp:
- 02/20/2026 02:23:08 PM (6 weeks ago)
- Location:
- bundlecraft/trunk
- Files:
-
- 6 edited
-
bundlecraft.php (modified) (1 diff)
-
documentation/Licensing/readme.txt (modified) (3 diffs)
-
documentation/index.html (modified) (1 diff)
-
includes/class-bundlecraft-ajax.php (modified) (1 diff)
-
includes/class-bundlecraft-discounts.php (modified) (2 diffs)
-
readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
bundlecraft/trunk/bundlecraft.php
r3465132 r3465867 3 3 * Plugin Name: BundleCraft 4 4 * Description: Lets WooCommerce store owners create unlimited product bundles or combo offers with complete flexibility. 5 * Version: 1.2. 05 * Version: 1.2.1 6 6 * Author: Technical Himanshu 7 7 * Author URI: https://www.technicalhimanshu.in -
bundlecraft/trunk/documentation/Licensing/readme.txt
r3465132 r3465867 4 4 Requires at least: 5.8 5 5 Tested up to: 6.9 6 Stable tag: 1.2. 06 Stable tag: 1.2.1 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 77 77 78 78 == Changelog == 79 80 = 1.2.1 = 81 * Fixed conditional bundle discount logic. 82 * Improved cart recalculation behavior. 83 * Ensured discount removes when bundle incomplete. 84 * Minor internal stability improvements. 79 85 80 86 = 1.2.0 = … … 176 182 == Upgrade Notice == 177 183 184 = 1.2.1 = 185 Improved bundle discount logic to ensure discounts apply only when all bundle products are present in the cart. Recommended update for accuracy and stability. 186 178 187 = 1.2.0 = 179 188 Major **pricing accuracy upgrade** with sale-price support, transparent savings, and secure single-discount cart logic. -
bundlecraft/trunk/documentation/index.html
r3465132 r3465867 1437 1437 <div class="changelog"> 1438 1438 1439 1439 <!-- Version 1.2.1 --> 1440 <div class="changelog-version"> 1441 <div class="version-header"> 1442 <div class="version-title">Version 1.2.1</div> 1443 <div class="version-date">Released: 20th February 2026</div> 1444 </div> 1445 1446 <ul class="changelog-list"> 1447 1448 <li> 1449 <span class="changelog-badge badge-fixed">FIX</span> 1450 Fixed conditional bundle discount logic 1451 </li> 1452 1453 <li> 1454 <span class="changelog-badge badge-improved">IMPROVED</span> 1455 Improved WooCommerce cart recalculation behavior 1456 </li> 1457 1458 <li> 1459 <span class="changelog-badge badge-fixed">FIX</span> 1460 Ensured bundle discount automatically removes when bundle is incomplete 1461 </li> 1462 1463 <li> 1464 <span class="changelog-badge badge-performance">STABILITY</span> 1465 Minor internal stability and logic improvements 1466 </li> 1467 1468 </ul> 1469 </div> 1440 1470 <!-- Version 1.2.0 --> 1471 1441 1472 <div class="changelog-version"> 1442 1473 <div class="version-header"> -
bundlecraft/trunk/includes/class-bundlecraft-ajax.php
r3465132 r3465867 208 208 } 209 209 210 /**211 * Add product to cart212 *213 * @param array $product Product data.214 * @return bool215 */216 private function add_to_cart( $product, $bundle_id, $all_products ) {217 $product_id = $product['product_id'];218 $variation_id = $product['variation_id'];219 $variation = $product['variation'];220 221 if ( $variation_id ) {222 $variation_product = wc_get_product( $variation_id );223 if ( ! $variation_product ) {224 return false;225 }226 }227 228 $unit_price = $this->get_discounted_unit_price( $bundle_id, $all_products );229 230 return (bool) WC()->cart->add_to_cart(231 $product_id,232 1,233 $variation_id,234 $variation,235 array(236 'bundlecraft_bundle_price' => $unit_price,237 )238 );239 240 241 }242 243 210 /** 244 * Calculate discounted price per product for bundle211 * Add product to cart 245 212 * 213 * @param array $product Product data. 246 214 * @param int $bundle_id Bundle ID. 247 * @param array $ products Selectedproducts.248 * @return float215 * @param array $all_products All bundle products. 216 * @return bool 249 217 */ 250 private function get_discounted_unit_price( $bundle_id, $products ) { 251 252 $helpers = BundleCraft_Helpers::get_instance(); 253 $pricing = $helpers->get_bundle_pricing( $bundle_id ); 254 255 $total_regular = floatval( $pricing['regular'] ); 256 $total_discount = floatval( $pricing['discounted'] ); 257 258 if ( $total_regular <= 0 || empty( $products ) ) { 259 return 0; 260 } 261 262 // Equal distribution per product (safe + predictable) 263 $sanitized_products = $this->sanitize_products( $products ); 264 265 if ( empty( $sanitized_products ) ) { 266 return 0; 267 } 268 269 $unit_price = $total_discount / count( $sanitized_products ); 270 271 272 return round( $unit_price, 2 ); 273 } 274 218 private function add_to_cart( $product, $bundle_id, $all_products ) { 219 220 $product_id = $product['product_id']; 221 $variation_id = isset( $product['variation_id'] ) ? absint( $product['variation_id'] ) : 0; 222 $variation = isset( $product['variation'] ) ? $product['variation'] : array(); 223 224 if ( $variation_id ) { 225 $variation_product = wc_get_product( $variation_id ); 226 if ( ! $variation_product ) { 227 return false; 228 } 229 } 230 231 // Store bundle ID only (no price override anymore) 232 return (bool) WC()->cart->add_to_cart( 233 $product_id, 234 1, 235 $variation_id, 236 $variation, 237 array( 238 'bundlecraft_bundle_id' => absint( $bundle_id ), 239 ) 240 ); 241 } 275 242 /** 276 243 * Admin live bundle price preview (SAFE) -
bundlecraft/trunk/includes/class-bundlecraft-discounts.php
r3465132 r3465867 1 1 <?php 2 2 /** 3 * BundleCraft discount handling (Model-A safe cart price override)3 * BundleCraft Model-B Conditional Discount Engine 4 4 */ 5 5 … … 35 35 36 36 /** 37 * Override cart prices BEFORE totals calculation 38 * Official WooCommerce-safe hook. 37 * Apply conditional bundle discount dynamically 39 38 */ 40 39 add_action( 41 40 'woocommerce_before_calculate_totals', 42 array( $this, ' override_bundle_price' ),41 array( $this, 'apply_bundle_discount' ), 43 42 20 44 );45 46 /**47 * Prevent coupons from applying to bundle items48 * Stops double discount.49 */50 add_filter(51 'woocommerce_coupon_is_valid_for_product',52 array( $this, 'block_coupon_on_bundle_items' ),53 10,54 455 43 ); 56 44 } 57 45 58 46 /** 59 * Override WooCommerce cart item price for bundle products47 * Apply bundle discount only if ALL bundle products exist in cart 60 48 * 61 * Model-A: 62 * Bundle price is FINAL → cart must NOT recalculate. 49 * Model-B Logic: 50 * - No price locking 51 * - No stored bundle price 52 * - Discount recalculates dynamically 63 53 * 64 * @param WC_Cart $cart WooCommerce cart object.54 * @param WC_Cart $cart Cart object. 65 55 * @return void 66 56 */ 67 public function override_bundle_price( $cart ) {57 public function apply_bundle_discount( $cart ) { 68 58 69 // Prevent running multiple times in one request59 // Prevent multiple executions 70 60 if ( did_action( 'woocommerce_before_calculate_totals' ) > 1 ) { 71 61 return; 72 62 } 73 63 74 // S afety: avoid running inadmin (except AJAX)64 // Skip admin (except AJAX) 75 65 if ( is_admin() && ! defined( 'DOING_AJAX' ) ) { 76 66 return; 77 67 } 78 68 79 // Safety: empty cart or invalid cart object80 69 if ( ! $cart || $cart->is_empty() ) { 81 70 return; 82 71 } 83 72 84 foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {73 $helpers = BundleCraft_Helpers::get_instance(); 85 74 86 /** 87 * Check if this item was added by BundleCraft 88 */ 89 if ( empty( $cart_item['bundlecraft_bundle_price'] ) ) { 75 $cart_items = $cart->get_cart(); 76 77 // Group cart items by bundle ID 78 $bundles_in_cart = array(); 79 80 foreach ( $cart_items as $cart_item_key => $cart_item ) { 81 82 if ( empty( $cart_item['bundlecraft_bundle_id'] ) ) { 90 83 continue; 91 84 } 92 85 93 $ final_price = floatval( $cart_item['bundlecraft_bundle_price'] );86 $bundle_id = absint( $cart_item['bundlecraft_bundle_id'] ); 94 87 95 if ( $final_price <= 0 ) { 88 if ( ! isset( $bundles_in_cart[ $bundle_id ] ) ) { 89 $bundles_in_cart[ $bundle_id ] = array(); 90 } 91 92 $bundles_in_cart[ $bundle_id ][ $cart_item_key ] = $cart_item; 93 } 94 95 if ( empty( $bundles_in_cart ) ) { 96 return; 97 } 98 99 // Process each detected bundle 100 foreach ( $bundles_in_cart as $bundle_id => $bundle_items ) { 101 102 $bundle_products = $helpers->get_bundle_products( $bundle_id ); 103 104 if ( empty( $bundle_products ) ) { 96 105 continue; 97 106 } 98 107 99 /** 100 * LOCK the product price 101 * Prevent WooCommerce from recalculating totals. 102 */ 103 if ( isset( $cart_item['data'] ) && is_object( $cart_item['data'] ) ) { 104 $cart_item['data']->set_price( $final_price ); 108 // Collect product IDs currently in cart for this bundle 109 $cart_product_ids = array(); 110 111 foreach ( $bundle_items as $item ) { 112 $cart_product_ids[] = absint( $item['product_id'] ); 113 } 114 115 $cart_product_ids = array_unique( $cart_product_ids ); 116 117 // Check if bundle is complete 118 $missing_products = array_diff( $bundle_products, $cart_product_ids ); 119 120 if ( ! empty( $missing_products ) ) { 121 // Incomplete bundle → no discount applied 122 continue; 123 } 124 125 // Bundle complete → apply discount 126 $pricing = $helpers->get_bundle_pricing( $bundle_id ); 127 $discount = floatval( $pricing['discount'] ); 128 129 if ( $discount <= 0 ) { 130 continue; 131 } 132 133 foreach ( $bundle_items as $cart_item_key => $cart_item ) { 134 135 if ( empty( $cart_item['data'] ) || ! is_object( $cart_item['data'] ) ) { 136 continue; 137 } 138 139 $product = $cart_item['data']; 140 141 // Get real current product price (sale-aware) 142 $current_price = floatval( $product->get_price() ); 143 144 if ( $current_price <= 0 ) { 145 continue; 146 } 147 148 // Apply percentage discount dynamically 149 $new_price = $current_price - ( $current_price * $discount / 100 ); 150 151 $product->set_price( round( $new_price, 2 ) ); 105 152 } 106 153 } 107 154 } 108 109 /**110 * Block coupons from applying to bundle items111 *112 * Prevents DOUBLE DISCOUNT.113 *114 * @param bool $valid Coupon validity.115 * @param WC_Product $product Product object.116 * @param WC_Coupon $coupon Coupon object.117 * @param array $values Cart item values.118 *119 * @return bool120 */121 public function block_coupon_on_bundle_items( $valid, $product, $coupon, $values ) {122 123 // If BundleCraft price exists → disallow coupon124 if ( ! empty( $values['bundlecraft_bundle_price'] ) ) {125 return false;126 }127 128 return $valid;129 }130 155 } -
bundlecraft/trunk/readme.txt
r3465132 r3465867 4 4 Requires at least: 5.8 5 5 Tested up to: 6.9 6 Stable tag: 1.2. 06 Stable tag: 1.2.1 7 7 Requires PHP: 7.4 8 8 License: GPLv2 or later … … 77 77 78 78 == Changelog == 79 80 = 1.2.1 = 81 * Fixed conditional bundle discount logic. 82 * Improved cart recalculation behavior. 83 * Ensured discount removes when bundle incomplete. 84 * Minor internal stability improvements. 79 85 80 86 = 1.2.0 = … … 176 182 == Upgrade Notice == 177 183 184 = 1.2.1 = 185 Improved bundle discount logic to ensure discounts apply only when all bundle products are present in the cart. Recommended update for accuracy and stability. 186 178 187 = 1.2.0 = 179 188 Major **pricing accuracy upgrade** with sale-price support, transparent savings, and secure single-discount cart logic.
Note: See TracChangeset
for help on using the changeset viewer.