16

I had created a custom module in which I am overriding the add to cart form on the product page, and I successfully did it with catalog_product_view.xml file. Now I can see a custom input field on the front view of the product page, but I need to post the value of this field to database with Qty, price, etc. and retrieve it again in order history.

I searched a while and also got success in creating new custom columns in quote_item & sales_order tables. (As per my information, add to cart entries go to quote_item and orders after checkout goes to sales_order table. If I am wrong about it, please correct me in this as well since I am still a learner.)

I tried a lot and searched for it but didn't find a relevant solution. My vendor name is Cloudways and module name is Mymodule. Below are the files for my module:

Cloudways/Mymodule/registration.php

<?php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Cloudways_Mymodule',
    __DIR__
);

Cloudways/Mymodule/etc/module.xml

<?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Cloudways_Mymodule" setup_version="1.0.1"></module>
</config>

Cloudways/Mymodule/Setup/UpgradeSchema.php

<?php

namespace Cloudways\Mymodule\Setup;

use Magento\Framework\Setup\UpgradeSchemaInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\Setup\ModuleContextInterface;

class UpgradeSchema implements UpgradeSchemaInterface
{
    public function upgrade(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        if (version_compare($context->getVersion(), '1.0.1') < 0) {

        $installer = $setup;
        $installer->startSetup();
        $connection = $installer->getConnection();
        //cart table
        $connection->addColumn(
                $installer->getTable('quote_item'),
                'remarks',
                [
                    'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    'length' => 255,
                    'comment' =>'Remarks'
                ]
            );
        //Order address table
        $connection->addColumn(
                $installer->getTable('sales_order'),
                'remarks',
                [
                    'type' => \Magento\Framework\DB\Ddl\Table::TYPE_TEXT,
                    'length' => 255,
                    'comment' =>'Remarks'

                ]
            );
        $installer->endSetup(); }
    }
}

Cloudways/Mymodule/view/frontend/layout/catalog_product_view.xml

<?xml version="1.0"?>
<page layout="1column" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceBlock name="product.info.addtocart">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Cloudways_Mymodule::catalog/product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>
        <referenceBlock name="product.info.addtocart.additional">
            <action method="setTemplate">
                <argument name="template" xsi:type="string">Cloudways_Mymodule::catalog/product/view/addtocart.phtml</argument>
            </action>
        </referenceBlock>
    </body>
</page>

Cloudways/Mymodule/view/frontend/templates/catalog/product/view/addtocart.phtml

<?php
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */

// @codingStandardsIgnoreFile

/** @var $block \Magento\Catalog\Block\Product\View */
?>
<?php $_product = $block->getProduct(); ?>
<?php $buttonTitle = __('Add to Cart'); ?>
<?php if ($_product->isSaleable()): ?>
<div class="box-tocart">
    <div class="fieldset">
        <?php if ($block->shouldRenderQuantity()): ?>
        <div class="field qty">
            <label class="label" for="qty"><span><?php /* @escapeNotVerified */ echo __('Qty') ?></span></label>
            <div class="control">
                <input type="number"
                       name="qty"
                       id="qty"
                       maxlength="12"
                       value="<?php /* @escapeNotVerified */ echo $block->getProductDefaultQty() * 1 ?>"
                       title="<?php /* @escapeNotVerified */ echo __('Qty') ?>" class="input-text qty"
                       data-validate="<?php echo $block->escapeHtml(json_encode($block->getQuantityValidators())) ?>"
                       />
            </div>
        </div>
        <!-- Custom Input Field -->
        <div>
            <input
                type="text"
                name="remarks"
                id="remarks"
                maxlength="255"
                placeholder="Remarks"
            />
        </div>
        <!-- Custom Input Field -->
        <br>
        <?php endif; ?>
        <div class="actions">
            <button type="submit"
                    title="<?php /* @escapeNotVerified */ echo $buttonTitle ?>"
                    class="action primary tocart"
                    id="product-addtocart-button">
                <span><?php /* @escapeNotVerified */ echo $buttonTitle ?></span>
            </button>
            <?php echo $block->getChildHtml('', true) ?>
        </div>
    </div>
</div>
<?php endif; ?>
<?php if ($block->isRedirectToCartEnabled()) : ?>
<script type="text/x-magento-init">
    {
        "#product_addtocart_form": {
            "Magento_Catalog/product/view/validation": {
                "radioCheckboxClosest": ".nested"
            }
        }
    }
