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.

6 REPLIES

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.