Question

How to add image upload field to a CMS page and show this on top of my CMS page as Hero image like shown below?

Maybe there are simple solutions/tutorials, but I can't find right one.

Hero image example

Was it helpful?

Solution

First you have to create your own module to override the save action and dataProvider for cms page. If you don't know how to create module Please refer this link http://inchoo.net/magento-2/how-to-create-a-basic-module-in-magento-2/.

After creating a module, follow the following steps:

Step 1. Add column for custom image on cms_page table

Create InstallSchema.php under [Vendor][Module]\Setup. Your file location will be [Vendor][Module]\Setup\InstallSchema.php

namespace [Vendor]\[Module]\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;

class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();
        $connection = $installer->getConnection();

        $connection->addColumn('cms_page','your_image_field_name',['type' =>\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,'comment' => 'Your Image Field Name']);
        $installer->endSetup();
    }
}

If your module is already activated, please delete your module from setup_module table.

After Creating InstallSchema.php, open your terminal and hit following commands. It will add column to database table and clear caches.

php bin/magento cache:flush;
php bin/magento setup:upgrade;
php bin/magento setup:di:compile;
rm -rf var/generation/* var/di/* var/cache/* var/page_cache/* ;
rm -rf pub/static/frontend pub/static/_requirejs pub/static/adminhtml ;

Step 2. Add image field

Create cms_page_form.xml inside the folder [Vendor]/[Module]/view/adminhtml/ui_component . Your file location will be like [Vendor]/[Module]/view/adminhtml/ui_component/cms_page_form.xml. Add the following code.

<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<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="your_image_field_name">
            <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">category</item>
                    <item name="label" xsi:type="string" translate="true">Your image field Name</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="required" xsi:type="boolean">false</item>
                    <item name="uploaderConfig" xsi:type="array">
                        <item name="url" xsi:type="url" path="[module]/cms_heroimage/upload"/>
                    </item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

This will generate field under content tab.

Step 3. Add a route for upload action.

Create routes.xml under [Vendor]/[Module]/etc/adminhtml. Your file location will be [Vendor]/[Module]/etc/adminhtml/routes.xml.

Add the following code.

<?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="[module]" frontName="[module]">
            <module name="[Vendor]_[Module]" />
        </route>
    </router>
</config>

Step 4. Create Controller for upload action.

Create Upload.php on [Vendor]/[Module]/Controller/Adminhtml/Cms/Heroimage. Your file location will be [Vendor]/[Module]/Controller/Adminhtml/Cms/Heroimage/Upload.php.

Add following code.

namespace [Vendor]\[Module]\Controller\Adminhtml\Cms\Heroimage;

use Magento\Framework\Controller\ResultFactory;

class Upload extends \Magento\Backend\App\Action
{
    /**
     * Image uploader
     *
     * @var \[Vendor]\[Module]\Model\ImageUploader
     */
    protected $imageUploader;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Catalog\Model\ImageUploader $imageUploader
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Catalog\Model\ImageUploader $imageUploader
    ) {
        parent::__construct($context);
        $this->imageUploader = $imageUploader;
    }


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

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

Step 5. Override Save action.

Create di.xml under [Vendor][Module]\etc\adminhtml. Your file location will be [Vendor][Module]\etc\adminhtml\di.xml add following line to override Save Action and DataProvider for cms page.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Cms\Controller\Adminhtml\Page\Save" type="[Vendor]\[Module]\Controller\Adminhtml\Cms\Page\Save" />
    <preference for="Magento\Cms\Model\Page\DataProvider" type="[Vendor]\[Module]\Model\Cms\Page\DataProvider" />
</config>

Step 6. Create Save.php file. Create Save.php file under [Vendor][Module]\Controller\Adminhtml\Cms\Page. Your file location will be [Vendor][Module]\Controller\Adminhtml\Cms\Page\Save.php. Add Following code.

namespace [Vendor]\[Module]\Controller\Adminhtml\Cms\Page;

use Magento\Backend\App\Action;
use Magento\Cms\Model\Page;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Exception\LocalizedException;

class Save extends \Magento\Cms\Controller\Adminhtml\Page\Save
{
    /**
     * Authorization level of a basic admin session
     *
     * @see _isAllowed()
     */
    const ADMIN_RESOURCE = 'Magento_Cms::save';

