Skip to content

Commit ec6f153

Browse files
committed
Merge remote-tracking branch 'origin/MC-39444' into 2.4-develop-pr121
2 parents 6b94ec3 + 9ce80c9 commit ec6f153

File tree

4 files changed

+228
-19
lines changed

4 files changed

+228
-19
lines changed

app/code/Magento/Catalog/Model/Product/Type/AbstractType.php

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -504,21 +504,17 @@ public function processFileQueue()
504504
/** @var $uploader \Zend_File_Transfer_Adapter_Http */
505505
$uploader = $queueOptions['uploader'] ?? null;
506506
$isUploaded = false;
507-
if ($uploader && $uploader->isValid()) {
507+
if ($uploader && $uploader->isValid($src)) {
508+
// phpcs:ignore Magento2.Functions.DiscouragedFunction
508509
$path = pathinfo($dst, PATHINFO_DIRNAME);
509510
$uploader = $this->uploaderFactory->create(['fileId' => $src]);
510511
$uploader->setFilesDispersion(false);
511512
$uploader->setAllowRenameFiles(true);
513+
// phpcs:ignore Magento2.Functions.DiscouragedFunction
512514
$isUploaded = $uploader->save($path, pathinfo($dst, PATHINFO_FILENAME));
513515
}
514516

515517
if (empty($src) || empty($dst) || !$isUploaded) {
516-
/**
517-
* @todo: show invalid option
518-
*/
519-
if (isset($queueOptions['option'])) {
520-
$queueOptions['option']->setIsValid(false);
521-
}
522518
throw new \Magento\Framework\Exception\LocalizedException(
523519
__('The file upload failed. Try to upload again.')
524520
);

dev/tests/integration/testsuite/Magento/Catalog/Model/Product/Type/AbstractTypeTest.php

Lines changed: 171 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,23 @@
1414
use Magento\Catalog\Model\ProductRepository;
1515
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
1616
use Magento\Eav\Model\Config;
17+
use Magento\Framework\App\Filesystem\DirectoryList;
1718
use Magento\Framework\DataObject;
1819
use Magento\Framework\Event\ManagerInterface;
1920
use Magento\Framework\Exception\LocalizedException;
21+
use Magento\Framework\File\Uploader;
22+
use Magento\Framework\File\UploaderFactory;
2023
use Magento\Framework\Filesystem;
24+
use Magento\Framework\HTTP\Adapter\FileTransferFactory;
2125
use Magento\Framework\Registry;
2226
use Magento\Framework\Serialize\Serializer\Json;
2327
use Magento\MediaStorage\Helper\File\Storage\Database;
2428
use Magento\TestFramework\Helper\Bootstrap;
29+
use Magento\TestFramework\ObjectManager;
2530
use PHPUnit\Framework\TestCase;
2631
use Psr\Log\LoggerInterface;
32+
use ReflectionMethod;
33+
use StdClass;
2734

2835
/**
2936
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -35,35 +42,41 @@ class AbstractTypeTest extends TestCase
3542
*/
3643
protected $_model;
3744

45+
/** @var ObjectManager */
46+
private $objectManager;
47+
48+
/** @var ProductRepositoryInterface */
49+
private $productRepository;
50+
51+
/**
52+
* @inheritdoc
53+
*/
3854
protected function setUp(): void
3955
{
40-
$productRepository = Bootstrap::getObjectManager()->get(
41-
ProductRepositoryInterface::class
42-
);
43-
$catalogProductOption = Bootstrap::getObjectManager()->get(
44-
Option::class
45-
);
56+
$this->objectManager = Bootstrap::getObjectManager();
57+
$this->productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
58+
$catalogProductOption = $this->objectManager->get(Option::class);
4659
$catalogProductType = $this->createMock(Type::class);
4760
$eventManager = $this->createPartialMock(ManagerInterface::class, ['dispatch']);
4861
$fileStorageDb = $this->createMock(Database::class);
4962
$filesystem = $this->createMock(Filesystem::class);
5063
$registry = $this->createMock(Registry::class);
5164
$logger = $this->createMock(LoggerInterface::class);
52-
$serializer = Bootstrap::getObjectManager()->get(
65+
$serializer = $this->objectManager->get(
5366
Json::class
5467
);
5568
$this->_model = $this->getMockForAbstractClass(
5669
AbstractType::class,
5770
[
5871
$catalogProductOption,
59-
Bootstrap::getObjectManager()->get(Config::class),
72+
$this->objectManager->get(Config::class),
6073
$catalogProductType,
6174
$eventManager,
6275
$fileStorageDb,
6376
$filesystem,
6477
$registry,
6578
$logger,
66-
$productRepository,
79+
$this->productRepository,
6780
$serializer
6881
]
6982
);
@@ -384,7 +397,7 @@ public function testGetSetStoreFilter()
384397
{
385398
$product = new DataObject();
386399
$this->assertNull($this->_model->getStoreFilter($product));
387-
$store = new \StdClass();
400+
$store = new StdClass();
388401
$this->_model->setStoreFilter($store, $product);
389402
$this->assertSame($store, $this->_model->getStoreFilter($product));
390403
}
@@ -473,7 +486,7 @@ public function testGetSearchableData()
473486

474487
public function testGetProductsToPurchaseByReqGroups()
475488
{
476-
$product = new \StdClass();
489+
$product = new StdClass();
477490
$this->assertSame([[$product]], $this->_model->getProductsToPurchaseByReqGroups($product));
478491
$this->_model->setConfig(['composite' => 1]);
479492
$this->assertEquals([], $this->_model->getProductsToPurchaseByReqGroups($product));
@@ -509,7 +522,7 @@ public function testPrepareOptions(): void
509522
);
510523
$product->load(1);
511524
$buyRequest = new DataObject(['product' => 1]);
512-
$method = new \ReflectionMethod(
525+
$method = new ReflectionMethod(
513526
AbstractType::class,
514527
'_prepareOptions'
515528
);
@@ -523,4 +536,150 @@ public function testPrepareOptions(): void
523536
}
524537
$this->assertTrue($exceptionIsThrown);
525538
}
539+
540+
/**
541+
* Test if product can be prepared for cart with more than one custom file option
542+
*
543+
* @magentoAppIsolation enabled
544+
* @magentoDataFixture Magento/Catalog/_files/product_simple_with_two_custom_file_options.php
545+
* @return void
546+
*/
547+
public function testPrepareForCartAdvancedWithMultipleCustomFileOptions(): void
548+
{
549+
/** @var $product Product */
550+
$product = $this->productRepository->get('simple_with_custom_file_option');
551+
$optionIds = array_reduce($product->getOptions(), function ($result, $item) {
552+
$result[] = $item->getOptionId();
553+
return $result;
554+
}, []);
555+
$this->prepareEnv($optionIds);
556+
557+
$buyRequest = new DataObject(
558+
[
559+
'qty' => 5,
560+
'options' => ['files_prefix' => 'item_simple_with_custom_file_option_'],
561+
]
562+
);
563+
$model = $this->objectManager->create(Simple::class);
564+
$product->setTypeInstance($model);
565+
$result = $model->prepareForCartAdvanced($buyRequest, $product);
566+
567+
//result is exception string value in case if exception occurs
568+
self::assertTrue(is_array($result));
569+
$product = reset($result);
570+
$options = $product->getOptions();
571+
self::assertCount(2, $options);
572+
}
573+
574+
/**
575+
* Test if exception occurs in case if file is not uploaded
576+
*
577+
* @magentoAppIsolation enabled
578+
* @magentoDataFixture Magento/Catalog/_files/product_simple_with_two_custom_file_options.php
579+
* @return void
580+
*/
581+
public function testPrepareForCartAdvancedFileOptionFailedToUpload(): void
582+
{
583+
/** @var $product Product */
584+
$product = $this->productRepository->get('simple_with_custom_file_option');
585+
$optionIds = array_reduce($product->getOptions(), function ($result, $item) {
586+
$result[] = $item->getOptionId();
587+
return $result;
588+
}, []);
589+
$this->prepareEnv($optionIds);
590+
591+
$uploaderFactory = $this->createPartialMock(UploaderFactory::class, ['create']);
592+
$uploader = $this->createPartialMock(Uploader::class, ['save']);
593+
$uploaderFactory->method('create')->willReturn($uploader);
594+
$this->objectManager->addSharedInstance($uploaderFactory, UploaderFactory::class);
595+
596+
$buyRequest = new DataObject(
597+
[
598+
'qty' => 5,
599+
'options' => ['files_prefix' => 'item_simple_with_custom_file_option_'],
600+
]
601+
);
602+
$model = $this->objectManager->create(Simple::class);
603+
$product->setTypeInstance($model);
604+
$this->expectException(LocalizedException::class);
605+
$model->prepareForCartAdvanced($buyRequest, $product);
606+
}
607+
608+
/**
609+
* Prepare file upload environment
610+
*
611+
* @param array $optionIds
612+
* @return void
613+
*/
614+
private function prepareEnv(array $optionIds): void
615+
{
616+
$file = 'magento_thumbnail.jpg';
617+
$fixtureDir = realpath(__DIR__ . '/../../../_files/');
618+
619+
/** @var Filesystem $filesystem */
620+
$filesystem = $this->objectManager->get(Filesystem::class);
621+
$tmpDirectory = $filesystem->getDirectoryWrite(DirectoryList::SYS_TMP);
622+
$filePath = $tmpDirectory->getAbsolutePath($file);
623+
copy($fixtureDir . DIRECTORY_SEPARATOR . $file, $filePath);
624+
copy($fixtureDir . DIRECTORY_SEPARATOR . $file, $filePath . '_1');
625+
626+
$_FILES["item_simple_with_custom_file_option_options_$optionIds[0]_file"] = [
627+
'name' => 'test.jpg',
628+
'type' => 'image/jpeg',
629+
'tmp_name' => $filePath,
630+
'error' => 0,
631+
'size' => '3046',
632+
];
633+
$_FILES["item_simple_with_custom_file_option_options_$optionIds[1]_file"] = [
634+
'name' => 'test.jpg',
635+
'type' => 'image/jpeg',
636+
'tmp_name' => $filePath . '_1',
637+
'error' => 0,
638+
'size' => '3046',
639+
];
640+
$this->prepareUploaderFactoryMock();
641+
}
642+
643+
/**
644+
* Prepare file upload validator mock
645+
*
646+
* @return void
647+
*/
648+
private function prepareUploaderFactoryMock(): void
649+
{
650+
$uploaderMock = $this->getPreparedUploader();
651+
/** @var FileTransferFactory $httpFactory */
652+
$httpFactoryMock = $this->createPartialMock(FileTransferFactory::class, ['create']);
653+
$httpFactoryMock->expects($this->at(0))
654+
->method('create')
655+
->willReturn($uploaderMock);
656+
$httpFactoryMock->expects($this->at(1))
657+
->method('create')
658+
->willReturn(clone $uploaderMock);
659+
$this->objectManager->addSharedInstance($httpFactoryMock, FileTransferFactory::class);
660+
}
661+
662+
/**
663+
* Create prepared uploader instance for test
664+
*
665+
* @return \Zend_File_Transfer_Adapter_Http
666+
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
667+
*/
668+
private function getPreparedUploader(): \Zend_File_Transfer_Adapter_Http
669+
{
670+
$uploader = new \Zend_File_Transfer_Adapter_Http();
671+
$refObject = new \ReflectionObject($uploader);
672+
$validators = $refObject->getProperty('_validators');
673+
$validators->setAccessible(true);
674+
$validators->setValue($uploader, []);
675+
$files = $refObject->getProperty('_files');
676+
$files->setAccessible(true);
677+
$filesValues = $files->getValue($uploader);
678+
foreach (array_keys($filesValues) as $value) {
679+
$filesValues[$value]['validators'] = [];
680+
}
681+
$files->setValue($uploader, $filesValues);
682+
683+
return $uploader;
684+
}
526685
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
use Magento\Catalog\Api\Data\ProductCustomOptionInterface;
9+
use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory;
10+
use Magento\Catalog\Api\ProductRepositoryInterface;
11+
use Magento\TestFramework\Helper\Bootstrap;
12+
use Magento\TestFramework\ObjectManager;
13+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
14+
15+
/** @var ObjectManager $objectManager */
16+
$objectManager = Bootstrap::getObjectManager();
17+
Resolver::getInstance()->requireDataFixture('Magento/Catalog/_files/product_simple_with_custom_file_option.php');
18+
/** @var ProductRepositoryInterface $productRepository */
19+
$productRepository = $objectManager->get(ProductRepositoryInterface::class);
20+
$productRepository->cleanCache();
21+
$product = $productRepository->get('simple_with_custom_file_option');
22+
$option = [
23+
'title' => 'file option 2',
24+
'type' => 'file',
25+
'is_require' => true,
26+
'sort_order' => 1,
27+
'price' => 20.0,
28+
'price_type' => 'percent',
29+
'sku' => 'sku4',
30+
'file_extension' => 'jpg, png, gif',
31+
'image_size_x' => 1000,
32+
'image_size_y' => 1000,
33+
];
34+
$customOptions = $product->getOptions();
35+
/** @var ProductCustomOptionInterfaceFactory $customOptionFactory */
36+
$customOptionFactory = $objectManager->create(ProductCustomOptionInterfaceFactory::class);
37+
/** @var ProductCustomOptionInterface $customOption */
38+
$customOption = $customOptionFactory->create(['data' => $option]);
39+
$customOption->setProductSku($product->getSku());
40+
$customOptions[] = $customOption;
41+
$product->setOptions($customOptions);
42+
$productRepository->save($product);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
declare(strict_types=1);
7+
8+
use Magento\TestFramework\Workaround\Override\Fixture\Resolver;
9+
10+
Resolver::getInstance()->requireDataFixture(
11+
'Magento/Catalog/_files/product_simple_with_custom_file_option_rollback.php'
12+
);

0 commit comments

Comments
 (0)