2

As Administrator I want to be able to upload files for each products in order to show link on frontend.

I created upgrade script to create attribute with this properties:

'type' => 'varchar',
'input' => 'file',
'label' => 'Datasheet',
'required' => false,
'visible_on_front' => true,
'backend' => 'My\Module\Model\Product\Attribute\Backend\Datasheet'

Here is my backend model:

namespace My\Module\Model\Product\Attribute\Backend;

use Magento\Framework\App\Filesystem\DirectoryList;

class Datasheet extends \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
{
    const MEDIA_SUBFOLDER = 'datasheet';

    protected $_uploaderFactory;
    protected $_filesystem;
    protected $_fileUploaderFactory;
    protected $_logger;

    /**
     * Construct
     *
     * @param \Psr\Log\LoggerInterface $logger
     * @param \Magento\Framework\Filesystem $filesystem
     * @param \Magento\Framework\File\UploaderFactory $uploaderFactory
     */
    public function __construct(
        \Psr\Log\LoggerInterface $logger,
        \Magento\Framework\Filesystem $filesystem,
        \Magento\Framework\File\UploaderFactory $uploaderFactory
    ) {
        $this->_filesystem = $filesystem;
        $this->_uploaderFactory = $uploaderFactory;
        $this->_logger = $logger;
    }

    public function afterSave($object)
    {
        $attributeName = $this->getAttribute()->getName();
        $fileName = $this->uploadFileAndGetName($attributeName, $this->_filesystem->getDirectoryWrite(DirectoryList::MEDIA)->getAbsolutePath(self::MEDIA_SUBFOLDER));

        if ($fileName) {
            $object->setData($attributeName, $fileName);
            $this->getAttribute()->getEntity()->saveAttribute($object, $attributeName);
        }

        return $this;
    }

    public function uploadFileAndGetName($input, $destinationFolder)
    {
        try {
            $uploader = $this->_uploaderFactory->create(array('fileId' => 'product['.$input.']'));
            $uploader->setAllowedExtensions(['pdf']);
            $uploader->setAllowRenameFiles(true);
            $uploader->setFilesDispersion(true);
            $uploader->setAllowCreateFolders(true);
            $uploader->save($destinationFolder);

            return $uploader->getUploadedFileName();
        } catch (\Exception $e) {
            if ($e->getCode() != \Magento\Framework\File\Uploader::TMP_NAME_EMPTY) {
                throw new \FrameworkException($e->getMessage());
            }
        }

        return '';
    }
}

Attribute is shown in backend and saves value in database.

PROBLEM

Attribute value is not shown in backend.

References:

6
  • Created issue in magento2 github: github.com/magento/magento2/issues/7023 Oct 14, 2016 at 17:09
  • Hmm if that's all you have then you're missing the part to actually make it show the value on the backend. Can you provide the full module? I've done something similar to category attributes, pretty sure it will not be that much different. Oct 17, 2016 at 14:25
  • That's all I have. I assume I need to add some layout xml and create some template for this field type Oct 17, 2016 at 17:02
  • I'll have a look at this when i get some free time today. Oct 18, 2016 at 12:03
  • 1
    @AndréFerraz can you please check my answer? Oct 19, 2016 at 14:13

2 Answers 2

11

Investigation

file input is not implemented in Magento 2.1 yet.

in vendor/magento/module-ui/view/base/web/templates/form/element/media.html there is only uploader and no view and delete button:

<!--
/**
 * Copyright © 2016 Magento. All rights reserved.
 * See COPYING.txt for license details.
 */
-->
<input class="admin__control-file" type="file" data-bind="
    hasFocus: focused,
    attr: {
        name: inputName,
        placeholder: placeholder,
        'aria-describedby': noticeId,
        id: uid,
        disabled: disabled,
        form: formId
    }"
/>

It will be implemented in next Magento2 release hopefully.

So I thought to implement a custom renderer, but

Solution

Magento2 way is to use UI components and PHP Modifiers

Create 3 files:

etc/adminhtml/di.xml - dependency injection configuration for adminhtml

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <virtualType name="Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\Pool">
        <arguments>
            <argument name="modifiers" xsi:type="array">
                <item name="your_module_datasheet" xsi:type="array">
                    <item name="class" xsi:type="string">Vendor\Module\Ui\DataProvider\Product\Form\Modifier\Datasheet</item>
                    <item name="sortOrder" xsi:type="number">150</item>
                </item>
            </argument>
        </arguments>
    </virtualType>