    /**
     * @var PostDataProcessor
     */
    protected $dataProcessor;

    /**
     * @var DataPersistorInterface
     */
    protected $dataPersistor;


    /**
     * Save action
     *
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $data = $this->getRequest()->getPostValue();


        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        if ($data) {
            $data = $this->dataProcessor->filter($data);
            if (isset($data['is_active']) && $data['is_active'] === 'true') {
                $data['is_active'] = Page::STATUS_ENABLED;
            }
            if (empty($data['page_id'])) {
                $data['page_id'] = null;
            }

            /** @var \Magento\Cms\Model\Page $model */
            $model = $this->_objectManager->create('Magento\Cms\Model\Page');

            $id = $this->getRequest()->getParam('page_id');
            if ($id) {
                $model->load($id);
            }

            // Add custom image field to data
            if(isset($data['your_image_field_name']) && is_array($data['your_image_field_name'])){
                $data['your_image_field_name']=$data['your_image_field_name'][0]['name'];
            }


            $model->setData($data);

            $this->_eventManager->dispatch(
                'cms_page_prepare_save',
                ['page' => $model, 'request' => $this->getRequest()]
            );

            if (!$this->dataProcessor->validate($data)) {
                return $resultRedirect->setPath('*/*/edit', ['page_id' => $model->getId(), '_current' => true]);
            }

            try {
                $model->save();
                $this->messageManager->addSuccess(__('You saved the page.'));
                $this->dataPersistor->clear('cms_page');
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['page_id' => $model->getId(), '_current' => true]);
                }
                return $resultRedirect->setPath('*/*/');
            } catch (LocalizedException $e) {
                $this->messageManager->addError($e->getMessage());
            } catch (\Exception $e) {
                $this->messageManager->addException($e, __('Something went wrong while saving the page.'));
            }

            $this->dataPersistor->set('cms_page', $data);
            return $resultRedirect->setPath('*/*/edit', ['page_id' => $this->getRequest()->getParam('page_id')]);
        }
        return $resultRedirect->setPath('*/*/');
    }
}
?>

Step 7. Create DataProvider.php

Create DataProvider.php under [Vendor]\Module\Model\Cms\Page. Your File location will be [Vendor]\Module\Model\Cms\Page\DataProvider.php

<?php
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace [Vendor]\[Module]\Model\Cms\Page;

use Magento\Cms\Model\ResourceModel\Page\CollectionFactory;
use Magento\Framework\App\Request\DataPersistorInterface;

/**
 * Class DataProvider
 */
class DataProvider extends \Magento\Cms\Model\Page\DataProvider
{

    /**
     * Get data
     *
     * @return array
     */
    public function getData()
    {
        if (isset($this->loadedData)) {
            return $this->loadedData;
        }
        $items = $this->collection->getItems();
        /** @var $page \Magento\Cms\Model\Page */
        foreach ($items as $page) {
            $this->loadedData[$page->getId()] = $page->getData();
        }

        $data = $this->dataPersistor->get('cms_page');


        if (!empty($data)) {
            $page = $this->collection->getNewEmptyItem();

            $page->setData($data);
            $this->loadedData[$page->getId()] = $page->getData();
            $this->dataPersistor->clear('cms_page');
        }
        /* For Modify  You custom image field data */
        if(!empty($this->loadedData[$page->getId()]['your_image_field_name'])){
            $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
            $storeManager = $objectManager->get('Magento\Store\Model\StoreManagerInterface');
            $currentStore = $storeManager->getStore();
            $media_url=$currentStore->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);

            $image_name=$this->loadedData[$page->getId()]['your_image_field_name'];
            unset($this->loadedData[$page->getId()]['your_image_field_name']);
            $this->loadedData[$page->getId()]['your_image_field_name'][0]['name']=$image_name;
            $this->loadedData[$page->getId()]['your_image_field_name'][0]['url']=$media_url."cms/hero/tmp/".$image_name;
        }
        return $this->loadedData;
    }
}

Note. Please Replace "your_image_field_name" with your field name.

enter image description here

OTHER TIPS

I would add to etc/adminhtml/di.xml

