0

I have created a module to upload custom bundle product images but how I can save them in catalog_product_bundle_selection table?

enter image description here

Tu Van
  • 6,868
  • 2
  • 11
  • 22
Amit Kumar
  • 73
  • 1
  • 6

2 Answers2

2

In this post, I'll give instructions on how to create and save a custom image in the bundle product selection step-by-step.

For the purposes of this tutorial Magetu is the name of the vendor associated with a module named BundleSelectionImage.

  1. Create registration.php:
    File path: app/code/Magetu/BundleSelectionImage/registration.php
<?php

use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magetu_BundleSelectionImage', DIR);

  1. Create module.xml:
    File path: app/code/Magetu/BundleSelectionImage/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="Magetu_BundleSelectionImage">
        <sequence>
            <module name="Magento_Bundle"/>
            <module name="Magento_Catalog"/>
        </sequence>
    </module>
</config>

  1. Create db_schema.xml to add a new column for bundle selection image in catalog_product_bundle_selection table.

File path: app/code/Magetu/BundleSelectionImage/etc/db_schema.xml

<?xml version="1.0"?>
<schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
    <table name="catalog_product_bundle_selection">
        <column xsi:type="varchar" name="selection_image" nullable="true" length="255" comment="Selection Image"/>
    </table>
</schema>
  1. Create the di.xml file to:
  • define a modifier to modify bundle selection structure to add the selection image field
  • config the Upload class
  • override Magento\Bundle\Model\LinkManagement class to save the bundle selection image value

File path: app/code/Magetu/BundleSelectionImage/etc/adminhtml/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">
    <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="bundle_selection_image" xsi:type="array">
                    <item name="class" xsi:type="string">Magetu\BundleSelectionImage\Ui\DataProvider\Product\Form\Modifier\SelectionImage</item>
                    <item name="sortOrder" xsi:type="number">181</item><!-- run after core bundle modifier -->
                </item>
            </argument>
        </arguments>
    </virtualType>
&lt;virtualType name=&quot;Magetu\BundleSelectionImage\ImageUpload&quot; type=&quot;Magento\Catalog\Model\ImageUploader&quot;&gt;
    &lt;arguments&gt;
        &lt;argument name=&quot;baseTmpPath&quot; xsi:type=&quot;string&quot;&gt;catalog/tmp/product/selection_images&lt;/argument&gt;
        &lt;argument name=&quot;basePath&quot; xsi:type=&quot;string&quot;&gt;catalog/product/selection_images&lt;/argument&gt;
        &lt;argument name=&quot;allowedExtensions&quot; xsi:type=&quot;array&quot;&gt;
            &lt;item name=&quot;jpg&quot; xsi:type=&quot;string&quot;&gt;jpg&lt;/item&gt;
            &lt;item name=&quot;jpeg&quot; xsi:type=&quot;string&quot;&gt;jpeg&lt;/item&gt;
            &lt;item name=&quot;gif&quot; xsi:type=&quot;string&quot;&gt;gif&lt;/item&gt;
            &lt;item name=&quot;png&quot; xsi:type=&quot;string&quot;&gt;png&lt;/item&gt;
            &lt;item name=&quot;svg&quot; xsi:type=&quot;string&quot;&gt;svg&lt;/item&gt;
            &lt;item name=&quot;svgz&quot; xsi:type=&quot;string&quot;&gt;svgz&lt;/item&gt;
            &lt;item name=&quot;webp&quot; xsi:type=&quot;string&quot;&gt;webp&lt;/item&gt;
            &lt;item name=&quot;avif&quot; xsi:type=&quot;string&quot;&gt;avif&lt;/item&gt;
            &lt;item name=&quot;avifs&quot; xsi:type=&quot;string&quot;&gt;avifs&lt;/item&gt;
        &lt;/argument&gt;
        &lt;argument name=&quot;allowedMimeTypes&quot; xsi:type=&quot;array&quot;&gt;
            &lt;item name=&quot;jpg&quot; xsi:type=&quot;string&quot;&gt;image/jpg&lt;/item&gt;
            &lt;item name=&quot;jpeg&quot; xsi:type=&quot;string&quot;&gt;image/jpeg&lt;/item&gt;
            &lt;item name=&quot;gif&quot; xsi:type=&quot;string&quot;&gt;image/gif&lt;/item&gt;
            &lt;item name=&quot;png&quot; xsi:type=&quot;string&quot;&gt;image/png&lt;/item&gt;
            &lt;item name=&quot;svg&quot; xsi:type=&quot;string&quot;&gt;image/svg+xml&lt;/item&gt;
            &lt;item name=&quot;svgz&quot; xsi:type=&quot;string&quot;&gt;image/svg+xml&lt;/item&gt;
            &lt;item name=&quot;webp&quot; xsi:type=&quot;string&quot;&gt;image/webp&lt;/item&gt;
            &lt;item name=&quot;avif&quot; xsi:type=&quot;string&quot;&gt;image/avif&lt;/item&gt;
            &lt;item name=&quot;avifs&quot; xsi:type=&quot;string&quot;&gt;image/avif-sequence&lt;/item&gt;
        &lt;/argument&gt;
    &lt;/arguments&gt;
