Skip to content

How to add a file uploader field in the Product form using PHP UI component Data Provider?

I’m working on a dynamic product row attribute in Magento 2, and I’ve successfully added multiple fields. Now, I want to include a video uploader field in the product form. The text fields are displaying correctly, as shown in the attached screenshot. However, I’m unsure how to add a video uploader (file uploader) field to the form.

Here’s the code I’m trying: UiDataProviderProductFormModifierDynamicRowAttribute.php

'video_upload' => [
    'arguments' => [
        'data' => [
            'config' => [
                'componentType' => 'field',
                'formElement' => 'fileUploader',
                'dataType' => 'text',
                'label' => __('Video Upload'),
                'dataScope' => 'video_upload',
                'uploaderConfig' => [
                    'url' => $this->urlBuilder->getUrl('adminhtml/media/upload'), // Using $this->urlBuilder
                ],
                'allowedExtensions' => ['mp4', 'avi', 'mkv'],
                'maxFileSize' => 1024 * 1000 * 1000, // 1GB max file size
                'sortOrder' => 40,
            ],
        ],
    ],
],

enter image description here

Here is complete code of this file:

<?php

namespace CICoursePreviewUiDataProviderProductFormModifier;

use MagentoCatalogModelLocatorLocatorInterface;
use MagentoCatalogUiDataProviderProductFormModifierAbstractModifier;
use MagentoUiComponentFormField;
use MagentoUiComponentFormElementInput;
use MagentoUiComponentDynamicRows;
use MagentoUiComponentContainer;
use MagentoUiComponentFormElementDataTypeText;
use MagentoEavModelResourceModelEntityAttributeSetCollectionFactory as AttributeSetCollection;
use MagentoFrameworkStdlibArrayManager;
use MagentoFrameworkSerializeSerializerInterface;
use PsrLogLoggerInterface;

class DynamicRowAttribute extends AbstractModifier
{
    public const PRODUCT_ATTRIBUTE_CODE = 'course_previews';
    public const FIELD_IS_DELETE = 'is_delete';
    public const FIELD_SORT_ORDER_NAME = 'sort_order';

    private $locator;
    private $attributeSetCollection;
    private $serializer;
    private $arrayManager;
    private $logger;

    /**
     * Dependency Initialization
     *
     * @param LocatorInterface $locator
     * @param AttributeSetCollection $attributeSetCollection
     * @param SerializerInterface $serializer
     * @param ArrayManager $arrayManager
     * @param LoggerInterface $logger
     */
    public function __construct(
        LocatorInterface $locator,
        AttributeSetCollection $attributeSetCollection,
        SerializerInterface $serializer,
        ArrayManager $arrayManager,
        LoggerInterface $logger
    ) {
        $this->locator = $locator;
        $this->attributeSetCollection = $attributeSetCollection;
        $this->serializer = $serializer;
        $this->arrayManager = $arrayManager;
        $this->logger = $logger;
    }

    /**
     * Modify Data
     *
     * @param array $data
     * @return array
     */
    public function modifyData(array $data)
    {
        $this->logger->info('Modify Data Called');
        $fieldCode = self::PRODUCT_ATTRIBUTE_CODE;

        $model = $this->locator->getProduct();
        $modelId = $model->getId();

        $this->logger->info('Product ID: ' . $modelId);

        $coursePreviewsData = $model->getData(self::PRODUCT_ATTRIBUTE_CODE);

        if ($coursePreviewsData) {
            $this->logger->info('Course Previews Data from Product: ' . json_encode($coursePreviewsData));
            $coursePreviewsData = $this->serializer->unserialize($coursePreviewsData, true);
            $path = $modelId . '/' . self::DATA_SOURCE_DEFAULT . '/' . $fieldCode;
            $data = $this->arrayManager->set($path, $data, $coursePreviewsData);
        } else {
            $this->logger->info('Course Previews Data is empty');
        }

        return $data;
    }

    /**
     * Modify Meta
     *
     * @param array $meta
     * @return array
     */
    public function modifyMeta(array $meta)
    {
        $this->logger->info('Modify Meta Called');
    
        // Log the entire meta array before searching for the path
        //$this->logger->info('Full Meta Array Before Path Search: ' . json_encode($meta));
    
        // Find the path for course previews in the meta structure
        $coursePreviewsPath = $this->arrayManager->findPath(
            self::PRODUCT_ATTRIBUTE_CODE,
            $meta,
            null,
            'children'
        );
    
        // Log the result of the findPath call
        $this->logger->info('Find Path Result for Course Previews: ' . json_encode($coursePreviewsPath));
    
        if ($coursePreviewsPath) {
            $this->logger->info('Course Previews Path Found: ' . $coursePreviewsPath);
    
            // Merge and update meta with the dynamic row field structure
            $meta = $this->arrayManager->merge(
                $coursePreviewsPath,
                $meta,
                $this->initDynamicRowFieldStructure($meta, $coursePreviewsPath)
            );
    
            // Set and remove original path if needed
            $meta = $this->arrayManager->set(
                $this->arrayManager->slicePath($coursePreviewsPath, 0, -3)
                    . '/' . self::PRODUCT_ATTRIBUTE_CODE,
                $meta,
                $this->arrayManager->get($coursePreviewsPath, $meta)
            );
    
            $meta = $this->arrayManager->remove(
                $this->arrayManager->slicePath($coursePreviewsPath, 0, -2),
                $meta
            );
    
        } else {
            // Log that the path was not found and output the meta structure
            $this->logger->info('Course Previews Path Not Found. Full Meta: ' . json_encode($meta));
        }
    
        return $meta;
    }

