Question

I need a custom product type to be able to use other transaction mail templates. For that purpose I am using an existing module which contains the following code:

<vendor>/<module>/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 name="customtype" label="Custom Product Type" modelInstance="<vendor>\<module>\Model\Product\Type\Custom" composite="true" indexPriority="110" sortOrder="110">
            <priceModel instance="Magento\Bundle\Model\Product\Price" />
            <indexerModel instance="Magento\Bundle\Model\ResourceModel\Indexer\Price" />
            <stockIndexerModel instance="Magento\Bundle\Model\ResourceModel\Indexer\Stock" />
            <allowedSelectionTypes>
                <type name="simple" />
                <type name="virtual" />
            </allowedSelectionTypes>
            <customAttributes>
                <attribute name="refundable" value="true"/>
            </customAttributes>
        </type>
    </config>

<vendor>/<module>/Model/Product/Type/Custom.php:

<?php

namespace <vendor>\<model>\Model\Product\Type;

class Custom extends \Magento\Bundle\Model\Product\Type
{
    const TYPE_ID = 'customtype';
}

<vendor>/<module>/Setup/UpgradeData.php:

<?php
namespace <vendor>\<module>/Setup;

use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\UpgradeDataInterface;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Eav\Setup\EavSetup;
use Magento\Catalog\Model\Product;
use <vendor>\<module>\Model\Product\Type\Custom as CustomProductType;

/**
 * Upgrade Data script
 * @codeCoverageIgnore
 */
class UpgradeData implements UpgradeDataInterface
{
    /**
     * EAV setup factory
     *
     * @var EavSetupFactory
     */
    private $eavSetupFactory;

    /**
     * Init
     *
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(EavSetupFactory $eavSetupFactory)
    {
        $this->eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     */
    public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        if (version_compare($context->getVersion(), '0.0.6') < 0) {
            /** Version under 0.0.6 -> Upgrade */
            $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]);

            $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',
                'tax_class_id',
            ];

            foreach ($fieldList as $field) {
                $applyTo = explode(
                    ',',
                    $eavSetup->getAttribute(Product::ENTITY, $field, 'apply_to')
                );
                if (!in_array(CustomProductType::TYPE_ID, $applyTo)) {
                    $applyTo[] = CustomProductType::TYPE_ID;
                    $eavSetup->updateAttribute(
                        Product::ENTITY,
                        $field,
                        'apply_to',
                        implode(',', $applyTo)
                    );
                }
            }
        }
    }
}

The Upgradescript runs successfull and I can see according changes in catalog_eav_attribute's apply_to column.

With all that done I lack the option to add options for containing products: Screenshot showing lack of

Was it helpful?

Solution

Nice work on this post, it helped me come to the solution and get this working for myself. I answered it in this post here (Custom Product Bundle Options are not saving on version 2.3)

Assuming you have gone through and done everything to create a custom product type you will need to do the following additional step.

  1. In etc/di.xml add
preference for="Magento\Bundle\Model\SaveHandler" type="vendor\package\Model\SaveHandler"
  1. Next add the model in your package for saveHandler app/code/vendor/package/Model/SaveHandler.php
<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace Vendor\Package\Model;

use Magento\Bundle\Model\Option\SaveAction;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository;
use Magento\Bundle\Api\ProductLinkManagementInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\EntityManager\Operation\ExtensionInterface;

use Magento\Bundle\Model\Product\Type as Type;
use Vendor\Package\Model\Product\Type as CustomType;

/**
 * Class SaveHandler
 */
class SaveHandler implements ExtensionInterface
{
    /**
     * @var OptionRepository
     */
    protected $optionRepository;

    /**
     * @var ProductLinkManagementInterface
     */
    protected $productLinkManagement;

    /**
     * @var MetadataPool
     */
    private $metadataPool;

    /**
     * @var SaveAction
     */
    private $optionSave;

    /**
     * @param OptionRepository $optionRepository
     * @param ProductLinkManagementInterface $productLinkManagement
     * @param SaveAction $optionSave
     * @param MetadataPool|null $metadataPool
     */
    public function __construct(
        OptionRepository $optionRepository,
        ProductLinkManagementInterface $productLinkManagement,
        SaveAction $optionSave,
        MetadataPool $metadataPool = null
    ) {
        $this->optionRepository = $optionRepository;
        $this->productLinkManagement = $productLinkManagement;
        $this->optionSave = $optionSave;
        $this->metadataPool = $metadataPool
            ?: ObjectManager::getInstance()->get(MetadataPool::class);
    }

