Add image upload field to Magento 2 UI component form

This post is about adding image upload field/attribute to Admin CMS page.

Start with creating a new module. In our case it is Magebrew_ImageUploadFormField.

We need to add a new field for storing image path to cms_page table. Let’s use declarative schema for this purpose (available since Magento 2.3). Create app/code/Magebrew/ImageUploadFormField/etc/db_schema.xml file with following content:

  1. <?xml version="1.0"?>
  2. <schema xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Setup/Declaration/Schema/etc/schema.xsd">
  3. <table name="cms_page" resource="default" engine="innodb" comment="CMS Page Table">
  4. <column xsi:type="varchar" name="banner_image" nullable="true" length="255" comment="Banner Image"/>
  5. </table>
  6. </schema>
<?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="cms_page" resource="default" engine="innodb" comment="CMS Page Table">
        <column xsi:type="varchar" name="banner_image" nullable="true" length="255" comment="Banner Image"/>
    </table>
</schema>

With declarative schema we also need to add db_schema_whitelist.json file:

  1. {
  2. "cms_page": {
  3. "column": {
  4. "banner_image": true
  5. }
  6. }
  7. }
{
    "cms_page": {
        "column": {
            "banner_image": true
        }
    }
}

After running bin/magento setup:upgrade we should have new banner_image field added to cms_page table.

Next step is to add field to form. Create view/adminhtml/ui_component/cms_page_form.xml with the following code:

  1. <?xml version="1.0"?>
  2. <form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
  3. <fieldset name="general">
  4. <field name="banner_image">
  5. <argument name="data" xsi:type="array">
  6. <item name="config" xsi:type="array">
  7. <item name="dataType" xsi:type="string">string</item>
  8. <item name="source" xsi:type="string">page</item>
  9. <item name="label" xsi:type="string" translate="true">Banner Image</item>
  10. <item name="visible" xsi:type="boolean">true</item>
  11. <item name="formElement" xsi:type="string">fileUploader</item>
  12. <item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
  13. <item name="previewTmpl" xsi:type="string">Magento_Catalog/image-preview</item>
  14. <item name="baseTmpPath" xsi:type="string">cms/tmp</item>
  15. <item name="required" xsi:type="boolean">false</item>
  16. <item name="sortOrder" xsi:type="number">60</item>
  17. <item name="uploaderConfig" xsi:type="array">
  18. <item name="url" xsi:type="url" path="cmsbanner/cmspage_banner/upload"/>
  19. </item>
  20. </item>
  21. </argument>
  22. </field>
  23. </fieldset>
  24. </form>
<?xml version="1.0"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
    <fieldset name="general">
        <field name="banner_image">
            <argument name="data" xsi:type="array">
                <item name="config" xsi:type="array">
                    <item name="dataType" xsi:type="string">string</item>
                    <item name="source" xsi:type="string">page</item>
                    <item name="label" xsi:type="string" translate="true">Banner Image</item>
                    <item name="visible" xsi:type="boolean">true</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">Magento_Catalog/image-preview</item>
                    <item name="baseTmpPath" xsi:type="string">cms/tmp</item>
                    <item name="required" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="number">60</item>
                    <item name="uploaderConfig" xsi:type="array">
                        <item name="url" xsi:type="url" path="cmsbanner/cmspage_banner/upload"/>
                    </item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

Now CMS page form should contain new field.