</config>

your_module_datasheet can be any name.

With sortOrder from 10 to 50 it didn't work for me. $meta variable in Modifier was empty.

Ui/DataProvider/Product/Form/Modifier/Datasheet.php - php modifier

<?php   
namespace Vendor\Module\Ui\DataProvider\Product\Form\Modifier;

use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Framework\Stdlib\ArrayManager;
use Magento\Framework\UrlInterface;

/**
 * Data provider for "Datasheet" field of product page
 */
class Datasheet extends AbstractModifier
{
    /**
     * @param LocatorInterface            $locator
     * @param UrlInterface                $urlBuilder
     * @param ArrayManager                $arrayManager
     */
    public function __construct(
        LocatorInterface $locator,
        UrlInterface $urlBuilder,
        ArrayManager $arrayManager
    ) {
        $this->locator = $locator;
        $this->urlBuilder = $urlBuilder;
        $this->arrayManager = $arrayManager;
    }

    public function modifyMeta(array $meta)
    {
        $fieldCode = 'datasheet';
        $elementPath = $this->arrayManager->findPath($fieldCode, $meta, null, 'children');
        $containerPath = $this->arrayManager->findPath(static::CONTAINER_PREFIX . $fieldCode, $meta, null, 'children');

        if (!$elementPath) {
            return $meta;
        }

        $meta = $this->arrayManager->merge(
            $containerPath,
            $meta,
            [
                'children'  => [
                    $fieldCode => [
                        'arguments' => [
                            'data' => [
                                'config' => [
                                    'elementTmpl'   => 'Vendor_Module/grid/filters/elements/datasheet',
                                ],
                            ],
                        ],
                    ]
                ]
            ]
        );

        return $meta;
    }

    /**
     * {@inheritdoc}
     */
    public function modifyData(array $data)
    {
        return $data;
    }
}

view/adminhtml/web/template/grid/filters/elements/datasheet.html - knockout js template

<!-- ko if: $parent.source.data.product[code] -->
<div>
    <!-- todo: dynamically get path to file from config or controller -->
    <a attr="href: '/pub/media/datasheet'+$parent.source.data.product[code]" text="$parent.source.data.product[code]"></a>
    <label attr="for: uid+'_delete'">
        <!-- todo: generate name -->
        <input type="checkbox" attr="name: 'product['+code + '_delete]', id: uid+'_delete', form: formId">
        <span data-bind="i18n:'Delete'"></span>
    </label>
</div>
<!-- /ko -->
<input class="admin__control-file" type="file" data-bind="
    hasFocus: focused,
    attr: {
        name: inputName,
        placeholder: placeholder,
        'aria-describedby': noticeId,
        id: uid,
        disabled: disabled,
        form: formId
    }"
/>

It is still looks dirty to me and a lot of things to improve.

Alternatives

I found some interesting files, which can bring you other ideas how to solve it:

vendor/magento/module-catalog/view/adminhtml/ui_component/design_config_form.xml

<item name="formElement" xsi:type="string">fileUploader</item>

vendor/magento/module-ui/view/base/ui_component/etc/definition.xml

<fileUploader class="Magento\Ui\Component\Form\Element\DataType\Media">
5
  • I did the second solution but always return this field is required when save despite uploading file. Also is it possible to have custom controller to save this file your own way. Thanks for sharing Jan 18, 2017 at 8:23
  • What do you mean by second solution? You should use modifier class and you can have different modifiers for different field names. Jan 18, 2017 at 9:54
  • I am getting the Exception "$_FILES array is empty" - did you also encounter this? I am on 2.1.6 Jun 1, 2017 at 12:55
  • i am not able to make this work do it works ? Oct 14, 2018 at 15:28
  • @AlekseyRazbakov can you tell will with above way can i create attribute type file or it adds new attribute as i tried but it did not worked Oct 14, 2018 at 15:32
0

If you need to upload the files in product page tab, no need to create the product custom attribute. just install the following extension

Then php bin/magento setup:upgrade php bin/magento setup:static-content:deploy en_GB php bin/magento indexer:reindex php bin/magento cache:clean

working perfectly.

https://github.com/Sebwite/Magento2-Product-Downloads/tree/2.1-dev

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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