    /**
     * Perform action on Bundle product relation/extension attribute
     *
     * @param object $entity
     * @param array $arguments
     *
     * @return ProductInterface|object
     *
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
     */
    public function execute($entity, $arguments = [])
    {
        /** @var \Magento\Bundle\Api\Data\OptionInterface[] $bundleProductOptions */
        $bundleProductOptions = $entity->getExtensionAttributes()->getBundleProductOptions() ?: [];
        //Only processing bundle products.
        if (($entity->getTypeId() !== Type::TYPE_CODE && $entity->getTypeId() !== CustomType::TYPE_CODE) || empty($bundleProductOptions)) {
            return $entity;
        }

        $existingBundleProductOptions = $this->optionRepository->getList($entity->getSku());
        $existingOptionsIds = !empty($existingBundleProductOptions)
            ? $this->getOptionIds($existingBundleProductOptions)
            : [];
        $optionIds = !empty($bundleProductOptions)
            ? $this->getOptionIds($bundleProductOptions)
            : [];

        if (!$entity->getCopyFromView()) {
            $this->processRemovedOptions($entity, $existingOptionsIds, $optionIds);
            $newOptionsIds = array_diff($optionIds, $existingOptionsIds);
            $this->saveOptions($entity, $bundleProductOptions, $newOptionsIds);
        } else {
            //save only labels and not selections + product links
            $this->saveOptions($entity, $bundleProductOptions);
            $entity->setCopyFromView(false);
        }

        return $entity;
    }

    /**
     * Remove option product links
     *
     * @param string $entitySku
     * @param \Magento\Bundle\Api\Data\OptionInterface $option
     * @return void
     */
    protected function removeOptionLinks($entitySku, $option)
    {
        $links = $option->getProductLinks();
        if (!empty($links)) {
            foreach ($links as $link) {
                $this->productLinkManagement->removeChild($entitySku, $option->getId(), $link->getSku());
            }
        }
    }

    /**
     * Perform save for all options entities.
     *
     * @param object $entity
     * @param array $options
     * @param array $newOptionsIds
     * @return void
     */
    private function saveOptions($entity, array $options, array $newOptionsIds = []): void
    {
        foreach ($options as $option) {
            if (in_array($option->getOptionId(), $newOptionsIds, true)) {
                $option->setOptionId(null);
            }

            $this->optionSave->save($entity, $option);
        }
    }

    /**
     * Get options ids from array of the options entities.
     *
     * @param array $options
     * @return array
     */
    private function getOptionIds(array $options): array
    {
        $optionIds = [];

        if (!empty($options)) {
            /** @var \Magento\Bundle\Api\Data\OptionInterface $option */
            foreach ($options as $option) {
                if ($option->getOptionId()) {
                    $optionIds[] = $option->getOptionId();
                }
            }
        }

        return $optionIds;
    }

    /**
     * Removes old options that no longer exists.
     *
     * @param ProductInterface $entity
     * @param array $existingOptionsIds
     * @param array $optionIds
     * @return void
     */
    private function processRemovedOptions(ProductInterface $entity, array $existingOptionsIds, array $optionIds): void
    {
        $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
        $parentId = $entity->getData($metadata->getLinkField());
        foreach (array_diff($existingOptionsIds, $optionIds) as $optionId) {
            $option = $this->optionRepository->get($entity->getSku(), $optionId);
            $option->setParentId($parentId);
            $this->removeOptionLinks($entity->getSku(), $option);
            $this->optionRepository->delete($option);
        }
    }
}

The main changes you need to make sure you implement are:

use Magento\Bundle\Model\Product\Type as Type;
use ForeverCompanies\DynamicBundle\Model\Product\Type as DynamicType;

AND

if (($entity->getTypeId() !== Type::TYPE_CODE && $entity->getTypeId() !== DynamicType::TYPE_CODE) || empty($bundleProductOptions)) {
            return $entity;
}

This allows the options to save when you have created a custom product type by referencing the base and custom class types. I'm not an expert in M2 yet, but something tells me the issue is actually more from the way the extended product extension is made because the instance of type should inherit from the custom type not the core module I would think.

OTHER TIPS

Turned out I had to do the following additional modifications:

<vendor>/<module>/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">
    <preference for="Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite" type="<vendor>\<module>\Ui\DataProvider\Product\Form\Modifier\Composite" />
    <preference for="Magento\Bundle\Model\OptionManagement" type="<vendor>\<module>\Model\Bundle\OptionManagement" />
    <preference for="Magento\Bundle\Model\LinkManagement" type="<vendor>\<module>\Model\Bundle\LinkManagement" />
    <preference for="Magento\Bundle\Model\OptionRepository" type="<vendor>\<module>\Model\Bundle\OptionRepository" />