&lt;/virtualType&gt;

&lt;type name=&quot;Magetu\BundleSelectionImage\Controller\Adminhtml\Product\Image\Upload&quot;&gt;
    &lt;arguments&gt;
        &lt;argument name=&quot;imageUploader&quot; xsi:type=&quot;object&quot;&gt;Magetu\BundleSelectionImage\ImageUpload&lt;/argument&gt;
    &lt;/arguments&gt;
&lt;/type&gt;

&lt;preference for=&quot;Magento\Bundle\Model\LinkManagement&quot; type=&quot;Magetu\BundleSelectionImage\Model\LinkManagement&quot;/&gt;

&lt;type name=&quot;Magetu\BundleSelectionImage\Model\LinkManagement&quot;&gt;
    &lt;arguments&gt;
        &lt;argument name=&quot;imageUploader&quot; xsi:type=&quot;object&quot;&gt;Magetu\BundleSelectionImage\ImageUpload&lt;/argument&gt;
    &lt;/arguments&gt;
&lt;/type&gt;

</config>

  1. Create routes.xml to use the upload controller.

File path: app/code/Magetu/BundleSelectionImage/etc/adminhtml/routes.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="bundleselectionimage" frontName="bundleselectionimage">
            <module name="Magetu_BundleSelectionImage" before="Magento_Backend"/>
        </route>
    </router>
</config>

  1. Create a modifier class which is defined in di.xml in step 4 in order to modify bundle selection structure to add the selection image field:

File path: app/code/Magetu/BundleSelectionImage/Ui/DataProvider/Product/Form/Modifier/SelectionImage.php

<?php
namespace Magetu\BundleSelectionImage\Ui\DataProvider\Product\Form\Modifier;

use Magento\Bundle\Model\Product\Type; use Magento\Bundle\Model\ResourceModel\Selection as SelectionResource; use Magento\Bundle\Model\SelectionFactory; use Magento\Bundle\Ui\DataProvider\Product\Form\Modifier\BundlePanel; use Magento\Catalog\Model\Category\FileInfo; use Magento\Catalog\Model\Locator\LocatorInterface; use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier; use Magento\Framework\UrlInterface;