<virtualType name="Magento\Catalog\CategoryImageUpload" type="Magento\Catalog\Model\ImageUploader">
    <arguments>
        <argument name="baseTmpPath" xsi:type="string">your path here</argument>
        <argument name="basePath" xsi:type="string">your path here</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="[Vendor]\[Module]\Controller\Adminhtml\Cms\Heroimage\Upload">
    <arguments>
        <argument name="imageUploader" xsi:type="object">Magento\Catalog\CategoryImageUpload</argument>
    </arguments>
</type>

and

replace in DataProvider.php

 if(!empty($this->loadedData[$page->getId()]['your_image_field_name']))
to
 if (!empty($this->loadedData))

On frontend, as i was getting js error in file-uploader.js:79 Uncaught TypeError: value.map

I used this in DataProvider.php

namespace VENDOR\MODULE\Model\Cms\Page;

use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Cms\Model\ResourceModel\Page\CollectionFactory;
use Magento\Store\Model\StoreManagerInterface;

class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
{

    private $loadedData;

    /**
     * @var \Magento\Framework\App\Request\DataPersistorInterface
     */
    private $dataPersistor;

    /**
     * @var Magento\Cms\Model\ResourceModel\Page\CollectionFactory
     */
    public $collection;

    /**
     * Constructor
     *
     * @param string $name
     * @param string $primaryFieldName
     * @param string $requestFieldName
     * @param CollectionFactory $blockCollectionFactory
     * @param DataPersistorInterface $dataPersistor
     * @param array $meta
     * @param array $data
     */
    public function __construct(
        $name,
        $primaryFieldName,
        $requestFieldName,
        CollectionFactory $collectionFactory,
        DataPersistorInterface $dataPersistor,
        StoreManagerInterface $storeManager,
        array $meta = [],
        array $data = []
    ) {
        $this->collection = $collectionFactory->create();
        $this->dataPersistor = $dataPersistor;
        $this->storeManager = $storeManager;
        parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
    }

    /**
     * Get data
     *
     * @return array
     */
    public function getData()
    {
        if (isset($this->loadedData)) {
            return $this->loadedData;
        }

        $items = $this->collection->getItems();
        foreach ($items as $model) {
            $this->loadedData[$model->getId()] = $model->getData();
            // echo $model->getCmsImage();exit();
            if ($model->getCmsImage()) {
                $m['cms_image'][0]['name'] = $model->getCmsImage();
                $m['cms_image'][0]['url'] = $this->getMediaUrl().$model->getCmsImage();
                $fullData = $this->loadedData;
                $this->loadedData[$model->getId()] = array_merge($fullData[$model->getId()], $m);
            }
        }

        $data = $this->dataPersistor->get('cms_page');

        if (!empty($data)) {
            $model = $this->collection->getNewEmptyItem();
            $model->setData($data);
            $this->loadedData[$model->getId()] = $model->getData();
            $this->dataPersistor->clear('cms_page');
        }

        return $this->loadedData;
    }

    public function getMediaUrl()
    {
        $mediaUrl = $this->storeManager->getStore()
            ->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA).'cms/tmp/image/';
        return $mediaUrl;
    }
}

No need to overwrite Magento\Cms\Controller\Adminhtml\Page\Save

you can use an observer

create file <Venodr>/<ModueName>/etc/adminhtml/events.xml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Event/etc/events.xsd">
    <event name="cms_page_prepare_save">
        <observer name="custom_observer_cms_page_prepare_save" instance="<Venodr>\<ModueName>\Observer\SaveImageObserver" />
    </event>
</config>

then create the observer file <Venodr>/<ModueName>/Observer/SaveImageObserver.php

namespace <Venodr>\<ModueName>\Observer;

class SaveImageObserver implements \Magento\Framework\Event\ObserverInterface
{
    public function execute(\Magento\Framework\Event\Observer $observer)
    {
        $pageModel = $observer->getData('page');
  
        $imageAttributes = ['custom_image'];
        $data = $observer->getData('request')->getPostValue();
    
        foreach($imageAttributes as $attributeCode){
            if(
                isset($data[$attributeCode])
                && is_array($data[$attributeCode])
            ) {
                $pageModel->setData($attributeCode, $data[$attributeCode][0]['name']);
            }
        }

        return $this;
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with magento.stackexchange
scroll top