How to attach a file in product admin form and display attachment in product detail page in Magento 2?

In this post, I will be guiding you on how to attach a file in product admin form and display attachment in product detail page.

Here we will be using Aureatelabs as the Vendor name and ProductAttachment as the name of the module.  You can change this according to your Vendor and Module name.

Step 1: Create module registration file

First, create a registration.php file in app/code/Aureatelabs/ProductAttachment directory

<?php

\Magento\Framework\Component\ComponentRegistrar::register(
   \Magento\Framework\Component\ComponentRegistrar::MODULE,
   'Aureatelabs_ProductAttachment',
   __DIR__
);

Next create a module.xml file in app/code/Aureatelabs/ProductAttachment/etc directory.

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
   <module name="Aureatelabs_ProductAttachment" setup_version="1.0.0" />
</config>

Step 2: Create InstallData script to add product attachment attribute

Next, we will create a script to add product attribute. So create an InstallData.php in app/code/Aureatelabs/ProductAttachment/Setup directory.

<?php
namespace Aureatelabs\ProductAttachment\Setup;

use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Eav\Setup\EavSetupFactory;

/**
 * Class InstallData
 * @package Aureatelabs\ProductAttachment\Setup
 *
 * @codeCoverageIgnore
 */
class InstallData implements InstallDataInterface
{
    /**
     * @var EavSetupFactory
     */
    protected $_eavSetupFactory;

    public function __construct(
        EavSetupFactory $eavSetupFactory
    ) {
        $this->_eavSetupFactory = $eavSetupFactory;
    }

    /**
     * {@inheritdoc}
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
     * @SuppressWarnings(PHPMD.NPathComplexity)
     */
    public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
    {
        $eavSetup = $this->_eavSetupFactory->create(["setup"=>$setup]);
        $eavSetup->addAttribute(
            \Magento\Catalog\Model\Product::ENTITY,
            'attachment',
            [
                'group' => 'Product Attachment',
                'type' => 'varchar',
                'label' => 'Attachment',
                'input' => 'file',
                'backend' => 'Aureatelabs\ProductAttachment\Model\Product\Attribute\Backend\File',
                'frontend' => '',
                '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' => false,
                'unique' => false,
                'apply_to' => 'simple,configurable', // applicable for simple and configurable product
                'used_in_product_listing' => true
            ]
        );
    }
}

Here attachment is product attribute code.

  • group: Here we have used Product Attachment in the group. So new attribute created in product admin form will be added in a new tab named  Product Attachment.
  • type: Here we will define varchar in type. We will save file name in attribute so we need varchar in type.
  • label: Define the product attribute label, this label will be shown in product admin form.
  • input: Define file to display file type input in product admin form.
  • backend: In this define the backend model path. This backend model will handle the file upload and will save the value of the attribute when saving the product.
  • user_defined: It is custom attribute so will use true in this column.
  • apply_to: In this column define different product type so this attribute will work with only these product types.
  • used_in_product_listing: To use this attribute value in the frontend in product listing set this to true.

Next, we will create a backend model file to save uploaded file in the media folder and save the file name to this attribute value.

Step 3: Create backend model file to upload product attachment file and save value in attachment attribute

So create File.php in app/code/Aureatelabs/ProductAttachment/Model/Product/Attribute/Backend directory

<?php
namespace Aureatelabs\ProductAttachment\Model\Product\Attribute\Backend;

use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;

class File extends AbstractBackend
{
    /**
     * @var \Magento\Framework\Filesystem\Driver\File
     */
    protected $_file;

    /**
     * @var \Psr\Log\LoggerInterface
     */
    protected $_logger;

    /**
     * @var \Magento\Framework\Filesystem
     */
    protected $_filesystem;

    /**
     * @var \Magento\MediaStorage\Model\File\UploaderFactory
     */
    protected $_fileUploaderFactory;


    /**
     * 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\Framework\Filesystem\Driver\File $file,
        \Magento\MediaStorage\Model\File\UploaderFactory $fileUploaderFactory
    ) {
        $this->_file = $file;
        $this->_filesystem = $filesystem;
        $this->_fileUploaderFactory = $fileUploaderFactory;
        $this->_logger = $logger;
    }

    public function afterSave($object)
    {

        $path = $this->_filesystem->getDirectoryRead(
            DirectoryList::MEDIA
        )->getAbsolutePath(
            'catalog/product/attachment/'
        );
        $delete = $object->getData($this->getAttribute()->getName() . '_delete');

        if ($delete) {
            $fileName = $object->getData($this->getAttribute()->getName());
            $object->setData($this->getAttribute()->getName(), '');
            $this->getAttribute()->getEntity()->saveAttribute($object, $this->getAttribute()->getName());
            if ($this->_file->isExists($path.$fileName))  {
                $this->_file->deleteFile($path.$fileName);
            }

        }

        if (empty($_FILES['product']['tmp_name'][$this->getAttribute()->getName()])) {
            return $this;
        }

        try {
            /** @var $uploader \Magento\MediaStorage\Model\File\Uploader */
            $uploader = $this->_fileUpzloaderFactory->create(['fileId' => 'product['.$this->getAttribute()->getName().']']);
            $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;
    }

}