</script>
<?php else : ?>
<script>
    require([
        'jquery',
        'mage/mage',
        'Magento_Catalog/product/view/validation',
        'Magento_Catalog/js/catalog-add-to-cart'
    ], function ($) {
        'use strict';

        $('#product_addtocart_form').mage('validation', {
            radioCheckboxClosest: '.nested',
            submitHandler: function (form) {
                var widget = $(form).catalogAddToCart({
                    bindSubmit: false
                });

                widget.catalogAddToCart('submitForm', $(form));

                return false;
            }
        });
    });
</script>
<?php endif; ?>

Here is the screenshot of front view:

enter image description here

All I need is to post the value of the custom input field and save it in the database along with the order. Thanks in advance!

EDIT: I MADE RESPECTIVE CHANGES ACCORDING TO THE ANSWER OF R.S. AND HERE IS THE PROBLEM I AM FACING: (P.S. I AM USING MAGENTO 2.0.9)

enter image description here

I checked the log file and here is what I have found:

[2016-08-26 07:29:38] main.CRITICAL: exception 'Exception' with message 'Report ID: webapi-57bfefe2d8272; Message: Warning: Invalid argument supplied for foreach() in /home/41209-54048.cloudwaysapps.com/yyzmyegjdk/public_html/app/code/Cloudways/Mymodule/Observer/SalesModelServiceQuoteSubmitBeforeObserver.php on line 67' in /home/41209-54048.cloudwaysapps.com/yyzmyegjdk/public_html/vendor/magento/framework/Webapi/ErrorProcessor.php:194
Stack trace:
#0 /home/41209-54048.cloudwaysapps.com/yyzmyegjdk/public_html/vendor/magento/framework/Webapi/ErrorProcessor.php(139): Magento\Framework\Webapi\ErrorProcessor->_critical(Object(Exception))
#1 /home/41209-54048.cloudwaysapps.com/yyzmyegjdk/public_html/vendor/magento/module-webapi/Controller/Rest.php(163): Magento\Framework\Webapi\ErrorProcessor->maskException(Object(Exception))
#2 /home/41209-54048.cloudwaysapps.com/yyzmyegjdk/public_html/var/generation/Magento/Webapi/Controller/Rest/Interceptor.php(24): Magento\Webapi\Controller\Rest->dispatch(Object(Magento\Framework\App\Request\Http))
#3 /home/41209-54048.cloudwaysapps.com/yyzmyegjdk/public_html/vendor/magento/framework/App/Http.php(115): Magento\Webapi\Controller\Rest\Interceptor->dispatch(Object(Magento\Framework\App\Request\Http))
#4 /home/41209-54048.cloudwaysapps.com/yyzmyegjdk/public_html/vendor/magento/framework/App/Bootstrap.php(258): Magento\Framework\App\Http->launch()
#5 /home/41209-54048.cloudwaysapps.com/yyzmyegjdk/public_html/index.php(39): Magento\Framework\App\Bootstrap->run(Object(Magento\Framework\App\Http))
#6 {main} [] []

Any suggestions please?

14
  • Take a look at this magento 1 example stackoverflow.com/questions/9412074/… ... you should be able to use "additional_options" to store this info using an observer github.com/magento/magento2/… ... Using additional_options will automatically show this info in admin, sales order email etc Commented Aug 18, 2016 at 20:03
  • Is there a reason you are not using a simple product with custom options to accomplish this? Commented Aug 18, 2016 at 20:04
  • @R.S I need to do it programmatically because we will further extend this custom cart with different custom options which I think can only be done with custom module. Commented Aug 19, 2016 at 6:12
  • Can you add a (github) link to your code, so I can download and try to implement additional_options Commented Aug 19, 2016 at 11:51
  • @R.S Yes sure, please give me few mins. Commented Aug 19, 2016 at 12:15

5 Answers 5

24

To accomplish this you could use Magento built-in "additional_options" functionality so that you don't have to edit email template, admin order view, customer order view (etc) to display your custom options.

Github : https://github.com/srenon/Cloudways_Mymodule

/app/code/MagePal/CustomItemAddToCart/etc/events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="checkout_cart_product_add_after">
        <observer name="magepal_Customitemaddtocart_checkout_cart_product_add_after" instance="MagePal\CustomItemAddToCart\Observer\CheckoutCartProductAddAfterObserver" />
    </event>
    <event name="sales_model_service_quote_submit_before">
        <observer name="magepal_Customitemaddtocart_sales_model_service_quote_submit_before" instance="MagePal\CustomItemAddToCart\Observer\SalesModelServiceQuoteSubmitBeforeObserver" />
    </event>
