Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ function OverViewArea(props) {
var offerMessage = "20% off only for you";

if ( createBumpData.offer_type === 'discount' ) {
offerAmout = createBumpData.offer_product_regular_price - createBumpData.offer_product_regular_price*createBumpData.offer_amount/100;
// Use the current price (sale price if available, otherwise regular price) for discount calculation
const currentPrice = createBumpData.offer_product_regular_price;
offerAmout = currentPrice - currentPrice*createBumpData.offer_amount/100;
Comment on lines +18 to +20
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use current price for discount calc and coerce/round numerics

Comment says “current price” but code still uses regular price. Also ensure numeric coercion and 2-decimal rounding to avoid float artifacts.

-        // Use the current price (sale price if available, otherwise regular price) for discount calculation
-        const currentPrice = createBumpData.offer_product_regular_price;
-        offerAmout = currentPrice - currentPrice*createBumpData.offer_amount/100;
+        // Use the current price (sale if available) for discount calculation
+        const currentPrice = Number(
+          createBumpData.offer_product_current_price
+          ?? createBumpData.current_price
+          ?? createBumpData.offer_product_regular_price
+          ?? 0
+        );
+        const discountPct = Number(createBumpData.offer_amount ?? 0);
+        offerAmout = Math.round((currentPrice - (currentPrice * discountPct / 100)) * 100) / 100;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Use the current price (sale price if available, otherwise regular price) for discount calculation
const currentPrice = createBumpData.offer_product_regular_price;
offerAmout = currentPrice - currentPrice*createBumpData.offer_amount/100;
// Use the current price (sale if available) for discount calculation
const currentPrice = Number(
createBumpData.offer_product_current_price
?? createBumpData.current_price
?? createBumpData.offer_product_regular_price
?? 0
);
const discountPct = Number(createBumpData.offer_amount ?? 0);
offerAmout = Math.round((currentPrice - (currentPrice * discountPct / 100)) * 100) / 100;
🤖 Prompt for AI Agents
In
modules/upsell-order-bump/assets/src/components/appearance/template/overview-area/OverViewArea.js
around lines 18-20, the code assigns currentPrice to the regular price but
should use the sale price when present, and it doesn’t coerce/round numeric
values; update currentPrice to use createBumpData.offer_product_sale_price if
truthy else createBumpData.offer_product_regular_price, coerce prices and
offer_amount to Number, compute the discounted amount as currentPrice -
(currentPrice * offer_amount / 100), and round the final offerAmount to two
decimals (e.g., Math.round(value * 100) / 100 or Number(value.toFixed(2))) so
there are no floating-point artifacts.

offerMessage = createBumpData.offer_amount + "% off only for you";
} else if ( createBumpData.offer_type === 'price' ) {
offerAmout = createBumpData.offer_amount;
Expand Down
36 changes: 30 additions & 6 deletions modules/upsell-order-bump/includes/EnqueueScript.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,11 +183,11 @@ public function prodcut_list() {
);

$_product = wc_get_product( $product->ID );
$sale_price = $_product->get_sale_price();
$current_price = $_product->get_price();
$regular_price = $_product->get_regular_price();

// Prepare woocommerce price data.
$price = esc_html( $regular_price );
$price = esc_html( $current_price );
$price = wp_strip_all_tags( html_entity_decode( wc_price( $price ) ) );

// Render woocommerce price with currency symbol.
Expand All @@ -206,7 +206,7 @@ public function prodcut_list() {
// Get categories csv.
$category_names = implode( ', ', $category_names );

if ( $_product->is_type( 'simple' ) && $regular_price ) {
if ( $_product->is_type( 'simple' ) && $current_price ) {
$simple_product_for_offer[] = array(
'price' => $price,
'value' => $product->ID,
Expand Down Expand Up @@ -259,11 +259,20 @@ public function prodcut_list_for_view() {
$_product = wc_get_product( $product->ID );

if ( $_product->is_type( 'simple' ) ) {
// Use get_price() method directly since it gives the current price
$current_price = $_product->get_price();
$regular_price = $_product->get_regular_price();

// Ensure we have valid numeric values for number_format
$current_price = is_numeric( $current_price ) ? (float) $current_price : 0.00;
$regular_price = is_numeric( $regular_price ) ? (float) $regular_price : $current_price;

$product_list_for_view[ $product->ID ] = array(
'ID' => $product->ID,
'post_title' => $_product->get_title(),
'image_url' => wp_get_attachment_url( get_post_thumbnail_id( $product->ID ), 'thumbnail' ),
'regular_price' => number_format( (int) $_product->get_regular_price(), 2 ),
'regular_price' => number_format( $regular_price, 2 ),
'current_price' => number_format( $current_price, 2 ),
);
}
if ( $_product->is_type( 'variable' ) ) {
Expand All @@ -272,7 +281,21 @@ public function prodcut_list_for_view() {
foreach ( $variations as $variation ) {
$variation_id = $variation['variation_id'];
$variation_attributes = $variation['attributes'];
$regular_price = number_format( $variation['display_regular_price'], 2 );
$variation_product = wc_get_product( $variation_id );

// Ensure we have a valid variation product
if ( ! $variation_product ) {
continue;
}

$current_variation_price = $variation_product->get_price();
$variation_regular_price = $variation_product->get_regular_price();

// Ensure we have valid numeric values for number_format
$current_variation_price = is_numeric( $current_variation_price ) ? (float) $current_variation_price : 0.00;
$variation_regular_price = is_numeric( $variation_regular_price ) ? (float) $variation_regular_price : $current_variation_price;

$formatted_price = number_format( $current_variation_price, 2 );
$variation_root_name = $_product->get_title();
$variation_name = $variation_root_name . '(' . implode( ', ', $variation_attributes ) . ')';
$image_url = $variation['image']['url'];
Expand All @@ -281,7 +304,8 @@ public function prodcut_list_for_view() {
'ID' => $variation_id,
'post_title' => $variation_name,
'image_url' => $image_url,
'regular_price' => $regular_price,
'regular_price' => number_format( $variation_regular_price, 2 ),
'current_price' => $formatted_price,
);
}
}
Expand Down
213 changes: 202 additions & 11 deletions modules/upsell-order-bump/includes/OrderBump.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ class OrderBump implements HookRegistry {
public function register_hooks(): void {
add_action( 'woocommerce_review_order_before_submit', array( $this, 'bump_product_frontend_view' ) );
add_action( 'woocommerce_before_calculate_totals', array( $this, 'woocommerce_custom_price_to_cart_item' ) );
add_action( 'woocommerce_cart_item_removed', array( $this, 'handle_target_product_removal' ), 10, 2 );
add_action( 'woocommerce_after_cart_item_quantity_update', array( $this, 'handle_cart_quantity_update' ), 10, 4 );
}

/**
Expand All @@ -46,12 +48,33 @@ public function bump_product_frontend_view() {
$bump_list = get_posts( $args_bump );

foreach ( $all_cart_products as $value ) {
// Get categories from the current cart item (variation or simple product)
$cat_ids = $value['data']->get_category_ids();
foreach ( $cat_ids as $cat_id ) {
$all_cart_category_ids[] = $cat_id;
$all_cart_category_ids[] = (int) $cat_id;
}

// For variable products, also get categories from the parent product
if ( isset($value['variation_id']) && $value['variation_id'] > 0 ) {
$parent_product = wc_get_product( $value['product_id'] );
if ( $parent_product ) {
$parent_cat_ids = $parent_product->get_category_ids();
foreach ( $parent_cat_ids as $cat_id ) {
$all_cart_category_ids[] = (int) $cat_id;
}
}
}

// For variable products, include both parent product ID and variation ID
// Convert to integers to ensure consistent data types
$all_cart_product_ids[] = (int) $value['product_id'];
if ( isset($value['variation_id']) && $value['variation_id'] > 0 ) {
$all_cart_product_ids[] = (int) $value['variation_id'];
}
$all_cart_product_ids[] = $value['variation_id'] === 0 ? $value['product_id'] : $value['variation_id'];
}

// Remove duplicate category IDs
$all_cart_category_ids = array_unique( $all_cart_category_ids );
foreach ( $bump_list as $bump ) {
$bump_info = maybe_unserialize( $bump->post_excerpt );
$bump_info = (object) $bump_info;
Expand All @@ -64,13 +87,15 @@ public function bump_product_frontend_view() {
$checked = 'checked';
}

$_product = wc_get_product( $offer_product_id );
$regular_price = $_product->get_regular_price();
if ( 'discount' === $offer_type ) {
$offer_price = ( $regular_price - ( $regular_price * $offer_amount / 100 ) );
} else {
$offer_price = $offer_amount;
}
$_product = wc_get_product( $offer_product_id );
$regular_price = $_product->get_regular_price();
// Use sale price if available, otherwise use regular price for discount calculation
$current_price = $_product->get_sale_price() ? $_product->get_sale_price() : $regular_price;
if ( 'discount' === $offer_type ) {
$offer_price = ( $current_price - ( $current_price * $offer_amount / 100 ) );
} else {
$offer_price = $offer_amount;
}
Comment on lines +90 to +98
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use get_price() for “current” price (respects schedules/filters)

get_sale_price() ignores sale schedules and filter-based pricing. get_price() returns the effective “current” price.

-		$_product      = wc_get_product( $offer_product_id );
-		$regular_price = $_product->get_regular_price();
-		// Use sale price if available, otherwise use regular price for discount calculation
-		$current_price = $_product->get_sale_price() ? $_product->get_sale_price() : $regular_price;
-		if ( 'discount' === $offer_type ) {
-			$offer_price = ( $current_price - ( $current_price * $offer_amount / 100 ) );
-		} else {
-			$offer_price = $offer_amount;
-		}
+		$_product      = wc_get_product( $offer_product_id );
+		$current_price = (float) $_product->get_price();
+		$regular_price = (float) $_product->get_regular_price();
+		if ( 'discount' === $offer_type ) {
+			$offer_price = round( $current_price - ( $current_price * ( (float) $offer_amount / 100 ) ), 2 );
+		} else {
+			$offer_price = (float) $offer_amount;
+		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$_product = wc_get_product( $offer_product_id );
$regular_price = $_product->get_regular_price();
// Use sale price if available, otherwise use regular price for discount calculation
$current_price = $_product->get_sale_price() ? $_product->get_sale_price() : $regular_price;
if ( 'discount' === $offer_type ) {
$offer_price = ( $current_price - ( $current_price * $offer_amount / 100 ) );
} else {
$offer_price = $offer_amount;
}
$_product = wc_get_product( $offer_product_id );
$current_price = (float) $_product->get_price();
$regular_price = (float) $_product->get_regular_price();
if ( 'discount' === $offer_type ) {
$offer_price = round( $current_price - ( $current_price * ( (float) $offer_amount / 100 ) ), 2 );
} else {
$offer_price = (float) $offer_amount;
}
🤖 Prompt for AI Agents
In modules/upsell-order-bump/includes/OrderBump.php around lines 90 to 98, the
code uses get_regular_price() and get_sale_price() to determine the current
price, which ignores sale schedules and filters; replace that logic to call
$_product->get_price() to obtain the effective current price, cast it to a float
for math safety, then compute $offer_price as before (if 'discount' calculate
current_price - (current_price * offer_amount / 100), else use the fixed
offer_amount). Ensure you handle an empty/non-numeric price by defaulting to 0.0
before the calculation.


$cart = WC()->cart;
$product_already_added_from_shop = false;
Expand All @@ -94,12 +119,18 @@ public function bump_product_frontend_view() {
$bump_type = ! empty( $bump_info->bump_type ) ? esc_html( $bump_info->bump_type ) : 'products';
if ( $bump_type === 'products' ) {
$target_products = ! empty( $bump_info->target_products ) ? wc_clean( $bump_info->target_products ) : array();
if ( $target_products && ( count( $all_cart_product_ids ) !== count( array_diff( $all_cart_product_ids, $target_products ) ) ) ) {
// Convert target products to integers to ensure consistent data types
$target_products = array_map( 'intval', $target_products );

// Check if any target products are in cart (simple intersection check)
if ( $target_products && ! empty( array_intersect( $all_cart_product_ids, $target_products ) ) ) {
include __DIR__ . '/../templates/bump-product-front-view.php';
}
} else {
$target_categories = ! empty( $bump_info->target_categories ) ? wc_clean( $bump_info->target_categories ) : array();
if ( $target_categories && ( count( $all_cart_category_ids ) !== count( array_diff( $all_cart_category_ids, $target_categories ) ) ) ) {

// Check if any target categories are in cart (simple intersection check)
if ( $target_categories && ! empty( array_intersect( $all_cart_category_ids, $target_categories ) ) ) {
include __DIR__ . '/../templates/bump-product-front-view.php';
}
}
Expand All @@ -121,4 +152,164 @@ public function woocommerce_custom_price_to_cart_item( $cart_object ) {
}
}
}

/**
* Handle when a target product is removed from cart - clean up any related bump products.
*
* @param string $cart_item_key The cart item key.
* @param object $cart The cart object.
*/
public function handle_target_product_removal( $cart_item_key, $cart ) {
$removed_item = $cart->removed_cart_contents[ $cart_item_key ];
if ( ! $removed_item ) {
return;
}

$removed_product_id = $removed_item['product_id'];
$removed_variation_id = isset( $removed_item['variation_id'] ) ? $removed_item['variation_id'] : 0;

// For variable products, check both parent and variation IDs
$removed_item_ids = array( $removed_product_id );
if ( $removed_variation_id > 0 ) {
$removed_item_ids[] = $removed_variation_id;
}

$this->validate_bump_products_after_removal( $removed_item_ids );
}
Comment on lines +162 to +178
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard against undefined index on removed_cart_contents

Directly indexing can emit notices. Check with isset/empty first.

-	public function handle_target_product_removal( $cart_item_key, $cart ) {
-		$removed_item = $cart->removed_cart_contents[ $cart_item_key ];
-		if ( ! $removed_item ) {
-			return;
-		}
+	public function handle_target_product_removal( $cart_item_key, $cart ) {
+		if ( empty( $cart->removed_cart_contents[ $cart_item_key ] ) ) {
+			return;
+		}
+		$removed_item = $cart->removed_cart_contents[ $cart_item_key ];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function handle_target_product_removal( $cart_item_key, $cart ) {
$removed_item = $cart->removed_cart_contents[ $cart_item_key ];
if ( ! $removed_item ) {
return;
}
$removed_product_id = $removed_item['product_id'];
$removed_variation_id = isset( $removed_item['variation_id'] ) ? $removed_item['variation_id'] : 0;
// For variable products, check both parent and variation IDs
$removed_item_ids = array( $removed_product_id );
if ( $removed_variation_id > 0 ) {
$removed_item_ids[] = $removed_variation_id;
}
$this->validate_bump_products_after_removal( $removed_item_ids );
}
public function handle_target_product_removal( $cart_item_key, $cart ) {
- $removed_item = $cart->removed_cart_contents[ $cart_item_key ];
- if ( ! $removed_item ) {
- return;
if ( empty( $cart->removed_cart_contents[ $cart_item_key ] ) ) {
return;
}
$removed_item = $cart->removed_cart_contents[ $cart_item_key ];
$removed_product_id = $removed_item['product_id'];
$removed_variation_id = isset( $removed_item['variation_id'] ) ? $removed_item['variation_id'] : 0;
// For variable products, check both parent and variation IDs
$removed_item_ids = array( $removed_product_id );
if ( $removed_variation_id > 0 ) {
$removed_item_ids[] = $removed_variation_id;
}
$this->validate_bump_products_after_removal( $removed_item_ids );
}
🤖 Prompt for AI Agents
In modules/upsell-order-bump/includes/OrderBump.php around lines 162-178, the
code directly accesses $cart->removed_cart_contents[$cart_item_key], which can
raise undefined index notices; first verify that removed_cart_contents exists
and that the specific key is set (e.g., use isset($cart->removed_cart_contents)
&& isset($cart->removed_cart_contents[$cart_item_key]) or empty checks) and
return early if not; then proceed to read product_id/variation_id and build
$removed_item_ids as before.


/**
* Handle cart quantity updates - if quantity becomes 0, treat as removal.
*
* @param string $cart_item_key The cart item key.
* @param int $quantity New quantity.
* @param int $old_quantity Old quantity.
* @param object $cart The cart object.
*/
public function handle_cart_quantity_update( $cart_item_key, $quantity, $old_quantity, $cart ) {
// If quantity was reduced to 0, the item is effectively removed
if ( $quantity === 0 && $old_quantity > 0 ) {
$cart_item = $cart->cart_contents[ $cart_item_key ];
if ( $cart_item ) {
$product_id = $cart_item['product_id'];
$variation_id = isset( $cart_item['variation_id'] ) ? $cart_item['variation_id'] : 0;

// For variable products, check both parent and variation IDs
$item_ids = array( $product_id );
if ( $variation_id > 0 ) {
$item_ids[] = $variation_id;
}

$this->validate_bump_products_after_removal( $item_ids );
}
}
}
Comment on lines +188 to +205
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Also guard cart_contents access on quantity update

Prevents notices when key is absent.

-		if ( $quantity === 0 && $old_quantity > 0 ) {
-			$cart_item = $cart->cart_contents[ $cart_item_key ];
-			if ( $cart_item ) {
+		if ( $quantity === 0 && $old_quantity > 0 ) {
+			if ( isset( $cart->cart_contents[ $cart_item_key ] ) ) {
+				$cart_item = $cart->cart_contents[ $cart_item_key ];
 				$product_id = $cart_item['product_id'];
 				$variation_id = isset( $cart_item['variation_id'] ) ? $cart_item['variation_id'] : 0;
 				
 				// For variable products, check both parent and variation IDs
 				$item_ids = array( $product_id );
 				if ( $variation_id > 0 ) {
 					$item_ids[] = $variation_id;
 				}
 
 				$this->validate_bump_products_after_removal( $item_ids );
-			}
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function handle_cart_quantity_update( $cart_item_key, $quantity, $old_quantity, $cart ) {
// If quantity was reduced to 0, the item is effectively removed
if ( $quantity === 0 && $old_quantity > 0 ) {
$cart_item = $cart->cart_contents[ $cart_item_key ];
if ( $cart_item ) {
$product_id = $cart_item['product_id'];
$variation_id = isset( $cart_item['variation_id'] ) ? $cart_item['variation_id'] : 0;
// For variable products, check both parent and variation IDs
$item_ids = array( $product_id );
if ( $variation_id > 0 ) {
$item_ids[] = $variation_id;
}
$this->validate_bump_products_after_removal( $item_ids );
}
}
}
public function handle_cart_quantity_update( $cart_item_key, $quantity, $old_quantity, $cart ) {
// If quantity was reduced to 0, the item is effectively removed
if ( $quantity === 0 && $old_quantity > 0 ) {
if ( isset( $cart->cart_contents[ $cart_item_key ] ) ) {
$cart_item = $cart->cart_contents[ $cart_item_key ];
$product_id = $cart_item['product_id'];
$variation_id = isset( $cart_item['variation_id'] ) ? $cart_item['variation_id'] : 0;
// For variable products, check both parent and variation IDs
$item_ids = array( $product_id );
if ( $variation_id > 0 ) {
$item_ids[] = $variation_id;
}
$this->validate_bump_products_after_removal( $item_ids );
}
}
}
🤖 Prompt for AI Agents
In modules/upsell-order-bump/includes/OrderBump.php around lines 188 to 205, the
code accesses $cart->cart_contents[$cart_item_key] without ensuring that key
exists which can raise notices; before reading $cart_item, add a guard using
isset($cart->cart_contents[$cart_item_key]) (or empty check) and return early if
missing so the function only proceeds when the cart item is present, then
continue with the existing product/variation ID logic and call to
validate_bump_products_after_removal.


/**
* Validate and clean up bump products when their target products are removed.
*
* @param array $removed_item_ids Array of removed product/variation IDs.
*/
private function validate_bump_products_after_removal( $removed_item_ids ) {
// Get all bump offers
$args_bump = array(
'post_type' => 'sgsb_order_bump',
'posts_per_page' => -1,
);
$bump_list = get_posts( $args_bump );

foreach ( $bump_list as $bump ) {
$bump_info = maybe_unserialize( $bump->post_excerpt );
$bump_info = (object) $bump_info;

$bump_type = ! empty( $bump_info->bump_type ) ? esc_html( $bump_info->bump_type ) : 'products';
$offer_product_id = $bump_info->offer_product;

// Check if the removed item was a target for this bump offer
$was_target = false;

if ( $bump_type === 'products' ) {
$target_products = ! empty( $bump_info->target_products ) ? wc_clean( $bump_info->target_products ) : array();
$was_target = ! empty( array_intersect( $removed_item_ids, $target_products ) );
} else {
// For category-based bumps, check if removed product was in target categories
$was_target = false;
foreach ( $removed_item_ids as $removed_item_id ) {
$removed_product_categories = wp_get_post_terms( $removed_item_id, 'product_cat', array( 'fields' => 'ids' ) );
$target_categories = ! empty( $bump_info->target_categories ) ? wc_clean( $bump_info->target_categories ) : array();
if ( ! empty( array_intersect( $removed_product_categories, $target_categories ) ) ) {
$was_target = true;
break;
}
}
}
Comment on lines +224 to +244
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Category-based cleanup misses variation removals (parent categories)

Variations don’t carry product_cat terms; you need to resolve to the parent product before checking categories. Otherwise orphaned bumps can persist.

-			} else {
-				// For category-based bumps, check if removed product was in target categories
-				$was_target = false;
-				foreach ( $removed_item_ids as $removed_item_id ) {
-					$removed_product_categories = wp_get_post_terms( $removed_item_id, 'product_cat', array( 'fields' => 'ids' ) );
-					$target_categories = ! empty( $bump_info->target_categories ) ? wc_clean( $bump_info->target_categories ) : array();
-					if ( ! empty( array_intersect( $removed_product_categories, $target_categories ) ) ) {
-						$was_target = true;
-						break;
-					}
-				}
-			}
+			} else {
+				// For category-based bumps, check parent categories for variations
+				$was_target = false;
+				$target_categories = ! empty( $bump_info->target_categories ) ? wc_clean( $bump_info->target_categories ) : array();
+				foreach ( $removed_item_ids as $removed_item_id ) {
+					$prod           = wc_get_product( $removed_item_id );
+					$term_source_id = ( $prod && $prod->is_type( 'variation' ) ) ? $prod->get_parent_id() : $removed_item_id;
+					$removed_product_categories = wp_get_post_terms( $term_source_id, 'product_cat', array( 'fields' => 'ids' ) );
+					if ( ! empty( array_intersect( $removed_product_categories, $target_categories ) ) ) {
+						$was_target = true;
+						break;
+					}
+				}
+			}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
$bump_type = ! empty( $bump_info->bump_type ) ? esc_html( $bump_info->bump_type ) : 'products';
$offer_product_id = $bump_info->offer_product;
// Check if the removed item was a target for this bump offer
$was_target = false;
if ( $bump_type === 'products' ) {
$target_products = ! empty( $bump_info->target_products ) ? wc_clean( $bump_info->target_products ) : array();
$was_target = ! empty( array_intersect( $removed_item_ids, $target_products ) );
} else {
// For category-based bumps, check if removed product was in target categories
$was_target = false;
foreach ( $removed_item_ids as $removed_item_id ) {
$removed_product_categories = wp_get_post_terms( $removed_item_id, 'product_cat', array( 'fields' => 'ids' ) );
$target_categories = ! empty( $bump_info->target_categories ) ? wc_clean( $bump_info->target_categories ) : array();
if ( ! empty( array_intersect( $removed_product_categories, $target_categories ) ) ) {
$was_target = true;
break;
}
}
}
} else {
// For category-based bumps, check parent categories for variations
$was_target = false;
$target_categories = ! empty( $bump_info->target_categories )
? wc_clean( $bump_info->target_categories )
: array();
foreach ( $removed_item_ids as $removed_item_id ) {
$prod = wc_get_product( $removed_item_id );
$term_source_id = ( $prod && $prod->is_type( 'variation' ) )
? $prod->get_parent_id()
: $removed_item_id;
$removed_product_categories = wp_get_post_terms(
$term_source_id,
'product_cat',
array( 'fields' => 'ids' )
);
if ( ! empty( array_intersect( $removed_product_categories, $target_categories ) ) ) {
$was_target = true;
break;
}
}
}
🤖 Prompt for AI Agents
modules/upsell-order-bump/includes/OrderBump.php lines 224-244: category-based
removal check fails for product variations because variations don’t have
product_cat terms; update the loop so for each $removed_item_id you resolve the
parent product ID (use the variation’s post_parent or WooCommerce product API to
get_parent_id) and then fetch categories from the parent product ID before
intersecting with $target_categories; ensure $target_categories is normalized to
an array of ints and use the parent ID only when it exists so category-based
bumps are correctly detected and cleaned up.


if ( $was_target ) {
// Check if there are still other target products in cart
$remaining_targets = $this->get_remaining_target_products( $bump_info, $bump_type );

if ( empty( $remaining_targets ) ) {
// No more target products in cart, remove or reset the bump product
$this->handle_orphaned_bump_product( $offer_product_id );
}
}
}
}

/**
* Get remaining target products in cart for a bump offer.
*
* @param object $bump_info The bump offer info.
* @param string $bump_type The bump type (products or categories).
* @return array Array of remaining target product IDs.
*/
private function get_remaining_target_products( $bump_info, $bump_type ) {
$cart = WC()->cart;
$remaining_targets = array();

foreach ( $cart->get_cart() as $cart_item ) {
$cart_product_id = $cart_item['product_id'];
$cart_variation_id = isset( $cart_item['variation_id'] ) ? $cart_item['variation_id'] : 0;

// For variable products, check both parent and variation IDs
$cart_item_ids = array( $cart_product_id );
if ( $cart_variation_id > 0 ) {
$cart_item_ids[] = $cart_variation_id;
}

if ( $bump_type === 'products' ) {
$target_products = ! empty( $bump_info->target_products ) ? wc_clean( $bump_info->target_products ) : array();
if ( ! empty( array_intersect( $cart_item_ids, $target_products ) ) ) {
$remaining_targets = array_merge( $remaining_targets, $cart_item_ids );
}
} else {
$cart_categories = wp_get_post_terms( $cart_product_id, 'product_cat', array( 'fields' => 'ids' ) );
$target_categories = ! empty( $bump_info->target_categories ) ? wc_clean( $bump_info->target_categories ) : array();
if ( ! empty( array_intersect( $cart_categories, $target_categories ) ) ) {
$remaining_targets = array_merge( $remaining_targets, $cart_item_ids );
}
}
}

return $remaining_targets;
}

/**
* Handle orphaned bump product - remove it from cart.
*
* @param int $offer_product_id The bump offer product ID.
*/
private function handle_orphaned_bump_product( $offer_product_id ) {
$cart = WC()->cart;

foreach ( $cart->get_cart() as $cart_item_key => $cart_item ) {
$cart_product_id = $cart_item['product_id'];
$cart_variation_id = isset( $cart_item['variation_id'] ) ? $cart_item['variation_id'] : 0;
$cart_item_id = $cart_variation_id > 0 ? $cart_variation_id : $cart_product_id;

// Check if this is the bump product with custom pricing and remove it
if ( $cart_item_id == $offer_product_id && isset( $cart_item['custom_price'] ) ) {
$cart->remove_cart_item( $cart_item_key );
}
}
}
}