The question:
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' => 'MyModuleModelProductAttributeBackendDatasheet'
Here is my backend model:
namespace MyModuleModelProductAttributeBackend;
use MagentoFrameworkAppFilesystemDirectoryList;
class Datasheet extends MagentoEavModelEntityAttributeBackendAbstractBackend
{
const MEDIA_SUBFOLDER = 'datasheet';
protected $_uploaderFactory;
protected $_filesystem;
protected $_fileUploaderFactory;
protected $_logger;
/**
* Construct
*
* @param PsrLogLoggerInterface $logger
* @param MagentoFrameworkFilesystem $filesystem
* @param MagentoFrameworkFileUploaderFactory $uploaderFactory
*/
public function __construct(
PsrLogLoggerInterface $logger,
MagentoFrameworkFilesystem $filesystem,
MagentoFrameworkFileUploaderFactory $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() != MagentoFrameworkFileUploader::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:
- https://stackoverflow.com/questions/37115850/how-to-add-custom-file-upload-function-in-magento-2
- https://community.magento.com/t5/Programming-Questions/Product-custom-attribute-file-upload/td-p/29460
- create product image attribute in magento2
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
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="MagentoCatalogUiDataProviderProductFormModifierPool">
<arguments>
<argument name="modifiers" xsi:type="array">
<item name="your_module_datasheet" xsi:type="array">
<item name="class" xsi:type="string">VendorModuleUiDataProviderProductFormModifierDatasheet</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 VendorModuleUiDataProviderProductFormModifier;
use MagentoCatalogModelLocatorLocatorInterface;
use MagentoCatalogUiDataProviderProductFormModifierAbstractModifier;
use MagentoFrameworkStdlibArrayManager;
use MagentoFrameworkUrlInterface;
/**
* 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="MagentoUiComponentFormElementDataTypeMedia">
Method 2
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
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