Skip to content

Instantly share code, notes, and snippets.

@xlplugins
Created December 12, 2025 13:09
Show Gist options
  • Select an option

  • Save xlplugins/9735f250b551e554a990330b4b9e3aa2 to your computer and use it in GitHub Desktop.

Select an option

Save xlplugins/9735f250b551e554a990330b4b9e3aa2 to your computer and use it in GitHub Desktop.
Order Bump Product Cloner with Discount
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