    /**
     * Add dynamic rows for course previews
     *
     * @param int $sortOrder
     * @return array
     */
    protected function addDynamicRowConfig($sortOrder)
    {
        return [
            'arguments' => [
                'data' => [
                    'config' => [
                        'addButtonLabel' => __('Add Course Preview'),
                        'componentType' => DynamicRows::NAME,
                        'component' => 'Magento_Ui/js/dynamic-rows/dynamic-rows',
                        'additionalClasses' => 'admin__field-wide',
                        'deleteProperty' => static::FIELD_IS_DELETE,
                        'deleteValue' => '1',
                        'renderDefaultRecord' => false,
                        'sortOrder' => $sortOrder,
                    ],
                ],
            ],
            'children' => [
                'record' => [
                    'arguments' => [
                        'data' => [
                            'config' => [
                                'componentType' => Container::NAME,
                                'component' => 'Magento_Ui/js/dynamic-rows/record',
                                'positionProvider' => static::FIELD_SORT_ORDER_NAME,
                                'isTemplate' => true,
                                'is_collection' => true,
                            ],
                        ],
                    ],
                    'children' => [
                        'title' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'componentType' => Field::NAME,
                                        'formElement' => Input::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Video Title'),
                                        'dataScope' => 'title',
                                        'sortOrder' => 10,
                                        'validation' => [
                                            'required-entry' => true,
                                        ],
                                    ],
                                ],
                            ],
                        ],
                        'description' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'componentType' => Field::NAME,
                                        'formElement' => Input::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Video Description'),
                                        'dataScope' => 'description',
                                        'sortOrder' => 20,
                                    ],
                                ],
                            ],
                        ],
                        'video_url' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'componentType' => Field::NAME,
                                        'formElement' => Input::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Video URL'),
                                        'dataScope' => 'video_url',
                                        'sortOrder' => 30,
                                    ],
                                ],
                            ],
                        ],
                        'video_upload' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'componentType' => Field::NAME,
                                        'formElement' => Input::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Video Upload'),
                                        'dataScope' => 'video_upload',
                                        'sortOrder' => 40,
                                    ],
                                ],
                            ],
                        ],
                        'actionDelete' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'componentType' => 'actionDelete',
                                        'dataType' => Text::NAME,
                                        'label' => '',
                                        'sortOrder' => 50,
                                    ],
                                ],
                            ],
                        ],
                    ]
                ]
            ]
        ];
    }

    /**
     * Initialize dynamic row field structure
     *
     * @param array $meta
     * @param string $coursePreviewsPath
     * @return array
     */
    protected function initDynamicRowFieldStructure($meta, $coursePreviewsPath)
    {
        return [
            'arguments' => [
                'data' => [
                    'config' => [
                        'componentType' => 'dynamicRows',
                        'label' => __('Course Previews'),
                        'renderDefaultRecord' => false,
                        'recordTemplate' => 'record',
                        'dataScope' => '',
                        'dndConfig' => [
                            'enabled' => false,
                        ],
                        'disabled' => false,
                        'sortOrder' => $this->arrayManager->get($coursePreviewsPath . '/arguments/data/config/sortOrder', $meta),
                    ],
                ],
            ],
            'children' => [
                'record' => [
                    'arguments' => [
                        'data' => [
                            'config' => [
                                'componentType' => Container::NAME,
                                'isTemplate' => true,
                                'is_collection' => true,
                                'component' => 'Magento_Ui/js/dynamic-rows/record',
                                'dataScope' => '',
                            ],
                        ],
                    ],
                    'children' => [
                        'title' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'formElement' => Input::NAME,
                                        'componentType' => Field::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Video Title'),
                                        'dataScope' => 'title',
                                    ],
                                ],
                            ],
                        ],
                        'description' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'formElement' => Input::NAME,
                                        'componentType' => Field::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Video Description'),
                                        'dataScope' => 'description',
                                    ],
                                ],
                            ],
                        ],
                        'video_url' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'formElement' => Input::NAME,
                                        'componentType' => Field::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Video URL'),
                                        'dataScope' => 'video_url',
                                    ],
                                ],
                            ],
                        ],
                        'video_upload' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'formElement' => Input::NAME,
                                        'componentType' => Field::NAME,
                                        'dataType' => Text::NAME,
                                        'label' => __('Video Upload'),
                                        'dataScope' => 'video_upload',
                                    ],
                                ],
                            ],
                        ],
                        'actionDelete' => [
                            'arguments' => [
                                'data' => [
                                    'config' => [
                                        'componentType' => 'actionDelete',
                                        'dataType' => Text::NAME,
                                        'label' => '',
                                    ],
                                ],
                            ],
                        ],
                    ]
                ],
            ],
        ];
    }
}