Image Upload in Magento 2 UI Form

Continuing with the UI component problem in this post we will learn how to create an image upload in Magento 2 Ui form. In the article Create UI Form in Magento 2 we have created a simple form to continue we will add an image upload field.

We will return to the Simple module we practiced in the previous posts and update some files.

1. Update UI form

Open file app\code\Magerubik\Simple\view\adminhtml\ui_component\message_send_form.xml then add below code to update image field.

<!-- image field -->
<field name="img_attachment">
	<argument name="data" xsi:type="array">
		<item name="config" xsi:type="array">
			<item name="label" xsi:type="string">Attachment Image</item>
			<item name="formElement" xsi:type="string">fileUploader</item>
			<item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
			<item name="previewTmpl" xsi:type="string">Magerubik_Simple/form/image-preview</item>
			<item name="maxFileSize" xsi:type="number">2097152</item>
			<item name="allowedExtensions" xsi:type="string">jpg jpeg gif png</item>
			<item name="uploaderConfig" xsi:type="array">
				<item name="url" xsi:type="url" path="simple/uploader/messageimage"/>
			</item>
		</item>
	</argument>
</field>
<!-- End image field-->

2. Create image preview template file

Create file app\code\Magerubik\Message\frontend\web\template\form\image-preview.html with below content.

<div class="file-uploader-summary">
    <div class="file-uploader-preview">
        <a attr="href: $parent.getFilePreview($file)" target="_blank">
            <img class="preview-image"
                 tabindex="0"
                 event="load: $parent.onPreviewLoad.bind($parent)"
                 attr="src: $parent.getFilePreview($file),alt: $file.name">
        </a>
        <div class="actions">
            <button
                    type="button"
                    class="action-remove"
                    data-role="delete-button"
                    attr="title: $t('Delete image')"
                    click="$parent.removeFile.bind($parent, $file)">
                <span translate="'Delete image'"/>
            </button>
        </div>
    </div>

    <div class="file-uploader-filename" text="$file.name"/>
    <div class="file-uploader-meta">
        <text args="$file.previewWidth"/>x<text args="$file.previewHeight"/>
    </div>
</div>

2. Create controller to upload image

Create file app\code\Magerubik\Simple\Controller\Adminhtml\Uploader\MessageImage.php with below content.

<?php
namespace Magerubik\Simple\Controller\Adminhtml\Uploader;
use Magento\Framework\Controller\ResultFactory;
class MessageImage extends \Magento\Backend\App\Action
{
    private $imageUploader;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Catalog\Model\ImageUploader $imageUploader
    ) {
        parent::__construct($context);
        $this->imageUploader = $imageUploader;
    }
    public function execute()
    {
        try {
            $imageField = '';
            foreach ($this->getRequest()->getFiles() as $key => $file) {
                $imageField = $key;
                break;
            }
            $result = $this->imageUploader->saveFileToTmpDir($imageField);
            $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);
    }
}

3. Update Provider File

Open file app\code\Magerubik\Message\Model\DataProvider\MessageDataProvider.php then add below code to fill data image show on UI from.

<?php
namespace Magerubik\Simple\Model\DataProvider;
use Magerubik\Simple\Model\ResourceModel\Message\CollectionFactory;
use Magerubik\Simple\Model\ImageProcessor;
use Magento\Ui\DataProvider\AbstractDataProvider;
use Magento\Framework\App\Request\DataPersistorInterface;
class MessageDataProvider extends AbstractDataProvider
{
    protected $loadedData;
	private $imageProcessor;
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
		ImageProcessor $imageProcessor,
		DataPersistorInterface $dataPersistor,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create();
		$this->imageProcessor = $imageProcessor;
		$this->dataPersistor = $dataPersistor;
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }
    public function getData()
    {
        $items = $this->collection->getItems();
        foreach ($items as $model) {
            $this->loadedData[$model->getId()] = $model->getData();
			/* start with img */
			if($model->getImgAttachment()) {
                $img['img_attachment'][0]['name'] = $model->getImgAttachment();
                $img['img_attachment'][0]['url'] = $this->imageProcessor->getThumbnailUrl($model->getImgAttachment(), 'img_attachment');
                $fullData = $this->loadedData;
                $this->loadedData[$model->getId()] = array_merge($fullData[$model->getId()], $img);
            }
			/*======end======*/
        }
		$data = $this->dataPersistor->get('mrsimple_message');
        if (!empty($data)) {
            $model = $this->collection->getNewEmptyItem();
            $model->setData($data);
            $this->loadedData[$model->getId()] = $model->getData();
            $this->dataPersistor->clear('mrsimple_message');
        }
        return $this->loadedData;
    }
}