</config>

Add Option to Quote

/app/code/MagePal/CustomItemAddToCart/Observer/CheckoutCartProductAddAfterObserver.php

<?php

namespace MagePal\CustomItemAddToCart\Observer;

use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;
use Magento\Framework\App\RequestInterface;

class CheckoutCartProductAddAfterObserver implements ObserverInterface
{

    protected $_request;

    /**
     * @param RequestInterface $request
     */
    public function __construct(RequestInterface $request){
            $this->_request = $request;
    }

    /**
     * @param EventObserver $observer
     * @return void
     */
    public function execute(EventObserver $observer)
    {
        /* @var \Magento\Quote\Model\Quote\Item $item */
        $item = $observer->getQuoteItem();

        $additionalOptions = array();

        if ($additionalOption = $item->getOptionByCode('additional_options')){
            $additionalOptions = (array) unserialize($additionalOption->getValue());
        }

        $post = $this->_request->getParam('magepal');

        if(is_array($post)){
            foreach($post as $key => $value){
                if($key == '' || $value == ''){
                    continue;
                }

                $additionalOptions[] = [
                    'label' => $key,
                    'value' => $value
                ];
            }
        }

        if(count($additionalOptions) > 0){
            $item->addOption(array(
                'code' => 'additional_options',
                'value' => serialize($additionalOptions)
            ));
        }


        /* To Do */

        // Edit Cart - May need to remove option and read them
        // Pre-fill remarks on product edit pages


        /* Issues */

        // Create new cart item with identical option values will add a new line item, instead of increment the previous item qty

    }
}

Method #1 - Copying Option from quote_item to order_item using Observer See Magento 2 fieldset.xml; copy fields from quote to order

/app/code/MagePal/CustomItemAddToCart/Observer/SalesModelServiceQuoteSubmitBeforeObserver.php

<?php

namespace MagePal\CustomItemAddToCart\Observer;

use Magento\Framework\Event\Observer as EventObserver;
use Magento\Framework\Event\ObserverInterface;


class SalesModelServiceQuoteSubmitBeforeObserver implements ObserverInterface
{

    private $quoteItems = [];

    private $quote = null;
    private $order = null;

    /**
     * Add order information into GA block to render on checkout success pages
     *
     * @param EventObserver $observer
     * @return void
     */
    public function execute(EventObserver $observer)
    {

        $this->quote = $observer->getQuote();
        $this->order = $observer->getOrder();

        // can not find an equivalent event for sales_convert_quote_item_to_order_item


        /* @var  \Magento\Sales\Model\Order\Item $orderItem */
        foreach($this->order->getItems() as $orderItem){
            if(!$orderItem->getParentItemId() && $orderItem->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE){

                if($quoteItem = $this->getQuoteItemById($orderItem->getQuoteItemId())){
                    if ($additionalOptionsQuote = $quoteItem->getOptionByCode('additional_options')) {

                        //Todo
                        // - check to make sure element are not added twice
                        // - $additionalOptionsQuote - may not be an array
                        if($additionalOptionsOrder = $orderItem->getProductOptionByCode('additional_options')){
                            $additionalOptions = array_merge($additionalOptionsQuote, $additionalOptionsOrder);
                        }
                        else{
                            $additionalOptions = $additionalOptionsQuote;
                        }


                        if(count($additionalOptions) > 0){
                            $options = $orderItem->getProductOptions();
                            //As of Magento ~2.2, the unserialize() was discontinued in favor of json_encode.
                            // See https://devdocs.magento.com/guides/v2.4/ext-best-practices/tutorials/serialized-to-json-data-upgrade.html 
                            $options['additional_options'] = unserialize($additionalOptions->getValue());
                            $orderItem->setProductOptions($options);
                        }

                    }
                }
            }
        }

    }


    private function getQuoteItemById($id){
        if(empty($this->quoteItems)){
            /* @var  \Magento\Quote\Model\Quote\Item $item */
            foreach($this->quote->getItems() as $item){

                //filter out config/bundle etc product
                if(!$item->getParentItemId() && $item->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE){
                    $this->quoteItems[$item->getId()] = $item;
                }
            }
        }


        if(array_key_exists($id, $this->quoteItems)){
            return $this->quoteItems[$id];
        }

        return null;
    }
}

Method #2 - Copying Option from quote_item to order_item using Plugin

