Hello,
I was trying to create a new custom product attribute to upload a KML file. Although the file upload field is displayed when I save the product the field is not part of the POST data. The content type seem fine:
Content-Type:multipart/form-data;
Here is the code to create the attribute:
$eavSetup->addAttribute( \Magento\Catalog\Model\Product::ENTITY, 'starting_point_kml', [ 'type' => 'varchar', 'label' => 'Starting point kml', 'input' => 'file', 'backend' => 'My\Module\Model\Product\Attribute\Backend\Kml', 'frontend' => 'My\Module\Model\Product\Attribute\Frontend\Kml', 'class' => '', 'source' => '', 'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_GLOBAL, 'visible' => true, 'required' => false, 'user_defined' => true, 'default' => '', 'searchable' => false, 'filterable' => false, 'comparable' => false, 'visible_on_front' => true, 'unique' => false, 'apply_to' => 'customProductType', 'used_in_product_listing' => false ] );
The backend and frontend model are based on magento/module-catalog/Model/Category/Attribute/Backend/Image.php
Here is my backend model:
app/code/My/Module/Model/Product/Attribute/Backend/Kml.php
<?php namespace My\Module\Model\Product\Attribute\Backend; use Magento\Framework\App\Filesystem\DirectoryList; class Kml extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend { protected $_uploaderFactory; protected $_filesystem; protected $_fileUploaderFactory; protected $_logger; /** * Construct * * @param \Psr\Log\LoggerInterface $logger * @param \Magento\Framework\Filesystem $filesystem * @param \Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory */ 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; } public function afterSave($object) { $value = $object->getData($this->getAttribute()->getName() . '_additional_data'); // if no image was set - nothing to do if (empty($value) && empty($_FILES)) { return $this; } if (is_array($value) && !empty($value['delete'])) { $object->setData($this->getAttribute()->getName(), ''); $this->getAttribute()->getEntity()->saveAttribute($object, $this->getAttribute()->getName()); return $this; } $path = $this->_filesystem->getDirectoryRead( DirectoryList::MEDIA )->getAbsolutePath( 'catalog/product/kml/' ); $this->_logger->debug($path); try { /** @var $uploader \Magento\MediaStorage\Model\File\Uploader */ $uploader = $this->_fileUploaderFactory->create(['fileId' => $this->getAttribute()->getName()]); $uploader->setAllowedExtensions(['kml']); $uploader->setAllowRenameFiles(true); $result = $uploader->save($path); $object->setData($this->getAttribute()->getName(), $result['file']); $this->getAttribute()->getEntity()->saveAttribute($object, $this->getAttribute()->getName()); } catch (\Exception $e) { if ($e->getCode() != \Magento\MediaStorage\Model\File\Uploader::TMP_NAME_EMPTY) { $this->_logger->critical($e); } } return $this; } }
And here is the frontend model:
app/code/My/Module/Model/Product/Attribute/Frontend/Kml.php
<?php namespace My\Module\Model\Product\Attribute\Frontend; class Kml extends \Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend { /** * Store manager * * @var \Magento\Store\Model\StoreManagerInterface */ protected $_storeManager; /** * Construct * * @param \Magento\Store\Model\StoreManagerInterface $storeManager */ public function __construct(\Magento\Store\Model\StoreManagerInterface $storeManager) { $this->_storeManager = $storeManager; } /** * Returns url to product image * * @param \Magento\Catalog\Model\Product $product * * @return string|false */ public function getUrl($product) { $kml = $product->getData($this->getAttribute()->getAttributeCode()); $url = false; if (!empty($kml)) { $url = $this->_storeManager->getStore($product->getStore()) ->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA) . 'catalog/product/kml/' . $kml; } return $url; } }
Am I missing something about the input field of type file ? Why is the file not sent to the server ? is there something with the AJAX maybe ?
Thank you.
Hello, did you already find a solution for this?
Hi fperrin, perhaps you already found a solution but just leaving my solution here for the internet..
After a lot of debugging I found out the data will be sended to the server (the $_FILES var) but it contains the wrong format.
A normal formatted $_FILES in Magento 2 looks like (took an image as example):
Array ( [myattribute] => Array ( [name] => myimage.jpg [type] => image/jpeg [tmp_name] => C:\Users\user\AppData\Local\Temp\php7B9A.tmp [error] => 0 [size] => 455590 ) )
But the code below outputs the $_FILES into this format:
Array ( [product] => Array ( [name] => Array ( [myattribute] => myimage.jpg ) [type] => Array ( [myattribute] => image/jpeg ) [tmp_name] => Array ( [myattribute] => C:\Users\user\AppData\Local\Temp\php6A5D.tmp ) [error] => Array ( [myattribute] => 0 ) [size] => Array ( [myattribute] => 455590 ) ) )
As you see the custom attribute (starting_point_kml) is inserted in each key of the sended data.
My workaround is to manipulate and transform the $_FILES variable using following code:
$attributeCode = $this->getAttribute()->getAttributeCode(); foreach($_FILES['product'] as $value => $key) { $_FILES[$attributeCode][$value] = $key[$attributeCode]; } unset($_FILES['product']);
And upload it with a little modification to your code:
$path = $this->_filesystem->getDirectoryRead( DirectoryList::MEDIA )->getAbsolutePath( 'my/path' ); try { $uploader = $this->uploaderFactory->create(['fileId' => $attributeCode]); $uploader->setAllowRenameFiles(true); $uploader->setFilesDispersion(true); $uploader->setAllowCreateFolders(true); $result = $uploader->save($path); $object->setData($attributeCode, $result['file']); return parent::beforeSave($object); } catch (\Exception $e) { if ($e->getCode() != \Magento\Framework\File\Uploader::TMP_NAME_EMPTY) { throw new FrameworkException($e->getMessage()); } else { echo 'TMP NAME EMPTY ERROR'; return false; } }
I have not worked with the frontend model yet, but now at least the file is saved and inserted to the db. Hope this helped.
another way is to specify $attributeCode as product[myattribute]
Thanks for your solution suiteseven_nl. I've tried implementing it but I'm getting some errors. Could you please show the full code for the backend model so I can see the complete code. Thanks in advance for your help!
Your code definitely work. But I need to change afterSave method to beforeSave.
Hi tried to follow this article to create the attachment for the products. I was able to upload the file as well as update the database. However, in the edit screen, it does not show the file that was uploaded. Can you please suggest.
hi can u send whole code as i am unable to make it work
Can you please share code of file upload.
Thank you