Add product Image upload field and preview image in Magento 2 Product UI Component Form

Media Image
Let's start by creating a custom module.
Find the Complete module on Github at Magelearn_ProductImage
app/code/Magelearn/ProductImageCreate a module registration file
<?php /** * Copyright © All rights reserved. * See COPYING.txt for license details. */ use Magento\Framework\Component\ComponentRegistrar; ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magelearn_ProductImage', __DIR__);Add composer.json file in it:
{
"name": "magelearn/module-ProductImage",
"description": "Magento2 image attachment in product add/edit admin form and display image on product page",
"type": "magento2-module",
"license": "OSL-3.0",
"authors": [
{
"email": "vijaymrami@gmail.com",
"name": "vijay rami"
}
],
"minimum-stability": "dev",
"require": {},
"autoload": {
"files": [
"registration.php"
],
"psr-4": {
"Magelearn\\ProductImage\\": ""
}
}
}<?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="Magelearn_ProductImage" setup_version="1.0.0">
</module>
</config>Add etc/acl.xml file in it:
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:Acl/etc/acl.xsd">
<acl>
<resources>
<resource id="Magento_Backend::admin">
<resource id="Magento_Backend::content">
<resource id="Magelearn_ProductImage::upload"
title="Default Product Image Settings"
sortOrder="20"/>
</resource>
</resource>
</resources>
</acl>
</config>Create InstallData script to add product custom image attribute<?php
namespace Magelearn\ProductImage\Setup;
use Magelearn\ProductImage\Model\Product\Attribute\Backend\ImageUploader as ImageUploaderBackend;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Eav\Setup\EavSetupFactory;
/**
* Class InstallData
* @package Magelearn\Productattachement\Setup
*
* @codeCoverageIgnore
*/
class InstallData implements InstallDataInterface
{
/**
* @var EavSetupFactory
*/
protected $_eavSetupFactory;
public function __construct(
EavSetupFactory $eavSetupFactory
) {
$this->_eavSetupFactory = $eavSetupFactory;
}
/**
* {@inheritdoc}
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$eavSetup = $this->_eavSetupFactory->create(["setup"=>$setup]);
$eavSetup->addAttribute(
\Magento\Catalog\Model\Product::ENTITY,
'product_label_image',
[
'group' => 'Content',
'type' => 'varchar',
'label' => 'Product Label Image',
'input' => 'text',
'backend' => ImageUploaderBackend::class,
'frontend' => '',
'class' => '',
'source' => '',
'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL,
'visible' => true,
'required' => false,
'user_defined' => true,
'default' => '',
'searchable' => false,
'filterable' => false,
'comparable' => false,
'visible_on_front' => true,
'unique' => false,
'apply_to' => 'simple,configurable', // applicable for simple and configurable product
'used_in_product_listing' => true
]
);
}
}
Now, as per highlighted code above, we will add our backed class.<?php
declare(strict_types = 1);
namespace Magelearn\ProductImage\Model\Product\Attribute\Backend;
use Magelearn\ProductImage\Model\ImageIO;
use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
class ImageUploader extends AbstractBackend
{
/**
* @var ImageIO
*/
private $imageIO;
/**
* ImageUploader constructor.
*
* @param ImageIO $imageIO
*/
public function __construct(
ImageIO $imageIO
) {
$this->imageIO = $imageIO;
}
/**
* @inheritdoc
*/
public function beforeSave($object)
{
$attribute = $this->getAttribute();
$attrCode = $attribute->getAttributeCode();
$value = $object->getData($attrCode);
if ($value && is_array($value) && isset($value[0])) {
if (!isset($value[0]['id'])) {
$object->setData($attrCode, null);
} elseif (isset($value[0]['url']) && $this->isWysiwygPath($value[0]['url'])) {
$object->setData($attrCode, $value[0]['url']);
} elseif (isset($value[0]['file'])) {
$object->setData($attrCode, $value[0]['file']);
}
}
return parent::beforeSave($object);
}
/**
* @inheritdoc
*/
public function afterSave($object)
{
$value = $object->getData($this->getAttribute()->getName());
if ($value && is_array($value) && isset($value[0]) && isset($value[0]['url'])) {
$imagepath = (string)$value['0']['url'];
if($this->isWysiwygPath($imagepath)) {
$this->imageIO->moveFileFromTmp($imagepath);
}
} elseif ($value && !$this->isWysiwygPath($value)) {
$this->imageIO->moveFileFromTmp($value);
}
return $this;
}
/**
* @inheritdoc
*/
public function afterLoad($object)
{
$attrName = $this->getAttribute()->getName();
if ($value = $object->getData($attrName)) {
$isWysiwygPath = $this->isWysiwygPath($value);
$object->setData(
$attrName,
[
[
'name' => $isWysiwygPath ? $this->getFileNameFromPath($value) : $value,
'url' => $this->imageIO->getImageUrl($value, $isWysiwygPath),
'type' => 'image',
]
]
);
}
return parent::afterLoad($object);
}
/**
* @param string $path
*
* @return bool
*/
public function isWysiwygPath(string $path): bool
{
return (bool) strpos($path, 'wysiwyg');
}
/**
* @param string $path
*
* @return string
*/
private function getFileNameFromPath(string $path): string
{
$parts = explode('/', $path);
return end($parts);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<fieldset name="content">
<field name="product_label_image">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="dataType" xsi:type="string">string</item>
<item name="label" xsi:type="string" translate="true">Product Label Image</item>
<item name="visible" xsi:type="boolean">true</item>
<item name="allowedExtensions" xsi:type="string">jpg jpeg gif png svg</item>
<item name="maxFileSize" xsi:type="number">2097152</item>
<item name="formElement" xsi:type="string">imageUploader</item>
<item name="previewTmpl" xsi:type="string">Magento_Catalog/image-preview</item>
<item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
<item name="dataScope" xsi:type="string">product_label_image</item>
<item name="sortOrder" xsi:type="number">40</item>
<item name="uploaderConfig" xsi:type="array">
<item name="url" xsi:type="url" path="productimage/image/upload">
<param name="target_element_id">product_label_image</param>
<param name="type">image</param>
</item>
</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">false</item>
</item>
</item>
</argument>
</field>
</fieldset>
</form>To make this uploaderConfig URL working properly, we will add etc/adminhtml/routes.xml file.
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="admin">
<route id="productimage" frontName="productimage">
<module name="Magelearn_ProductImage" />
</route>
</router>
</config>Now note that we have added this `product_label_image` field as an input type 'text'.
So we need to modify this field to display it properly on product edit form.
For that add file at Magelearn/ProductImage/etc/adminhtml/di.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="add_product_image" xsi:type="array">
<item name="class" xsi:type="string">Magelearn\ProductImage\Ui\DataProvider\Product\Form\Modifier\Image</item>
<item name="sortOrder" xsi:type="number">200</item>
</item>
</argument>
</arguments>
</virtualType>
</config>Now as per highlighted code above,
Add Magelearn/ProductImage/Ui/DataProvider/Product/Form/Modifier/Image.php file.
<?php
declare(strict_types = 1);
namespace Magelearn\ProductImage\Ui\DataProvider\Product\Form\Modifier;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
class Image extends AbstractModifier
{
/**
* @param array $data
*
* @return array
*/
public function modifyData(array $data): array
{
return $data;
}
/**
* @param array $meta
*
* @return array
*/
public function modifyMeta(array $meta): array
{
if (isset($meta['content']['children']['container_product_label_image'])) {
$meta['content']['children']['container_product_label_image']['children'] = array_replace_recursive(
$meta['content']['children']['container_product_label_image']['children'],
[
'product_label_image' => [
'arguments' => [
'data' => [
'config' => [
'visible' => 0,
],
]
]
]
]
);
}
return $meta;
}
}Now we will give our imageUploader configuration for our custom backend model.
For that Add a file at etc/di.xml
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Magelearn\ProductImage\Model\Product\Attribute\Backend\ImageUploader">
<arguments>
<argument name="imageIO" xsi:type="object">Magelearn\ProductImage\Model\Virtual\ImageIO</argument>
</arguments>
</type>
<type name="Magelearn\ProductImage\Controller\Adminhtml\Image\Upload">
<arguments>
<argument name="imageIO" xsi:type="object">Magelearn\ProductImage\Model\Virtual\ImageIO</argument>
</arguments>
</type>
<virtualType name="Magelearn\ProductImage\Model\Virtual\ImageIO" type="Magelearn\ProductImage\Model\ImageIO">
<arguments>
<argument name="baseTmpPath" xsi:type="string">catalog/tmp/product/labels</argument>
<argument name="basePath" xsi:type="string">catalog/product/labels</argument>
<argument name="allowedExtensions" xsi:type="array">
<item name="jpg" xsi:type="string">jpg</item>
<item name="jpeg" xsi:type="string">jpeg</item>
<item name="png" xsi:type="string">png</item>
<item name="svg" xsi:type="string">svg</item>
<item name="gif" xsi:type="string">gif</item>
</argument>
</arguments>
</virtualType>
</config>Now as per highlighted above code, we will add our controller and imageUploader file.
Add file at Controller/Adminhtml/Image/Upload.php
<?php
declare(strict_types = 1);
namespace Magelearn\ProductImage\Controller\Adminhtml\Image;
use Exception;
use Magelearn\ProductImage\Model\ImageIO;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Controller\ResultFactory;
class Upload extends Action
{
/**
* @var ImageIO
*/
private $imageIO;
/**
* Upload constructor.
*
* @param Context $context
* @param ImageIO $imageIO
*/
public function __construct(
Context $context,
ImageIO $imageIO
) {
$this->imageIO = $imageIO;
parent::__construct($context);
}
/**
* @return mixed
*/
public function _isAllowed() {
return $this->_authorization->isAllowed('Magelearn_ProductImage::upload');
}
/**
* @inheritdoc
*/
public function execute()
{
$imageId = $this->_request->getParam('param_name', 'product_label_image');
try {
$result = $this->imageIO->saveFileToTmpDir($imageId);
$result['cookie'] = [
'name' => $this->_getSession()->getName(),
'value' => $this->_getSession()->getSessionId(),
'lifetime' => $this->_getSession()->getCookieLifetime(),
'path' => $this->_getSession()->getCookiePath(),
'domain' => $this->_getSession()->getCookieDomain(),
];
} catch (Exception $e) {
$result = [
'error' => $e->getMessage(),
'errorcode' => $e->getCode()
];
}
return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($result);
}
}Add file at Model/ImageIO.php
<?php
namespace Magelearn\ProductImage\Model;
use Exception;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\UrlInterface;
class ImageIO
{
const BASE_TMP_PATH = "catalog/tmp/product/labels";
const BASE_PATH = "catalog/product/labels";
const ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'svg'];
/**
* @var string
*/
public $baseTmpPath;
/**
* @var string
*/
public $basePath;
/**
* @var string[]
*/
public $allowedExtensions;
/**
* @var \Magento\MediaStorage\Helper\File\Storage\Database
*/
private $coreFileStorageDatabase;
/**
* @var \Magento\Framework\Filesystem\Directory\WriteInterface
*/
private $mediaDirectory;
/**
* @var \Magento\MediaStorage\Model\File\UploaderFactory
*/
private $uploaderFactory;
/**
* @var \Magento\Store\Model\StoreManagerInterface
*/
private $storeManager;
/**
* @var \Psr\Log\LoggerInterface
*/
private $logger;
/**
* ImageUploader constructor.
* @param \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase
* @param \Magento\Framework\Filesystem $filesystem
* @param \Magento\MediaStorage\Model\File\UploaderFactory $uploaderFactory
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Psr\Log\LoggerInterface $logger
* @throws \Magento\Framework\Exception\FileSystemException
*/
public function __construct(
\Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase,
\Magento\Framework\Filesystem $filesystem,
\Magento\MediaStorage\Model\File\UploaderFactory $uploaderFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Psr\Log\LoggerInterface $logger
) {
$this->coreFileStorageDatabase = $coreFileStorageDatabase;
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->uploaderFactory = $uploaderFactory;
$this->storeManager = $storeManager;
$this->logger = $logger;
$this->baseTmpPath = self::BASE_TMP_PATH;
$this->basePath = self::BASE_PATH;
$this->allowedExtensions = self::ALLOWED_EXTENSIONS;
}
/**
* @param $imageName
* @return mixed
* @throws LocalizedException
*/
public function moveFileFromTmp($imageName)
{
$baseTmpPath = $this->getBaseTmpPath();
$basePath = $this->getBasePath();
$baseImagePath = $this->getFilePath($basePath, $imageName);
$baseTmpImagePath = $this->getFilePath($baseTmpPath, $imageName);
try {
$this->coreFileStorageDatabase->copyFile(
$baseTmpImagePath,
$baseImagePath
);
$this->mediaDirectory->renameFile(
$baseTmpImagePath,
$baseImagePath
);
} catch (Exception $e) {
throw new LocalizedException(
__('Something went wrong while saving the file(s).')
);
}
return $imageName;
}
/**
* @return string
*/
public function getBaseTmpPath()
{
return $this->baseTmpPath;
}
/**
* @param $baseTmpPath
*/
public function setBaseTmpPath($baseTmpPath)
{
$this->baseTmpPath = $baseTmpPath;
}
/**
* @return string
*/
public function getBasePath()
{
return $this->basePath;
}
/**
* @param $basePath
*/
public function setBasePath($basePath)
{
$this->basePath = $basePath;
}
/**
* @param $path
* @param $imageName
* @return string
*/
public function getFilePath($path, $imageName)
{
return rtrim($path, '/') . '/' . ltrim($imageName, '/');
}
/**
* @param $fileId
* @return mixed
* @throws LocalizedException
* @throws NoSuchEntityException
*/
public function saveFileToTmpDir($fileId)
{
$baseTmpPath = $this->getBaseTmpPath();
$uploader = $this->uploaderFactory->create(['fileId' => $fileId]);
$uploader->setAllowedExtensions($this->getAllowedExtensions());
$uploader->setAllowRenameFiles(true);
$result = $uploader->save($this->mediaDirectory->getAbsolutePath($baseTmpPath));
if (!$result) {
throw new LocalizedException(
__('File can not be saved to the destination folder.')
);
}
$result['tmp_name'] = str_replace('\\', '/', $result['tmp_name']);
$result['path'] = str_replace('\\', '/', $result['path']);
$result['url'] = $this->storeManager
->getStore()
->getBaseUrl(
UrlInterface::URL_TYPE_MEDIA
) . $this->getFilePath($baseTmpPath, $result['file']);
$result['name'] = $result['file'];
if (isset($result['file'])) {
try {
$relativePath = rtrim($baseTmpPath, '/') . '/' . ltrim($result['file'], '/');
$this->coreFileStorageDatabase->saveFile($relativePath);
} catch (Exception $e) {
$this->logger->critical($e);
throw new LocalizedException(
__('Something went wrong while saving the file(s).')
);
}
}
return $result;
}
/**
* @return string[]
*/
public function getAllowedExtensions()
{
return $this->allowedExtensions;
}
/**
* @param $allowedExtensions
*/
public function setAllowedExtensions($allowedExtensions)
{
$this->allowedExtensions = $allowedExtensions;
}
public function saveMediaImage($imageName, $imagePath)
{
$baseTmpPath = $this->getBaseTmpPath();
$basePath = $this->getBasePath();
$baseImagePath = $this->getFilePath($basePath, $imageName);
$mediaPath = substr($imagePath, 0, strpos($imagePath, "media"));
$baseTmpImagePath = str_replace($mediaPath . "media/", "", $imagePath);
if ($baseImagePath == $baseTmpImagePath) {
return $imageName;
}
try {
$this->mediaDirectory->copyFile(
$baseTmpImagePath,
$baseImagePath
);
} catch (Exception $e) {
throw new LocalizedException(
__('Something went wrong while saving the file(s).')
);
}
return $imageName;
}
/**
* @param string $value
* @param bool $fromWysiwyg
*
* @return string
*/
public function getImageUrl(string $value, bool $fromWysiwyg = false): string
{
try {
$basUrl = $this->storeManager->getStore()->getBaseUrl(UrlInterface::URL_TYPE_MEDIA);
if (strpos($value, $basUrl) !== false) {
return $value;
}
return $fromWysiwyg ? $basUrl . str_replace('/media/', '', $value) :
$basUrl . $this->getFilePath($this->getBasePath(), $value);
} catch (NoSuchEntityException $e) {
return '';
}
}
}After adding the above code, custom image upload button will be displayed at Product Add/Edit form.
Now we will check how to add this product custom image attribute`product_label_image` at frontend.
For that add layout file at view/frontend/layout/catalog_product_view.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceContainer name="product.info.main">
<block class="Magento\Catalog\Block\Product\View" name="product.info.product_label_image" template="Magelearn_ProductImage::display-product-label-image.phtml" />
</referenceContainer>
</body>
</page>Also at template file at view/frontend/templates/display-product-label-image.phtml
<?php
$product = $block->getProduct();
$product_label_image = $product->getProductLabelImage();
if(is_array($product_label_image) && isset($product_label_image['0']['type'])) {
if($product_label_image['0']['type'] == 'image') {
$product_label_image_name = $product_label_image['0']['name'];
$product_label_image_url = $product_label_image['0']['url'];
}
}
?>
<?php if ($product_label_image_url && $product_label_image_name): ?>
<div class="product_label_image">
<label class="label"><span><strong><?= __('Product Label Image: ')?></strong>
<img src="<?= $block->escapeHtml($product_label_image_url) ?>" alt="<?= $block->escapeHtml($product_label_image_name) ?>">
</span></label>
</div>
<?php endif; ?>