Now you can see the product attachment tab and input type file attribute in a new tab in product admin form.

Now after attaching file and save the product, the file is uploaded in pub/media/catalog/product/attachment directory and file name stored in attachment attribute value.

Step 4: Display product attachment link and delete checkbox after saving product in admin form

But after saving the product in product attachment tab file name is not showing up or how to remove file checkbox is not showing up.

So we will display an anchor tag that contains the file path and we will also add delete checkbox to remove the file in the product attachment tab.

We will use a modifier to add these changes in product attachment tab.

So we will create a di.xml in app/code/Aureatelabs/ProductAttachment/etc/adminhtml directory.

<?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" type="Magento\Ui\DataProvider\Modifier\Pool">
       <arguments>
           <argument name="modifiers" xsi:type="array">
               <item name="attachment" xsi:type="array">
                   <item name="class" xsi:type="string">Aureatelabs\ProductAttachment\Ui\DataProvider\Product\Form\Modifier\File</item>
                   <item name="sortOrder" xsi:type="number">1000</item>
               </item>
           </argument>
       </arguments>
   </virtualType>
</config>

Next, we will create File.php in app/code/Aureatelabs/ProductAttachment/Ui/DataProvider/Product/Form/Modifier directory.

<?php
namespace Aureatelabs\ProductAttachment\Ui\DataProvider\Product\Form\Modifier;

use Magento\Framework\Stdlib\ArrayManager;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;

class File extends AbstractModifier
{
    /**
     * @var ArrayManager
     */
    protected $arrayManager;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @param ArrayManager $arrayManager
     * @param StoreManagerInterface $storeManager
     */
    public function __construct(
        ArrayManager $arrayManager,
        StoreManagerInterface $storeManager
    ) {
        $this->arrayManager = $arrayManager;
        $this->storeManager = $storeManager;
    }

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

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

        $mediaUrl =  $this->storeManager->getStore()
            ->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);

        $meta = $this->arrayManager->merge(
            $containerPath,
            $meta,
            [
                'children'  => [
                    $fieldCode => [
                        'arguments' => [
                            'data' => [
                                'config' => [
                                    'elementTmpl'   => 'Aureatelabs_ProductAttachment/elements/file',
                                    'media_url' => $mediaUrl
                                ],
                            ],
                        ],
                    ]
                ]
            ]
        );
        return $meta;
    }

    public function modifyData(array $data)
    {
        return $data;
    }
}

We have defined an HTML template file in the modifier.

So next we will create file.html in app/code/Aureatelabs/ProductAttachment/view/adminhtml/web/template/elements diectory.

<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
    }"
/>
<!-- ko if: $parent.source.data.product[code] -->
<span>
    <a attr="href: media_url+'catalog/product/attachment/'+$parent.source.data.product[code]" text="$parent.source.data.product[code]" target="_blank"></a>
    <label attr="for: uid+'_delete'">
        <input type="checkbox" attr="name: 'product['+code + '_delete]', id: uid+'_delete', form: formId">
        <span data-bind="i18n:'Delete'"></span>
    </label>
</span>
<!-- /ko -->

Now after saving the product, it will display anchor tag with attachment file path and delete checkbox.

We have already added define delete file code in backend model app/code/Aureatelabs/ProductAttachment/Model/Product/Attribute/Backend/File.php.

Step 5: Display product attachment in product detail page in frontend

Next, we will create a new product attachment tab in the product detail page and will show product attachment for that product.

So next we will create a catalog_product_view.xml in app/code/Aureatelabs/ProductAttachment/view/frontend/layout directory.

<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
	<body>
    	<referenceBlock name="product.info.details">
        	<block class="Aureatelabs\ProductAttachment\Block\Attachment" name="product.attachment.tab" template="Aureatelabs_ProductAttachment::product_attachment.phtml" group="detailed_info">
            	<arguments>
                	<argument translate="true" name="title" xsi:type="string">Product Attachment</argument>
            	</arguments>
        	</block>
    	</referenceBlock>
	</body>
</page>

As we have defined in layout file we will create block and phtml file.

So next we will create an Attachment.php in app/code/Aureatelabs/ProductAttachment/Block directory.

<?php
namespace Aureatelabs\ProductAttachment\Block;

class Attachment extends \Magento\Catalog\Block\Product\View {

	public function getMediaUrl() {
    	return $this->_storeManager->getStore()
        	->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);
	}

	public function getProductAttachmentUrl($attachment) {

    	return $this->getMediaUrl()."catalog/product/attachment/".$attachment;

	}
}

Next we will create a product_attachment.phtml in app/code/Aureatelabs/ProductAttachment/view/frontend/templates directory.

<?php
// get current product data
$product = $block->getProduct();


if (!empty($product->getAttachment())) {

	$attachmentUrl = $block->getProductAttachmentUrl($product->getAttachment());
	?>
	<a href="<?php echo $attachmentUrl ?>" target="_blank"><?php echo $product->getAttachment() ?></a>
<?php
}

Now flush cache and open product detail page. Now you can see the product attachment tab.

  • Share :