Skip to content

Commit a4071d4

Browse files
authored
Merge pull request #6761 from magento-tsg/2.4-develop-pr138
[Arrows] Fixes for 2.4 (pr138) (2.4-develop)
2 parents 1d1e97f + d948bc6 commit a4071d4

File tree

22 files changed

+971
-62
lines changed

22 files changed

+971
-62
lines changed

app/code/Magento/Bundle/Model/Product/SaveHandler.php

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,11 @@
88
namespace Magento\Bundle\Model\Product;
99

1010
use Magento\Bundle\Api\Data\OptionInterface;
11+
use Magento\Bundle\Api\ProductLinkManagementInterface;
12+
use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository;
1113
use Magento\Bundle\Model\Option\SaveAction;
14+
use Magento\Bundle\Model\ProductRelationsProcessorComposite;
1215
use Magento\Catalog\Api\Data\ProductInterface;
13-
use Magento\Bundle\Api\ProductOptionRepositoryInterface as OptionRepository;
14-
use Magento\Bundle\Api\ProductLinkManagementInterface;
1516
use Magento\Framework\App\ObjectManager;
1617
use Magento\Framework\EntityManager\MetadataPool;
1718
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
@@ -48,26 +49,35 @@ class SaveHandler implements ExtensionInterface
4849
*/
4950
private $checkOptionLinkIfExist;
5051

52+
/**
53+
* @var ProductRelationsProcessorComposite
54+
*/
55+
private $productRelationsProcessorComposite;
56+
5157
/**
5258
* @param OptionRepository $optionRepository
5359
* @param ProductLinkManagementInterface $productLinkManagement
5460
* @param SaveAction $optionSave
5561
* @param MetadataPool $metadataPool
5662
* @param CheckOptionLinkIfExist|null $checkOptionLinkIfExist
63+
* @param ProductRelationsProcessorComposite|null $productRelationsProcessorComposite
5764
*/
5865
public function __construct(
5966
OptionRepository $optionRepository,
6067
ProductLinkManagementInterface $productLinkManagement,
6168
SaveAction $optionSave,
6269
MetadataPool $metadataPool,
63-
?CheckOptionLinkIfExist $checkOptionLinkIfExist = null
70+
?CheckOptionLinkIfExist $checkOptionLinkIfExist = null,
71+
?ProductRelationsProcessorComposite $productRelationsProcessorComposite = null
6472
) {
6573
$this->optionRepository = $optionRepository;
6674
$this->productLinkManagement = $productLinkManagement;
6775
$this->optionSave = $optionSave;
6876
$this->metadataPool = $metadataPool;
69-
$this->checkOptionLinkIfExist = $checkOptionLinkIfExist ??
70-
ObjectManager::getInstance()->get(CheckOptionLinkIfExist::class);
77+
$this->checkOptionLinkIfExist = $checkOptionLinkIfExist
78+
?? ObjectManager::getInstance()->get(CheckOptionLinkIfExist::class);
79+
$this->productRelationsProcessorComposite = $productRelationsProcessorComposite
80+
?? ObjectManager::getInstance()->get(ProductRelationsProcessorComposite::class);
7181
}
7282

