i've extended bundle product type of magento 2 named Box product, everything works excepts, add option is not appearing in extended new type... Here are the files, Please check..
Arslan/BoxType/etc/product_types.xml
<?xml version="1.0" ?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Catalog:etc/product_types.xsd">
<type label="Box Product" modelInstance="Arslan\BoxType\Model\Product\Type\Box" name="box" composite='true' indexPriority="40" sortOrder="50">
<priceModel instance="Arslan\BoxType\Model\Product\Price"/>
<allowedSelectionTypes>
<type name="simple" />
<type name="virtual" />
</allowedSelectionTypes>
<customAttributes>
<attribute name="refundable" value="true"/>
</customAttributes>
</type>now the model Arslan/BoxType/Model/Product/Type/Box.php
<?php
namespace Arslan\BoxType\Model\Product\Type;
class Box extends \Magento\Bundle\Model\Product\Type
{
const TYPE_ID = 'box';
/**
* {@inheritdoc}
*/
public function deleteTypeSpecificData(\Magento\Catalog\Model\Product $product)
{
// method intentionally empty
}
}Now Arslan/BoxType/Setup/InstallData.php
<?php
namespace Arslan\BoxType\Setup;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
class InstallData implements InstallDataInterface
{
private $eavSetupFactory;
/**
* Constructor
*
* @param \Magento\Eav\Setup\EavSetupFactory $eavSetupFactory
*/
public function __construct(EavSetupFactory $eavSetupFactory)
{
$this->eavSetupFactory = $eavSetupFactory;
}
/**
* {@inheritdoc}
*/
public function install(
ModuleDataSetupInterface $setup,
ModuleContextInterface $context
) {
$eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);
// associate these attributes with new product type
$fieldList = [
'price',
'special_price',
'special_from_date',
'special_to_date',
'minimal_price',
'cost',
'tier_price',
'weight',
'price_type',
'sku_type',
'weight_type',
'price_view',
'shipment_type',
];
// make these attributes applicable to new product type
foreach ($fieldList as $field) {
$applyTo = explode(
',',
$eavSetup->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $field, 'apply_to')
);
if (!in_array(\Arslan\BoxType\Model\Product\Type\Box::TYPE_ID, $applyTo)) {
$applyTo[] = \Arslan\BoxType\Model\Product\Type\Box::TYPE_ID;
$eavSetup->updateAttribute(
\Magento\Catalog\Model\Product::ENTITY,
$field,
'apply_to',
implode(',', $applyTo)
);
}
}
}
}Now The Missing Portion in my extended product type is "add options" Please see the screenshot
Solved! Go to Solution.
Hi @arslantS
You need to change modifier. For example (vendor/magento/module-bundle/etc/adminhtml/di.xml):
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="box" xsi:type="array">
<item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite</item>
<item name="sortOrder" xsi:type="number">180</item>
</item>
<item name="bundle_stock_data" xsi:type="array">
<item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\StockData</item>
<item name="sortOrder" xsi:type="number">190</item>
</item>
</argument>
</arguments>
</virtualType>If you open Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite then check
/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
if ($this->locator->getProduct()->getTypeId() === Type::TYPE_CODE) {
foreach ($this->modifiers as $bundleClass) {
/** @var ModifierInterface $bundleModifier */
$bundleModifier = $this->objectManager->get($bundleClass);
if (!$bundleModifier instanceof ModifierInterface) {
throw new \InvalidArgumentException(
'Type "' . $bundleClass . '" is not an instance of ' . ModifierInterface::class
);
}
$meta = $bundleModifier->modifyMeta($meta);
}
}
return $meta;
}Here Type::TYPE_CODE is 'bundle', that's why you missing some option.
So create your own modifier and modify type code will working.
Create Arslan/BoxType/etc/adminhtml/di.xml
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="box" xsi:type="array">
<item name="class" xsi:type="string">Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite</item>
<item name="sortOrder" xsi:type="number">180</item>
</item>
<item name="bundle_stock_data" xsi:type="array">
<item name="class" xsi:type="string">Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\StockData</item>
<item name="sortOrder" xsi:type="number">190</item>
</item>
</argument>
</arguments>
</virtualType><type name="Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="bundleSku" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleSku</item>
<item name="bundlePrice" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePrice</item>
<item name="bundleWeight" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleWeight</item>
<item name="bundleQuantity" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleQuantity</item>
<item name="bundleAdvancedPricing" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleAdvancedPricing</item>
<item name="bundlePanel" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel</item>
<item name="bundleCustomOptions" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleCustomOptions</item>
</argument>
</arguments>
</type>
Create Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite.php
namespace Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\ObjectManagerInterface;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use Magento\Bundle\Api\ProductOptionRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel;
/**
* Class Bundle customizes Bundle product creation flow
*/
class Composite extends AbstractModifier
{
/**
* @var LocatorInterface
*/
protected $locator;
/**
* @var array
*/
protected $modifiers = [];
/**
* Object Manager
*
* @var ObjectManagerInterface
*/
protected $objectManager;
/**
* @var ProductOptionRepositoryInterface
*/
protected $optionsRepository;
/**
* @var ProductRepositoryInterface
*/
protected $productRepository;
/**
* @param LocatorInterface $locator
* @param ObjectManagerInterface $objectManager
* @param ProductOptionRepositoryInterface $optionsRepository
* @param ProductRepositoryInterface $productRepository
* @param array $modifiers
*/
public function __construct(
LocatorInterface $locator,
ObjectManagerInterface $objectManager,
ProductOptionRepositoryInterface $optionsRepository,
ProductRepositoryInterface $productRepository,
array $modifiers = []
) {
$this->locator = $locator;
$this->objectManager = $objectManager;
$this->optionsRepository = $optionsRepository;
$this->productRepository = $productRepository;
$this->modifiers = $modifiers;
}
/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
if ($this->locator->getProduct()->getTypeId() === 'box') {
foreach ($this->modifiers as $bundleClass) {
/** @var ModifierInterface $bundleModifier */
$bundleModifier = $this->objectManager->get($bundleClass);
if (!$bundleModifier instanceof ModifierInterface) {
throw new \InvalidArgumentException(
'Type "' . $bundleClass . '" is not an instance of ' . ModifierInterface::class
);
}
$meta = $bundleModifier->modifyMeta($meta);
}
}
return $meta;
}
/**
* {@inheritdoc}
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function modifyData(array $data)
{
/** @var \Magento\Catalog\Api\Data\ProductInterface $product */
$product = $this->locator->getProduct();
$modelId = $product->getId();
$isBundleProduct = $product->getTypeId() === 'box';
if ($isBundleProduct && $modelId) {
$data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS] = [];
/** @var \Magento\Bundle\Api\Data\OptionInterface $option */
foreach ($this->optionsRepository->getList($product->getSku()) as $option) {
$selections = [];
/** @var \Magento\Bundle\Api\Data\LinkInterface $productLink */
foreach ($option->getProductLinks() as $productLink) {
$linkedProduct = $this->productRepository->get($productLink->getSku());
$integerQty = 1;
if ($linkedProduct->getExtensionAttributes()->getStockItem()) {
if ($linkedProduct->getExtensionAttributes()->getStockItem()->getIsQtyDecimal()) {
$integerQty = 0;
}
}
$selections[] = [
'selection_id' => $productLink->getId(),
'option_id' => $productLink->getOptionId(),
'product_id' => $linkedProduct->getId(),
'name' => $linkedProduct->getName(),
'sku' => $linkedProduct->getSku(),
'is_default' => ($productLink->getIsDefault()) ? '1' : '0',
'selection_price_value' => $productLink->getPrice(),
'selection_price_type' => $productLink->getPriceType(),
'selection_qty' => $integerQty ? (int)$productLink->getQty() : $productLink->getQty(),
'selection_can_change_qty' => $productLink->getCanChangeQuantity(),
'selection_qty_is_integer' => (bool)$integerQty,
'position' => $productLink->getPosition(),
'delete' => '',
];
}
$data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS][] = [
'position' => $option->getPosition(),
'option_id' => $option->getOptionId(),
'title' => $option->getTitle(),
'default_title' => $option->getDefaultTitle(),
'type' => $option->getType(),
'required' => ($option->getRequired()) ? '1' : '0',
'bundle_selections' => $selections,
];
}
}
return $data;
}
}
Hi @arslantS
You need to change modifier. For example (vendor/magento/module-bundle/etc/adminhtml/di.xml):
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="box" xsi:type="array">
<item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite</item>
<item name="sortOrder" xsi:type="number">180</item>
</item>
<item name="bundle_stock_data" xsi:type="array">
<item name="class" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\StockData</item>
<item name="sortOrder" xsi:type="number">190</item>
</item>
</argument>
</arguments>
</virtualType>If you open Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite then check
/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
if ($this->locator->getProduct()->getTypeId() === Type::TYPE_CODE) {
foreach ($this->modifiers as $bundleClass) {
/** @var ModifierInterface $bundleModifier */
$bundleModifier = $this->objectManager->get($bundleClass);
if (!$bundleModifier instanceof ModifierInterface) {
throw new \InvalidArgumentException(
'Type "' . $bundleClass . '" is not an instance of ' . ModifierInterface::class
);
}
$meta = $bundleModifier->modifyMeta($meta);
}
}
return $meta;
}Here Type::TYPE_CODE is 'bundle', that's why you missing some option.
So create your own modifier and modify type code will working.
Create Arslan/BoxType/etc/adminhtml/di.xml
<virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="box" xsi:type="array">
<item name="class" xsi:type="string">Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite</item>
<item name="sortOrder" xsi:type="number">180</item>
</item>
<item name="bundle_stock_data" xsi:type="array">
<item name="class" xsi:type="string">Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\StockData</item>
<item name="sortOrder" xsi:type="number">190</item>
</item>
</argument>
</arguments>
</virtualType><type name="Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="bundleSku" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleSku</item>
<item name="bundlePrice" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePrice</item>
<item name="bundleWeight" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleWeight</item>
<item name="bundleQuantity" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleQuantity</item>
<item name="bundleAdvancedPricing" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleAdvancedPricing</item>
<item name="bundlePanel" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel</item>
<item name="bundleCustomOptions" xsi:type="string">Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundleCustomOptions</item>
</argument>
</arguments>
</type>
Create Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier\Composite.php
namespace Arslan\BoxType\Ui\DataProvider\Product\Form\Modifier;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\ObjectManagerInterface;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use Magento\Bundle\Api\ProductOptionRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel;
/**
* Class Bundle customizes Bundle product creation flow
*/
class Composite extends AbstractModifier
{
/**
* @var LocatorInterface
*/
protected $locator;
/**
* @var array
*/
protected $modifiers = [];
/**
* Object Manager
*
* @var ObjectManagerInterface
*/
protected $objectManager;
/**
* @var ProductOptionRepositoryInterface
*/
protected $optionsRepository;
/**
* @var ProductRepositoryInterface
*/
protected $productRepository;
/**
* @param LocatorInterface $locator
* @param ObjectManagerInterface $objectManager
* @param ProductOptionRepositoryInterface $optionsRepository
* @param ProductRepositoryInterface $productRepository
* @param array $modifiers
*/
public function __construct(
LocatorInterface $locator,
ObjectManagerInterface $objectManager,
ProductOptionRepositoryInterface $optionsRepository,
ProductRepositoryInterface $productRepository,
array $modifiers = []
) {
$this->locator = $locator;
$this->objectManager = $objectManager;
$this->optionsRepository = $optionsRepository;
$this->productRepository = $productRepository;
$this->modifiers = $modifiers;
}
/**
* {@inheritdoc}
*/
public function modifyMeta(array $meta)
{
if ($this->locator->getProduct()->getTypeId() === 'box') {
foreach ($this->modifiers as $bundleClass) {
/** @var ModifierInterface $bundleModifier */
$bundleModifier = $this->objectManager->get($bundleClass);
if (!$bundleModifier instanceof ModifierInterface) {
throw new \InvalidArgumentException(
'Type "' . $bundleClass . '" is not an instance of ' . ModifierInterface::class
);
}
$meta = $bundleModifier->modifyMeta($meta);
}
}
return $meta;
}
/**
* {@inheritdoc}
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function modifyData(array $data)
{
/** @var \Magento\Catalog\Api\Data\ProductInterface $product */
$product = $this->locator->getProduct();
$modelId = $product->getId();
$isBundleProduct = $product->getTypeId() === 'box';
if ($isBundleProduct && $modelId) {
$data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS] = [];
/** @var \Magento\Bundle\Api\Data\OptionInterface $option */
foreach ($this->optionsRepository->getList($product->getSku()) as $option) {
$selections = [];
/** @var \Magento\Bundle\Api\Data\LinkInterface $productLink */
foreach ($option->getProductLinks() as $productLink) {
$linkedProduct = $this->productRepository->get($productLink->getSku());
$integerQty = 1;
if ($linkedProduct->getExtensionAttributes()->getStockItem()) {
if ($linkedProduct->getExtensionAttributes()->getStockItem()->getIsQtyDecimal()) {
$integerQty = 0;
}
}
$selections[] = [
'selection_id' => $productLink->getId(),
'option_id' => $productLink->getOptionId(),
'product_id' => $linkedProduct->getId(),
'name' => $linkedProduct->getName(),
'sku' => $linkedProduct->getSku(),
'is_default' => ($productLink->getIsDefault()) ? '1' : '0',
'selection_price_value' => $productLink->getPrice(),
'selection_price_type' => $productLink->getPriceType(),
'selection_qty' => $integerQty ? (int)$productLink->getQty() : $productLink->getQty(),
'selection_can_change_qty' => $productLink->getCanChangeQuantity(),
'selection_qty_is_integer' => (bool)$integerQty,
'position' => $productLink->getPosition(),
'delete' => '',
];
}
$data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS][] = [
'position' => $option->getPosition(),
'option_id' => $option->getOptionId(),
'title' => $option->getTitle(),
'default_title' => $option->getDefaultTitle(),
'type' => $option->getType(),
'required' => ($option->getRequired()) ? '1' : '0',
'bundle_selections' => $selections,
];
}
}
return $data;
}
}
Hi, the add option button appears, but The product didn't save.
"the attribute "price" must be set" i solved by updating di.xml
Backend working fine..
Now frontend display of new product type is not working same as bundle product type..
which files i need to update for frontend display of New product type..
thanks, we need to override etc/di.xml too... & it works perfectly..
The product didn't save."the attribute "price" must be set"
How do you fix this issue ? can you provide me code to fix this. thanks ![]()
Hi there @arslantS and @Sohel great post, this really help me a lot!
But I'm stuck in this issue that you mentioned:
The "Price" attribute value is empty. Set the attribute and try again.
Could you help me and share the changes you've made in etc/di.xml
I've tryed to make some changes in etc/di.xml, but nothing works to allowed me to save my custom product type extending bundle type..
One more doubt, please @Sohel could you guide me how can I create a custom frontend template for this custom product type.. we need that for this custom product type we have a different layout page, totally different as default bundle product page, but with all funcionalities as default bundle.
Just one more doubt, can I create a custom product extending bundle product type and sell this products by recurring paymments, like weekly, montly or annualy?