Skip to content

Commit 8060971

Browse files
author
Oleksandr Gorkun
committed
Merge branch 'MC-22139' of https://github.com/magento-qwerty/magento2ce into MC-18685-and-MC-22139
� Conflicts: � app/code/Magento/Catalog/etc/di.xml
2 parents 82ea599 + d399015 commit 8060971

File tree

40 files changed

+2553
-111
lines changed

40 files changed

+2553
-111
lines changed

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

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1271,12 +1271,11 @@ public function getSpecialToDate()
12711271
public function getRelatedProducts()
12721272
{
12731273
if (!$this->hasRelatedProducts()) {
1274-
$products = [];
1275-
$collection = $this->getRelatedProductCollection();
1276-
foreach ($collection as $product) {
1277-
$products[] = $product;
1274+
//Loading all linked products.
1275+
$this->getProductLinks();
1276+
if (!$this->hasRelatedProducts()) {
1277+
$this->setRelatedProducts([]);
12781278
}
1279-
$this->setRelatedProducts($products);
12801279
}
12811280
return $this->getData('related_products');
12821281
}
@@ -1333,12 +1332,13 @@ public function getRelatedLinkCollection()
13331332
public function getUpSellProducts()
13341333
{
13351334
if (!$this->hasUpSellProducts()) {
1336-
$products = [];
1337-
foreach ($this->getUpSellProductCollection() as $product) {
1338-
$products[] = $product;
1335+
//Loading all linked products.
1336+
$this->getProductLinks();
1337+
if (!$this->hasUpSellProducts()) {
1338+
$this->setUpSellProducts([]);
13391339
}
1340-
$this->setUpSellProducts($products);
13411340
}
1341+
13421342
return $this->getData('up_sell_products');
13431343
}
13441344

@@ -1394,12 +1394,13 @@ public function getUpSellLinkCollection()
13941394
public function getCrossSellProducts()
13951395
{
13961396
if (!$this->hasCrossSellProducts()) {
1397-
$products = [];
1398-
foreach ($this->getCrossSellProductCollection() as $product) {
1399-
$products[] = $product;
1397+
//Loading all linked products.
1398+
$this->getProductLinks();
1399+
if (!$this->hasCrossSellProducts()) {
1400+
$this->setCrossSellProducts([]);
14001401
}
1401-
$this->setCrossSellProducts($products);
14021402
}
1403+
14031404
return $this->getData('cross_sell_products');
14041405
}
14051406

@@ -1455,7 +1456,11 @@ public function getCrossSellLinkCollection()
14551456
public function getProductLinks()
14561457
{
14571458
if ($this->_links === null) {
1458-
$this->_links = $this->getLinkRepository()->getList($this);
1459+
if ($this->getSku() && $this->getId()) {
1460+
$this->_links = $this->getLinkRepository()->getList($this);
1461+
} else {
1462+
$this->_links = [];
1463+
}
14591464
}
14601465
return $this->_links;
14611466
}

app/code/Magento/Catalog/Model/Product/Link/SaveHandler.php

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
* See COPYING.txt for license details.
55
*/
66

7+
declare(strict_types=1);
8+
79
namespace Magento\Catalog\Model\Product\Link;
810

911
use Magento\Catalog\Api\ProductLinkRepositoryInterface;
1012
use Magento\Catalog\Model\ResourceModel\Product\Link;
1113
use Magento\Framework\EntityManager\MetadataPool;
14+
use Magento\Catalog\Api\Data\ProductLinkInterface;
1215

1316
/**
14-
* Class SaveProductLinks
17+
* Save product links.
1518
*/
1619
class SaveHandler
1720
{
@@ -47,22 +50,24 @@ public function __construct(
4750
}
4851

4952
/**
50-
* @param string $entityType
51-
* @param object $entity
53+
* Save product links for the product.
54+
*
55+
* @param string $entityType Product type.
56+
* @param \Magento\Catalog\Api\Data\ProductInterface $entity
5257
* @return \Magento\Catalog\Api\Data\ProductInterface
5358
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
5459
*/
5560
public function execute($entityType, $entity)
5661
{
5762
$link = $entity->getData($this->metadataPool->getMetadata($entityType)->getLinkField());
5863
if ($this->linkResource->hasProductLinks($link)) {
59-
/** @var \Magento\Catalog\Api\Data\ProductInterface $entity */
6064
foreach ($this->productLinkRepository->getList($entity) as $link) {
6165
$this->productLinkRepository->delete($link);
6266
}
6367
}
6468

6569
// Build links per type
70+
/** @var ProductLinkInterface[][] $linksByType */
6671
$linksByType = [];
6772
foreach ($entity->getProductLinks() as $link) {
6873
$linksByType[$link->getLinkType()][] = $link;
@@ -71,13 +76,17 @@ public function execute($entityType, $entity)
7176
// Set array position as a fallback position if necessary
7277
foreach ($linksByType as $linkType => $links) {
7378
if (!$this->hasPosition($links)) {
74-
array_walk($linksByType[$linkType], function ($productLink, $position) {
75-
$productLink->setPosition(++$position);
76-
});
79+
array_walk(
80+
$linksByType[$linkType],
81+
function (ProductLinkInterface $productLink, $position) {
82+
$productLink->setPosition(++$position);
83+
}
84+
);
7785
}
7886
}
7987

8088
// Flatten multi-dimensional linksByType in ProductLinks
89+
/** @var ProductLinkInterface[] $productLinks */
8190
$productLinks = array_reduce($linksByType, 'array_merge', []);
8291

8392
if (count($productLinks) > 0) {
@@ -90,13 +99,14 @@ public function execute($entityType, $entity)
9099

91100
/**
92101
* Check if at least one link without position
93-
* @param array $links
102+
*
103+
* @param ProductLinkInterface[] $links
94104
* @return bool
95105
*/
96-
private function hasPosition(array $links)
106+
private function hasPosition(array $links): bool
97107
{
98108
foreach ($links as $link) {
99-
if (!array_key_exists('position', $link->getData())) {
109+
if ($link->getPosition() === null) {
100110
return false;
101111
}
102112
}

app/code/Magento/Catalog/Model/ProductLink/CollectionProvider.php

Lines changed: 149 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
* See COPYING.txt for license details.
55
*/
66

7+
declare(strict_types=1);
8+
79
namespace Magento\Catalog\Model\ProductLink;
810

11+
use Magento\Catalog\Model\Product;
912
use Magento\Catalog\Model\ProductLink\Converter\ConverterPool;
1013
use Magento\Framework\Exception\NoSuchEntityException;
1114

@@ -19,6 +22,11 @@ class CollectionProvider
1922
*/
2023
protected $providers;
2124

25+
/**
26+
* @var MapProviderInterface[]
27+
*/
28+
private $mapProviders;
29+
2230
/**
2331
* @var ConverterPool
2432
*/
@@ -27,43 +35,169 @@ class CollectionProvider
2735
/**
2836
* @param ConverterPool $converterPool
2937
* @param CollectionProviderInterface[] $providers
38+
* @param MapProviderInterface[] $mapProviders
3039
*/
31-
public function __construct(ConverterPool $converterPool, array $providers = [])
40+
public function __construct(ConverterPool $converterPool, array $providers = [], array $mapProviders = [])
3241
{
3342
$this->converterPool = $converterPool;
3443
$this->providers = $providers;
44+
$this->mapProviders = $mapProviders;
45+
}
46+
47+
/**
48+
* Extract link data from linked products.
49+
*
50+
* @param Product[] $linkedProducts
51+
* @param string $type
52+
* @return array
53+
*/
54+
private function prepareList(array $linkedProducts, string $type): array
55+
{
56+
$converter = $this->converterPool->getConverter($type);
57+
$links = [];
58+
foreach ($linkedProducts as $item) {
59+
$itemId = $item->getId();
60+
$links[$itemId] = $converter->convert($item);
61+
$links[$itemId]['position'] = $links[$itemId]['position'] ?? 0;
62+
$links[$itemId]['link_type'] = $type;
63+
}
64+
65+
return $links;
3566
}
3667

3768
/**
3869
* Get product collection by link type
3970
*
40-
* @param \Magento\Catalog\Model\Product $product
71+
* @param Product $product
4172
* @param string $type
4273
* @return array
4374
* @throws NoSuchEntityException
4475
*/
45-
public function getCollection(\Magento\Catalog\Model\Product $product, $type)
76+
public function getCollection(Product $product, $type)
4677
{
4778
if (!isset($this->providers[$type])) {
4879
throw new NoSuchEntityException(__("The collection provider isn't registered."));
4980
}
5081

5182
$products = $this->providers[$type]->getLinkedProducts($product);
52-
$converter = $this->converterPool->getConverter($type);
53-
$sorterItems = [];
54-
foreach ($products as $item) {
55-
$itemId = $item->getId();
56-
$sorterItems[$itemId] = $converter->convert($item);
57-
$sorterItems[$itemId]['position'] = $sorterItems[$itemId]['position'] ?? 0;
83+
84+
$linkData = $this->prepareList($products, $type);
85+
usort(
86+
$linkData,
87+
function (array $itemA, array $itemB): int {
88+
$posA = (int)$itemA['position'];
89+
$posB = (int)$itemB['position'];
90+
91+
return $posA <=> $posB;
92+
}
93+
);
94+
95+
return $linkData;
96+
}
97+
98+
/**
99+
* Load maps from map providers.
100+
*
101+
* @param array $map
102+
* @param array $typeProcessors
103+
* @param Product[] $products
104+
* @return void
105+
*/
106+
private function retrieveMaps(array &$map, array $typeProcessors, array $products): void
107+
{
108+
/**
109+
* @var MapProviderInterface $processor
110+
* @var string[] $types
111+
*/
112+
foreach ($typeProcessors as $processorIndex => $types) {
113+
$typeMap = $this->mapProviders[$processorIndex]->fetchMap($products, $types);
114+
/**
115+
* @var string $sku
116+
* @var Product[][] $links
117+
*/
118+
foreach ($typeMap as $sku => $links) {
119+
$linkData = [];
120+
foreach ($links as $linkType => $linkedProducts) {
121+
$linkData[] = $this->prepareList($linkedProducts, $linkType);
122+
}
123+
if ($linkData) {
124+
$existing = [];
125+
if (array_key_exists($sku, $map)) {
126+
$existing = $map[$sku];
127+
}
128+
// phpcs:ignore Magento2.Performance.ForeachArrayMerge
129+
$map[$sku] = array_merge($existing, ...$linkData);
130+
}
131+
}
132+
}
133+
}
134+
135+
/**
136+
* Load links for each product separately.
137+
*
138+
* @param \SplObjectStorage $map
139+
* @param string[] $types
140+
* @param Product[] $products
141+
* @return void
142+
* @throws NoSuchEntityException
143+
*/
144+
private function retrieveSingles(array &$map, array $types, array $products): void
145+
{
146+
foreach ($products as $product) {
147+
$linkData = [];
148+
foreach ($types as $type) {
149+
$linkData[] = $this->getCollection($product, $type);
150+
}
151+
$linkData = array_filter($linkData);
152+
if ($linkData) {
153+
$existing = [];
154+
if (array_key_exists($product->getSku(), $map)) {
155+
$existing = $map[$product->getSku()];
156+
}
157+
// phpcs:ignore Magento2.Performance.ForeachArrayMerge
158+
$map[$product->getSku()] = array_merge($existing, ...$linkData);
159+
}
58160
}
161+
}
59162

60-
usort($sorterItems, function ($itemA, $itemB) {
61-
$posA = (int)$itemA['position'];
62-
$posB = (int)$itemB['position'];
163+
/**
164+
* Load map of linked product data.
165+
*
166+
* Link data consists of link_type, type, sku, position, extension attributes? and custom_attributes?.
167+
*
168+
* @param Product[] $products
169+
* @param array $types Keys - string names, values - codes.
170+
* @return array Keys - SKUs, values containing link data.
171+
* @throws NoSuchEntityException
172+
* @throws \InvalidArgumentException
173+
*/
174+
public function getMap(array $products, array $types): array
175+
{
176+
if (!$types) {
177+
throw new \InvalidArgumentException('Types are required');
178+
}
179+
$map = [];
180+
$typeProcessors = [];
181+
/** @var string[] $singleProcessors */
182+
$singleProcessors = [];
183+
//Finding map processors
184+
foreach ($types as $type => $typeCode) {
185+
foreach ($this->mapProviders as $i => $mapProvider) {
186+
if ($mapProvider->canProcessLinkType($type)) {
187+
if (!array_key_exists($i, $typeProcessors)) {
188+
$typeProcessors[$i] = [];
189+
}
190+
$typeProcessors[$i][$type] = $typeCode;
191+
continue 2;
192+
}
193+
}
194+
//No map processor found, will process 1 by 1
195+
$singleProcessors[] = $type;
196+
}
63197

64-
return $posA <=> $posB;
65-
});
198+
$this->retrieveMaps($map, $typeProcessors, $products);
199+
$this->retrieveSingles($map, $singleProcessors, $products);
66200

67-
return $sorterItems;
201+
return $map;
68202
}
69203
}

0 commit comments

Comments
 (0)