The question:
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?
The Solutions:
Below are the methods you can try. The first solution is probably the best. Try others if the first one doesn’t work. Senior developers aren’t just copying/pasting – they read the methods carefully & apply them wisely to each case.
Method 1
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 MagentoCatalogControllerAdminhtmlCategory;
use MagentoStoreModelStoreManagerInterface;
+use MagentoCatalogApiDataCategoryAttributeInterface;
/**
* Class Save
@@ -49,6 +50,11 @@
private $storeManager;
/**
+ * @var MagentoEavModelConfig
+ */
+ private $eavConfig;
+
+ /**
* Constructor
*
* @param MagentoBackendAppActionContext $context
@@ -62,37 +68,15 @@
MagentoFrameworkControllerResultRawFactory $resultRawFactory,
MagentoFrameworkControllerResultJsonFactory $resultJsonFactory,
MagentoFrameworkViewLayoutFactory $layoutFactory,
- StoreManagerInterface $storeManager
+ StoreManagerInterface $storeManager,
+ MagentoEavModelConfig $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 MagentoCatalogModelCategoryAttributeBackendImage) {
+ 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 MagentoEavModelEntityAttributeBackendAbstractBackend
{
+ const ADDITIONAL_DATA_SUFFIX = '_additional_data';
+
/**
* @var MagentoMediaStorageModelFileUploaderFactory
*
@@ -21,8 +23,6 @@
protected $_uploaderFactory;
/**
- * Filesystem facade
- *
* @var MagentoFrameworkFilesystem
*
* @deprecated
@@ -30,8 +30,6 @@
protected $_filesystem;
/**
- * File Uploader factory
- *
* @var MagentoMediaStorageModelFileUploaderFactory
*
* @deprecated
@@ -46,15 +44,11 @@
protected $_logger;
/**
- * Image uploader
- *
* @var MagentoCatalogModelImageUploader
*/
private $imageUploader;
/**
- * Image constructor.
- *
* @param PsrLogLoggerInterface $logger
* @param MagentoFrameworkFilesystem $filesystem
* @param MagentoMediaStorageModelFileUploaderFactory $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 MagentoFrameworkDataObject $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 MagentoCatalogModelImageUploader
*
* @deprecated
@@ -80,9 +116,10 @@
{
if ($this->imageUploader === null) {
$this->imageUploader = MagentoFrameworkAppObjectManager::getInstance()->get(
- 'MagentoCatalogCategoryImageUpload'
+ MagentoCatalogCategoryImageUpload::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 MagentoCatalogModelCategoryAttributeBackendImage) {
+ 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 MagentoFrameworkExceptionLocalizedException
*/
- 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
- composer validate composer.json (To Vallidate composer file).
- composer update
- php bin/magento setup:upgrade
6) Now its time to create your module for new file upload option.
Create a file
appcodeVendorModuleregistration.php
MagentoFrameworkComponentComponentRegistrar::register(
MagentoFrameworkComponentComponentRegistrar::MODULE,
"Vendor_Module",
__DIR__
);
Create a file
appcodeVendorModuleetcmodule.xml
<?xml version="1.0" encoding="UTF-8" ?>
<config>
<module name="Vendor_Module" setup_version="1.0" />
</config>
Create a file
appcodeVendorModuleSetupInstallData.php
<?php
namespace VendorModuleSetup;
use MagentoFrameworkModuleSetupMigration;
use MagentoFrameworkSetupInstallDataInterface;
use MagentoFrameworkSetupModuleContextInterface;
use MagentoFrameworkSetupModuleDataSetupInterface;
use MagentoCatalogSetupCategorySetupFactory;
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(MagentoCatalogModelCategory::ENTITY);
$attributeSetId = $categorySetup->getDefaultAttributeSetId($entityTypeId);
$categorySetup->removeAttribute(
MagentoCatalogModelCategory::ENTITY, 'featured_image' );
$categorySetup->addAttribute(
MagentoCatalogModelCategory::ENTITY, 'featured_image', [
'type' => 'varchar',
'label' => 'Featured Image',
'input' => 'image',
'backend' => 'MagentoCatalogModelCategoryAttributeBackendImage',
'required' => false,
'sort_order' => 5,
'global' => MagentoEavModelEntityAttributeScopedAttributeInterface::SCOPE_STORE,
'group' => 'General Information',
]
);
$installer->endSetup();
}
}
Create a file
appcodeVendorModuleviewadminhtmlui_componentcategory_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.
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0