class SelectionImage extends AbstractModifier { public const FIELD_IMAGE = 'selection_image';

/**
 * @var LocatorInterface
 */
private $locator;

/**
 * @var UrlInterface
 */
private $urlBuilder;

/**
 * @var FileInfo
 */
private $fileInfo;

/**
 * @var SelectionFactory
 */
private $selectionFactory;

/**
 * @var SelectionResource
 */
private $selectionResource;

public function __construct(
    LocatorInterface $locator,
    UrlInterface $urlBuilder,
    FileInfo $fileInfo,
    SelectionFactory $selectionFactory,
    SelectionResource $selectionResource
) {
    $this-&gt;locator = $locator;
    $this-&gt;urlBuilder = $urlBuilder;
    $this-&gt;fileInfo = $fileInfo;
    $this-&gt;selectionFactory = $selectionFactory;
    $this-&gt;selectionResource = $selectionResource;
}

/**
 * {@inheritdoc}
 *
 * Converts selection image data to acceptable for rendering format
 * Display selection image
 */
public function modifyData(array $data)
{
    $product = $this-&gt;locator-&gt;getProduct();
    $modelId = $product-&gt;getId();
    $isBundleProduct = $product-&gt;getTypeId() === Type::TYPE_CODE;
    if ($isBundleProduct &amp;&amp; $modelId) {
        $selectionModel = $this-&gt;selectionFactory-&gt;create();
        foreach ($data[$modelId][BundlePanel::CODE_BUNDLE_OPTIONS][BundlePanel::CODE_BUNDLE_OPTIONS] as &amp;$option) {
            foreach ($option['bundle_selections'] as &amp;$selection) {
                $this-&gt;selectionResource-&gt;load($selectionModel, $selection['selection_id']);
                $selectionImage = $selectionModel-&gt;getData('selection_image');
                if ($selectionImage !== null &amp;&amp; $this-&gt;fileInfo-&gt;isExist($selectionImage)) {
                    $stat = $this-&gt;fileInfo-&gt;getStat($selectionImage);
                    $mime = $this-&gt;fileInfo-&gt;getMimeType($selectionImage);

                    // phpcs:ignore Magento2.Functions.DiscouragedFunction
                    $imageUrl = $product-&gt;getStore()-&gt;getBaseUrl() . $selectionImage;
                    $imageRendering = [];
                    $imageRendering[0]['name'] = basename($selectionImage);
                    $imageRendering[0]['url'] = $imageUrl;
                    $imageRendering[0]['size'] = $stat['size'];
                    $imageRendering[0]['type'] = $mime;

                    $selectionModel-&gt;setData('selection_image', $imageRendering);
                }
                $selection['selection_image'] = $selectionModel-&gt;getData('selection_image');
            }
        }
    }

    return $data;
}

/**
 * Add selection image field
 *
 * @param array $meta
 * @return array
 */
public function modifyMeta(array $meta) {
    if ($this-&gt;locator-&gt;getProduct()-&gt;getTypeId() === Type::TYPE_CODE) {
        $groupCode = BundlePanel::CODE_BUNDLE_DATA;
        $meta[$groupCode]['children']['bundle_options']['children']['record']['children']
        ['product_bundle_container']['children']['bundle_selections']
        ['children']['record']['children'][static::FIELD_IMAGE] = $this-&gt;getSelectionImageFieldConfig();
    }

    return $meta;
}

/**
 * Get selection image field config
 *
 * @return array
 */
private function getSelectionImageFieldConfig()
{
    return [
        'arguments' =&gt; [
            'data' =&gt; [
                'config' =&gt; [
                    'componentType' =&gt; 'imageUploader',
                    'formElement' =&gt; 'imageUploader',
                    'template' =&gt; 'Magetu_BundleSelectionImage/form/element/uploader/image',
                    'fileInputName' =&gt; static::FIELD_IMAGE,
                    'uploaderConfig' =&gt; [
                        'url' =&gt; $this-&gt;urlBuilder-&gt;getUrl(
                            'bundleselectionimage/product_image/upload'
                        ),
                    ],
                    'dataScope' =&gt; static::FIELD_IMAGE,
                    'sortOrder' =&gt; 129,// Before the last field (action_delete)
                ],
            ],
        ],
    ];
}

}

  1. Create image uploader html file to upload the selection image file:

File path: app/code/Magetu/BundleSelectionImage/view/adminhtml/web/template/form/element/uploader/image.html

<!--
/**
 * Custom template for upload image file in bundle selection,
 * based on Magento_Ui::form/element/uploader/image.html
 *
 * NOTE: the variable name of the upload file input name should not be declared as 'inputName'
 * to allow the upload controller get the file
 */
