I try do something similar to 'product addons', but manually. I would have few simple products, that have to be as companions. Their ids will be the same. I want to make them as checkboxes, with photo, name and price. If checkbox is checked - that product has to add to cart together with main on the product page. That's a code, already tweaked by gpt
// Function to display checkboxes for additional products
function display_additional_products_checkboxes() {
$additional_products_ids = array(220, 222, 224, 226); // Replace with your product IDs
echo '<div class="additional-products">';
echo '<h3>Additional Products</h3>';
foreach ($additional_products_ids as $product_id) {
$additional_product = wc_get_product($product_id);
if ($additional_product) {
$product_name = $additional_product->get_name();
$product_price = $additional_product->get_price_html();
$product_id_sanitized = sanitize_title($additional_product->get_slug());
echo '<div class="additional-product">';
echo '<label for="additional_product_' . $product_id_sanitized . '">';
echo '<input type="checkbox" name="additional_products[]" id="additional_product_' . $product_id_sanitized . '" value="' . $product_id . '">';
echo $product_name . ' - ' . $product_price;
echo '</label>';
echo '</div>';
}
}
echo '</div>';
}
add_action('woocommerce_before_add_to_cart_button', 'display_additional_products_checkboxes');
// Function to process additional products checkboxes
function add_additional_products_to_cart($cart_item_data, $product_id, $variation_id) {
if (isset($_POST['additional_products']) && !empty($_POST['additional_products'])) {
$additional_products = wc_clean(wp_unslash($_POST['additional_products']));
$cart_item_data['additional_products'] = $additional_products;
}
return $cart_item_data;
}
add_filter('woocommerce_add_cart_item_data', 'add_additional_products_to_cart', 10, 3);
// Function to display additional products in cart
function display_additional_products_in_cart($item_data, $cart_item) {
if (isset($cart_item['additional_products']) && !empty($cart_item['additional_products'])) {
$additional_products = $cart_item['additional_products'];
$additional_products_names = array();
foreach ($additional_products as $product_id) {
$product = wc_get_product($product_id);
if ($product) {
$additional_products_names[] = $product->get_name();
} else {
error_log("Failed to get additional product with ID: $product_id");
}
}
$item_data[] = array(
'key' => __('Additional Products', 'woocommerce'),
'value' => implode(', ', $additional_products_names),
);
}
return $item_data;
}
add_filter('woocommerce_get_item_data', 'display_additional_products_in_cart', 10, 2);
// Function to add additional products to order
function add_additional_products_to_order($item, $cart_item_key, $values, $order) {
if (isset($values['additional_products']) && !empty($values['additional_products'])) {
$additional_products = $values['additional_products'];
foreach ($additional_products as $product_id) {
$product = wc_get_product($product_id);
if ($product) {
$item->add_meta_data(__('Additional Product', 'woocommerce'), $product->get_name());
} else {
error_log("Failed to get additional product with ID: $product_id");
}
}
}
}
add_action('woocommerce_checkout_create_order_line_item', 'add_additional_products_to_order', 10, 4);
// Function to add additional products as separate line items in the cart
function add_additional_products_as_separate_items($cart_item_key, $product_id, $quantity, $variation_id, $variation, $cart_item_data) {
if (isset($cart_item_data['additional_products']) && !empty($cart_item_data['additional_products'])) {
if (!isset(WC()->session->additional_products_added)) {
WC()->session->set('additional_products_added', array());
}
$processed = WC()->session->get('additional_products_added');
$additional_products = $cart_item_data['additional_products'];
foreach ($additional_products as $additional_product_id) {
if (!in_array($additional_product_id, $processed)) {
$additional_product = wc_get_product($additional_product_id);
if ($additional_product) {
error_log("Adding additional product to cart: " . $additional_product->get_name());
WC()->cart->add_to_cart($additional_product_id);
$processed[] = $additional_product_id; // Mark this additional product as processed
} else {
error_log("Failed to add additional product with ID: $additional_product_id");
}
}
}
WC()->session->set('additional_products_added', $processed);
}
}
add_action('woocommerce_add_to_cart', 'add_additional_products_as_separate_items', 10, 6);
// Clear session data after checkout
function clear_additional_products_session() {
if (isset(WC()->session->additional_products_added)) {
WC()->session->__unset('additional_products_added');
}
}
add_action('woocommerce_checkout_order_processed', 'clear_additional_products_session');
But I still have the same issue, and gpt can't help - I have it again and again. Log shows something like
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
[25-Jun-2024 04:21:09 UTC] Adding additional product to cart: Present One
Like endless loop. Gpt tried to help with session moment, but it didn't help. Maybe some human would know better where I'm wrong?
You are making things more complicated than they should be. You don't need to use WC Sessions, you don't need a function to display the additional products, as you are adding them to cart, and you don't need a function to add the additional products as order items.
The main issue is that you are making an infinite loop when using woocommerce_add_to_cart
:
As woocommerce_add_to_cart
hook is located in WC_Cart add_to_cart() method, when adding to cart the additional products, they trigger themselves woocommerce_add_to_cart
again and again...
Below I have added to WooCommerce Admin Product settings a multi-select field that will allow you to define the additional products at the product level:
// Display a multi select field to Admin Product edit pages
add_action('woocommerce_product_options_general_product_data', 'add_admin_product_custom_field');
function add_admin_product_custom_field() {
global $post, $product_object;
$field_key = 'bundled_ids';
printf('<div class="options_group"><p class="form-field"><label for="%s">%s</label>
<select class="wc-product-search" multiple="multiple" style="%s" id="%s" name="%s[]" '.
'data-placeholder="%s" data-action="woocommerce_json_search_products_and_variations" data-exclude="%d">',
$field_key, esc_html__('Bundled products', 'woocommerce'), 'width: 50%;', $field_key,
$field_key, esc_attr__('Search for a product…', 'woocommerce'), intval($post->ID) );
$product_ids = (array) $product_object->get_meta('_'.$field_key);
foreach ( $product_ids as $product_id ) {
$product = wc_get_product( $product_id );
if ( is_object( $product ) ) {
echo '<option value="' . esc_attr( $product_id ) . '"' . selected( true, true, false ) . '>' . esc_html( wp_strip_all_tags( $product->get_formatted_name() ) ) . '</option>';
}
}
echo '</select>' . wc_help_tip( __( 'Bundled products are products that will be added to cart optionally.', 'woocommerce' ) ); // WPCS: XSS ok.
echo '</p></div>';
}
// Save custom field values from product edit page
add_action('woocommerce_admin_process_product_object', 'save_admin_product_custom_field_value');
function save_admin_product_custom_field_value( $product ) {
$field_key = 'bundled_ids';
$product->update_meta_data('_'.$field_key, isset($_POST[$field_key]) ? wc_clean( wp_unslash($_POST[$field_key]) ) : array());
}
You will get:
Then here is your revised code that display the additional products checkboxes and add to cart the selected additional products as separated cart items (commented):
// Display checkbox(es) for additional product(s)
add_action('woocommerce_before_add_to_cart_button', 'display_additional_products_checkboxes');
function display_additional_products_checkboxes() {
global $product;
if ( $bundled_product_ids = $product->get_meta('_bundled_ids') ) {
echo '<div class="additional-products">
<h3>'. __('Additional Products') .'</h3>';
foreach ($bundled_product_ids as $_product_id) {
$_product = wc_get_product($_product_id);
if ( is_object($_product) ) {
printf('<div class="form-row"><label for="bundled-products-%d">
<input type="checkbox" name="bundled_products[]" id="bundled-products-%d" value="%d">
%s: %s</label></div>',
$_product_id, $_product_id, $_product_id, $_product->get_name(), $_product->get_price_html() );
}
}
echo '</div><br>';
}
}
// Add selected additional product IDs as custom cart item data
add_filter( 'woocommerce_add_cart_item_data', 'set_bundled_products_as_custom_item_data', 10 );
function set_bundled_products_as_custom_item_data( $cart_item_data ) {
if ( ! isset($cart_item_data['parent_id'])
&& isset($_POST['bundled_products']) && ! empty($_POST['bundled_products']) ) {
$cart_item_data['bundled_products'] = wc_clean( wp_unslash($_POST['bundled_products']) );
}
return $cart_item_data;
}
// Add selected additional products as separated cart items
add_action( 'template_redirect', 'add_to_cart_bundled_products' );
function add_to_cart_bundled_products( $cart_item_data ) {
if ( isset($_POST['bundled_products']) && ! empty($_POST['bundled_products']) ) {
// Get the additional products
$bundled_products = wc_clean( wp_unslash($_POST['bundled_products']) );
// Get the parent bundle product ID
$parent_id = isset($_POST['add-to-cart']) && $_POST['add-to-cart'] > 0 ? intval($_POST['add-to-cart']) : null;
$parent_id = isset($_POST['variation_id']) && $_POST['variation_id'] > 0 ? intval($_POST['variation_id']) : $parent_id ;
// Loop through additional selected product IDs
foreach ( $bundled_products as $product_id ) {
$product = wc_get_product($product_id); // Get the product Object
$is_variation = $product->is_type('variation'); // Is it a variation
if ( is_object($product) ) {
$variation_id = $is_variation ? $product_id : 0;
$product_id = $is_variation ? $product->get_parent_id() : $product_id;
$attributes = $is_variation ? $product->get_variation_attributes() : [];
// Add additional product to cart (with the parent bundle product ID as custom cart item data)
WC()->cart->add_to_cart($product_id, 1, $variation_id, $attributes, ['parent_id' => $parent_id ]);
}
}
}
}
Code goes in functions.php file of your child theme (or in a plugin). Tested and work.
Now the additional products are also added to cart as separated items.
If you want to use an array of product IDs like in your code, replace:
if ( $bundled_product_ids = $product->get_meta('_bundled_ids') ) {
with:
$bundled_product_ids = array(220, 222, 224, 226); // Define your product IDs
if ( $bundled_product_ids ) {
For testing:
Display cart item data (and custom data) in the footer:
add_action('wp_footer', 'wp_footer_output_cart_items_data_for_test', 10);
function wp_footer_output_cart_items_data_for_test() {
echo '<pre>'. print_r( WC()->session->get('cart'), true ) . '</pre>';
}