Magento: Adding simple products from a bundle to separate lines in the cart

You will need an observer:

    <checkout_cart_product_add_after>
        <observers>
            <reporting>
                <type>singleton</type>
                <class>Yourcompany_yourmodelname_Model_Observer</class>
                <method>onCartProductAdd</method>
            </reporting>
        </observers>
    </checkout_cart_product_add_after>

Then do the observer:

class ourcompany_yourmodelname_Model_Observer extends Mage_Core_Model_Abstract
{
    /**
     * Binds to the checkout_cart_product_add_after Event and passes control to the helper function to process the quote
     *
     * @param Varien_Event_Observer $observer
     * @return void
     */
    public function onCartProductAdd($observer){
        $product = $observer->getProduct();
        $isProductBundle = ($product->getTypeId() == 'bundle');
        $items_to_add = array();
        $oTypeInstance = $oProduct->getTypeInstance(true);
        $aSelections = $oTypeInstance->getSelectionsCollection($aOptionIds, $product );
        $aOptions = $oTypeInstance->getOptionsByIds($aOptionIds, $product);
        $bundleOptions = $aOptions->appendSelections($aSelections, true);
        foreach ($bundleOptions as $bundleOption) {
            if ($bundleOption->getSelections()) {
                $bundleSelections = $bundleOption->getSelections();    
                foreach ($bundleSelections as $bundleSelection) {
                       $items_to_add[] = $bundleSelection.getID();

               }

            }
       }
       insertExtractedProducts($items_to_add);
    }

     /**
     * Add extracted products into quote
     *
     * @param array $items_to_add
      */
    public function insertExtractedProducts($items_to_add){
        /**@var $cart Mage_Checkout_Model_Cart**/
        $cart = Mage::helper('checkout/cart')->getCart();
        $ids_to_add = array();
        foreach($items_to_add as $item_to_be_added){
            $ids_to_add[] = $item_to_be_added->getProductId();
        }
        $cart->addProductsByIDs($ids_to_add);
        $cart->save();
        Mage::getSingleton('checkout/session')->setCartWasUpdated(true);
        }
   }

Just a simple sample, but it might help.

Bundled products can be complicated to understand, when working with it via code: Here is a sample image: enter image description here

Each Bundled product, have one to many options, which in the end will be links to products to be added to the bundle in the Shopping Cart.

Each Option consists of one to many Selections, which will be the linked products that will end up in the Shopping cart, under this bundled product. One Selection, can typically be set as the default, and will already be selected on the product page. More information can be found at this link on how to create and configure bundled products, because in this document, we will only discuss the programming side of it. The bundled product’s display page, will look like this: enter image description here

It could look like this in the shopping cart, once you clicked on “Add to Cart”: Bundle Sample

Sample Shampoo
1 x Moisturiser-125ml $29.95
Default Conditioner
1 x Moisturiser-60g $99.95

When interrogating this product through code, you load it like any normal product:

$oProduct->load($vProductId);

Once it is loaded, you need to get a product type instance, so that you can load the options for this product type.

$oTypeInstance = $oProduct->getTypeInstance(true);

Now we can get the list of option ID’s for this product in this manner:

$oTypeInstance = $oProduct->getTypeInstance(true);

To interrogate the Selections, we will add the list of options to a collection, then get the Options collection, as well as their respective Selections:

$aSelections = $oTypeInstance->getSelectionsCollection($aOptionIds, $oProduct );
$aOptions = $oTypeInstance->getOptionsByIds($aOptionIds, $oProduct);
$bundleOptions = $aOptions->appendSelections($aSelections, true);

Now we have a Collection of Options, and each Option will have a collection of Selections. We can now loop through the options, and look at their Selctions.

foreach ($bundleOptions as $bundleOption) {
            if ($bundleOption->getSelections()) {
                $bundleSelections = $bundleOption->getSelections();

                foreach ($bundleSelections as $bundleSelection) {
                     // get some data here
                     $vName = $bundleOption->getTitle();
               }

            }
}

To get a list of the Required Selection Products for the Bundled product, you can use the following code:

$requiredChildren = $this->getChildrenIds($product->getId(),$required=true);

