Plugin Directory

Changeset 3465867


Ignore:
Timestamp:
02/20/2026 02:23:08 PM (6 weeks ago)
Author:
technicalhimanshu
Message:

Update trunk to version 1.2.1

Location:
bundlecraft/trunk
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • bundlecraft/trunk/bundlecraft.php

    r3465132 r3465867  
    33 * Plugin Name: BundleCraft
    44 * Description: Lets WooCommerce store owners create unlimited product bundles or combo offers with complete flexibility.
    5  * Version: 1.2.0
     5 * Version: 1.2.1
    66 * Author: Technical Himanshu
    77 * Author URI: https://www.technicalhimanshu.in
  • bundlecraft/trunk/documentation/Licensing/readme.txt

    r3465132 r3465867  
    44Requires at least: 5.8
    55Tested up to: 6.9
    6 Stable tag: 1.2.0
     6Stable tag: 1.2.1
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    7777
    7878== 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.
    7985
    8086= 1.2.0 =
     
    176182== Upgrade Notice ==
    177183
     184= 1.2.1 =
     185Improved bundle discount logic to ensure discounts apply only when all bundle products are present in the cart. Recommended update for accuracy and stability.
     186
    178187= 1.2.0 =
    179188Major **pricing accuracy upgrade** with sale-price support, transparent savings, and secure single-discount cart logic.
  • bundlecraft/trunk/documentation/index.html

    r3465132 r3465867  
    14371437                    <div class="changelog">
    14381438
    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>
    14401470                        <!-- Version 1.2.0 -->
     1471                         
    14411472<div class="changelog-version">
    14421473    <div class="version-header">
  • bundlecraft/trunk/includes/class-bundlecraft-ajax.php

    r3465132 r3465867  
    208208    }
    209209
    210     /**
    211      * Add product to cart
    212      *
    213      * @param array $product Product data.
    214      * @return bool
    215      */
    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 
    243210/**
    244  * Calculate discounted price per product for bundle
     211 * Add product to cart
    245212 *
     213 * @param array $product Product data.
    246214 * @param int   $bundle_id Bundle ID.
    247  * @param array $products  Selected products.
    248  * @return float
     215 * @param array $all_products All bundle products.
     216 * @return bool
    249217 */
    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 
     218private 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}
    275242    /**
    276243 * Admin live bundle price preview (SAFE)
  • bundlecraft/trunk/includes/class-bundlecraft-discounts.php

    r3465132 r3465867  
    11<?php
    22/**
    3  * BundleCraft discount handling (Model-A safe cart price override)
     3 * BundleCraft Model-B Conditional Discount Engine
    44 */
    55
     
    3535
    3636        /**
    37          * Override cart prices BEFORE totals calculation
    38          * Official WooCommerce-safe hook.
     37         * Apply conditional bundle discount dynamically
    3938         */
    4039        add_action(
    4140            'woocommerce_before_calculate_totals',
    42             array( $this, 'override_bundle_price' ),
     41            array( $this, 'apply_bundle_discount' ),
    4342            20
    44         );
    45 
    46         /**
    47          * Prevent coupons from applying to bundle items
    48          * 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             4
    5543        );
    5644    }
    5745
    5846    /**
    59      * Override WooCommerce cart item price for bundle products
     47     * Apply bundle discount only if ALL bundle products exist in cart
    6048     *
    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
    6353     *
    64      * @param WC_Cart $cart WooCommerce cart object.
     54     * @param WC_Cart $cart Cart object.
    6555     * @return void
    6656     */
    67     public function override_bundle_price( $cart ) {
     57    public function apply_bundle_discount( $cart ) {
    6858
    69         // Prevent running multiple times in one request
     59        // Prevent multiple executions
    7060        if ( did_action( 'woocommerce_before_calculate_totals' ) > 1 ) {
    7161            return;
    7262        }
    7363
    74         // Safety: avoid running in admin (except AJAX)
     64        // Skip admin (except AJAX)
    7565        if ( is_admin() && ! defined( 'DOING_AJAX' ) ) {
    7666            return;
    7767        }
    7868
    79         // Safety: empty cart or invalid cart object
    8069        if ( ! $cart || $cart->is_empty() ) {
    8170            return;
    8271        }
    8372
    84         foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
     73        $helpers = BundleCraft_Helpers::get_instance();
    8574
    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'] ) ) {
    9083                continue;
    9184            }
    9285
    93             $final_price = floatval( $cart_item['bundlecraft_bundle_price'] );
     86            $bundle_id = absint( $cart_item['bundlecraft_bundle_id'] );
    9487
    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 ) ) {
    96105                continue;
    97106            }
    98107
    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 ) );
    105152            }
    106153        }
    107154    }
    108 
    109     /**
    110      * Block coupons from applying to bundle items
    111      *
    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 bool
    120      */
    121     public function block_coupon_on_bundle_items( $valid, $product, $coupon, $values ) {
    122 
    123         // If BundleCraft price exists → disallow coupon
    124         if ( ! empty( $values['bundlecraft_bundle_price'] ) ) {
    125             return false;
    126         }
    127 
    128         return $valid;
    129     }
    130155}
  • bundlecraft/trunk/readme.txt

    r3465132 r3465867  
    44Requires at least: 5.8
    55Tested up to: 6.9
    6 Stable tag: 1.2.0
     6Stable tag: 1.2.1
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    7777
    7878== 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.
    7985
    8086= 1.2.0 =
     
    176182== Upgrade Notice ==
    177183
     184= 1.2.1 =
     185Improved bundle discount logic to ensure discounts apply only when all bundle products are present in the cart. Recommended update for accuracy and stability.
     186
    178187= 1.2.0 =
    179188Major **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.