Magento2 | PWA | GraphQL

Magento 2 create new category EAV Image attribute with file upload and Display Image on Category page as well as on Category Top Menu


In this tutorial, you will learn how to Creating a new category EAV attribute with image upload button.

Let's start it by creating custom module.

Find Complete Module on Github

Create folder in app/code/Magelearn/CustomImage

Step 1: Create module registration file

Add registration.php file in it:
<?php
/**
 * Copyright ©  All rights reserved.
 * See COPYING.txt for license details.
 */
use Magento\Framework\Component\ComponentRegistrar;

ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magelearn_CustomImage', __DIR__);
Add composer.json file in it:
{
    "name": "magelearn/module-CustomImage",
    "description": "Magento2 image attachment in category add/edit admin form and display image on category page",
    "type": "magento2-module",
    "license": "OSL-3.0",
    "authors": [
        {
            "email": "info@mage2gen.com",
            "name": "Mage2Gen"
        },
        {
            "email": "vijaymrami@gmail.com",
            "name": "vijay rami"
        }
    ],
    "minimum-stability": "dev",
    "require": {},
    "autoload": {
        "files": [
            "registration.php"
        ],
        "psr-4": {
            "Magelearn\\CustomImage\\": ""
        }
    }
}
Add etc/module.xml file in it:
<?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_CustomImage" setup_version="1.0.0">
</module> </config>
Create InstallData.php inside Magelearn/CustomImage/Setup
<?php
namespace Magelearn\CustomImage\Setup;

use Magento\Eav\Setup\EavSetup;
use Magento\Eav\Setup\EavSetupFactory;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface;

/**
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * EAV setup factory.
     *
     * @var EavSetupFactory
     */
    private $_eavSetupFactory;
    protected $categorySetupFactory;

    /**
     * Init.
     *
     * @param EavSetupFactory $eavSetupFactory
     */
    public function __construct(EavSetupFactory $eavSetupFactory, \Magento\Catalog\Setup\CategorySetupFactory $categorySetupFactory)
    {
        $this->_eavSetupFactory = $eavSetupFactory;
        $this->categorySetupFactory = $categorySetupFactory;
    }

    /**
     * {@inheritdoc}
     *
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     */
    public function install(
        ModuleDataSetupInterface $setup,
        ModuleContextInterface $context
    ) {
        /** @var EavSetup $eavSetup */
        $eavSetup = $this->_eavSetupFactory->create(['setup' => $setup]);
        $setup = $this->categorySetupFactory->create(['setup' => $setup]);         
        $setup->addAttribute(
            \Magento\Catalog\Model\Category::ENTITY, 'custom_image', [
                'type' => 'varchar',
                'label' => 'Custom Image',
                'input' => 'image',
                'backend' => 'Magento\Catalog\Model\Category\Attribute\Backend\Image',
                'visible' =>  true,
                'required' => false,
                'sort_order' => 9,
                'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
                'group' => 'General Information'
            ]
        );
    }
}
Here we can use Backend option and Input type as Image itself, we can control that later in UI component and di(Dependency Injection).

Create category_form.xml inside Magelearn/CustomImage/view/adminhtml/ui_component
<?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="custom_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">category</item>
	                <item name="label" xsi:type="string" translate="true">Custom Category 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="required" xsi:type="boolean">false</item>
	                <item name="sortOrder" xsi:type="number">40</item>
	                <item name="uploaderConfig" xsi:type="array">
	                    <item name="url" xsi:type="url" path="customimage/category_image/upload"/>
	                </item>
	            </item>
	        </argument>
	    </field>
	</fieldset>
</form>
Create di.xml file inside Magelearn/CustomImage/etc

Here we can include the file extension types inside allowedExtensions section.
<?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\CustomImage\Controller\Adminhtml\Category\Image\Upload">
    <arguments>
        <argument name="imageUploader" xsi:type="object">Magento\Catalog\CategoryImageUpload</argument>
    </arguments>
</type>
<virtualType name="Magento\Catalog\CategoryImageUpload" type="Magento\Catalog\Model\ImageUploader">
    <arguments>
        <argument name="baseTmpPath" xsi:type="string">catalog/tmp/category</argument>
        <argument name="basePath" xsi:type="string">catalog/category</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>
<preference for="Magento\Catalog\Model\Category\DataProvider" type="Magelearn\CustomImage\Model\Category\DataProvider" />
</config>
Create routes.xml file inside Magelearn/CustomImage/etc/adminhtml 
<?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="customimage" frontName="customimage">
            <module name="Magelearn_CustomImage" before="Magento_Backend" />
        </route>
    </router>
</config>
Create Upload.php file inside Magelearn/CustomImage/Controller/Adminhtml/Category/Image
This is a Controller File Where we need to perform our Custom File Upload action
<?php
namespace Magelearn\CustomImage\Controller\Adminhtml\Category\Image;

use Magento\Framework\Controller\ResultFactory;

/**
 * Category Image Upload Controller
 */
class Upload extends \Magento\Backend\App\Action
{
    /**
     * Image uploader
     *
     * @var \Magento\Catalog\Model\ImageUploader
     */
    protected $imageUploader;

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

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

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

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

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

    /**
     * Upload constructor.
     *
     * @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,
        \Magento\MediaStorage\Model\File\UploaderFactory $uploaderFactory,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase,
        \Psr\Log\LoggerInterface $logger
    ) {
        parent::__construct($context);
        $this->imageUploader = $imageUploader;
        $this->uploaderFactory = $uploaderFactory;
        $this->mediaDirectory = $filesystem->getDirectoryWrite(\Magento\Framework\App\Filesystem\DirectoryList::MEDIA);
        $this->storeManager = $storeManager;
        $this->coreFileStorageDatabase = $coreFileStorageDatabase;
        $this->logger = $logger;
    }

    /**
     * Check admin permissions for this controller
     *
     * @return boolean
     */
    protected function _isAllowed()
    {
        return $this->_authorization->isAllowed('Magelearn_CustomImage::category');
    }

    /**
     * Upload file controller action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $imageId = $this->_request->getParam('param_name', 'custom_image');

        try {
            $result = $this->imageUploader->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);
    }
}
Finally, Create DataProvider.php file inside Magelearn/CustomImage/Model/Category
<?php
namespace Magelearn\CustomImage\Model\Category;

use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\Category\FileInfo;

class DataProvider extends \Magento\Catalog\Model\Category\DataProvider
{

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

    public function getData()
    {
        $data = parent::getData();
        $category = $this->getCurrentCategory();
        if ($category) {
            $categoryData = $category->getData();
            $categoryData = $this->addUseConfigSettings($categoryData);
            $categoryData = $this->filterFields($categoryData);
            //$categoryData = $this->convertValues($category, $categoryData);

            $this->loadedData[$category->getId()] = $categoryData;
        }

        if (isset($data['custom_image'])) {
            unset($data['custom_image']);
        }

        return $data;
    }

    protected function getFieldsMap()
    {
        $fields = parent::getFieldsMap();
        $fields['content'][] = 'custom_image'; // custom image field

        return $fields;
    }

    /**
     * Get FileInfo instance
     *
     * @return FileInfo
     *
     * @deprecated 101.1.0
     */
    private function getFileInfo()
    {
        if ($this->fileInfo === null) {
            $this->fileInfo = ObjectManager::getInstance()->get(FileInfo::class);
        }
        return $this->fileInfo;
    }

}

Now it is all done! Once the code is run, an option to upload the file will be included. This is how the exact output looks like.


Now, we will check how to display this "custom_image" on Category view page.

Create catalog_category_view.xml file inside Magelearn/CustomImage/view/frontend/layout

<?xml version="1.0"?>
<!--
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
    <body>
        <referenceContainer name="category.view.container">
            <block class="Magelearn\CustomImage\Block\Image" name="magelearn_category_image" template="Magelearn_CustomImage::image.phtml">
                <arguments>
                    <argument name="image_code" xsi:type="string">custom_image</argument>
                    <argument name="css_class" xsi:type="string">custom_image</argument>
                </arguments>
            </block>
        </referenceContainer>
    </body>
</page>
Here we define our custom Block as well our custom template file.
Now Create Block file Image.php file inside Magelearn/CustomImage/Block
<?php
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

/**
 * Product description block
 *
 * @author     Magento Core Team <core@magentocommerce.com>
 */
namespace Magelearn\CustomImage\Block;

use Magento\Catalog\Model\Product;

class Image extends \Magento\Framework\View\Element\Template
{
    /**
     * @var Product
     */
    protected $_category = null;