Next we create image uploader model where we define some settings and how image is saved. File Model/BannerUploader.php

  1. <?php
  2. declare(strict_types=1);
  3. namespace Magebrew\ImageUploadFormField\Model;
  4. use Magento\Framework\App\Filesystem\DirectoryList;
  5. use Magento\Framework\Exception\LocalizedException;
  6. use Magento\Framework\Filesystem;
  7. use Magento\Framework\UrlInterface;
  8. use Magento\MediaStorage\Helper\File\Storage\Database;
  9. use Magento\MediaStorage\Model\File\UploaderFactory;
  10. use Magento\Store\Model\StoreManagerInterface;
  11. use Psr\Log\LoggerInterface;
  12. /**
  13. * Class BannerUploader
  14. */
  15. class BannerUploader
  16. {
  17. /**
  18. * @var string
  19. */
  20. const IMAGE_TMP_PATH = 'cmsbanner/tmp';
  21. /**
  22. * @var string
  23. */
  24. const IMAGE_PATH = 'cmsbanner';
  25. /**
  26. * @var string
  27. */
  28. const FILE_TMP_PATH = 'cmsbanner/tmp/images/file';
  29. /**
  30. * @var string
  31. */
  32. const FILE_PATH = 'cmsbanner/images/file';
  33. /**
  34. * Core file storage database
  35. *
  36. * @var \Magento\MediaStorage\Helper\File\Storage\Database
  37. */
  38. protected $coreFileStorageDatabase;
  39. /**
  40. * Media directory object (writable).
  41. *
  42. * @var \Magento\Framework\Filesystem\Directory\WriteInterface
  43. */
  44. protected $mediaDirectory;
  45. /**
  46. * Store manager
  47. *
  48. * @var \Magento\Store\Model\StoreManagerInterface
  49. */
  50. protected $storeManager;
  51. /**
  52. * @var \Psr\Log\LoggerInterface
  53. */
  54. protected $logger;
  55. /**
  56. * Base tmp path
  57. *
  58. * @var string
  59. */
  60. protected $baseTmpPath;
  61. /**
  62. * Base path
  63. *
  64. * @var string
  65. */
  66. protected $basePath;
  67. /**
  68. * Allowed extensions
  69. *
  70. * @var string
  71. */
  72. protected $allowedExtensions;
  73. /**
  74. * Uploader factory
  75. *
  76. * @var \Magento\MediaStorage\Model\File\UploaderFactory
  77. */
  78. private $uploaderFactory;
  79. /**
  80. * @param Database $coreFileStorageDatabase
  81. * @param Filesystem $filesystem
  82. * @param UploaderFactory $uploaderFactory
  83. * @param StoreManagerInterface $storeManager
  84. * @param LoggerInterface $logger
  85. * @param $baseTmpPath
  86. * @param $basePath
  87. * @param array $allowedExtensions
  88. * @throws \Magento\Framework\Exception\FileSystemException
  89. */
  90. public function __construct(
  91. Database $coreFileStorageDatabase,
  92. Filesystem $filesystem,
  93. UploaderFactory $uploaderFactory,
  94. StoreManagerInterface $storeManager,
  95. LoggerInterface $logger,
  96. $baseTmpPath,
  97. $basePath,
  98. $allowedExtensions = []
  99. ) {
  100. $this->coreFileStorageDatabase = $coreFileStorageDatabase;
  101. $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
  102. $this->uploaderFactory = $uploaderFactory;
  103. $this->storeManager = $storeManager;
  104. $this->logger = $logger;
  105. $this->baseTmpPath = $baseTmpPath;
  106. $this->basePath = $basePath;
  107. $this->allowedExtensions = $allowedExtensions;
  108. }
  109. /**
  110. * Retrieve base tmp path
  111. *
  112. * @return string
  113. */
  114. public function getBaseTmpPath()
  115. {
  116. return $this->baseTmpPath;
  117. }
  118. /**
  119. * Set base tmp path
  120. *
  121. * @param string $baseTmpPath
  122. *
  123. * @return void
  124. */
  125. public function setBaseTmpPath($baseTmpPath)
  126. {
  127. $this->baseTmpPath = $baseTmpPath;
  128. }
  129. /**
  130. * Retrieve base path
  131. *
  132. * @return string
  133. */
  134. public function getBasePath()
  135. {
  136. return $this->basePath;
  137. }
  138. /**
  139. * Set base path
  140. *
  141. * @param string $basePath
  142. *
  143. * @return void
  144. */
  145. public function setBasePath($basePath)
  146. {
  147. $this->basePath = $basePath;
  148. }
  149. /**
  150. * Retrieve base path
  151. *
  152. * @return string[]
  153. */
  154. public function getAllowedExtensions()
  155. {
  156. return $this->allowedExtensions;
  157. }
  158. /**
  159. * Set allowed extensions
  160. *
  161. * @param string[] $allowedExtensions
  162. *
  163. * @return void
  164. */
  165. public function setAllowedExtensions($allowedExtensions)
  166. {
  167. $this->allowedExtensions = $allowedExtensions;
  168. }
  169. /**
  170. * Retrieve path
  171. *
  172. * @param string $path
  173. * @param string $name
  174. *
  175. * @return string
  176. */
  177. public function getFilePath($path, $name)
  178. {
  179. return rtrim($path, '/') . '/' . ltrim($name, '/');
  180. }
  181. /**
  182. * Checking file for moving and move it
  183. *
  184. * @param string $name
  185. * @return string
  186. * @throws \Magento\Framework\Exception\LocalizedException
  187. */
  188. public function moveFileFromTmp($name)
  189. {
  190. $baseTmpPath = $this->getBaseTmpPath();
  191. $basePath = $this->getBasePath();
  192. $baseFilePath = $this->getFilePath($basePath, $name);
  193. $baseTmpFilePath = $this->getFilePath($baseTmpPath, $name);
  194. try {
  195. $this->coreFileStorageDatabase->copyFile(
  196. $baseTmpFilePath,
  197. $baseFilePath
  198. );
  199. $this->mediaDirectory->renameFile(
  200. $baseTmpFilePath,
  201. $baseFilePath
  202. );
  203. } catch (\Exception $e) {
  204. throw new LocalizedException(
  205. __('Something went wrong while saving the file(s).')
  206. );
  207. }
  208. return $name;
  209. }
  210. public function getBaseUrl()
  211. {
  212. return $this->storeManager
  213. ->getStore()
  214. ->getBaseUrl(
  215. UrlInterface::URL_TYPE_MEDIA
  216. );
  217. }
  218. /**
  219. * Checking file for save and save it to tmp dir
  220. *
  221. * @param string $fileId
  222. * @return string[]
  223. * @throws \Magento\Framework\Exception\LocalizedException
  224. */
  225. public function saveFileToTmpDir($fileId)
  226. {
  227. $baseTmpPath = $this->getBaseTmpPath();
  228. $uploader = $this->uploaderFactory->create(['fileId' => $fileId]);
  229. $uploader->setAllowedExtensions($this->getAllowedExtensions());
  230. $uploader->setAllowRenameFiles(true);
  231. $uploader->setFilesDispersion(false);
  232. $result = $uploader->save($this->mediaDirectory->getAbsolutePath($baseTmpPath));
  233. if (!$result) {
  234. throw new LocalizedException(
  235. __('File can not be saved to the destination folder.')
  236. );
  237. }
  238. /**
  239. * Workaround for prototype 1.7 methods "isJSON", "evalJSON" on Windows OS
  240. */
  241. $result['tmp_name'] = str_replace('\\', '/', $result['tmp_name']);
  242. $result['path'] = str_replace('\\', '/', $result['path']);
  243. $result['url'] = $this->getBaseUrl() . $this->getFilePath($baseTmpPath, $result['file']);
  244. if (isset($result['file'])) {
  245. try {
  246. $relativePath = rtrim($baseTmpPath, '/') . '/' . ltrim($result['file'], '/');
  247. $this->coreFileStorageDatabase->saveFile($relativePath);
  248. } catch (\Exception $e) {
  249. $this->logger->critical($e);
  250. throw new LocalizedException(
  251. __('Something went wrong while saving the file(s).')
  252. );
  253. }
  254. }
  255. return $result;
  256. }
  257. /**
  258. * @param $input
  259. * @param $data
  260. * @return string
  261. */
  262. public function uploadFileAndGetName($input, $data)
  263. {
  264. if (!isset($data[$input])) {
  265. return '';
  266. }
  267. if (is_array($data[$input]) && !empty($data[$input]['delete'])) {
  268. return '';
  269. }
  270. if (isset($data[$input][0]['name']) && isset($data[$input][0]['tmp_name'])) {
  271. try {
  272. $result = $this->moveFileFromTmp($data[$input][0]['file']);
  273. return $result;
  274. } catch (\Exception $e) {
  275. return '';
  276. }
  277. } elseif (isset($data[$input][0]['name'])) {
  278. return $data[$input][0]['name'];
  279. }
  280. return '';
  281. }
  282. }
