4

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?

  • 1
    Thanks you ! but alert "Atention The file was not uploaded." Help me! – Roger Sep 13 '16 at 18:33
  • @Dreams clear var/generation folder and run setup:di:compile or enable developer mode – nethead Sep 16 '16 at 9:03
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.

  • Thanks for the answer, I will update after I've tried this out. – V.S Aug 19 '16 at 5:01
  • this is for 2.1.0 are you using this version ? – Ashish Madankar M2 Professiona Aug 19 '16 at 5:23
  • Yes, I also listed this in the question title. – V.S Aug 19 '16 at 5:34
  • @KandarpBPatel thankx for the suggestion will try it soon. – Ashish Madankar M2 Professiona Dec 5 '16 at 1:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.