How to “add to cart” a product with custom input field and save it to Database?
-
15-04-2021 - |
Вопрос
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:
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)
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?
Решение
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;
}
}
Base off of Magento1 - Quote/order product item attribute based on user input
Другие советы
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).
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?