</config>

<vendor>/<module>/etc/extension_attributes.xml:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Api/etc/extension_attributes.xsd">
    <extension_attributes for="Magento\Catalog\Api\Data\ProductInterface">
        <attribute code="bundle_product_options" type="Magento\Bundle\Api\Data\OptionInterface[]" />
    </extension_attributes>
    <extension_attributes for="Magento\Quote\Api\Data\ProductOptionInterface">
        <attribute code="bundle_options" type="Magento\Bundle\Api\Data\BundleOptionInterface[]" />
    </extension_attributes>
    <extension_attributes for="Magento\Catalog\Api\Data\ProductOptionInterface">
        <attribute code="bundle_options" type="Magento\Bundle\Api\Data\BundleOptionInterface[]" />
    </extension_attributes>
</config>

<vendor>/<module>/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="<vendor>_<module>" setup_version="<version>">
        <sequence>
            <module name="Magento_Backend"/>
            <module name="Magento_Catalog"/>
            <module name="Magento_Bundle"/>
        </sequence>
    </module>
</config>

<vendor>/<module>/Model/Bundle/LinkManagement.php:

<?php

namespace <vendor>\<odule>\Model\Bundle;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\InputException;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Bundle\Model\SelectionFactory;
use Magento\Bundle\Model\ResourceModel\BundleFactory;
use Magento\Bundle\Model\ResourceModel\Option\CollectionFactory;
use <vendor>\<module>\Model\Product\Type\customtype;

class LinkManagement extends \Magento\Bundle\Model\LinkManagement
{
    /**
     * LinkManagement constructor.
     * @param ProductRepositoryInterface $productRepository
     * @param \Magento\Bundle\Api\Data\LinkInterfaceFactory $linkFactory
     * @param SelectionFactory $bundleSelection
     * @param BundleFactory $bundleFactory
     * @param CollectionFactory $optionCollection
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
     */
    public function __construct(
        ProductRepositoryInterface $productRepository,
        \Magento\Bundle\Api\Data\LinkInterfaceFactory $linkFactory,
        SelectionFactory $bundleSelection,
        BundleFactory $bundleFactory,
        CollectionFactory $optionCollection,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
    )
    {
        parent::__construct($productRepository, $linkFactory, $bundleSelection, $bundleFactory, $optionCollection, $storeManager, $dataObjectHelper);
    }

