5

I have created a custom category thumbnail attribute according to the post (Magento 2 create category attribute thumbnail and upload image using File Uploader Component).

The image gets saved in the "tmp" directory but is not saved when I save the category.

what is the issue for that?

asked Aug 4, 2016 at 6:31
2
  • 1
    Thanks you ! but alert "Atention The file was not uploaded." Help me! Commented Sep 13, 2016 at 18:33
  • @Dreams clear var/generation folder and run setup:di:compile or enable developer mode Commented Sep 16, 2016 at 9:03

1 Answer 1

4

This is quite a big answer.

In Magento 2.1.0 you need to apply a patch to create a additional file upload attribute for category.

As they are using hardcoded values for image attribute on many places. for more details have a look at link1 and link2

Steps to apply a patch.

1) Edit composer.json file located at root directory as follows.

{
 "name": "magento/project-community-edition",
 "description": "eCommerce Platform for Growth (Community Edition)",
 "type": "project",
 "version": "2.1.0",
 "license": [
 "OSL-3.0",
 "AFL-3.0"
 ],
 "repositories": [
 {
 "type": "composer",
 "url": "https://repo.magento.com/"
 }
 ],
 "require": {
 "magento/product-community-edition": "2.1.0",
 "cweagans/composer-patches": "~1.4.0",
 "composer/composer": "@alpha" 
 },
 "require-dev": {
 "phpunit/phpunit": "4.1.0",
 "squizlabs/php_codesniffer": "1.5.3",
 "phpmd/phpmd": "2.3.*",
 "pdepend/pdepend": "2.2.2",
 "sjparkinson/static-review": "~4.1",
 "fabpot/php-cs-fixer": "~1.2",
 "lusitanian/oauth": "~0.3 <=0.7.0"
 },
 "config": {
 "use-include-path": true
 },
 "autoload": {
 "psr-4": {
 "Magento\\Framework\\": "lib/internal/Magento/Framework/",
 "Magento\\Setup\\": "setup/src/Magento/Setup/",
 "Magento\\": "app/code/Magento/"
 },
 "psr-0": {
 "": "app/code/"
 },
 "files": [
 "app/etc/NonComposerComponentRegistration.php"
 ]
 },
 "autoload-dev": {
 "psr-4": {
 "Magento\\Sniffs\\": "dev/tests/static/framework/Magento/Sniffs/",
 "Magento\\Tools\\": "dev/tools/Magento/Tools/",
 "Magento\\Tools\\Sanity\\": "dev/build/publication/sanity/Magento/Tools/Sanity/",
 "Magento\\TestFramework\\Inspection\\": "dev/tests/static/framework/Magento/TestFramework/Inspection/",
 "Magento\\TestFramework\\Utility\\": "dev/tests/static/framework/Magento/TestFramework/Utility/"
 }
 },
 "minimum-stability": "alpha",
 "prefer-stable": true,
 "extra": {
 "patches": {
 "magento/module-catalog": {
 "Fix: https://github.com/magento/magento2/issues/5438": "patches/Patch-Magento_Catalog-M2.1.0-image-attribute-backend-model-hardcoded-attribute-code-removal.patch"
 },
 "magento/module-ui": {
 "Fix: https://github.com/magento/magento2/issues/5438": "patches/Patch-Magento_Ui-M2.1.0-allow-backend-to-know-the-origin-input-of-the-upload-request.patch"
 }
 },
 "magento-force": "override"
 }
}

As mentioned on link2 above newly added code is in extra and require.

2) Create a patches folder on root directory.

3) Create file Patch-Magento_Catalog-M2.1.0-image-attribute-backend-model-hardcoded-attribute-code-removal.patch under patches folder with following contains.

--- Controller/Adminhtml/Category/Save.php.org 2016年08月03日 21:35:33.772109252 +0200
+++ Controller/Adminhtml/Category/Save.php 2016年08月03日 21:36:33.982229407 +0200
@@ -6,6 +6,7 @@
 namespace Magento\Catalog\Controller\Adminhtml\Category;
 use Magento\Store\Model\StoreManagerInterface;
