Skip to content

Commit 76dd91b

Browse files
MC-35123: An invoiced order of a product with a Customizable Option (file) can not be reordered
1 parent 02b87f6 commit 76dd91b

File tree

6 files changed

+83
-25
lines changed

6 files changed

+83
-25
lines changed

app/code/Magento/Bundle/Test/Unit/Model/Product/TypeTest.php

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use Magento\Bundle\Model\Product\Type;
1212
use Magento\Bundle\Model\ResourceModel\BundleFactory;
1313
use Magento\Bundle\Model\ResourceModel\Option\Collection;
14+
use Magento\CatalogRule\Model\ResourceModel\Product\CollectionProcessor;
1415
use Magento\Bundle\Model\ResourceModel\Selection\Collection as SelectionCollection;
1516
use Magento\Bundle\Model\ResourceModel\Selection\CollectionFactory;
1617
use Magento\Bundle\Model\Selection;
@@ -42,6 +43,8 @@
4243
use PHPUnit\Framework\TestCase;
4344

4445
/**
46+
* Test for bundle product type
47+
*
4548
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
4649
*/
4750
class TypeTest extends TestCase
@@ -116,6 +119,11 @@ class TypeTest extends TestCase
116119
*/
117120
private $arrayUtility;
118121

122+
/**
123+
* @var CollectionProcessor|MockObject
124+
*/
125+
private $catalogRuleProcessor;
126+
119127
/**
120128
* @return void
121129
*/
@@ -172,20 +180,20 @@ protected function setUp(): void
172180
->setMethods(['create'])
173181
->disableOriginalConstructor()
174182
->getMock();
175-
176183
$this->serializer = $this->getMockBuilder(Json::class)
177184
->setMethods(null)
178185
->disableOriginalConstructor()
179186
->getMock();
180-
181187
$this->metadataPool = $this->getMockBuilder(MetadataPool::class)
182188
->disableOriginalConstructor()
183189
->getMock();
184-
185190
$this->arrayUtility = $this->getMockBuilder(ArrayUtils::class)
186191
->setMethods(['flatten'])
187192
->disableOriginalConstructor()
188193
->getMock();
194+
$this->catalogRuleProcessor = $this->getMockBuilder(CollectionProcessor::class)
195+
->disableOriginalConstructor()
196+
->getMock();
189197