    /**
     * {@inheritdoc}
     */
    public function getChildren($productSku, $optionId = null)
    {
        $product = $this->productRepository->get($productSku, true);
        if ($product->getTypeId() !== customtype::TYPE_ID) {
            throw new InputException(__('Only implemented for bundle product'));
        }

        $childrenList = [];
        foreach ($this->getOptions($product) as $option) {
            if (!$option->getSelections() || ($optionId !== null && $option->getOptionId() != $optionId)) {
                continue;
            }
            /** @var \Magento\Catalog\Model\Product $selection */
            foreach ($option->getSelections() as $selection) {
                $childrenList[] = $this->buildLink($selection, $product);
            }
        }
        return $childrenList;
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function saveChild(
        $sku,
        \Magento\Bundle\Api\Data\LinkInterface $linkedProduct
    ) {
        $product = $this->productRepository->get($sku, true);
        if ($product->getTypeId() != customtype::TYPE_ID) {
            throw new InputException(
                __('Product with specified sku: "%1" is not a bundle product', [$product->getSku()])
            );
        }

        /** @var \Magento\Catalog\Model\Product $linkProductModel */
        $linkProductModel = $this->productRepository->get($linkedProduct->getSku());
        if ($linkProductModel->isComposite()) {
            throw new InputException(__('Bundle product could not contain another composite product'));
        }

        if (!$linkedProduct->getId()) {
            throw new InputException(__('Id field of product link is required'));
        }

        /** @var \Magento\Bundle\Model\Selection $selectionModel */
        $selectionModel = $this->bundleSelection->create();
        $selectionModel->load($linkedProduct->getId());
        if (!$selectionModel->getId()) {
            throw new InputException(__('Can not find product link with id "%1"', [$linkedProduct->getId()]));
        }
        $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
        $selectionModel = $this->mapProductLinkToSelectionModel(
            $selectionModel,
            $linkedProduct,
            $linkProductModel->getId(),
            $product->getData($linkField)
        );

        try {
            $selectionModel->save();
        } catch (\Exception $e) {
            throw new CouldNotSaveException(__('Could not save child: "%1"', $e->getMessage()), $e);
        }

        return true;
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     */
    public function addChild(
        \Magento\Catalog\Api\Data\ProductInterface $product,
        $optionId,
        \Magento\Bundle\Api\Data\LinkInterface $linkedProduct
    ) {
        if ($product->getTypeId() != customtype::TYPE_ID) {
            throw new InputException(
                __('Product with specified sku: "%1" is not a bundle product', $product->getSku())
            );
        }

        $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();

        $options = $this->optionCollection->create();

        $options->setIdFilter($optionId);
        $options->setProductLinkFilter($product->getData($linkField));

        $existingOption = $options->getFirstItem();

        if (!$existingOption->getId()) {
            throw new InputException(
                __(
                    'Product with specified sku: "%1" does not contain option: "%2"',
                    [$product->getSku(), $optionId]
                )
            );
        }

        /* @var $resource \Magento\Bundle\Model\ResourceModel\Bundle */
        $resource = $this->bundleFactory->create();
        $selections = $resource->getSelectionsData($product->getData($linkField));
        /** @var \Magento\Catalog\Model\Product $linkProductModel */
        $linkProductModel = $this->productRepository->get($linkedProduct->getSku());
        if ($linkProductModel->isComposite()) {
            throw new InputException(__('Bundle product could not contain another composite product'));
        }

        if ($selections) {
            foreach ($selections as $selection) {
                if ($selection['option_id'] == $optionId &&
                    $selection['product_id'] == $linkProductModel->getEntityId() &&
                    $selection['parent_product_id'] == $product->getData($linkField)) {
                    if (!$product->getCopyFromView()) {
                        throw new CouldNotSaveException(
                            __(
                                'Child with specified sku: "%1" already assigned to product: "%2"',
                                [$linkedProduct->getSku(), $product->getSku()]
                            )
                        );
                    } else {
                        return $this->bundleSelection->create()->load($linkProductModel->getEntityId());
                    }
                }
            }
        }

        $selectionModel = $this->bundleSelection->create();
        $selectionModel = $this->mapProductLinkToSelectionModel(
            $selectionModel,
            $linkedProduct,
            $linkProductModel->getEntityId(),
            $product->getData($linkField)
        );
        $selectionModel->setOptionId($optionId);

        try {
            $selectionModel->save();
            $resource->addProductRelation($product->getData($linkField), $linkProductModel->getEntityId());
        } catch (\Exception $e) {
            throw new CouldNotSaveException(__('Could not save child: "%1"', $e->getMessage()), $e);
        }

        return $selectionModel->getId();
    }

    /**
     * {@inheritdoc}
     */
    public function removeChild($sku, $optionId, $childSku)
    {
        $product = $this->productRepository->get($sku, true);

        if ($product->getTypeId() != customtype::TYPE_ID) {
            throw new InputException(__('Product with specified sku: %1 is not a bundle product', $sku));
        }

        $excludeSelectionIds = [];
        $usedProductIds = [];
        $removeSelectionIds = [];
        foreach ($this->getOptions($product) as $option) {
            /** @var \Magento\Bundle\Model\Selection $selection */
            foreach ($option->getSelections() as $selection) {
                if ((strcasecmp($selection->getSku(), $childSku) == 0) && ($selection->getOptionId() == $optionId)) {
                    $removeSelectionIds[] = $selection->getSelectionId();
                    $usedProductIds[] = $selection->getProductId();
                    continue;
                }
                $excludeSelectionIds[] = $selection->getSelectionId();
            }
        }
        if (empty($removeSelectionIds)) {
            throw new \Magento\Framework\Exception\NoSuchEntityException(
                __('Requested bundle option product doesn\'t exist')
            );
        }
        $linkField = $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField();
        /* @var $resource \Magento\Bundle\Model\ResourceModel\Bundle */
        $resource = $this->bundleFactory->create();
        $resource->dropAllUnneededSelections($product->getData($linkField), $excludeSelectionIds);
        $resource->removeProductRelations($product->getData($linkField), array_unique($usedProductIds));

        return true;
    }

}

<vendor>/<module>/Model/Bundle/OptionManagement.php:

<?php

namespace <vendor>\<module>\Model\Bundle;

use Magento\Bundle\Api\ProductOptionRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Exception\InputException;
use <vendor>\<module>\Model\Product\Type\<customtype>;

class OptionManagement extends \Magento\Bundle\Model\OptionManagement {

    /**
     * OptionManagement constructor.
     * @param ProductOptionRepositoryInterface $optionRepository
     * @param ProductRepositoryInterface $productRepository
     */
    public function __construct(
        ProductOptionRepositoryInterface $optionRepository,
        ProductRepositoryInterface $productRepository
    )
    {
        parent::__construct($optionRepository, $productRepository);
    }

    /**
     * {@inheritdoc}
     */
    public function save(\Magento\Bundle\Api\Data\OptionInterface $option)
    {
        $product = $this->productRepository->get($option->getSku(), true);
        if ($product->getTypeId() !== <customtype>::TYPE_ID) {
            throw new InputException(__('Only implemented for bundle product'));
        }
        return $this->optionRepository->save($product, $option);
    }
}

<vendor>/<module>/Bundle/OptionRepository.php:

<?php

namespace <vendor>\<module>\Model\Bundle;

use <vendor>\<module>\Model\Product\Type\<customtype>;
use Magento\Framework\Exception\InputException;

class OptionRepository extends \Magento\Bundle\Model\OptionRepository
{

    public function __construct(
        \Magento\Catalog\Api\ProductRepositoryInterface $productRepository,
        \Magento\Bundle\Model\Product\Type $type,
        \Magento\Bundle\Api\Data\OptionInterfaceFactory $optionFactory,
        \Magento\Bundle\Model\ResourceModel\Option $optionResource,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Bundle\Api\ProductLinkManagementInterface $linkManagement,
        \Magento\Bundle\Model\Product\OptionList $productOptionList,
        \Magento\Bundle\Model\Product\LinksList $linkList,
        \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
    )
    {
        parent::__construct($productRepository, $type, $optionFactory, $optionResource, $storeManager, $linkManagement, $productOptionList, $linkList, $dataObjectHelper);
    }

    /**
     * @param string $sku
     * @return \Magento\Catalog\Api\Data\ProductInterface
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    private function getProduct($sku)
    {
        $product = $this->productRepository->get($sku, true);
        if ($product->getTypeId() !== <customtype>::TYPE_ID) {
            throw new InputException(__('Only implemented for bundle product'));
        }
        return $product;
    }

    /**
     * {@inheritdoc}
     */
    public function getList($sku)
    {
        $product = $this->getProduct($sku);
        return $this->getListByProduct($product);
    }

}

<vendor>/<module>/Ui/DataProvider/Product/Form/Modifier/BundlePanel.php:

<?php
namespace <vendor>\<module>/Ui/DataProvider/Product/Form/Modifier;


use Magento\Bundle\Api\ProductOptionRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Framework\ObjectManagerInterface;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use <vendor>\<module>\Model\Product\Type\<customtype>;
use Magento\Bundle\Model\Product\Type;

class BundlePanel extends \Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel
{

}

<vendor>/<module>/Ui/DataProvider/Product/Form/Modifier/Composite.php:

<?php
namespace <vendor>\<module>\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Framework\ObjectManagerInterface;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use <vendor>\<module>\Model\Product\Type\<customtype>;
use <vendor>\<module>\Model\Bundle\OptionRepository as ProductOptionRepositoryInterface;
use Magento\Bundle\Model\Product\Type;

class Composite extends \Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\Composite
{
    protected $_<customtype>;

    public function __construct(
        LocatorInterface $locator,
        ObjectManagerInterface $objectManager,
        ProductOptionRepositoryInterface $optionsRepository,
        ProductRepositoryInterface $productRepository,
        array $modifiers = []
    )
    {
        parent::__construct($locator, $objectManager, $optionsRepository, $productRepository, $modifiers);
        $this->optionsRepository = $optionsRepository;
    }

    /**
     * {@inheritdoc}
     */
    public function modifyMeta(array $meta)
    {
        /* Addition: Produkttyp wird alternativ auf <customtype> geprüft */
        $productType = $this->locator->getProduct()->getTypeId();
        if ($productType === Type::TYPE_CODE || $productType === <customtype>::TYPE_ID) {
            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();
        /* Addition: Produkttyp wird alternativ auf <customtype> geprüft */
        $isBundleProduct = ($product->getTypeId() === <customtype>::TYPE_ID || $product->getTypeId() === Type::TYPE_CODE);
        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;
    }
}

Edit: That solves the problem about the button only. Bundle productlinks (Adding simples) can't be saved at the moment.

Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top