<?php

declare(strict_types=1);

namespace Magebrew\ImageUploadFormField\Model;

use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Filesystem;
use Magento\Framework\UrlInterface;
use Magento\MediaStorage\Helper\File\Storage\Database;
use Magento\MediaStorage\Model\File\UploaderFactory;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;

/**
 * Class BannerUploader
 */
class BannerUploader
{
    /**
     * @var string
     */
    const IMAGE_TMP_PATH = 'cmsbanner/tmp';

    /**
     * @var string
     */
    const IMAGE_PATH = 'cmsbanner';

    /**
     * @var string
     */
    const FILE_TMP_PATH = 'cmsbanner/tmp/images/file';

    /**
     * @var string
     */
    const FILE_PATH = 'cmsbanner/images/file';

    /**
     * Core file storage database
     *
     * @var \Magento\MediaStorage\Helper\File\Storage\Database
     */
    protected $coreFileStorageDatabase;

    /**
     * Media directory object (writable).
     *
     * @var \Magento\Framework\Filesystem\Directory\WriteInterface
     */
    protected $mediaDirectory;

    /**
     * Store manager
     *
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var \Psr\Log\LoggerInterface
     */
    protected $logger;

    /**
     * Base tmp path
     *
     * @var string
     */
    protected $baseTmpPath;

