Created
December 12, 2025 13:09
-
-
Save xlplugins/9735f250b551e554a990330b4b9e3aa2 to your computer and use it in GitHub Desktop.
Order Bump Product Cloner with Discount
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class DoubleYourBump { | |
| /** | |
| * Order Bump ID | |
| */ | |
| private $order_bump_id = 5699; // Order Bump | |
| /** | |
| * Discount percentage (e.g., 10 for 10% off, 20 for 20% off) | |
| */ | |
| private $discount_percentage = 50; // 10% discount | |
| /** | |
| * Tracking relationship between original items and clones | |
| */ | |
| private $item_relationships = []; | |
| /** | |
| * Constructor | |
| */ | |
| public function __construct() { | |
| // Register hooks | |
| $this->register_hooks(); | |
| } | |
| /** | |
| * Register all hooks needed for functionality | |
| */ | |
| private function register_hooks() { | |
| // Original hooks | |
| add_action( 'wfob_after_add_to_cart', array( $this, 'clone_products_when_order_bump_added' ), 10, 1 ); | |
| add_action( 'wfob_after_remove_bump_from_cart', array( $this, 'remove_cloned_products' ), 10, 1 ); | |
| add_filter( 'woocommerce_get_item_data', array( $this, 'add_cloned_product_data' ), 10, 2 ); | |
| add_action( 'woocommerce_before_cart', array( $this, 'display_order_bump_notice' ) ); | |
| add_action( 'woocommerce_before_calculate_totals', array( $this, 'ensure_discount_prices' ), 10, 1 ); | |
| add_filter( 'wfacp_enable_delete_item', [ $this, 'do_not_display' ], 10, 2 ); | |
| add_filter( 'wfacp_show_item_quantity', [ $this, 'do_not_display' ], 10, 2 ); | |
| // New hooks for synchronization | |
| add_action( 'woocommerce_cart_item_set_quantity', array( $this, 'sync_cloned_item_quantity' ), 10, 3 ); | |
| add_action( 'woocommerce_cart_item_removed', array( $this, 'remove_related_cloned_item' ), 10, 2 ); | |
| // Load relationships from session when initializing | |
| add_action( 'woocommerce_cart_loaded_from_session', array( $this, 'load_relationships_from_session' ), 10, 1 ); | |
| } | |
| /** | |
| * Clone products with discount when order bump is added to cart | |
| * | |
| * @param array $bump_object The bump object | |
| */ | |
| public function clone_products_when_order_bump_added( $bump_object ) { | |
| try { | |
| // Get product ID from the bump object | |
| $bump_id = $bump_object['wfob_id']; | |
| // Check if the added product is our order bump | |
| if ( $bump_id !== $this->order_bump_id ) { | |
| return; | |
| } | |
| $bump_cart_item_key = $bump_object['cart_key']; | |
| $bump_cart_item = WC()->cart->get_cart_item( $bump_cart_item_key ); | |
| $product_key = $bump_cart_item['_wfob_product_key']; | |
| // Set a session flag to indicate order bump is active | |
| WC()->session->set( 'order_bump_active', 'yes' ); | |
| // Store the cloned item keys and relationships | |
| $cloned_keys = array(); | |
| $relationships = array(); | |
| // Get a snapshot of the current cart before we add items | |
| $current_cart = WC()->cart->get_cart(); | |
| // Loop through cart items and clone all products except the order bump | |
| foreach ( $current_cart as $cart_item_key => $cart_item ) { | |
| $product_in_cart = $cart_item['data']; | |
| $product_id_in_cart = $product_in_cart->get_id(); | |
| // Skip the order bump itself | |
| if ( $product_id_in_cart == $this->order_bump_id || isset( $cart_item['_wfob_options'] ) ) { | |
| continue; | |
| } | |
| // Skip items that are already cloned (prevents infinite cloning) | |
| if ( isset( $cart_item['is_cloned_item'] ) && $cart_item['is_cloned_item'] ) { | |
| continue; | |
| } | |
| // Calculate the discounted price (apply discount percentage) | |
| $original_price = $product_in_cart->get_price(); | |
| $discounted_price = $original_price * ( ( 100 - $this->discount_percentage ) / 100 ); | |
| // Get the original quantity | |
| $quantity = $cart_item['quantity']; | |
| // Prepare the cart item data to be copied | |
| $cart_item_data = array( | |
| 'is_cloned_item' => true, | |
| 'original_price' => $original_price, | |
| 'discounted_price' => $discounted_price, | |
| 'original_cart_key' => $cart_item_key, | |
| '_wfob_product' => $bump_cart_item['_wfob_product'], | |
| '_wfob_product_key' => $bump_cart_item['_wfob_product_key'], | |
| '_wfob_options' => $bump_cart_item['_wfob_options'], | |
| ); | |
| // Copy any additional custom data from the original item (excluding WC internal data) | |
| foreach ( $cart_item as $key => $value ) { | |
| // Skip WC internal keys and our own custom keys | |
| $skip_keys = array( | |
| 'key', | |
| 'product_id', | |
| 'variation_id', | |
| 'variation', | |
| 'quantity', | |
| 'data', | |
| 'is_cloned_item', | |
| 'original_price', | |
| 'discounted_price', | |
| 'original_cart_key' | |
| ); | |
| if ( ! in_array( $key, $skip_keys ) ) { | |
| $cart_item_data[ $key ] = $value; | |
| } | |
| } | |
| // Clone the product and add to cart with all the same properties | |
| $cloned_item_key = WC()->cart->add_to_cart( $product_id_in_cart, $quantity, // Use same quantity as original | |
| isset( $cart_item['variation_id'] ) ? $cart_item['variation_id'] : 0, // Variation ID | |
| isset( $cart_item['variation'] ) ? $cart_item['variation'] : array(), // Variation attributes | |
| $cart_item_data // All custom data including our tracking info | |
| ); | |
| // Store the cloned item key for later removal | |
| if ( $cloned_item_key ) { | |
| $cloned_keys[] = $cloned_item_key; | |
| // Store relationship between original and cloned item | |
| $relationships[ $cart_item_key ] = $cloned_item_key; | |
| // Get the cart item and apply the discount | |
| $cloned_cart_item = WC()->cart->get_cart_item( $cloned_item_key ); | |
| if ( $cloned_cart_item ) { | |
| $cloned_cart_item['data']->set_price( $discounted_price ); | |
| WC()->cart->cart_contents[ $cloned_item_key ] = $cloned_cart_item; | |
| } | |
| } | |
| } | |
| WC()->cart->remove_cart_item( $bump_cart_item_key ); | |
| $session_products[ $bump_id ][ $product_key ] = []; | |
| $session_products[ $bump_id ][ $product_key ]['cart_key'] = $cloned_item_key; | |
| WC()->session->set( 'wfob_added_bump_product', $session_products ); | |
| // Store the cloned keys in session for later use | |
| WC()->session->set( 'cloned_cart_items', $cloned_keys ); | |
| // Store relationships for synchronization | |
| WC()->session->set( 'item_clone_relationships', $relationships ); | |
| $this->item_relationships = $relationships; | |
| // Recalculate totals | |
| WC()->cart->calculate_totals(); | |
| } catch ( \Exception|\Error $e ) { | |
| } | |
| } | |
| /** | |
| * Remove cloned products when order bump is removed | |
| * | |
| * @param array $bump_object The bump object | |
| */ | |
| public function remove_cloned_products( $bump_object ) { | |
| try { | |
| // Get product ID from the bump object | |
| $wfob_id = $bump_object['wfob_id']; | |
| // Check if the removed product is our order bump | |
| if ( $wfob_id == $this->order_bump_id ) { | |
| // Set session flag to indicate order bump is not active | |
| WC()->session->set( 'order_bump_active', 'no' ); | |
| // Get stored cloned item keys | |
| $cloned_keys = WC()->session->get( 'cloned_cart_items', array() ); | |
| // Remove each cloned item from cart | |
| foreach ( $cloned_keys as $cloned_key ) { | |
| WC()->cart->remove_cart_item( $cloned_key ); | |
| } | |
| // Clear the stored keys and relationships | |
| WC()->session->set( 'cloned_cart_items', array() ); | |
| WC()->session->set( 'item_clone_relationships', array() ); | |
| $this->item_relationships = array(); | |
| // Recalculate totals | |
| WC()->cart->calculate_totals(); | |
| } | |
| } catch ( \Exception $e ) { | |
| } | |
| } | |
| /** | |
| * Load relationships from session when cart is loaded | |
| * | |
| * @param \WC_Cart $cart The cart object | |
| */ | |
| public function load_relationships_from_session( $cart ) { | |
| $relationships = WC()->session->get( 'item_clone_relationships', array() ); | |
| $this->item_relationships = $relationships; | |
| } | |
| /** | |
| * Synchronize cloned item quantity when original item quantity changes | |
| * | |
| * @param string $cart_item_key Cart item key | |
| * @param int $quantity New quantity | |
| * @param int $old_quantity Old quantity | |
| */ | |
| public function sync_cloned_item_quantity( $cart_item_key, $quantity, $old_quantity ) { | |
| try { | |
| // Only process if order bump is active | |
| if ( WC()->session->get( 'order_bump_active' ) !== 'yes' ) { | |
| return; | |
| } | |
| // Check if this is an original item with a clone | |
| if ( isset( $this->item_relationships[ $cart_item_key ] ) ) { | |
| $cloned_key = $this->item_relationships[ $cart_item_key ]; | |
| // Get the cloned cart item | |
| $cloned_item = WC()->cart->get_cart_item( $cloned_key ); | |
| if ( $cloned_item ) { | |
| // Remove action temporarily to prevent infinite loop | |
| remove_action( 'woocommerce_cart_item_set_quantity', array( $this, 'sync_cloned_item_quantity' ), 10 ); | |
| // Update the cloned item quantity | |
| WC()->cart->set_quantity( $cloned_key, $quantity, true ); | |
| // Re-add the action | |
| add_action( 'woocommerce_cart_item_set_quantity', array( $this, 'sync_cloned_item_quantity' ), 10, 3 ); | |
| } | |
| } | |
| // Check if this is a cloned item (for reverse sync if needed) | |
| foreach ( $this->item_relationships as $original_key => $cloned_key ) { | |
| if ( $cloned_key === $cart_item_key ) { | |
| // This is a cloned item - update original if needed | |
| // Note: Usually not needed since cloned items have quantity controls hidden | |
| // But added for completeness | |
| // Remove action temporarily to prevent infinite loop | |
| remove_action( 'woocommerce_cart_item_set_quantity', array( $this, 'sync_cloned_item_quantity' ), 10 ); | |
| // Update the original item quantity | |
| WC()->cart->set_quantity( $original_key, $quantity, true ); | |
| // Re-add the action | |
| add_action( 'woocommerce_cart_item_set_quantity', array( $this, 'sync_cloned_item_quantity' ), 10, 3 ); | |
| break; | |
| } | |
| } | |
| } catch ( \Exception $e ) { | |
| } | |
| } | |
| /** | |
| * Remove related cloned item when original item is removed | |
| * | |
| * @param string $cart_item_key Cart item key that was removed | |
| * @param \WC_Cart $cart The cart object | |
| */ | |
| public function remove_related_cloned_item( $cart_item_key, $cart ) { | |
| try { | |
| // Only process if order bump is active | |
| if ( WC()->session->get( 'order_bump_active' ) !== 'yes' ) { | |
| return; | |
| } | |
| // Check if this is an original item with a clone | |
| if ( isset( $this->item_relationships[ $cart_item_key ] ) ) { | |
| $cloned_key = $this->item_relationships[ $cart_item_key ]; | |
| // Remove the clone | |
| $cart->remove_cart_item( $cloned_key ); | |
| // Update relationships | |
| unset( $this->item_relationships[ $cart_item_key ] ); | |
| WC()->session->set( 'item_clone_relationships', $this->item_relationships ); | |
| // Update cloned keys list | |
| $cloned_keys = WC()->session->get( 'cloned_cart_items', array() ); | |
| $cloned_keys = array_diff( $cloned_keys, array( $cloned_key ) ); | |
| WC()->session->set( 'cloned_cart_items', $cloned_keys ); | |
| } | |
| } catch ( \Exception $e ) { | |
| } | |
| } | |
| /** | |
| * Add custom data to display for cloned products in cart | |
| * | |
| * @param array $item_data The current item data | |
| * @param array $cart_item The cart item | |
| * | |
| * @return array Modified item data | |
| */ | |
| public function add_cloned_product_data( $item_data, $cart_item ) { | |
| if ( isset( $cart_item['is_cloned_item'] ) && $cart_item['is_cloned_item'] ) { | |
| $item_data[] = array( | |
| 'key' => __( 'Discount', 'order-bump-product-cloner' ), | |
| 'value' => sprintf( __( '%d%% off (Order Bump)', 'order-bump-product-cloner' ), $this->discount_percentage ) | |
| ); | |
| } | |
| return $item_data; | |
| } | |
| /** | |
| * Ensure discounted prices remain applied | |
| * | |
| * @param \WC_Cart $cart The cart object | |
| */ | |
| public function ensure_discount_prices( $cart ) { | |
| try { | |
| if ( is_admin() && ! defined( 'DOING_AJAX' ) ) { | |
| return; | |
| } | |
| // Only process if order bump is active | |
| if ( WC()->session->get( 'order_bump_active' ) !== 'yes' ) { | |
| return; | |
| } | |
| // Loop through cart items | |
| foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) { | |
| // Check if this is one of our cloned items | |
| if ( isset( $cart_item['is_cloned_item'] ) && $cart_item['is_cloned_item'] ) { | |
| // Make sure it uses the discounted price | |
| if ( isset( $cart_item['discounted_price'] ) ) { | |
| $cart_item['data']->set_price( $cart_item['discounted_price'] ); | |
| } | |
| } | |
| } | |
| } catch ( \Exception $e ) { | |
| } | |
| } | |
| /** | |
| * Hide quantity/delete controls for cloned items | |
| */ | |
| public function do_not_display( $status, $cart_item ) { | |
| if ( isset( $cart_item['is_cloned_item'] ) ) { | |
| $status = false; | |
| } | |
| return $status; | |
| } | |
| /** | |
| * Display a notice when order bump is active | |
| */ | |
| public function display_order_bump_notice() { | |
| if ( WC()->session->get( 'order_bump_active' ) === 'yes' ) { | |
| wc_add_notice( sprintf( __( '%d%% discounted products added to your cart!', 'order-bump-product-cloner' ), $this->discount_percentage ), 'success' ); | |
| } | |
| } | |
| } | |
| new DoubleYourBump(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment