It’s much easier to add “Image” attribute during the upgrade or installation process of your module in the following files.
app/code/[ Vendor ]/[ Module ]/Setup/InstallData.php
Or here:
app/code/[ Vendor ]/[ Module ]/Setup/UpgradeData.php
Let me show you how to perform it during upgrade
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
public function __construct(\Magento\Eav\Setup\EavSetupFactory $eavSetupFactory) { $this->eavSetupFactory = $eavSetupFactory; } public function upgrade(ModuleDataSetupInterface $setup, ModuleContextInterface $context) { $setup->startSetup(); if (version_compare($context->getVersion(), '9.0.1', '<')) { $eavSetup = $this->eavSetupFactory->create(['setup' => $setup]); $eavSetup->addAttribute( \Magento\Catalog\Model\Category::ENTITY, 'image_thumb', [ 'type' => 'varchar', 'label' => 'Image - Thumb', 'input' => 'image', 'required' => false, 'sort_order' => 6, 'backend' => '[ Vendor ]\[ Module ]\Model\Category\Attribute\Backend\Thumb', 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE, 'group' => 'General Information', ] ); } $setup->endSetup(); } |
The second step you have to do is to add this attribute to a category form:
app/code/[ Vendor ]/[ Module ]/view/adminhtml/ui_component/category_form.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?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"> <argument name="data" xsi:type="array"> <item name="config" xsi:type="array"> <item name="label" xsi:type="string" translate="true">Content</item> <item name="collapsible" xsi:type="boolean">true</item> <item name="sortOrder" xsi:type="number">99</item> </item> </argument> <field name="image_thumb"> <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">Image - Thumb</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="[ module ]/category_thumb/upload"/> </item> </item> </argument> </field> </fieldset> </form> |
It would be enough for regular attributes, but not in case of the uploaded “image” attribute. Let’s find out what else we should do to add this attribute properly.
We have already defined the backend model:
1 |
'backend' => '[ Vendor ]\[ Module ]\Model\Category\Attribute\Backend\Thumb', |
And we have defined another URL for the upload:
1 |
<item name="url" xsi:type="url" path="[ module ]/category_thumb/upload"/> |
Probably you are wondering, why do we need another Controller and a new Model? Why can’t we use the default controller and model, that is already defined in the “Image” attribute:
vendor/magento/module-catalog/Model/Category/Attribute/Backend/Image.php
vendor/magento/module-catalog/Controller/Adminhtml/Category/Image/Upload.php
The answer is pretty simple: Magento 2.1.2 still has some versatility issues and code of attribute is placed right in the files. But we have our own attribute, that’s why we should create a new one:
app/code/[ Vendor ]/[ Module ]/Controller/Adminhtml/Category/Thumb/Upload.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?php namespace [ Vendor ]\[ Module ]\Controller\Adminhtml\Category\Thumb; use Magento\Framework\Controller\ResultFactory; class Upload extends \Magento\Backend\App\Action { protected $imageUploader; public function __construct( \Magento\Backend\App\Action\Context $context, \Magento\Catalog\Model\ImageUploader $imageUploader ) { parent::__construct($context); $this->imageUploader = $imageUploader; } protected function _isAllowed() { return $this->_authorization->isAllowed('Magento_Catalog::categories'); } public function execute() { try { $result = $this->imageUploader->saveFileToTmpDir('image_thumb'); $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); } } |
As you can see, the most important thing here is to specify the field in which the uploaded file $this->imageUploader->saveFileToTmpDir(‘image_thumb’) should be searched.
This Controller uploads an image to a temporary directory with the AJAX request. And shows its thumbnail in the admin panel even before the category has been saved.
Further, we need to define a model, which allows us to save the uploaded file, but now during the category saving process.
app/code/[ Vendor ]/[ Module ]/Model/Category/Attribute/Backend/Thumb.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
<?php namespace [ Vendor ]\[ Module ]\Model\Category\Attribute\Backend; class Thumb extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend { protected $_uploaderFactory; protected $_filesystem; protected $_fileUploaderFactory; protected $_logger; private $imageUploader; public function __construct( \Psr\Log\LoggerInterface $logger, \Magento\Framework\Filesystem $filesystem, \Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory ) { $this->_filesystem = $filesystem; $this->_fileUploaderFactory = $fileUploaderFactory; $this->_logger = $logger; } private function getImageUploader() { if ($this->imageUploader === NULL) { $this->imageUploader = \Magento\Framework\App\ObjectManager::getInstance()->get( '[ Vendor ]\[ Module ]\CategoryThumbUpload' ); } return $this->imageUploader; } public function beforeSave($object) { $attrCode = $this->getAttribute()->getAttributeCode(); if (!$object->hasData($attrCode)) { $object->setData($attrCode, NULL); } else { $values = $object->getData($attrCode); if (is_array($values)) { if (!empty($values['delete'])) { $object->setData($attrCode, NULL); } else { if (isset($values[0]['name']) && isset($values[0]['tmp_name'])) { $object->setData($attrCode, $values[0]['name']); } else { // don't update } } } } return $this; } public function afterSave($object) { $image = $object->getData($this->getAttribute()->getName(), NULL); if ($image !== NULL) { try { $this->getImageUploader()->moveFileFromTmp($image); } catch (\Exception $e) { $this->_logger->critical($e); } } return $this; } } |
After the AJAX request, imageUploader returns not only an image itself, but an array with the updated data for the category object. And we should write another string to add the attribute to varchar.
beforeSave – transforms received array to a file name.
afterSave – transfers a file from temporary category to a constant one.
But there is more to come. We haven’t defined those file types, that could be uploaded. And those categories in which should use the uploaded image.
However, we already specified the backend model for these configurations. They are defined in the
app/code/[ Vendor ]/[ Module ]/etc/di.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<?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="[ Vendor ]\[ Module ]\CategoryThumbUpload" 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> <type name="[ Vendor ]\[ Module ]\Controller\Adminhtml\Category\Thumb\Upload"> <arguments> <argument name="imageUploader" xsi:type="object">[ Vendor ]\[ Module ]\CategoryThumbUpload</argument> </arguments> </type> <preference for="Magento\Catalog\Model\Category\DataProvider" type="[ Vendor ]\[ Module ]\Model\Category\DataProvider" /> </config> |
It seems that we finally finished, but after image uploading and category saving admin panel is getting stucked.
Magento Webdesign
Take your online store to the next level with BelVG Magento Webdesign
Visit the pageAdditionally we should define the rewrite for DataProvider file in the di.xml: app/code/[ Vendor ]/[ Module ]/Model/Category/DataProvider.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<?php namespace [ Vendor ]\[ Module ]\Model\Category; class DataProvider extends \Magento\Catalog\Model\Category\DataProvider { protected function addUseDefaultSettings($category, $categoryData) { $data = parent::addUseDefaultSettings($category, $categoryData); if (isset($data['image_thumb'])) { unset($data['image_thumb']); $objectManager = \Magento\Framework\App\ObjectManager::getInstance(); $helper = $objectManager->get('\[ Vendor ]\[ Module ]\Helper\Data'); $data['image_thumb'][0]['name'] = $category->getData('image_thumb'); $data['image_thumb'][0]['url'] = $helper->getCategoryThumbUrl($category); } return $data; } protected function getFieldsMap() { $fields = parent::getFieldsMap(); $fields['content'][] = 'image_thumb'; // NEW FIELD return $fields; } } |
And in the end, we have to display this image on the frontend. There is only a file name in the category data. And the following function will help to create a full URL for it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public function getCategoryThumbUrl($category) { $url = false; $image = $category->getImageThumb(); if ($image) { if (is_string($image)) { $url = $this->_storeManager->getStore()->getBaseUrl( \Magento\Framework\UrlInterface::URL_TYPE_MEDIA ) . 'catalog/category/' . $image; } else { throw new \Magento\Framework\Exception\LocalizedException( __('Something went wrong while getting the image url.') ); } } return $url; } |
Check also:
Adding Image Attribute To a Category in Magento 2: Alternative Version
Customer Images Uploader
Take your online store to the new level with this quality Magento extension by BelVG
Download hereNeed to improve product management mechanism and categories visibility? Check out BelVG’s Groups Catalog for Magento 2 extension.
Hi, Jay
Thanks for the comment. Unfortunately, the code we provided in the article is relevant for the earlier Magento version. Follow our blog updates for more information.
hi ,
In magento 2.3.4 version, we have followed your blog for category thumb image but its not work properly and found below mentioned issue after save the category after upload thumb image:
Uncaught TypeError: value.map is not a function
at UiClass.setInitialValue (file-uploader.js:79)
at UiClass.initialize (abstract.js:77)
at UiClass.initialize (wrapper.js:109)
at new UiClass (class.js:49)
at Object.initComponent (layout.js:137)
at fire (jquery.js:3238)
at Object.fireWith [as resolveWith] (jquery.js:3368)
at Object.deferred. (jquery.js:3467)
at fire (jquery.js:3238)
at Object.fireWith [as resolveWith] (jquery.js:3368)
Hi, Sony! Thanks for your question.
As you could have already noticed, this article was written in 2016, and both code and expertise I provided there have become rather outdated. This article will soon be updated from the perspective of the latest Magento version and will contain a detailed answer to your question.
Hi, in my case, the Thumb Class has to extend \Magento\Catalog\Model\Category\Attribute\Backend\Image and not the abstract.
If you check the \Magento\Catalog\Model\Category\DataProvider class , specially the convertValues method, there is a check for the \Magento\Catalog\Model\Category\Attribute\Backend\Image class.
Error without this extends : get a js error value.map is not a function on the category page in the back office.
Andrew, thank you for sharing the solution! Hope it solves the problem indeed. I’m pretty sure it helps others as well.
Diego, unfortunately, I cannot resolve it without getting into details. Please contact our support department in case you need an individual consideration of the matter: [email protected]
We’ll be happy to assist.
When I hit the Upload Buttom a message display in page: ‘A technical problem with the server created an error. Try again to continue what you were doing. If the problem persists, try again later.’
fix come with rewrite magento core dataprovider
https://github.com/magento/magento2/issues/5438
Hello, i finded solution, changed the backend_model of attribute to standart magento “Magento\Catalog\Model\Category\Attribute\Backend\Image” and it work, but have an issue with standart field it apear with empty image and when try to save category image field got exception “Method Magento\Ui\TemplateEngine\Xhtml\Result::__toString() must not throw an exception”
The problem is when i add picture and save category the field is gone.
Thx Mishel,
But if you have solution can you write some words about this, im stack in it about 5 hours :)
Dear Andrew,
you are right, this method doesn’t work on Magento 2.2.0 and above. I’m going to cover this topic in my next articles. Unfortunately, I don’t have spare time for blogging right now :(
Regards,
Mishel
Solution not working in Magento 2.2 , can you update this?
Hello, Jo-Anne! Hope it’ll help.
<item name=”url” xsi:type=”url” path=”[ module ]/category_thumb/upload”/>
Thus:
routes.xml
<?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=”[ any key ]” frontName=”[ module ]”>
<module name=”[ Vendor ]_[ Module ]” before=”Magento_Backend” />
</route>
</router>
</config>
Yes Mishel, it won’t work. And while you’re at it, would you be so kind as to add the routes.xml with which you make it work? For me, the uninitiated and fledgling Magento ( 2 ) developer it’s awfully complicated. Most of the time.
Dear Sanket,
you are right, this method doesn’t work on Magento 2.2.0 and above. I’ll try to figure out how to fix it and cover this topic in my next articles.
Regards,
Mishel
Any help would be much appreciated
This is perfect work on above version of magento 2.2.0 But it’s not working on magento 2.2.0. Is’s display after save category ‘file-uploader.js:74 Uncaught TypeError: value.map is not a function’.
Thanks Mishel. This is very usefull
For last method we have to create Helper\Data.php
[ Vendor ]\[ Module ]\Helper\Data.php
_storeManager = $storeManager;
parent::__construct($context);
}
public function getCategoryThumbUrl($category) {
$url = false;
$image = $category->getImageThumb();
if ($image) {
if (is_string($image)) {
$url = $this->_storeManager->getStore()->getBaseUrl(
\Magento\Framework\UrlInterface::URL_TYPE_MEDIA
) . ‘catalog/category/’ . $image;
} else {
throw new \Magento\Framework\Exception\LocalizedException(
__(‘Something went wrong while getting the image url.’)
);
}
}
return $url;
}
}
It doesn’t upload any image file uploader is blank…
@ankit which code you had written in routes.xml
All id good, just need to add routes.xml and everything will working fine.
LOVE U guys…
This blog is very helpful to create category attribute in magento 2. thanks
Do you have a github link to the whole code? That’ll would be helpful :)
@Ali, you can try another our solution for the same problem http://blog.belvg.com/adding-image-attribute-to-a-category-in-magento-2-alternative-version.html
Thanks Mishel. A very useful tutorial.
It’s actually something I’ve stuck in for a week.
But I can’t get your code to work either. Would you kindly add a ready-to-use zip of your module code to your article.