    /**
     * Base path
     *
     * @var string
     */
    protected $basePath;

    /**
     * Allowed extensions
     *
     * @var string
     */
    protected $allowedExtensions;

    /**
     * Uploader factory
     *
     * @var \Magento\MediaStorage\Model\File\UploaderFactory
     */
    private $uploaderFactory;

    /**
     * @param Database $coreFileStorageDatabase
     * @param Filesystem $filesystem
     * @param UploaderFactory $uploaderFactory
     * @param StoreManagerInterface $storeManager
     * @param LoggerInterface $logger
     * @param $baseTmpPath
     * @param $basePath
     * @param array $allowedExtensions
     * @throws \Magento\Framework\Exception\FileSystemException
     */
    public function __construct(
        Database $coreFileStorageDatabase,
        Filesystem $filesystem,
        UploaderFactory $uploaderFactory,
        StoreManagerInterface $storeManager,
        LoggerInterface $logger,
        $baseTmpPath,
        $basePath,
        $allowedExtensions = []
    ) {
        $this->coreFileStorageDatabase = $coreFileStorageDatabase;
        $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
        $this->uploaderFactory = $uploaderFactory;
        $this->storeManager = $storeManager;
        $this->logger = $logger;
        $this->baseTmpPath = $baseTmpPath;
        $this->basePath = $basePath;
        $this->allowedExtensions = $allowedExtensions;
    }

    /**
     * Retrieve base tmp path
     *
     * @return string
     */
    public function getBaseTmpPath()
    {
        return $this->baseTmpPath;
    }

    /**
     * Set base tmp path
     *
     * @param string $baseTmpPath
     *
     * @return void
     */
    public function setBaseTmpPath($baseTmpPath)
    {
        $this->baseTmpPath = $baseTmpPath;
    }

    /**
     * Retrieve base path
     *
     * @return string
     */
    public function getBasePath()
    {
        return $this->basePath;
    }

    /**
     * Set base path
     *
     * @param string $basePath
     *
     * @return void
     */
    public function setBasePath($basePath)
    {
        $this->basePath = $basePath;
    }

    /**
     * Retrieve base path
     *
     * @return string[]
     */
    public function getAllowedExtensions()
    {
        return $this->allowedExtensions;
    }

    /**
     * Set allowed extensions
     *
     * @param string[] $allowedExtensions
     *
     * @return void
     */
    public function setAllowedExtensions($allowedExtensions)
    {
        $this->allowedExtensions = $allowedExtensions;
    }

    /**
     * Retrieve path
     *
     * @param string $path
     * @param string $name
     *
     * @return string
     */
    public function getFilePath($path, $name)
    {
        return rtrim($path, '/') . '/' . ltrim($name, '/');
    }

    /**
     * Checking file for moving and move it
     *
     * @param string $name
     * @return string
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function moveFileFromTmp($name)
    {
        $baseTmpPath = $this->getBaseTmpPath();
        $basePath = $this->getBasePath();
        $baseFilePath = $this->getFilePath($basePath, $name);
        $baseTmpFilePath = $this->getFilePath($baseTmpPath, $name);

        try {
            $this->coreFileStorageDatabase->copyFile(
                $baseTmpFilePath,
                $baseFilePath
            );
            $this->mediaDirectory->renameFile(
                $baseTmpFilePath,
                $baseFilePath
            );
        } catch (\Exception $e) {
            throw new LocalizedException(
                __('Something went wrong while saving the file(s).')
            );
        }

        return $name;
    }

    public function getBaseUrl()
    {
        return $this->storeManager
            ->getStore()
            ->getBaseUrl(
                UrlInterface::URL_TYPE_MEDIA
            );
    }

    /**
     * Checking file for save and save it to tmp dir
     *
     * @param string $fileId
     * @return string[]
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function saveFileToTmpDir($fileId)
    {
        $baseTmpPath = $this->getBaseTmpPath();

        $uploader = $this->uploaderFactory->create(['fileId' => $fileId]);
        $uploader->setAllowedExtensions($this->getAllowedExtensions());
        $uploader->setAllowRenameFiles(true);
        $uploader->setFilesDispersion(false);

        $result = $uploader->save($this->mediaDirectory->getAbsolutePath($baseTmpPath));

        if (!$result) {
            throw new LocalizedException(
                __('File can not be saved to the destination folder.')
            );
        }
        /**
         * Workaround for prototype 1.7 methods "isJSON", "evalJSON" on Windows OS
         */
        $result['tmp_name'] = str_replace('\\', '/', $result['tmp_name']);
        $result['path'] = str_replace('\\', '/', $result['path']);
        $result['url'] = $this->getBaseUrl() . $this->getFilePath($baseTmpPath, $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;
    }

    /**
     * @param $input
     * @param $data
     * @return string
     */
    public function uploadFileAndGetName($input, $data)
    {
        if (!isset($data[$input])) {
            return '';
        }
        if (is_array($data[$input]) && !empty($data[$input]['delete'])) {
            return '';
        }

        if (isset($data[$input][0]['name']) && isset($data[$input][0]['tmp_name'])) {
            try {
                $result = $this->moveFileFromTmp($data[$input][0]['file']);
                return $result;
            } catch (\Exception $e) {
                return '';
            }
        } elseif (isset($data[$input][0]['name'])) {
            return $data[$input][0]['name'];
        }
        return '';
    }
}

Uploader is used in controller that responsible for saving image in filesystem. File Controller/Adminhtml/Cmspage/Banner/Upload.php

  1. <?php
  2. declare(strict_types=1);
  3. namespace Magebrew\ImageUploadFormField\Controller\Adminhtml\Cmspage\Banner;
  4. use Magebrew\ImageUploadFormField\Model\BannerUploader;
  5. use Magento\Backend\App\Action;
  6. use Magento\Backend\App\Action\Context;
  7. use Magento\Framework\Controller\ResultFactory;
  8. /**
  9. * Class Upload
  10. */
  11. class Upload extends Action
  12. {
  13. /**
  14. * @var string
  15. */
  16. const ACTION_RESOURCE = 'Magebrew_ImageUploadFormField::imageuploadfield';
  17. /**
  18. * @var BannerUploader
  19. */
  20. protected $uploader;
  21. /**
  22. * Upload constructor.
  23. *
  24. * @param Context $context
  25. * @param BannerUploader $uploader
  26. */
  27. public function __construct(
  28. Context $context,
  29. BannerUploader $uploader
  30. ) {
  31. parent::__construct($context);
  32. $this->uploader = $uploader;
  33. }
  34. /**
  35. * Upload file controller action
  36. *
  37. * @return \Magento\Framework\Controller\ResultInterface
  38. */
  39. public function execute()
  40. {
  41. try {
  42. $result = $this->uploader->saveFileToTmpDir('banner_image');
  43. $result['cookie'] = [
  44. 'name' => $this->_getSession()->getName(),
  45. 'value' => $this->_getSession()->getSessionId(),
  46. 'lifetime' => $this->_getSession()->getCookieLifetime(),
  47. 'path' => $this->_getSession()->getCookiePath(),
  48. 'domain' => $this->_getSession()->getCookieDomain(),
  49. ];
  50. } catch (\Exception $e) {
  51. $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
  52. }
  53. return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($result);
  54. }
  55. /**
  56. * @return string
  57. */
  58. protected function getFieldName()
  59. {
  60. return $this->_request->getParam('banner_image');
  61. }
  62. }
<?php

declare(strict_types=1);

namespace Magebrew\ImageUploadFormField\Controller\Adminhtml\Cmspage\Banner;

use Magebrew\ImageUploadFormField\Model\BannerUploader;
use Magento\Backend\App\Action;
use Magento\Backend\App\Action\Context;
use Magento\Framework\Controller\ResultFactory;

/**
 * Class Upload
 */
class Upload extends Action
{
    /**
     * @var string
     */
    const ACTION_RESOURCE = 'Magebrew_ImageUploadFormField::imageuploadfield';

    /**
     * @var BannerUploader
     */
    protected $uploader;

    /**
     * Upload constructor.
     *
     * @param Context $context
     * @param BannerUploader $uploader
     */
    public function __construct(
        Context $context,
        BannerUploader $uploader
    ) {
        parent::__construct($context);
        $this->uploader = $uploader;
    }

    /**
     * Upload file controller action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        try {
            $result = $this->uploader->saveFileToTmpDir('banner_image');

            $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);
    }

    /**
     * @return string
     */
    protected function getFieldName()
    {
        return $this->_request->getParam('banner_image');
    }
}

To inject uploader model to controller we define virtual type in di.xml. File etc/di.xml

  1. <?xml version="1.0"?>
  2. <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
  3. <virtualType name="MagebrewBannerUploader" type="Magebrew\ImageUploadFormField\Model\BannerUploader">
  4. <arguments>
  5. <argument name="baseTmpPath" xsi:type="const">Magebrew\ImageUploadFormField\Model\BannerUploader::IMAGE_TMP_PATH</argument>
  6. <argument name="basePath" xsi:type="const">Magebrew\ImageUploadFormField\Model\BannerUploader::IMAGE_PATH</argument>
  7. <argument name="allowedExtensions" xsi:type="array">
  8. <item name="jpg" xsi:type="string">jpg</item>
  9. <item name="jpeg" xsi:type="string">jpeg</item>
  10. <item name="gif" xsi:type="string">gif</item>
  11. <item name="png" xsi:type="string">png</item>
  12. </argument>
  13. </arguments>
  14. </virtualType>
  15. <type name="Magebrew\ImageUploadFormField\Controller\Adminhtml\Cmspage\Banner\Upload">
  16. <arguments>
  17. <argument name="uploader" xsi:type="object">MagebrewBannerUploader</argument>
  18. </arguments>
  19. </type>
  20. </config>
<?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="MagebrewBannerUploader" type="Magebrew\ImageUploadFormField\Model\BannerUploader">
        <arguments>
            <argument name="baseTmpPath" xsi:type="const">Magebrew\ImageUploadFormField\Model\BannerUploader::IMAGE_TMP_PATH</argument>
            <argument name="basePath" xsi:type="const">Magebrew\ImageUploadFormField\Model\BannerUploader::IMAGE_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="Magebrew\ImageUploadFormField\Controller\Adminhtml\Cmspage\Banner\Upload">
        <arguments>
            <argument name="uploader" xsi:type="object">MagebrewBannerUploader</argument>
        </arguments>
    </type>
</config>

Now we need to take care of saving banner image path to cms_page table on CMS page save and retrieving existing banner image in Admin CMS form. For this purpose we create plugin for Magento\Cms\Model\PageRepository and Magento\Cms\Model\Page\DataProvider. Here is how etc/adminhtml/di.xml file looks like:

  1. <?xml version="1.0"?>
  2. <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
  3. <type name="Magento\Cms\Model\PageRepository">
  4. <plugin name="magebrew_save_banner_image" type="Magebrew\ImageUploadFormField\Plugin\Cms\Model\PageRepository\BeforeSave\SaveBannerImagePlugin" />
  5. </type>
  6. <type name="Magebrew\ImageUploadFormField\Plugin\Cms\Model\PageRepository\BeforeSave\SaveBannerImagePlugin">
  7. <arguments>
  8. <argument name="uploader" xsi:type="object">MagebrewBannerUploader</argument>
  9. </arguments>
  10. </type>
  11. <type name="Magento\Cms\Model\Page\DataProvider">
  12. <plugin name="magebrew_add_banner_image_to_form" type="Magebrew\ImageUploadFormField\Plugin\Cms\Model\Page\DataProvider\AfterGetData\ModifyBannerDataPlugin"/>
  13. </type>
  14. </config>
<?xml version="1.0"?>

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Cms\Model\PageRepository">
        <plugin name="magebrew_save_banner_image" type="Magebrew\ImageUploadFormField\Plugin\Cms\Model\PageRepository\BeforeSave\SaveBannerImagePlugin" />
    </type>
    <type name="Magebrew\ImageUploadFormField\Plugin\Cms\Model\PageRepository\BeforeSave\SaveBannerImagePlugin">
        <arguments>
            <argument name="uploader" xsi:type="object">MagebrewBannerUploader</argument>
        </arguments>
    </type>
    <type name="Magento\Cms\Model\Page\DataProvider">
        <plugin name="magebrew_add_banner_image_to_form" type="Magebrew\ImageUploadFormField\Plugin\Cms\Model\Page\DataProvider\AfterGetData\ModifyBannerDataPlugin"/>
    </type>
</config>

SaveBannerImagePlugin, file Plugin/Cms/Model/PageRepository/BeforeSave/SaveBannerImagePlugin.php

  1. <?php
  2. declare(strict_types=1);
  3. namespace Magebrew\ImageUploadFormField\Plugin\Cms\Model\PageRepository\BeforeSave;
  4. use Magebrew\ImageUploadFormField\Model\BannerUploader;
  5. use Magento\Cms\Api\Data\PageInterface;
  6. use Magento\Cms\Model\PageRepository;
  7. use Magento\Framework\App\RequestInterface;
  8. /**
  9. * Class SaveBannerImagePlugin
  10. */
  11. class SaveBannerImagePlugin
  12. {
  13. /**
  14. * @var BannerUploader
  15. */
  16. private $uploader;
  17. /**
  18. * @var RequestInterface
  19. */
  20. private $request;
  21. /**
  22. * SaveBannerImagePlugin constructor.
  23. * @param RequestInterface $request
  24. * @param BannerUploader $uploader
  25. */
  26. public function __construct(
  27. RequestInterface $request,
  28. BannerUploader $uploader
  29. ) {
  30. $this->uploader = $uploader;
  31. $this->request = $request;
  32. }
  33. /**
  34. * Save
  35. *
  36. * @param PageRepository $subject
  37. * @param PageInterface $page
  38. * @return array
  39. * @throws \Magento\Framework\Exception\LocalizedException
  40. */
  41. public function beforeSave(
  42. PageRepository $subject,
  43. PageInterface $page
  44. ): array {
  45. $data = $page->getData();
  46. $key = 'banner_image';
  47. if (isset($data[$key]) && is_array($data[$key])) {
  48. if (!empty($data[$key]['delete'])) {
  49. $data[$key] = null;
  50. } else {
  51. if (isset($data[$key][0]['name']) && isset($data[$key][0]['tmp_name'])) {
  52. $image = $data[$key][0]['name'];
  53. $image = $this->uploader->moveFileFromTmp($image);
  54. $data[$key] = $image;
  55. } else {
  56. if (isset($data[$key][0]['url'])) {
  57. $data[$key] = basename($data[$key][0]['url']);
  58. }
  59. }
  60. }
  61. $page->setData($data);
  62. } else {
  63. $data[$key] = null;
  64. }
  65. return [$page];
  66. }
  67. }
<?php

declare(strict_types=1);

namespace Magebrew\ImageUploadFormField\Plugin\Cms\Model\PageRepository\BeforeSave;

use Magebrew\ImageUploadFormField\Model\BannerUploader;
use Magento\Cms\Api\Data\PageInterface;
use Magento\Cms\Model\PageRepository;
use Magento\Framework\App\RequestInterface;

/**
 * Class SaveBannerImagePlugin
 */
class SaveBannerImagePlugin
{
    /**
     * @var BannerUploader
     */
    private $uploader;

    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * SaveBannerImagePlugin constructor.
     * @param RequestInterface $request
     * @param BannerUploader $uploader
     */
    public function __construct(
        RequestInterface $request,
        BannerUploader $uploader
    ) {
        $this->uploader = $uploader;
        $this->request = $request;
    }