/app/code/MagePal/CustomItemAddToCart/etc/di.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Quote\Model\Quote\Item\ToOrderItem">
        <plugin name="magepal_Customitemaddtocart_Sales_Quote_Item_ToOrderItem" type="MagePal\CustomItemAddToCart\Plugin\QuoteItemToOrderItemPlugin" />
    </type>
</config>

/app/code/MagePal/CustomItemAddToCart/Plugin/QuoteItemToOrderItemPlugin.php

<?php

namespace MagePal\CustomItemAddToCart\Plugin;

class QuoteItemToOrderItemPlugin
{

    public function aroundConvert(\Magento\Quote\Model\Quote\Item\ToOrderItem $subject, callable $proceed, $quoteItem, $data)
    {

        // get order item
        $orderItem = $proceed($quoteItem, $data);
        
        
        if(!$orderItem->getParentItemId() && $orderItem->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_SIMPLE){
            if ($additionalOptionsQuote = $quoteItem->getOptionByCode('additional_options')) {
                //To do
                // - check to make sure element are not added twice
                // - $additionalOptionsQuote - may not be an array
                if($additionalOptionsOrder = $orderItem->getProductOptionByCode('additional_options')){
                    $additionalOptions = array_merge($additionalOptionsQuote, $additionalOptionsOrder);
                }
                else{
                    $additionalOptions = $additionalOptionsQuote;
                }

                //As of Magento ~2.2, the unserialize() was discontinued in favor of json_encode.
                // See https://devdocs.magento.com/guides/v2.4/ext-best-practices/tutorials/serialized-to-json-data-upgrade.html 
                if(count($additionalOptions) > 0){
                    $options = $orderItem->getProductOptions();
                    $options['additional_options'] = unserialize($additionalOptions->getValue());
                    $orderItem->setProductOptions($options);
                }
            }
        }
        
        return $orderItem;
    }
}

Shopping cart and Admin order view page

Base off of Magento1 - Quote/order product item attribute based on user input

13
  • 1
    @renon I tried above functionality for simple product and it worked out great, thanks! but I need the same for configurable product but I get following error on cart page "Selected option(s) or their combination is not currently available." I know why this issue arises, but is there any way to overcome it, I mean filter out particular options?
    – Nausif
    Commented Feb 12, 2017 at 13:15
  • Just used this and it works like a charm. Thanks! Are you aware of any functionality in Magento that really allows properties per item (not per product)? For example a custom text on a shirt, but while having multiple of the same shirts with different texts. Magento now combines those products in the cart and thus I lose those really specific settings per item.
    – Jurgen
    Commented Feb 16, 2017 at 14:45
  • @Nausif, I was facing same issue, but now it resolved. Refer this url - magento.stackexchange.com/questions/177133/… Commented Jun 6, 2017 at 11:35
  • @renon I use this and values are showing in mini-cart, cart and checkout. I have configurable product to do this. I tried both the Quote conversion methods, but values not showing in order mail and order section in admin. Commented Dec 2, 2017 at 11:03
  • 2
    If anyone getting [invalidargumentexception] unable to unserialize value. use json_encode and json_decode instead of serialize and unserialize Commented Dec 4, 2017 at 4:53
1

If you get in Product View this error: {"message":"Unable to unserialize value. Error: Syntax error"}

or

if you get the jQuery error: Uncaught Error: [object Object]

Change in the inside of SalesModelServiceQuoteSubmitBeforeObserver.php and CheckoutCartProductAddAfterObserver.php :

serialize to json_encode

and

unserialize to json_decode

Now the next problem. If you tried before to go to the checkout with serialze and you were logged in with an user account, you need to delete the last quote item.

You need to delete in the database inside the quote table the last quote with the used e-mail.

If you can't complete the checkout process, change the SalesModelServiceQuoteSubmitBeforeObserver.php file on line 50:

if(count($additionalOptions) > 0) 

to:

if(is_array($additionalOptions) && count($additionalOptions) > 0){
0

To resolve this problem: // Create new cart item with identical option values will add a new line item, instead of increment previous item qty

I have added after plugin for method representProduct in Magento\Quote\Model\Quote\Item. In plugin i check is it my needed additional param and if it is return needed result (true).

0

This github solution https://github.com/srenon/Cloudways_Mymodule is not working for version 2.3. The error is to use PHP unserialize and serialize function. Can any one update this code for 2.3 version?

1
0

for version 2.3 and above The error is to use PHP unserialize and serialize function.Every one Can update this code for 2.3 and above using below solution. Magento 2 - Passing Values From Product Page to Cart

Not the answer you're looking for? Browse other questions tagged or ask your own question.