-->
<div class="admin__field" visible="visible" css="$data.additionalClasses">
    <div class="admin__field-control" css="'_with-tooltip': $data.tooltip">
        <div class="file-uploader image-uploader" data-role="drop-zone" css="_loading: isLoading">
            <div class="file-uploader-area">
                <input type="file" afterRender="onElementRender" attr="id: uid, name: fileInputName, multiple: isMultipleFiles" disable="disabled" />
                <label class="file-uploader-button action-default" attr="for: uid, disabled: disabled" disable="disabled" translate="'Upload'"></label>
                <render args="fallbackResetTpl" if="$data.showFallbackReset && $data.isDifferedFromDefault"></render>
                <p class="image-upload-requirements">
                    <span if="$data.maxFileSize">
                        <span translate="'Maximum file size'"></span>: <text args="formatSize($data.maxFileSize)"></text>.
                    </span>
                    <span if="$data.allowedExtensions">
                        <span translate="'Allowed file types'"></span>: <text args="getAllowedFileExtensionsInCommaDelimitedFormat()"></text>.
                    </span>
                </p>
            </div>
        &lt;render args=&quot;tooltipTpl&quot; if=&quot;$data.tooltip&quot;&gt;&lt;/render&gt;

        &lt;div class=&quot;admin__field-note&quot; if=&quot;$data.notice&quot; attr=&quot;id: noticeId&quot;&gt;
            &lt;span html=&quot;notice&quot;&gt;&lt;/span&gt;
        &lt;/div&gt;

        &lt;label class=&quot;admin__field-error&quot; if=&quot;error&quot; attr=&quot;for: uid&quot; text=&quot;error&quot;&gt;&lt;/label&gt;

        &lt;each args=&quot;data: value, as: '$file'&quot; render=&quot;$parent.getPreviewTmpl($file)&quot;&gt;&lt;/each&gt;

        &lt;div if=&quot;!hasData()&quot; class=&quot;image image-placeholder&quot; click=&quot;triggerImageUpload&quot;&gt;
            &lt;div class=&quot;file-uploader-summary product-image-wrapper&quot;&gt;
                &lt;div class=&quot;file-uploader-spinner image-uploader-spinner&quot;&gt;&lt;/div&gt;
                &lt;p class=&quot;image-placeholder-text&quot; translate=&quot;'Browse to find or drag image here'&quot;&gt;&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;render args=&quot;$data.service.template&quot; if=&quot;$data.hasService()&quot;&gt;&lt;/render&gt;
&lt;/div&gt;

</div>

  1. Create the uploader controller to handle upload the selection image file:

File path: app/code/Magetu/BundleSelectionImage/Controller/Adminhtml/Product/Image/Upload.php

<?php
namespace Magetu\BundleSelectionImage\Controller\Adminhtml\Product\Image;

use Magento\Backend\App\Action as BackendAction; use Magento\Backend\App\Action\Context; use Magento\Catalog\Model\ImageUploader; use Magento\Framework\App\Action\HttpPostActionInterface; use Magento\Framework\Controller\ResultFactory; use Magento\Framework\Controller\ResultInterface;

/**

  • The bundle selection image upload controller

/ class Upload extends BackendAction implements HttpPostActionInterface { /* * Authorization level of a basic admin session * * @see _isAllowed() */ public const ADMIN_RESOURCE = 'Magento_Catalog::products';

/**
 * @var ImageUploader
 */
private $imageUploader;

public function __construct(
    Context $context,
    ImageUploader $imageUploader
) {
    parent::__construct($context);
    $this-&gt;imageUploader = $imageUploader;
}

/**
 * Upload selection image file controller action
 */
public function execute(): ResultInterface
{
    $imageId = $this-&gt;_request-&gt;getParam('param_name', 'selection_image');
    try {
        $result = $this-&gt;imageUploader-&gt;saveFileToTmpDir($imageId);
    } catch (\Exception $e) {
        $result = ['error' =&gt; $e-&gt;getMessage(), 'errorcode' =&gt; $e-&gt;getCode()];
    }
    return $this-&gt;resultFactory-&gt;create(ResultFactory::TYPE_JSON)-&gt;setData($result);
}

}

  1. Create LinkManagement model class to override Magento\Bundle\Model\LinkManagement class to save the bundle selection image value in the database:
    File path: app/code/Magetu/BundleSelectionImage/Model/LinkManagement.php

Due to Magento 2.4.3 replacing the code of method to map product link to bundle selection model, the content for LinkManagement model class should be different:

  • For Magento version >= 2.4.3
<?php
namespace Magetu\BundleSelectionImage\Model;