+use \Magento\Catalog\Api\Data\CategoryAttributeInterface;
 /**
 * Class Save
@@ -49,6 +50,11 @@
 private $storeManager;
 /**
+ * @var \Magento\Eav\Model\Config
+ */
+ private $eavConfig;
+
+ /**
 * Constructor
 *
 * @param \Magento\Backend\App\Action\Context $context
@@ -62,37 +68,15 @@
 \Magento\Framework\Controller\Result\RawFactory $resultRawFactory,
 \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
 \Magento\Framework\View\LayoutFactory $layoutFactory,
- StoreManagerInterface $storeManager
+ StoreManagerInterface $storeManager,
+ \Magento\Eav\Model\Config $eavConfig
 ) {
 parent::__construct($context);
 $this->resultRawFactory = $resultRawFactory;
 $this->resultJsonFactory = $resultJsonFactory;
 $this->layoutFactory = $layoutFactory;
 $this->storeManager = $storeManager;
- }
-
- /**
- * Filter category data
- *
- * @param array $rawData
- * @return array
- */
- protected function _filterCategoryPostData(array $rawData)
- {
- $data = $rawData;
- // @todo It is a workaround to prevent saving this data in category model and it has to be refactored in future
- if (isset($data['image']) && is_array($data['image'])) {
- if (!empty($data['image']['delete'])) {
- $data['image'] = null;
- } else {
- if (isset($data['image'][0]['name']) && isset($data['image'][0]['tmp_name'])) {
- $data['image'] = $data['image'][0]['name'];
- } else {
- unset($data['image']);
- }
- }
- }
- return $data;
+ $this->eavConfig = $eavConfig;
 }
 /**
@@ -126,7 +110,7 @@
 $this->storeManager->setCurrentStore($store->getCode());
 $parentId = isset($categoryPostData['parent']) ? $categoryPostData['parent'] : null;
 if ($categoryPostData) {
- $category->addData($this->_filterCategoryPostData($categoryPostData));
+ $category->addData($categoryPostData);
 if ($isNewCategory) {
 $parentCategory = $this->getParentCategory($parentId, $storeId);
 $category->setPath($parentCategory->getPath());
@@ -247,22 +231,29 @@
 );
 }
- /**
- * Image data preprocessing
- *
- * @param array $data
- *
- * @return array
- */
 public function imagePreprocessing($data)
 {
- if (empty($data['image'])) {
- unset($data['image']);
- $data['image']['delete'] = true;
+ $entityType = $this->eavConfig->getEntityType(CategoryAttributeInterface::ENTITY_TYPE_CODE);
+
+ foreach ($entityType->getAttributeCollection() as $attributeModel) {
+ $attributeCode = $attributeModel->getAttributeCode();
+ $backendModel = $attributeModel->getBackend();
+
+ if (isset($data[$attributeCode])) {
+ continue;
+ }
+
+ if (!$backendModel instanceof \Magento\Catalog\Model\Category\Attribute\Backend\Image) {
+ continue;
+ }
+
+ $data[$attributeCode] = false;
 }
+
 return $data;
 }
+
 /**
 * Converting inputs from string to boolean
 *
--- Controller/Adminhtml/Category/Image/Upload.php.org 2016年08月01日 20:36:22.014237780 +0200
+++ Controller/Adminhtml/Category/Image/Upload.php 2016年08月01日 20:36:25.292475257 +0200
@@ -50,8 +50,10 @@
 */
 public function execute()
 {
+ $imageId = $this->_request->getParam('param_name', 'image');
+
 try {
- $result = $this->imageUploader->saveFileToTmpDir('image');
+ $result = $this->imageUploader->saveFileToTmpDir($imageId);
 $result['cookie'] = [
 'name' => $this->_getSession()->getName(),
--- Model/Category/Attribute/Backend/Image.php.org 2016年08月03日 21:36:05.695112561 +0200
+++ Model/Category/Attribute/Backend/Image.php 2016年08月03日 21:36:46.829828298 +0200
@@ -13,6 +13,8 @@
 class Image extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
 {
+ const ADDITIONAL_DATA_SUFFIX = '_additional_data';
+
 /**
 * @var \Magento\MediaStorage\Model\File\UploaderFactory
 *
@@ -21,8 +23,6 @@
 protected $_uploaderFactory;
 /**
- * Filesystem facade
- *
 * @var \Magento\Framework\Filesystem
 *
 * @deprecated
@@ -30,8 +30,6 @@
 protected $_filesystem;
 /**
- * File Uploader factory
- *
 * @var \Magento\MediaStorage\Model\File\UploaderFactory
 *
 * @deprecated
@@ -46,15 +44,11 @@
 protected $_logger;
 /**
- * Image uploader
- *
 * @var \Magento\Catalog\Model\ImageUploader
 */
 private $imageUploader;
 /**
- * Image constructor.
- *
 * @param \Psr\Log\LoggerInterface $logger
 * @param \Magento\Framework\Filesystem $filesystem
 * @param \Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory
@@ -70,8 +64,50 @@
 }
 /**
- * Get image uploader
+ * @param $value
+ * @return string|bool
+ */
+ protected function getUploadedImageName($value)
+ {
+ if (!is_array($value)) {
+ return false;
+ }
+
+ if (!count($value)) {
+ return false;
+ }
+
+ $imageData = reset($value);
+
+ if (!isset($imageData['name'])) {
+ return false;
+ }
+
+ return $imageData['name'];
+ }
+
+ /**
+ * Avoiding saving potential upload data to DB
 *
+ * @param \Magento\Framework\DataObject $object
+ * @return $this
+ */
+ public function beforeSave($object)
+ {
+ $attributeName = $this->getAttribute()->getName();
+ $value = $object->getData($attributeName);
+
+ if ($value === false || (is_array($value) && isset($value['delete']) && $value['delete'] === true)) {
+ $object->setData($attributeName, '');
+ } else if ($imageName = $this->getUploadedImageName($value)) {
+ $object->setData($attributeName . self::ADDITIONAL_DATA_SUFFIX, $value);
+ $object->setData($attributeName, $imageName);
+ }
+
+ return parent::beforeSave($object);
+ }
+
+ /**
 * @return \Magento\Catalog\Model\ImageUploader
 *
 * @deprecated
@@ -80,9 +116,10 @@
 {
 if ($this->imageUploader === null) {
 $this->imageUploader = \Magento\Framework\App\ObjectManager::getInstance()->get(
- 'Magento\Catalog\CategoryImageUpload'
+ \Magento\Catalog\CategoryImageUpload::class
 );
 }
+
 return $this->imageUploader;
 }
@@ -94,15 +131,18 @@
 */
 public function afterSave($object)
 {
- $image = $object->getData($this->getAttribute()->getName(), null);
+ $value = $object->getData($this->getAttribute()->getName() . self::ADDITIONAL_DATA_SUFFIX);
- if ($image !== null) {
- try {
- $this->getImageUploader()->moveFileFromTmp($image);
- } catch (\Exception $e) {
- $this->_logger->critical($e);
- }
+ if (!$imageName = $this->getUploadedImageName($value)) {
+ return $this;
 }
+
+ try {
+ $this->getImageUploader()->moveFileFromTmp($imageName);
+ } catch (\Exception $e) {
+ $this->_logger->critical($e);
+ }
+
 return $this;
 }
 }
--- Model/Category/DataProvider.php.org 2016年08月01日 21:35:43.567609510 +0200
+++ Model/Category/DataProvider.php 2016年08月01日 21:43:07.800993338 +0200
@@ -203,14 +203,24 @@
 $category = $this->getCurrentCategory();
 if ($category) {
 $categoryData = $category->getData();
+
 $categoryData = $this->addUseDefaultSettings($category, $categoryData);
 $categoryData = $this->addUseConfigSettings($categoryData);
 $categoryData = $this->filterFields($categoryData);
- if (isset($categoryData['image'])) {
- unset($categoryData['image']);
- $categoryData['image'][0]['name'] = $category->getData('image');
- $categoryData['image'][0]['url'] = $category->getImageUrl();
+
+ foreach ($category->getAttributes() as $attributeCode => $attribute) {
+ $backendModel = $attribute->getBackend();
+
+ if ($backendModel instanceof \Magento\Catalog\Model\Category\Attribute\Backend\Image) {
+ if (isset($categoryData[$attributeCode])) {
+ unset($categoryData[$attributeCode]);
+
+ $categoryData[$attributeCode][0]['name'] = $category->getData($attributeCode);
+ $categoryData[$attributeCode][0]['url'] = $category->getImageUrl($attributeCode);
+ }
+ }
 }
+
 $this->loadedData[$category->getId()] = $categoryData;
 }
 return $this->loadedData;
--- Model/Category.php.org 2016年08月01日 21:41:47.535876208 +0200
+++ Model/Category.php 2016年08月01日 21:42:57.916422400 +0200
@@ -652,14 +652,15 @@
 }
 /**
- * Retrieve image URL
+ * @param $attributeCode
 *
- * @return string
+ * @return bool|string
+ * @throws \Magento\Framework\Exception\LocalizedException
 */
- public function getImageUrl()
+ public function getImageUrl($attributeCode = 'image')
 {
 $url = false;
- $image = $this->getImage();
+ $image = $this->getData($attributeCode);
 if ($image) {
 if (is_string($image)) {
 $url = $this->_storeManager->getStore()->getBaseUrl(

4) Create file Patch-Magento_Ui-M2.1.0-allow-backend-to-know-the-origin-input-of-the-upload-request.patch under patches folder with following contains.

--- view/base/web/js/form/element/file-uploader.js.org 2016年08月01日 20:33:22.866555339 +0200
+++ view/base/web/js/form/element/file-uploader.js 2016年08月01日 20:33:42.384061881 +0200
@@ -302,7 +302,13 @@
 allowed = this.isFileAllowed(file);
 if (allowed.passed) {
- $(e.target).fileupload('process', data).done(function () {
+ var $target = $(e.target);
+
+ $target.on('fileuploadsend', function(event, postData) {
+ postData.data.set('param_name', this.paramName);
+ }.bind(data));
+
+ $target.fileupload('process', data).done(function () {
 data.submit();
 });
 } else {

5) Run following Commands

  1. composer validate composer.json (To Vallidate composer file).
  2. composer update
  3. php bin/magento setup:upgrade

6) Now its time to create your module for new file upload option.

Create a file

app\code\Vendor\Module\registration.php

\Magento\Framework\Component\ComponentRegistrar::register(
 \Magento\Framework\Component\ComponentRegistrar::MODULE,
 "Vendor_Module",
 __DIR__
 );

Create a file

app\code\Vendor\Module\etc\module.xml

<?xml version="1.0" encoding="UTF-8" ?>
 <config>
 <module name="Vendor_Module" setup_version="1.0" />
 </config>

Create a file

app\code\Vendor\Module\Setup\InstallData.php

<?php
 namespace Vendor\Module\Setup;
 use Magento\Framework\Module\Setup\Migration;
 use Magento\Framework\Setup\InstallDataInterface;
 use Magento\Framework\Setup\ModuleContextInterface;
 use Magento\Framework\Setup\ModuleDataSetupInterface;
 use Magento\Catalog\Setup\CategorySetupFactory;
 class InstallData implements InstallDataInterface
 {
 public function __construct(CategorySetupFactory $categorySetupFactory)
 {
 $this->categorySetupFactory = $categorySetupFactory;
 }
 public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
 {
 $installer = $setup;
 $installer->startSetup(); 
 $categorySetup = $this->categorySetupFactory->create(['setup' => $setup]);
 $entityTypeId = $categorySetup->getEntityTypeId(\Magento\Catalog\Model\Category::ENTITY);
 $attributeSetId = $categorySetup->getDefaultAttributeSetId($entityTypeId);
 $categorySetup->removeAttribute(
 \Magento\Catalog\Model\Category::ENTITY, 'featured_image' );
 $categorySetup->addAttribute(
 \Magento\Catalog\Model\Category::ENTITY, 'featured_image', [
 'type' => 'varchar',
 'label' => 'Featured Image',
 'input' => 'image',
 'backend' => 'Magento\Catalog\Model\Category\Attribute\Backend\Image',
 'required' => false,
 'sort_order' => 5,
 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
 'group' => 'General Information',
 ]
 );
 $installer->endSetup();
 }
 }

Create a file

app\code\Vendor\Module\view\adminhtml\ui_component\category_form.xml

<?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">10</item>
 </item>
 </argument>
 <field name="featured_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">Featured 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="catalog/category_image/upload"/>
 </item>
 </item>
 </argument>
 </field>
 </fieldset>
 </form>

Hope this helps you.

Dinesh Yadav
6,5252 gold badges24 silver badges51 bronze badges
answered Aug 18, 2016 at 9:47
4
  • Thanks for the answer, I will update after I've tried this out. Commented Aug 19, 2016 at 5:01
  • this is for 2.1.0 are you using this version ? Commented Aug 19, 2016 at 5:23
  • Yes, I also listed this in the question title. Commented Aug 19, 2016 at 5:34
  • @KandarpBPatel thankx for the suggestion will try it soon. Commented Dec 5, 2016 at 1:10

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.