    /**
     * Save
     *
     * @param PageRepository $subject
     * @param PageInterface $page
     * @return array
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function beforeSave(
        PageRepository $subject,
        PageInterface $page
    ): array {
        $data = $page->getData();
        $key = 'banner_image';

        if (isset($data[$key]) && is_array($data[$key])) {
            if (!empty($data[$key]['delete'])) {
                $data[$key] = null;
            } else {
                if (isset($data[$key][0]['name']) && isset($data[$key][0]['tmp_name'])) {
                    $image = $data[$key][0]['name'];

                    $image = $this->uploader->moveFileFromTmp($image);
                    $data[$key] = $image;
                } else {
                    if (isset($data[$key][0]['url'])) {
                        $data[$key] = basename($data[$key][0]['url']);
                    }
                }
            }

            $page->setData($data);
        } else {
            $data[$key] = null;
        }

        return [$page];
    }
}

ModifyBannerDataPlugin, file Plugin/Cms/Model/Page/DataProvider/AfterGetData/ModifyBannerDataPlugin.php

  1. <?php
  2. declare(strict_types=1);
  3. namespace Magebrew\ImageUploadFormField\Plugin\Cms\Model\Page\DataProvider\AfterGetData;
  4. use Magebrew\ImageUploadFormField\Model\BannerUploader;
  5. use Magento\Cms\Model\Page\DataProvider;
  6. use Magento\Framework\UrlInterface;
  7. use Magento\Store\Model\StoreManagerInterface;
  8. /**
  9. * Class ModifyBannerDataPlugin
  10. */
  11. class ModifyBannerDataPlugin
  12. {
  13. /**
  14. * @var StoreManagerInterface
  15. */
  16. private $storeManager;
  17. /**
  18. * ModifyBannerDataPlugin constructor.
  19. * @param StoreManagerInterface $storeManager
  20. */
  21. public function __construct(StoreManagerInterface $storeManager)
  22. {
  23. $this->storeManager = $storeManager;
  24. }
  25. /**
  26. * @param DataProvider $subject
  27. * @param $loadedData
  28. * @return array
  29. * @throws \Magento\Framework\Exception\NoSuchEntityException
  30. */
  31. public function afterGetData(
  32. DataProvider $subject,
  33. $loadedData
  34. ) {
  35. /** @var array $loadedData */
  36. if (is_array($loadedData) && count($loadedData) == 1) {
  37. foreach ($loadedData as $key => $item) {
  38. if (isset($item['banner_image']) && $item['banner_image']) {
  39. $imageArr = [];
  40. $imageArr[0]['name'] = 'Image';
  41. $imageArr[0]['url'] = $this->storeManager->getStore()
  42. ->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) .
  43. BannerUploader::IMAGE_PATH . DIRECTORY_SEPARATOR . $item['banner_image'];
  44. $loadedData[$key]['banner_image'] = $imageArr;
  45. }
  46. }
  47. }
  48. return $loadedData;
  49. }
  50. }
<?php

declare(strict_types=1);

namespace Magebrew\ImageUploadFormField\Plugin\Cms\Model\Page\DataProvider\AfterGetData;

use Magebrew\ImageUploadFormField\Model\BannerUploader;
use Magento\Cms\Model\Page\DataProvider;
use Magento\Framework\UrlInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
 * Class ModifyBannerDataPlugin
 */
class ModifyBannerDataPlugin
{
    /**
     * @var StoreManagerInterface
     */
    private $storeManager;

    /**
     * ModifyBannerDataPlugin constructor.
     * @param StoreManagerInterface $storeManager
     */
    public function __construct(StoreManagerInterface $storeManager)
    {
        $this->storeManager = $storeManager;
    }

    /**
     * @param DataProvider $subject
     * @param $loadedData
     * @return array
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function afterGetData(
        DataProvider $subject,
        $loadedData
    ) {
        /** @var array $loadedData */
        if (is_array($loadedData) && count($loadedData) == 1) {
            foreach ($loadedData as $key => $item) {
                if (isset($item['banner_image']) && $item['banner_image']) {
                    $imageArr = [];
                    $imageArr[0]['name'] = 'Image';
                    $imageArr[0]['url'] = $this->storeManager->getStore()
                            ->getBaseUrl(UrlInterface::URL_TYPE_MEDIA) .
                        BannerUploader::IMAGE_PATH . DIRECTORY_SEPARATOR . $item['banner_image'];
                    $loadedData[$key]['banner_image'] = $imageArr;
                }
            }
        }

        return $loadedData;
    }
}

Actually that is it. Now we should have ability to upload and save CMS page banner image. If you also need instructions on how to display this banner on storefront check complete module on GitHub.