    /**
     * Core registry
     *
     * @var \Magento\Framework\Registry
     */
    protected $_coreRegistry = null;

    /**
     * @var \SR\CategoryImage\Helper\Category
     */
    protected $_categoryHelper;

    /**
     * @param \Magento\Framework\View\Element\Template\Context $context
     * @param \Magento\Framework\Registry $registry
     * @param array $data
     */
    public function __construct(
        \Magento\Framework\View\Element\Template\Context $context,
        \Magento\Framework\Registry $registry,
        \Magelearn\CustomImage\Helper\Category $categoryHelper,
        array $data = []
    )
    {
        $this->_coreRegistry = $registry;
        $this->_categoryHelper = $categoryHelper;
        parent::__construct($context, $data);
    }


    /**
     * Retrieve current category model object
     *
     * @return \Magento\Catalog\Model\Category
     */
    public function getCurrentCategory()
    {
        if (!$this->_category) {
            $this->_category = $this->_coreRegistry->registry('current_category');

            if (!$this->_category) {
                throw new \Magento\Framework\Exception\LocalizedException(__('Category object could not be found in core registry'));
            }
        }
        return $this->_category;
    }


    public function getImageUrl()
    {

        $imageCode = $this->hasImageCode() ? $this->getImageCode() : 'image';

        $image = $this->getCurrentCategory()->getData($imageCode);

        return $this->_categoryHelper->getImageUrl($image);
    }
}
Also Create Helper file Category.php file inside Magelearn/CustomImage/Helper
<?php

namespace Magelearn\CustomImage\Helper;

use Magento\Framework\App\Helper\AbstractHelper;

class Category extends AbstractHelper
{
    /**
     * @return array
     */
    public function getAdditionalImageTypes()
    {
        return array('custom_image');
    }

    /**
     * Retrieve image URL
     * @param $image
     * @return string
     */
    public function getImageUrl($image)
    {
        $url = false;
        //$image = $this->getImage();
        if ($image) {
            if (is_string($image)) {
                $url = $this->_urlBuilder->getBaseUrl() . $image;
                //$url = $image;
            } else {
                throw new \Magento\Framework\Exception\LocalizedException(
                    __('Something went wrong while getting the image url.')
                );
            }
        }
        return $url;
    }

}
And finally, Create templete file image.phtml inside Magelearn/CustomImage/view/frontend/templates
<?php
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */

// @codingStandardsIgnoreFile

?>
<?php
/**
 * Category image template
 *
 * @var $block \Magelearn\CustomImage\Block\Image
 */
?>
<?php
$_helper    = $this->helper('Magento\Catalog\Helper\Output');
$_category = $block->getCurrentCategory();
$_imgHtml   = '';
if ($_imgUrl = $block->getImageUrl()) {
    $_imgHtml = '<div class="' . $block->getCssClass() . '"><img src="' . $_imgUrl . '" alt="' . $block->escapeHtml($_category->getName()) . '" title="' . $block->escapeHtml($_category->getName()) . '"/></div>';
    $_imgHtml = $_helper->categoryAttribute($_category, $_imgHtml, 'image');
    /* @escapeNotVerified */ echo $_imgHtml;
}
?>
This will display on front end as per below:

Now we will check another interesting part of this blog.
Where we will display this custom image on Category Top Menu.

Modify di.xml file inside Magelearn/CustomImage/etc
<?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\CustomImage\Controller\Adminhtml\Category\Image\Upload">
    <arguments>
        <argument name="imageUploader" xsi:type="object">Magento\Catalog\CategoryImageUpload</argument>
    </arguments>
</type>
<virtualType name="Magento\Catalog\CategoryImageUpload" type="Magento\Catalog\Model\ImageUploader">
    <arguments>
        <argument name="baseTmpPath" xsi:type="string">catalog/tmp/category</argument>
        <argument name="basePath" xsi:type="string">catalog/category</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>
<preference for="Magento\Catalog\Model\Category\DataProvider" type="Magelearn\CustomImage\Model\Category\DataProvider" />
<preference for="Magento\Theme\Block\Html\Topmenu" type="Magelearn\CustomImage\Block\Html\Topmenu" />
</config>
Create file 
Topmenu.php inside Magelearn/CustomImage/Block/Html
<?php
namespace Magelearn\CustomImage\Block\Html;
  