You can then loop through the array of ID’s and load the products by their ID’s to get more information regarding those products.

Bundled products in the shopping cart

In order to loop through the selected options of a Bundled product in the shopping card, you can use something like this:

/**
 * @var Mage_Bundle_Model_Product_Type
 */
$typeInstance = $this->getProduct()->getTypeInstance(true);

// get bundle options
$optionsQuoteItemOption =  $this->getItem()->getOptionByCode('bundle_option_ids');
$bundleOptionsIds = unserialize($optionsQuoteItemOption->getValue());
if ($bundleOptionsIds) {
    /**
    * @var Mage_Bundle_Model_Mysql4_Option_Collection
    */
    $optionsCollection = $typeInstance->getOptionsByIds($bundleOptionsIds, $this->getProduct());

    // get and add bundle selections collection
    $selectionsQuoteItemOption = $this->getItem()->getOptionByCode('bundle_selection_ids');

    $selectionsCollection = $typeInstance->getSelectionsByIds(
        unserialize($selectionsQuoteItemOption->getValue()),
        $this->getProduct()
    );

    $bundleOptions = $optionsCollection->appendSelections($selectionsCollection, true);
    foreach ($bundleOptions as $bundleOption) {
        if ($bundleOption->getSelections()) {
            $label = $bundleOption->getTitle()

            $bundleSelections = $bundleOption->getSelections();

            foreach ($bundleSelections as $bundleSelection) {                        
                    $sName = $bundleSelection->getName();
            }
            // some more code here to do stuff
        }
    }
}

This code gets the Options from the Quote Item Bundled Product, and then gets the Options for that product in a collection, and then finds the “Selected” Option Selection.

hth, Shaun


Here's how you can do this in Magento 2.3 via an "around" plugin (interceptor) for the Magento\Checkout\Model\Cart->addProduct() method.

The addProduct() method is called when the customer adds a product to the cart. By adding code to this method via a plugin, you can alter the way the bundle product is added to the cart.

  1. Define the plugin in Vendor/Module/etc/frontend/di.xml:
<?xml version="1.0"?>
<!--
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="\Magento\Checkout\Model\Cart">
        <plugin name="add-bundle-products-separate" type="Vendor\Module\Plugin\Checkout\Model\CartPlugin" sortOrder="1"/>
    </type>
</config>

This is what tells Magento a plugin exists for this particular method.

Reference: https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html

  1. Create the plugin's class. In Vendor/Module/Plugin/Checkout/Model/CartPlugin.php:
<?php
namespace Vendor\Module\Plugin\Checkout\Model;

use \Magento\Catalog\Model\Product;
use \Magento\Framework\DataObject;
use \Magento\Checkout\Model\Cart;

/**
 * Class CartPlugin
 *
 * @package Ppwd\CrossSell\Plugin\Checkout\Model
 *
 */
class CartPlugin {

    /**
     * @param Cart $subject
     * @param \Closure $proceed
     * @param Product $productInfo
     * @param DataObject|int|array $requestInfo
     * @return Cart $subject
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    public function aroundAddProduct(
        $subject,
        $proceed,
        $productInfo,
        $requestInfo = null
    ) {
        // Detect if we are adding a bundle product to cart
        if (!is_numeric($productInfo) && $productInfo->getTypeId() == 'bundle') {

            $buyRequest = new DataObject($requestInfo);

            // List of products selected as part of the bundle
            $cartCandidates = $productInfo->getTypeInstance()->prepareForCartAdvanced($buyRequest, $productInfo);
            $productIds = [];

            // Add each item in bundle as if it were separately added to cart
            /** @var Product $cartCandidate */
            foreach ($cartCandidates as $cartCandidate) {
                if ($cartCandidate->getTypeId() != 'bundle') {
                    for ($i = 0; $i < $cartCandidate->getCartQty(); $i++) {
                        $productIds[] = $cartCandidate->getId();
                    }
                }
            }
            $subject->addProductsByIds($productIds);
            return $subject;
        }

        // Return original result from addProduct() as if plugin didn't exist
        $result = $proceed($productInfo, $requestInfo);
        return $result;
    }
}

When done, if you add the bundle to the cart the line items will appear separately instead of grouped together like a bundle product normally is.