190198
$objectHelper = new ObjectManager($this);
191199
$this->model = $objectHelper->getObject(
@@ -1542,7 +1550,7 @@ public function testPrepareForCartAdvancedSpecifyProductOptions()
15421550

15431551
$this->parentClass($group, $option, $buyRequest, $product);
15441552

1545-
$product->expects($this->once())
1553+
$product->expects($this->any())
15461554
->method('getSkipCheckRequiredOption')
15471555
->willReturn(true);
15481556
$buyRequest->expects($this->once())
@@ -2424,9 +2432,6 @@ protected function parentClass($group, $option, $buyRequest, $product)
24242432
$group->expects($this->once())
24252433
->method('setProcessMode')
24262434
->willReturnSelf();
2427-
$group->expects($this->once())
2428-
->method('validateUserValue')
2429-
->willReturnSelf();
24302435
$group->expects($this->once())
24312436
->method('prepareForCart')
24322437
->willReturn('someString');

app/code/Magento/Catalog/Model/Product/Option/Type/File.php

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
/**
1717
* Catalog product option file type
1818
*
19-
* @author Magento Core Team <core@magentocommerce.com>
19+
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
2020
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
21+
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
2122
*/
2223
class File extends \Magento\Catalog\Model\Product\Option\Type\DefaultType
2324
{
@@ -262,7 +263,6 @@ public function validateUserValue($values)
262263
. "Make sure the options are entered and try again."
263264
)
264265
);
265-
break;
266266
default:
267267
$this->setUserValue(null);
268268
break;
@@ -330,7 +330,11 @@ public function prepareForCart()
330330
public function getFormattedOptionValue($optionValue)
331331
{
332332
if ($this->_formattedOptionValue === null) {
333-
$value = $this->serializer->unserialize($optionValue);
333+
try {
334+
$value = $this->serializer->unserialize($optionValue);
335+
} catch (\InvalidArgumentException $e) {
336+
return $optionValue;
337+
}
334338
if ($value === null) {
335339
return $optionValue;
336340
}
@@ -476,13 +480,13 @@ public function copyQuoteToOrder()
476480
try {
477481
$value = $this->serializer->unserialize($quoteOption->getValue());
478482
if (!isset($value['quote_path'])) {
479-
throw new \Exception();
483+
return $this;
480484
}
481485
$quotePath = $value['quote_path'];
482486
$orderPath = $value['order_path'];
483487

484488
if (!$this->mediaDirectory->isFile($quotePath) || !$this->mediaDirectory->isReadable($quotePath)) {
485-
throw new \Exception();
489+
return $this;
486490
}
487491

488492
if ($this->_coreFileStorageDatabase->checkDbUsage()) {

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

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,18 @@
99
use Magento\Catalog\Api\ProductRepositoryInterface;
1010
use Magento\Framework\App\Filesystem\DirectoryList;
1111
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\App\ObjectManager;
1213

1314
/**
14-
* @api
1515
* Abstract model for product type implementation
16+
*
17+
* phpcs:disable Magento2.Classes.AbstractApi
18+
* @api
19+
* @since 100.0.2
1620
* @SuppressWarnings(PHPMD.ExcessivePublicCount)
1721
* @SuppressWarnings(PHPMD.TooManyFields)
1822
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
1923
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
20-
* @since 100.0.2
2124
*/
2225
abstract class AbstractType
2326
{
@@ -207,7 +210,7 @@ public function __construct(
207210
$this->_filesystem = $filesystem;
208211
$this->_logger = $logger;
209212
$this->productRepository = $productRepository;
210-
$this->serializer = $serializer ?: \Magento\Framework\App\ObjectManager::getInstance()
213+
$this->serializer = $serializer ?: ObjectManager::getInstance()
211214
->get(\Magento\Framework\Serialize\Serializer\Json::class);
212215
}
213216

@@ -476,6 +479,7 @@ public function prepareForCart(\Magento\Framework\DataObject $buyRequest, $produ
476479
* @throws \Magento\Framework\Exception\LocalizedException
477480
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
478481
* @SuppressWarnings(PHPMD.NPathComplexity)
482+
* phpcs:disable Generic.Metrics.NestingLevel
479483
*/
480484
public function processFileQueue()
481485
{
@@ -492,6 +496,7 @@ public function processFileQueue()
492496
/** @var $uploader \Zend_File_Transfer_Adapter_Http */
493497
$uploader = isset($queueOptions['uploader']) ? $queueOptions['uploader'] : null;
494498

499+
// phpcs:ignore Magento2.Functions.DiscouragedFunction
495500
$path = dirname($dst);
496501

497502
try {
@@ -529,6 +534,7 @@ public function processFileQueue()
529534

530535
return $this;
531536
}
537+
//phpcs:enable
532538

533539
/**
534540
* Add file to File Queue
@@ -572,6 +578,7 @@ public function getSpecifyOptionMessage()
572578
* @param string $processMode
573579
* @return array
574580
* @throws LocalizedException
581+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
575582
*/
576583
protected function _prepareOptions(\Magento\Framework\DataObject $buyRequest, $product, $processMode)
577584
{
@@ -583,15 +590,22 @@ protected function _prepareOptions(\Magento\Framework\DataObject $buyRequest, $p
583590
}
584591
if ($options !== null) {
585592
$results = [];
593+
$optionsFromRequest = $buyRequest->getOptions();
586594
foreach ($options as $option) {
587595
/* @var $option \Magento\Catalog\Model\Product\Option */
588596
try {
589597
$group = $option->groupFactory($option->getType())
590598
->setOption($option)
591599
->setProduct($product)
592600
->setRequest($buyRequest)
593-
->setProcessMode($processMode)
594-
->validateUserValue($buyRequest->getOptions());
601+
->setProcessMode($processMode);
602+
603+
if ($product->getSkipCheckRequiredOption() !== true) {
604+
$group->validateUserValue($optionsFromRequest);
605+
} elseif ($optionsFromRequest !== null && isset($optionsFromRequest[$option->getId()])) {
606+
$transport->options[$option->getId()] = $optionsFromRequest[$option->getId()];
607+
}
608+
595609
} catch (LocalizedException $e) {
596610
$results[] = $e->getMessage();
597611
continue;
@@ -643,8 +657,7 @@ public function checkProductBuyState($product)
643657
}
644658

645659
/**
646-
* Prepare additional options/information for order item which will be
647-
* created from this product
660+
* Prepare additional options/information for order item which will be created from this product
648661
*
649662
* @param \Magento\Catalog\Model\Product $product
650663
* @return array
@@ -900,7 +913,7 @@ public function getStoreFilter($product)
900913
/**
901914
* Set store filter for associated products
902915
*
903-
* @param $store int|\Magento\Store\Model\Store
916+
* @param int|\Magento\Store\Model\Store $store
904917
* @param \Magento\Catalog\Model\Product $product
905918
* @return $this
906919
*/
@@ -913,6 +926,7 @@ public function setStoreFilter($store, $product)
913926

914927
/**
915928
* Allow for updates of children qty's
929+
*
916930
* (applicable for complicated product types. As default returns false)
917931
*
918932
* @param \Magento\Catalog\Model\Product $product
@@ -940,6 +954,7 @@ public function prepareQuoteItemQty($qty, $product)
940954

941955
/**
942956
* Implementation of product specify logic of which product needs to be assigned to option.
957+
*
943958
* For example if product which was added to option already removed from catalog.
944959
*
945960
* @param \Magento\Catalog\Model\Product $optionProduct
@@ -979,6 +994,7 @@ public function setConfig($config)
979994

980995
/**
981996
* Retrieve additional searchable data from type instance
997+
*
982998
* Using based on product id and store_id data
983999
*
9841000
* @param \Magento\Catalog\Model\Product $product
@@ -999,6 +1015,7 @@ public function getSearchableData($product)
9991015

10001016
/**
10011017
* Retrieve products divided into groups required to purchase
1018+
*
10021019
* At least one product in each group has to be purchased
10031020
*
10041021
* @param \Magento\Catalog\Model\Product $product
@@ -1092,6 +1109,8 @@ public function getIdentities(\Magento\Catalog\Model\Product $product)
10921109
}
10931110

10941111
/**
1112+
* Get Associated Products
1113+
*
10951114
* @param \Magento\Catalog\Model\Product\Type\AbstractType $product
10961115
* @return array
10971116
* @SuppressWarnings(PHPMD.UnusedFormalParameter)

app/code/Magento/Catalog/Test/Unit/Model/Product/Option/Type/FileTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
use PHPUnit\Framework\TestCase;
2525

2626
/**
27+
* Test file option type
28+
*
2729
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2830
*/
2931
class FileTest extends TestCase
@@ -142,6 +144,14 @@ protected function getFileObject()
142144
);
143145
}
144146

147+
public function testGetFormattedOptionValueWithUnserializedValue()
148+
{
149+
$fileObject = $this->getFileObject();
150+
151+
$value = 'some unserialized value, 1, 2.test';
152+
$this->assertEquals($value, $fileObject->getFormattedOptionValue($value));
153+
}
154+
145155
public function testGetCustomizedView()
146156
{
147157
$fileObject = $this->getFileObject();

app/code/Magento/Checkout/Model/Cart.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
* @api
1919
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
2020
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
21+
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
2122
* @deprecated 100.1.0 Use \Magento\Quote\Model\Quote instead
2223
* @see \Magento\Quote\Api\Data\CartInterface
2324
*/
@@ -272,6 +273,10 @@ public function addOrderItem($orderItem, $qtyFlag = null)
272273
* with the same id may have different sets of order attributes.
273274
*/
274275
$product = $this->productRepository->getById($orderItem->getProductId(), false, $storeId, true);
276+
if ($orderItem->getOrderId() !== null) {
277+
//reorder existing order
278+
$product->setSkipCheckRequiredOption(true);
279+
}
275280
} catch (NoSuchEntityException $e) {
276281
return $this;
277282
}
@@ -282,7 +287,14 @@ public function addOrderItem($orderItem, $qtyFlag = null)
282287
} else {
283288
$info->setQty(1);
284289
}
285-
290+
$productOptions = $orderItem->getProductOptions();
291+
if ($productOptions !== null && !empty($productOptions['options'])) {
292+
$formattedOptions = [];
293+
foreach ($productOptions['options'] as $option) {
294+
$formattedOptions[$option['option_id']] = $option['option_value'];
295+
}
296+
$info->setData('options', $formattedOptions);
297+
}
286298
$this->addProduct($product, $info);
287299
}
288300
return $this;
@@ -291,8 +303,8 @@ public function addOrderItem($orderItem, $qtyFlag = null)
291303
/**
292304
* Get product object based on requested product information
293305
*
294-
* @param Product|int|string $productInfo
295-
* @return Product
306+
* @param Product|int|string $productInfo
307+
* @return Product
296308
* @throws \Magento\Framework\Exception\LocalizedException
297309
*/
298310
protected function _getProduct($productInfo)
@@ -332,8 +344,8 @@ protected function _getProduct($productInfo)
332344
/**
333345
* Get request for product add to cart procedure
334346
*
335-
* @param \Magento\Framework\DataObject|int|array $requestInfo
336-
* @return \Magento\Framework\DataObject
347+
* @param \Magento\Framework\DataObject|int|array $requestInfo
348+
* @return \Magento\Framework\DataObject
337349
* @throws \Magento\Framework\Exception\LocalizedException
338350
*/
339351
protected function _getProductRequest($requestInfo)

app/code/Magento/Sales/Model/AdminOrder/Create.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,14 @@ public function initFromOrderItem(\Magento\Sales\Model\Order\Item $orderItem, $q
663663
if (is_numeric($qty)) {
664664
$buyRequest->setQty($qty);
665665
}
666+
$productOptions = $orderItem->getProductOptions();
667+
if ($productOptions !== null && !empty($productOptions['options'])) {
668+
$formattedOptions = [];
669+
foreach ($productOptions['options'] as $option) {
670+
$formattedOptions[$option['option_id']] = $option['option_value'];
671+
}
672+
$buyRequest->setData('options', $formattedOptions);
673+
}
666674
$item = $this->getQuote()->addProduct($product, $buyRequest);
667675
if (is_string($item)) {
668676
return $item;

0 commit comments

Comments
 (0)