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

3

6

My client is requesting that each simple product within a bundled product they are selling (Clothing Top and Bottom) be added as a separate line item in the cart whenever a user adds it. Can anyone direct me in how to accomplish this? I am fairly good with MVC and the Zend Framework, but I need a bit of help finding the exact files that control adding bundled products to the cart, or an alternate method for getting these items added separately. Please assume that the only possible product type for this clothing is the Bundled product type.

Chronoscope answered 28/3, 2012 at 20:49 Comment(1)
Updated my answer for you to be more clearZipah
Z
8

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

Zipah answered 28/3, 2012 at 21:40 Comment(4)
I've implemented the above example you've given me, but I seem to be getting every option from the bundled product added to the cart, as opposed to just the products that the user selected. Any advice? Also, do you have any information on just duplicating the bundled product and adding one of the options selected by the user to each bundled product? For example: user selects a small top and small bottom; the cart would show a bundled product with a small top, and then another bundled product with a small bottom.Chronoscope
$this in the observer is not in scope. Whenever I get the product in the observer, it is retrieving the general product instead of the configured bundle. When I getSelections(), the function returns all of the possible selections for that option instead of the selections that were chosen by the user.Chronoscope
You need to get the cart items from the cart, and then go through each item, and detect if it is a bundle, and then get the options of that productZipah
A lot of these variables are not properly defined.. for example $aOptionIds, or $vProductId. While it may be easy for most developers to see this, those who do not notice or know Magento that well will wonder what these values are supposed to be. Other than that this is a good solution, just incomplete.Vinic
E
3

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.

Ericson answered 25/1, 2019 at 0:16 Comment(1)
While this might answer the authors question, it lacks some explaining words and/or links to documentation or an example. You may also find how to write a good answer very helpful. Please edit your answerToadflax
D
1

I did this to solve the customer request to add bundle content as separate items in the cart. Just replace

$cart->addProduct($product, $params) 

with

if ($product->getTypeId() == 'bundle') {
    $request = new Varien_Object($params);
    $cartCandidates = $product->getTypeInstance(true)->prepareForCartAdvanced($request, $product, Mage_Catalog_Model_Product_Type_Abstract::PROCESS_MODE_FULL);
    $idstoadd = array();
    foreach ($cartCandidates as $cartCandidate) {
        if ($cartCandidate->getTypeId() == 'simple') {
            for ($i = 0; $i < $cartCandidate->getCartQty(); $i++) {
                $idstoadd[] = $cartCandidate->getId();
            }
        }
    }
    $cart->addProductsByIds($idstoadd);
} else {
    $cart->addProduct($product, $params);
}

in the file cartController.

Dreadful answered 12/2, 2015 at 18:29 Comment(4)
just a litle fix for handle user qty requeted for a bundle for ($i = 0; $i < $cartCandidate->getCartQty() * $params['qty']; $i++) {Dreadful
Is there a way to then add the bundled product itself but without the child products - as they are now already in the basket?Westbound
I'm trying to figure out your scenario but based only on your question it is possible add a Bundle based on the current child on cart. Be aware that you need to define rules to handle that otherwise you will get a Bundle per each child on cartDreadful
I started a new question to be a bit clearer here: magento.stackexchange.com/questions/75297/…Westbound

© 2022 - 2024 — McMap. All rights reserved.