7383
/**
@@ -107,6 +117,12 @@ public function execute($entity, $arguments = [])
107117
$entity->setCopyFromView(false);
108118
}
109119

120+
$this->productRelationsProcessorComposite->process(
121+
$entity,
122+
$existingBundleProductOptions,
123+
$bundleProductOptions
124+
);
125+
110126
return $entity;
111127
}
112128

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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+
namespace Magento\Bundle\Model;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
12+
/**
13+
* Composite processor to handle bundle product relations.
14+
*/
15+
class ProductRelationsProcessorComposite implements ProductRelationsProcessorInterface
16+
{
17+
/**
18+
* @var ProductRelationsProcessorInterface[]
19+
*/
20+
private $processors;
21+
22+
/**
23+
* @param ProductRelationsProcessorInterface[] $processors
24+
*/
25+
public function __construct(array $processors = [])
26+
{
27+
foreach ($processors as $processor) {
28+
if (!$processor instanceof ProductRelationsProcessorInterface) {
29+
throw new \InvalidArgumentException(
30+
__('Product relations processor must implement %1.', ProductRelationsProcessorInterface::class)
31+
);
32+
}
33+
}
34+
35+
$this->processors = $processors;
36+
}
37+
38+
/**
39+
* @inheritDoc
40+
*/
41+
public function process(
42+
ProductInterface $product,
43+
array $existingProductOptions,
44+
array $expectedProductOptions
45+
): void {
46+
foreach ($this->processors as $processor) {
47+
$processor->process($product, $existingProductOptions, $expectedProductOptions);
48+
}
49+
}
50+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
namespace Magento\Bundle\Model;
9+
10+
/**
11+
* Processor to handle bundle product relations.
12+
*/
13+
interface ProductRelationsProcessorInterface
14+
{
15+
/**
16+
* Process bundle product relations.
17+
*
18+
* @param \Magento\Catalog\Api\Data\ProductInterface $product
19+
* @param array $existingProductOptions
20+
* @param array $expectedProductOptions
21+
* @return void
22+
*/
23+
public function process(
24+
\Magento\Catalog\Api\Data\ProductInterface $product,
25+
array $existingProductOptions,
26+
array $expectedProductOptions
27+
): void;
28+
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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+
namespace Magento\Bundle\Model;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Framework\Reflection\TypeCaster;
12+
use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
13+
14+
/**
15+
* Recollect quota after handle product relations.
16+
*/
17+
class QuoteRecollectProcessor implements ProductRelationsProcessorInterface
18+
{
19+
/**
20+
* @var TypeCaster
21+
*/
22+
private $typeCaster;
23+
24+
/**
25+
* @var QuoteResource
26+
*/
27+
private $quoteResource;
28+
29+
/**
30+
* @var array
31+
*/
32+
private $comparisonFieldsTypeMapper;
33+
34+
/**
35+
* @param TypeCaster $typeCaster
36+
* @param QuoteResource $quoteResource
37+
* @param array $comparisonFieldsTypeMapper
38+
*/
39+
public function __construct(
40+
TypeCaster $typeCaster,
41+
QuoteResource $quoteResource,
42+
array $comparisonFieldsTypeMapper = []
43+
) {
44+
$this->typeCaster = $typeCaster;
45+
$this->quoteResource = $quoteResource;
46+
$this->comparisonFieldsTypeMapper = $comparisonFieldsTypeMapper;
47+
}
48+
49+
/**
50+
* Mark quotes to recollect if product options or links are changed.
51+
*
52+
* @param ProductInterface $product
53+
* @param array $existingProductOptions
54+
* @param array $expectedProductOptions
55+
* @return void
56+
*/
57+
public function process(
58+
ProductInterface $product,
59+
array $existingProductOptions,
60+
array $expectedProductOptions
61+
): void {
62+
if (empty($existingProductOptions)) {
63+
return;
64+
}
65+
66+
if ($this->isProductOptionsChanged($existingProductOptions, $expectedProductOptions)
67+
|| $this->isProductLinksChanged($existingProductOptions, $expectedProductOptions)
68+
) {
69+
$this->quoteResource->markQuotesRecollect($product->getId());
70+
}
71+
}
72+
73+
/**
74+
* Check product options change.
75+
*
76+
* @param array $existingProductOptions
77+
* @param array $expectedProductOptions
78+
* @return bool
79+
*/
80+
private function isProductOptionsChanged(
81+
array $existingProductOptions,
82+
array $expectedProductOptions
83+
): bool {
84+
if (count($existingProductOptions) !== count($expectedProductOptions)) {
85+
return true;
86+
}
87+
88+
$productOptionsDiff = array_udiff(
89+
$expectedProductOptions,
90+
$existingProductOptions,
91+
function ($expectedProductOption, $existingProductOption) {
92+
if ($expectedProductOption->getOptionId() === $existingProductOption->getOptionId()) {
93+
return $expectedProductOption->getRequired() - $existingProductOption->getRequired();
94+
}
95+
96+
return $expectedProductOption->getOptionId() - $existingProductOption->getOptionId();
97+
}
98+
);
99+
100+
return (bool)count($productOptionsDiff);
101+
}
102+
103+
/**
104+
* Check product links change.
105+
*
106+
* @param array $existingProductOptions
107+
* @param array $expectedProductOptions
108+
* @return bool
109+
*/
110+
private function isProductLinksChanged(
111+
array $existingProductOptions,
112+
array $expectedProductOptions
113+
): bool {
114+
$existingProductLinks = $this->flattenProductLinksData($existingProductOptions);
115+
$expectedProductLinks = $this->flattenProductLinksData($expectedProductOptions);
116+
117+
return $existingProductLinks != $expectedProductLinks;
118+
}
119+
120+
/**
121+
* Simplify product links data.
122+
*
123+
* @param array $productOptions
124+
* @return array
125+
*/
126+
private function flattenProductLinksData(array $productOptions): array
127+
{
128+
return array_reduce($productOptions, function ($result, $productOption) {
129+
$optionId = $productOption->getOptionId();
130+
$productLinks = [];
131+
foreach ($productOption->getProductLinks() as $productLink) {
132+
$productLinkData = $productLink->getData();
133+
$productLinkFilteredData = [];
134+
foreach ($this->comparisonFieldsTypeMapper as $fieldName => $fieldType) {
135+
if (isset($productLinkData[$fieldName])) {
136+
$productLinkFilteredData[$fieldName] = $this->typeCaster->castValueToType(
137+
$productLinkData[$fieldName],
138+
$fieldType
139+
);
140+
}
141+
}
142+
$productLinks[$productLink->getId()] = $productLinkFilteredData;
143+
}
144+
$result[$optionId] = $productLinks;
145+
146+
return $result;
147+
});
148+
}
149+
}

app/code/Magento/Bundle/etc/di.xml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,4 +253,22 @@
253253
type="Magento\Bundle\Plugin\Quote\UpdateBundleQuoteItemOptions"
254254
sortOrder="10"/>
255255
</type>
256+
<type name="Magento\Bundle\Model\ProductRelationsProcessorComposite">
257+
<arguments>
258+
<argument name="processors" xsi:type="array">
259+
<item name="quote_recollect" xsi:type="object">Magento\Bundle\Model\QuoteRecollectProcessor</item>
260+
</argument>
261+
</arguments>
262+
</type>
263+
<type name="Magento\Bundle\Model\QuoteRecollectProcessor">
264+
<arguments>
265+
<argument name="comparisonFieldsTypeMapper" xsi:type="array">
266+
<item name="sku" xsi:type="string">string</item>
267+
<item name="price" xsi:type="string">float</item>
268+
<item name="price_type" xsi:type="string">string</item>
269+
<item name="qty" xsi:type="string">float</item>
270+
<item name="selection_can_change_quantity" xsi:type="string">string</item>
271+
</argument>
272+
</arguments>
273+
</type>
256274
</config>

app/code/Magento/Bundle/i18n/en_US.csv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,4 @@ Type,Type
108108
"Cannot create shipment as bundle product ""%1"" has shipment type ""%2"". %3 should be shipped instead.","Cannot create shipment as bundle product ""%1"" has shipment type ""%2"". %3 should be shipped instead."
109109
"Bundle product itself","Bundle product itself"
110110
"Bundle product options","Bundle product options"
111+
"Product relations processor must implement %1.","Product relations processor must implement %1."

0 commit comments

Comments
 (0)