cancel
Showing results for 
Search instead for 
Did you mean: 

Product: custom attribute file upload

Product: custom attribute file upload

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.

8 REPLIES 8

Re: Product: custom attribute file upload

Hello, did you already find a solution for this?

Re: Product: custom attribute file upload

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.

Re: Product: custom attribute file upload

another way is to specify $attributeCode as product[myattribute]

Re: Product: custom attribute file upload

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!

Re: Product: custom attribute file upload

Your code definitely work. But I need to change afterSave method to beforeSave.

Re: Product: custom attribute file upload

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. 

Re: Product: custom attribute file upload

hi can u send whole code as i am unable to make it work

Re: Product: custom attribute file upload

Can you please share code of file upload.

Thank you