use Magento\Bundle\Api\Data\LinkInterface; use Magento\Bundle\Api\Data\LinkInterfaceFactory; use Magento\Bundle\Model\ResourceModel\Bundle; use Magento\Bundle\Model\ResourceModel\BundleFactory; use Magento\Bundle\Model\ResourceModel\Option\CollectionFactory; use Magento\Bundle\Model\Selection; use Magento\Bundle\Model\SelectionFactory; use Magento\Catalog\Api\Data\ProductInterface; use Magento\Catalog\Api\ProductRepositoryInterface; use Magento\Catalog\Model\ImageUploader; use Magento\Catalog\Model\Product; use Magento\Framework\Api\DataObjectHelper; use Magento\Framework\EntityManager\MetadataPool; use Magento\Framework\Exception\CouldNotSaveException; use Magento\Framework\Exception\InputException; use Magento\Framework\Exception\NoSuchEntityException; use Magento\Framework\Message\Manager as MessageManager; use Magento\Store\Model\StoreManagerInterface;

class LinkManagement extends \Magento\Bundle\Model\LinkManagement { /** * @var StoreManagerInterface */ private $storeManager;

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

/**
 * @var ImageUploader
 */
private $imageUploader;

/**
 * @var MessageManager
 */
private $messageManager;

public function __construct(
    ProductRepositoryInterface $productRepository,
    LinkInterfaceFactory $linkFactory,
    SelectionFactory $bundleSelection,
    BundleFactory $bundleFactory,
    CollectionFactory $optionCollection,
    StoreManagerInterface $storeManager,
    DataObjectHelper $dataObjectHelper,
    MetadataPool $metadataPool,
    ImageUploader $imageUploader,
    MessageManager $messageManager
) {
    parent::__construct(
        $productRepository,
        $linkFactory,
        $bundleSelection,
        $bundleFactory,
        $optionCollection,
        $storeManager,
        $dataObjectHelper,
        $metadataPool
    );
    $this-&gt;storeManager = $storeManager;
    $this-&gt;metadataPool = $metadataPool;
    $this-&gt;imageUploader = $imageUploader;
    $this-&gt;messageManager = $messageManager;
}

/**
 * This method has same content as in the parent class
 * Declare it here to allow override mapProductLinkToBundleSelectionModel which is private
 *
 * @inheritDoc
 */
public function saveChild(
    $sku,
    LinkInterface $linkedProduct
) {
    $product = $this-&gt;productRepository-&gt;get($sku, true);
    if ($product-&gt;getTypeId() != Product\Type::TYPE_BUNDLE) {
        throw new InputException(
            __('The product with the &quot;%1&quot; SKU isn\'t a bundle product.', [$product-&gt;getSku()])
        );
    }

    /** @var Product $linkProductModel */
    $linkProductModel = $this-&gt;productRepository-&gt;get($linkedProduct-&gt;getSku());
    if ($linkProductModel-&gt;isComposite()) {
        throw new InputException(__('The bundle product can\'t contain another composite product.'));
    }

    if (!$linkedProduct-&gt;getId()) {
        throw new InputException(__('The product link needs an ID field entered. Enter and try again.'));
    }

    /** @var Selection $selectionModel */
    $selectionModel = $this-&gt;bundleSelection-&gt;create();
    $selectionModel-&gt;load($linkedProduct-&gt;getId());
    if (!$selectionModel-&gt;getId()) {
        throw new InputException(
            __(
                'The product link with the &quot;%1&quot; ID field wasn\'t found. Verify the ID and try again.',
                [$linkedProduct-&gt;getId()]
            )
        );
    }
    $selectionModel = $this-&gt;mapProductLinkToBundleSelectionModel(
        $selectionModel,
        $linkedProduct,
        $product,
        (int)$linkProductModel-&gt;getId()
    );

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

    return true;
}

/**
 * This method has same content as in the parent class
 * Declare it here to allow override mapProductLinkToBundleSelectionModel which is private
 *
 * @inheritDoc
 */
public function addChild(
    ProductInterface $product,
                     $optionId,
    LinkInterface $linkedProduct
) {
    if ($product-&gt;getTypeId() != Product\Type::TYPE_BUNDLE) {
        throw new InputException(
            __('The product with the &quot;%1&quot; SKU isn\'t a bundle product.', $product-&gt;getSku())
        );
    }

    $linkField = $this-&gt;metadataPool-&gt;getMetadata(ProductInterface::class)-&gt;getLinkField();

    $options = $this-&gt;optionCollection-&gt;create();

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

    $existingOption = $options-&gt;getFirstItem();

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

    /* @var $resource Bundle */
    $resource = $this-&gt;bundleFactory-&gt;create();
    $selections = $resource-&gt;getSelectionsData($product-&gt;getData($linkField));
    /** @var Product $linkProductModel */
    $linkProductModel = $this-&gt;productRepository-&gt;get($linkedProduct-&gt;getSku());
    if ($linkProductModel-&gt;isComposite()) {
        throw new InputException(__('The bundle product can\'t contain another composite product.'));
    }

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

                return $this-&gt;bundleSelection-&gt;create()-&gt;load($linkProductModel-&gt;getEntityId());
            }
        }
    }

    $selectionModel = $this-&gt;bundleSelection-&gt;create();
    $selectionModel = $this-&gt;mapProductLinkToBundleSelectionModel(
        $selectionModel,
        $linkedProduct,
        $product,
        (int)$linkProductModel-&gt;getEntityId()
    );

    $selectionModel-&gt;setOptionId($optionId);

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

    return (int)$selectionModel-&gt;getId();
}

