Magento 2 custom options allows you to add file upload option to product. So on frontend customer is able to attach a file to product when placing an order.
Recently I have discovered that it is not possible to upload file other than image via Rest API. In the code below I suggest a sample Plugin code that allows you to overcome this restriction.
First declare plugin in app/code/Vendor/ModuleName/etc/webapi_rest/di.xml:
<?xml version="1.0"?> <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd"> <type name="Magento\Catalog\Model\Webapi\Product\Option\Type\File\Processor"> <plugin name="AllowFileUploadPlugin" type="Vendor\ModuleName\Plugin\Catalog\Webaip\ProductOptionTypeFileProcessor\AroundProcessFileContent\AllowFileUploadPlugin"/> </type> </config>
Plugin code can look like this:
<?php declare(strict_types=1); namespace Vendor\ModuleName\Plugin\Catalog\Webaip\ProductOptionTypeFileProcessor\AroundProcessFileContent; use Magento\Catalog\Model\Webapi\Product\Option\Type\File\Processor; use Magento\Framework\Api\Data\ImageContentInterface; use Magento\Framework\Api\Uploader; use Magento\Framework\App\Filesystem\DirectoryList; use Magento\Framework\Exception\FileSystemException; use Magento\Framework\Exception\InputException; use Magento\Framework\Filesystem; use Magento\Framework\Filesystem\Directory\WriteInterface; use Magento\Framework\Phrase; /** * Allow not only images to be uploaded * * Class AllowFileUploadPlugin */ class AllowFileUploadPlugin { public const FILE_EXTENSIONS_TO_PROCESS = [ 'pdf', 'docx' ]; /** * @var string */ protected $destinationFolder = 'custom_options/quote'; /** * 4Mb file size limit */ public const FILE_SIZE_LIMIT_BYTES = 4 * 1048576; /** * @var Filesystem */ private $filesystem; /** * @var Uploader */ private $uploader; /** * @var WriteInterface */ private $mediaDirectory; /** * AllowFileUploadPlugin constructor. * @param Filesystem $filesystem * @param Uploader $uploader * @throws FileSystemException */ public function __construct( Filesystem $filesystem, Uploader $uploader ) { $this->filesystem = $filesystem; $this->uploader = $uploader; $this->mediaDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::MEDIA); } /** * Core magento allows only images to be uploaded * Here we allow PDF and DOC files * * @param Processor $subject * @param callable $proceed * @param ImageContentInterface $fileContent * @return array * @throws InputException * @throws FileSystemException * @SuppressWarnings(PHPMD.UnusedFormalParameter) */ public function aroundProcessFileContent( Processor $subject, callable $proceed, ImageContentInterface $fileContent ): array { $fileMatches = array_search( strtolower($fileContent->getType()), array_map('strtolower', self::FILE_EXTENSIONS_TO_PROCESS), true ) !== false; if (!$fileMatches) { return $proceed($fileContent); } $filePath = $this->saveFile($fileContent); $fileAbsolutePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath($filePath); $fileHash = md5($this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->readFile($filePath)); $imageSize = getimagesize($fileAbsolutePath); $result = [ 'type' => $fileContent->getType(), 'title' => $fileContent->getName(), 'fullpath' => $fileAbsolutePath, 'quote_path' => $filePath, 'order_path' => $filePath, 'size' => filesize($fileAbsolutePath), 'width' => $imageSize ? $imageSize[0] : 0, 'height' => $imageSize ? $imageSize[1] : 0, 'secret_key' => substr($fileHash, 0, 20), ]; return $result; } /** * Save file * * @param ImageContentInterface $fileContent * @return string * @throws InputException * @throws FileSystemException */ private function saveFile(ImageContentInterface $fileContent): string { $this->validateSize($fileContent); $content = @base64_decode($fileContent->getBase64EncodedData(), true); $tmpDirectory = $this->filesystem->getDirectoryWrite(DirectoryList::SYS_TMP); $fileName = $this->getFileName($fileContent); $tmpFileName = substr(md5((string)mt_rand()), 0, 7) . '.' . $fileName; $tmpDirectory->writeFile($tmpFileName, $content); $fileAttributes = [ 'tmp_name' => $tmpDirectory->getAbsolutePath() . $tmpFileName, 'name' => $fileContent->getName() ]; $this->uploader->processFileAttributes($fileAttributes); $this->uploader->setFilesDispersion(true); $this->uploader->setFilenamesCaseSensitivity(false); $this->uploader->setAllowRenameFiles(true); $this->uploader->save($this->mediaDirectory->getAbsolutePath($this->destinationFolder), $fileName); $filePath = $this->uploader->getUploadedFileName(); return $this->destinationFolder . $filePath; } /** * Validate file size * * @param ImageContentInterface $fileContent * @throws InputException */ private function validateSize(ImageContentInterface $fileContent): void { //convert base 64 size to real one $fileSizeInBytes = strlen($fileContent->getBase64EncodedData()) / 4 * 3; if ($fileSizeInBytes > self::FILE_SIZE_LIMIT_BYTES) { throw new InputException(new Phrase('File size exceeds the limit')); } } /** * Get file name * * @param $fileContent * @return mixed|string */ private function getFileName($fileContent) { $fileName = $fileContent->getName(); if (!pathinfo($fileName, PATHINFO_EXTENSION)) { $fileName .= '.' . $fileContent->getType(); } return $fileName; } }
Hope it is helpful.