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>
- <?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>
<?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;
}
}
- <?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;
- }
- }
<?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.