/**
 * Fill selection model with product link data.
 * Save selection image value
 *
 * @param Selection $selectionModel
 * @param LinkInterface $productLink
 * @param ProductInterface $parentProduct
 * @param int $linkedProductId
 * @return Selection
 * @throws NoSuchEntityException
 */
private function mapProductLinkToBundleSelectionModel(
    Selection $selectionModel,
    LinkInterface $productLink,
    ProductInterface $parentProduct,
    int $linkedProductId
): Selection {
    $linkField = $this-&gt;metadataPool-&gt;getMetadata(ProductInterface::class)-&gt;getLinkField();
    $selectionModel-&gt;setProductId($linkedProductId);
    $selectionModel-&gt;setParentProductId($parentProduct-&gt;getData($linkField));
    if ($productLink-&gt;getSelectionId() !== null) {
        $selectionModel-&gt;setSelectionId($productLink-&gt;getSelectionId());
    }
    if ($productLink-&gt;getOptionId() !== null) {
        $selectionModel-&gt;setOptionId($productLink-&gt;getOptionId());
    }
    if ($productLink-&gt;getPosition() !== null) {
        $selectionModel-&gt;setPosition($productLink-&gt;getPosition());
    }
    if ($productLink-&gt;getQty() !== null) {
        $selectionModel-&gt;setSelectionQty($productLink-&gt;getQty());
    }
    if ($productLink-&gt;getPriceType() !== null) {
        $selectionModel-&gt;setSelectionPriceType($productLink-&gt;getPriceType());
    }
    if ($productLink-&gt;getPrice() !== null) {
        $selectionModel-&gt;setSelectionPriceValue($productLink-&gt;getPrice());
    }
    if ($productLink-&gt;getCanChangeQuantity() !== null) {
        $selectionModel-&gt;setSelectionCanChangeQty($productLink-&gt;getCanChangeQuantity());
    }
    if ($productLink-&gt;getIsDefault() !== null) {
        $selectionModel-&gt;setIsDefault($productLink-&gt;getIsDefault());
    }
    $selectionModel-&gt;setWebsiteId((int)$this-&gt;storeManager-&gt;getStore($parentProduct-&gt;getStoreId())-&gt;getWebsiteId());

    // Start customize
    $selectionImage = null;
    if ($productLink-&gt;getSelectionImage() !== null) {
        $selectionImage = $this-&gt;getSelectionImage($productLink-&gt;getSelectionImage());
    }
    $selectionModel-&gt;setSelectionImage($selectionImage);
    // End customize

    return $selectionModel;
}

/**
 * Move uploaded selection image file and get selection image path
 * @param $selectionImageData
 * @return string|null
 * @throws NoSuchEntityException
 */