4. Crate image upload Model

Create file app\code\Magerubik\Simple\Model\ImageProcessor.php with below content.

<?php
namespace Magerubik\Simple\Model;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\File\Uploader;
class ImageProcessor
{
    const SIMPLE_MEDIA_PATH = 'simple/message';
    const SIMPLE_MEDIA_TMP_PATH = 'simple/message/tmp';
    private $imageUploader;
    private $imageFactory;
    private $storeManager;
    private $mediaDirectory;
    private $filesystem;
    private $ioFile;
    private $coreFileStorageDatabase;
    private $logger;
    public function __construct(
        \Magento\Framework\Filesystem $filesystem,
        \Magento\Catalog\Model\ImageUploader $imageUploader,
        \Magento\Framework\ImageFactory $imageFactory,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\Framework\Filesystem\Io\File $ioFile,
        \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase,
        \Psr\Log\LoggerInterface $logger
    ) {
        $this->filesystem = $filesystem;
        $this->imageUploader = $imageUploader;
        $this->imageFactory = $imageFactory;
        $this->storeManager = $storeManager;
        $this->ioFile = $ioFile;
        $this->coreFileStorageDatabase = $coreFileStorageDatabase;
        $this->logger = $logger;
    }
    private function getMediaDirectory()
    {
        if ($this->mediaDirectory === null) {
            $this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
        }
        return $this->mediaDirectory;
    }
    public function getThumbnailUrl($imageName)
    {
        $pubDirectory = $this->filesystem->getDirectoryRead(DirectoryList::PUB);
        if ($pubDirectory->isExist($imageName)) {
            $result = $this->storeManager->getStore()->getBaseUrl() . trim($imageName, '/');
        } else {
            $result = $this->getCategoryIconMedia(self::SIMPLE_MEDIA_PATH) . '/' . $imageName;
        }
        return $result;
    }
    private function getImageRelativePath($iconName)
    {
        return self::SIMPLE_MEDIA_PATH . DIRECTORY_SEPARATOR . $iconName;
    }
    private function getCategoryIconMedia($mediaPath)
    {
        return $this->storeManager->getStore()
                ->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) . $mediaPath;
    }
    public function processCategoryIcon($iconName)
    {
        $this->imageUploader->moveFileFromTmp($iconName, true);
        $filename = $this->getMediaDirectory()->getAbsolutePath($this->getImageRelativePath($iconName));
        try {
            $imageProcessor = $this->imageFactory->create(['fileName' => $filename]);
            $imageProcessor->keepAspectRatio(true);
            $imageProcessor->keepFrame(true);
            $imageProcessor->keepTransparency(true);
            $imageProcessor->backgroundColor([255, 255, 255]);
            $imageProcessor->save();
        } catch (\Exception $e) {
            null;
        }
    }
    public function moveFile(array $images): ?string
    {
        $filePath = null;
        if (count($images) > 0) {
            foreach ($images as $image) {
                if (array_key_exists('file', $image)) {
                    $mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA);
                    if ($mediaDirectory->isExist(self::SIMPLE_MEDIA_TMP_PATH . '/' . $image['file'])) {
                        $filePath = $this->moveFileFromTmp($image['file']);
                        break;
                    }
                } elseif (isset($image['type'])) {
                    $filePath = $image['url'] ?? '';
                }
            }
        }
        return $filePath;
    }
    public function deleteImage($iconName)
    {
        $this->getMediaDirectory()->delete($this->getImageRelativePath($iconName));
    }
    public function copy($imageName)
    {
        $basePath = $this->getMediaDirectory()->getAbsolutePath($this->getImageRelativePath($imageName));
        $imageName = explode('.', $imageName);
        $imageName[0] .= '-' . random_int(1, 1000);
        $imageName = implode('.', $imageName);
        $newPath = $this->getMediaDirectory()->getAbsolutePath($this->getImageRelativePath($imageName));
        try {
            $this->ioFile->cp(
                $basePath,
                $newPath
            );
        } catch (\Exception $e) {
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Something went wrong while saving the file(s).')
            );
        }
        return $imageName;
    }
    public function moveFileFromTmp($imageName, $returnRelativePath = false): string
    {
        $baseTmpPath = $this->imageUploader->getBaseTmpPath();
        $basePath = $this->imageUploader->getBasePath();
        $baseImagePath = $this->imageUploader->getFilePath(
            $basePath,
            Uploader::getNewFileName(
                $this->getMediaDirectory()->getAbsolutePath($this->imageUploader->getFilePath($basePath, $imageName))
            )
        );
        $baseTmpImagePath = $this->imageUploader->getFilePath($baseTmpPath, $imageName);
        try {
            $this->coreFileStorageDatabase->copyFile($baseTmpImagePath, $baseImagePath);
            $this->getMediaDirectory()->renameFile($baseTmpImagePath, $baseImagePath);
        } catch (\Exception $e) {
            $this->logger->critical($e);
            throw new \Magento\Framework\Exception\LocalizedException(
                __('Something went wrong while saving the file(s).'),
                $e
            );
        }
        return $returnRelativePath ? $baseImagePath : $imageName;
    }
}