use Magento\Framework\Data\Tree\Node;
use Magento\Framework\DataObject;
use Magento\Framework\View\Element\Template;
use Magento\Framework\Data\Tree\NodeFactory;
use Magento\Framework\Data\TreeFactory;
  
class Topmenu extends \Magento\Theme\Block\Html\Topmenu
{
    protected $_categoryFactory;
    protected $_storeManager;
     
    public function __construct(
        Template\Context $context,
        NodeFactory $nodeFactory,
        TreeFactory $treeFactory,
        \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory $collecionFactory,
        \Magento\Store\Model\StoreManagerInterface $storeManager,
        array $data = []
        ) {
            parent::__construct($context, $nodeFactory, $treeFactory, $data);
            $this->_categoryFactory = $collecionFactory;
            $this->_storeManager = $storeManager;
         
    }
 
    protected function _getHtml(
        \Magento\Framework\Data\Tree\Node $menuTree,
        $childrenWrapClass,
        $limit,
        array $colBrakes = []
    ) {
        $html = '';
 
        $children = $menuTree->getChildren();
        $parentLevel = $menuTree->getLevel();
        $childLevel = $parentLevel === null ? 0 : $parentLevel + 1;
 
        $counter = 1;
        $itemPosition = 1;
        $childrenCount = $children->count();
 
        $parentPositionClass = $menuTree->getPositionClass();
        $itemPositionClassPrefix = $parentPositionClass ? $parentPositionClass . '-' : 'nav-';
 
        /** @var \Magento\Framework\Data\Tree\Node $child */
        foreach ($children as $child) {
            if ($childLevel === 0 && $child->getData('is_parent_active') === false) {
                continue;
            }
            $child->setLevel($childLevel);
            $child->setIsFirst($counter == 1);
            $child->setIsLast($counter == $childrenCount);
            $child->setPositionClass($itemPositionClassPrefix . $counter);
 
            $outermostClassCode = '';
            $outermostClass = $menuTree->getOutermostClass();
 
            if ($childLevel == 0 && $outermostClass) {
                $outermostClassCode = ' class="' . $outermostClass . '" ';
                $currentClass = $child->getClass();
 
                if (empty($currentClass)) {
                    $child->setClass($outermostClass);
                } else {
                    $child->setClass($currentClass . ' ' . $outermostClass);
                }
            }
 
            if (is_array($colBrakes) && count($colBrakes) && $colBrakes[$counter]['colbrake']) {
                $html .= '</ul></li><li class="column"><ul>';
            }
             
            $html .= '<li ' . $this->_getRenderedMenuItemAttributes($child) . '>';
            $html .= '<a href="' . $child->getUrl() . '" ' . $outermostClassCode . '><span>' . $this->escapeHtml(
                $child->getName()
                ) . $this->getCustomImage($child) . '</span></a>' . $this->_addSubMenu(
                $child,
                $childLevel,
                $childrenWrapClass,
                $limit
            ) . '</li>';
            $itemPosition++;
            $counter++;
        }
 
        if (is_array($colBrakes) && count($colBrakes) && $limit) {
            $html = '<li class="column"><ul>' . $html . '</ul></li>';
        }
 
        return $html;
    }
 
    public function getCustomImage($childObj)
    {
        if (!($childObj->getIsCategory() && $childObj->getLevel() == 1)) {
            return false;
        }
 
        $store = $this->_storeManager->getStore();
        $mediaBaseUrl = $store->getBaseUrl();
 
        $catNodeArr = explode('-', $childObj->getId());
        $catId = end($catNodeArr);
         
        $collection = $this->_categoryFactory
                ->create()
                ->addAttributeToSelect('custom_image')
                ->addAttributeToFilter('entity_id',['eq'=>$catId])
                ->setPageSize(1);
         
        if ($collection->getSize() && $collection->getFirstItem()->getCustomImage()) {
            $catCustomImageUrl = $mediaBaseUrl
                        . $collection->getFirstItem()->getCustomImage();
 
            return '<span class="cat-thumbnail"><img src="'.$catCustomImageUrl.'"></span>';
        }
    }
 
}
It will display on Category top menu as per below:


0 Comments On "Magento 2 create new category EAV Image attribute with file upload and Display Image on Category page as well as on Category Top Menu"

Back To Top