private function getSelectionImage($selectionImageData)
{
    $selectionImage = null;
    $store = $this-&gt;storeManager-&gt;getStore();
    $baseMediaDir = $store-&gt;getBaseMediaDir();
    $imageName = $selectionImageData[0]['name'];
    if (isset($selectionImageData[0]['tmp_name'])) {
        // Save new uploaded selection image
        try {
            $newImgRelativePath = $this-&gt;imageUploader-&gt;moveFileFromTmp($imageName, true);
            $selectionImage = $baseMediaDir . '/' . $newImgRelativePath;
        } catch (\Exception $e) {
            $message = __('Saving selection image: ') . $e-&gt;getMessage();
            $this-&gt;messageManager-&gt;addErrorMessage($message);
        }
    } else {
        // Save existing selection image
        /**
         * It was converted in Modifier for rendering in the admin interface:
         * Magetu\BundleSelectionImage\Ui\DataProvider\Product\Form\Modifier\SelectionImage::modifyData
         */
        $selectionImage = $baseMediaDir . '/' . $this-&gt;imageUploader-&gt;getBasePath() . '/' . $imageName;
    }

    return $selectionImage;
}

}

Full module directory structure

The module is now complete. Your module’s directory structure under app/code should look like the following:

Magetu
|-- BundleSelectionImage
    |
    |-- Controller
    |   |-- Adminhtml
    |       |-- Product
    |           |-- Image
    |               |-- Upload.php
    |-- etc
    |   |-- adminhtml
    |   |   |-- di.xml
    |   |   |-- routes.xml
    |   |-- db_schema.xml
    |   |-- module.xml
    |-- Model
    |   |-- LinkManagement.php
    |-- Ui
    |   |-- DataProvider
    |       |-- Product
    |           |-- Form
    |               |-- Modifier
    |                   |-- SelectionImage.php
    |-- view
    |   |-- adminhtml
    |       |-- web
    |           |-- template
    |               |-- form
    |                   |-- element
    |                       |-- uploader
    |                           |-- image.html
    |-- registration.php

Installing the module

Run the following commands to register the module, upgrade database, compile code and deploy static content:

bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f

enter image description here

Tu Van
  • 6,868
  • 2
  • 11
  • 22
  • 1
    Its working perfectly, Thank you – Amit Kumar Feb 20 '23 at 05:15
  • 1
    You're welcome. Glad to hear that! – Tu Van Feb 20 '23 at 07:48
  • 1
    I am getting below error for all other product type in admin panel when edit

    The "componentType" configuration parameter is required for the "bundle-items" component, Any idea ?

    – Amit Kumar Feb 24 '23 at 06:39
  • 1
    Correction public function modifyMeta(array $meta) { $product = $this->locator->getProduct(); $isBundleProduct = $product->getTypeId() === Type::TYPE_CODE; if ($isBundleProduct) { $groupCode = BundlePanel::CODE_BUNDLE_DATA; $meta[$groupCode]['children']['bundle_options']['children']['record']['children'] ['product_bundle_container']['children']['bundle_selections'] ['children']['record']['children'][static::FIELD_IMAGE] = $this->getSelectionImageFieldConfig(); }
        return $meta;
    }
    
    – Amit Kumar Feb 24 '23 at 07:48
  • 1
    @AmitKumar good catch. Cheers! – Tu Van Feb 24 '23 at 10:50
0

Try this code:

First you need to override below file.

After that you can get bundle option images from below code.

=> app/design/frontend/Vendor/Theme_Name/Magento_Bundle/templates/catalog/product/view/type/bundle/option/radio.phtml

          <?php
             $_imageHelper = \Magento\Framework\App\ObjectManager::getInstance()->get('Magento\Catalog\Helper\Image');

             $image = $_imageHelper->init($_selection, 'small_image', ['type' => 'small_image'])->keepAspectRatio(true)->resize('50', '50')->getUrl(); ?>

                <label class="label" 
                       for="bundle-option-<?= /* @escapeNotVerified */ $_option->getId() ?>-<?= /* @escapeNotVerified */ $_selection->getSelectionId() ?>">
                    <div class="bundle-option-image"><img src="<?php echo $image; ?>" /></div>
                </label>
        <?php endforeach; ?>

I hope this is work for you.

Deep Shah
  • 565
  • 5
  • 31
  • 1
    Thank you for your comment but how I can save custom image in database?? Actually I know how to use that custom image on frontend – Amit Kumar Feb 01 '23 at 05:07