5. Define Image Uploader configuration

Open file app\code\Magerubik\Simple\etc\di.xml then add below code to define image uploader configuration.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
	<!-- start with image uploader -->
	<virtualType name="Magerubik\Simple\Model\ImageUpload" type="Magento\Catalog\Model\ImageUploader">
        <arguments>
            <argument name="baseTmpPath" xsi:type="const">Magerubik\Simple\Model\ImageProcessor::SIMPLE_MEDIA_PATH</argument>
            <argument name="basePath" xsi:type="const">Magerubik\Simple\Model\ImageProcessor::SIMPLE_MEDIA_TMP_PATH</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="gif" xsi:type="string">gif</item>
                <item name="png" xsi:type="string">png</item>
            </argument>
        </arguments>
    </virtualType>
    <type name="Magerubik\Simple\Controller\Adminhtml\Uploader\MessageImage">
        <arguments>
            <argument name="imageUploader" xsi:type="object">Magerubik\Simple\Model\ImageUpload</argument>
        </arguments>
    </type>
	<type name="Magerubik\Simple\Model\ImageProcessor">
        <arguments>
            <argument name="imageUploader" xsi:type="object">Magerubik\Simple\Model\ImageUpload</argument>
        </arguments>
    </type>
	<!-- end image uploader -->
</config>

Now, go back to the backend to refresh the cache and check the results. If you see the below screenshot everything is ok.

Magento 2 upload image form

5. Save Image data

Finally, update the save controller to save the image name to the database.

Open file app\code\Magerubik\Simple\etc\di.xml then add below code.

<?php
namespace Magerubik\Simple\Controller\Adminhtml\Message;
use Magento\Framework\Exception\LocalizedException;
class Save extends \Magento\Backend\App\Action
{
    protected $dataPersistor;
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Framework\App\Request\DataPersistorInterface $dataPersistor
    ) {
        $this->dataPersistor = $dataPersistor;
        parent::__construct($context);
    }
    public function execute()
    {
        $resultRedirect = $this->resultRedirectFactory->create();
        $data = $this->getRequest()->getPostValue();
        if ($data) {
            $id = $this->getRequest()->getParam('messages_id');
			$data = $this->_filterAttachmentData($data); /* save image data */
			if($id){
				$model = $this->_objectManager->create(\Magerubik\Simple\Model\Message::class)->load($id);
				if (!$model->getMessagesId()) {
					$this->messageManager->addErrorMessage(__('This message no longer exists.'));
					return $resultRedirect->setPath('*/*/');
				}
				$model->setData($data)->setId($id);
			}else{
				$model = $this->_objectManager->create(\Magerubik\Simple\Model\Message::class);
				$model->setData($data)->setId(NULL);
			}
            try {
                $model->save();
                $this->messageManager->addSuccessMessage(__('You saved the message.'));
                $this->dataPersistor->clear('mrsimple_message');
                return $resultRedirect->setPath('*/*/edit', ['id' => $model->getMessagesId()]);
            } catch (LocalizedException $e) {
                $this->messageManager->addErrorMessage($e->getMessage());
            } catch (\Exception $e) {
                $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the message.'));
            }
            $this->dataPersistor->set('mrsimple_message', $data);
            return $resultRedirect->setPath('*/*/edit', ['id' => $this->getRequest()->getParam('messages_id')]);
        }
        return $resultRedirect->setPath('*/*/');
    }
	public function _filterAttachmentData(array $rawData)
    {
        $data = $rawData;
        if (isset($data['img_attachment'][0]['name'])) {
            $data['img_attachment'] = $data['img_attachment'][0]['name'];
        } else {
            $data['img_attachment'] = null;
        }
        return $data;
    }
}

Go to the backend flush cache then check save function. If you see like below the screenshot everything is ok.

Magento 2 ui form check save image function

Thanks for reading the whole article, good luck with your practice. Contact us if you face any problems during the installation process.

You can download the demo code for this entire series from GitHub