Skip to content

Commit a1b9be2

Browse files
committed
Merge branch 'ACP2E-2224' of https://github.com/magento-l3/magento2ce into 04-29-24-Tier4-Bugfix-Delivery
2 parents 995a05c + acd587c commit a1b9be2

File tree

17 files changed

+494
-70
lines changed

17 files changed

+494
-70
lines changed

app/code/Magento/Catalog/Block/Product/ListProduct.php

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@
1616
use Magento\Catalog\Model\Product;
1717
use Magento\Catalog\Model\ResourceModel\Product\Collection;
1818
use Magento\Catalog\Pricing\Price\FinalPrice;
19+
use Magento\Catalog\Pricing\Price\SpecialPriceBulkResolverInterface;
1920
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
2021
use Magento\Framework\App\ActionInterface;
2122
use Magento\Framework\App\Config\Element;
2223
use Magento\Framework\Data\Helper\PostHelper;
2324
use Magento\Framework\DataObject\IdentityInterface;
25+
use Magento\Framework\Exception\LocalizedException;
2426
use Magento\Framework\Exception\NoSuchEntityException;
2527
use Magento\Framework\Pricing\Render;
2628
use Magento\Framework\Url\Helper\Data;
@@ -43,15 +45,11 @@ class ListProduct extends AbstractProduct implements IdentityInterface
4345
protected $_defaultToolbarBlock = Toolbar::class;
4446

4547
/**
46-
* Product Collection
47-
*
4848
* @var AbstractCollection
4949
*/
5050
protected $_productCollection;
5151

5252
/**
53-
* Catalog layer
54-
*
5553
* @var Layer
5654
*/
5755
protected $_catalogLayer;
@@ -71,6 +69,16 @@ class ListProduct extends AbstractProduct implements IdentityInterface
7169
*/
7270
protected $categoryRepository;
7371

72+
/**
73+
* @var SpecialPriceBulkResolverInterface
74+
*/
75+
private SpecialPriceBulkResolverInterface $specialPriceBulkResolver;
76+
77+
/**
78+
* @var array|null
79+
*/
80+
private ?array $specialPriceMap = null;
81+
7482
/**
7583
* @param Context $context
7684
* @param PostHelper $postDataHelper
@@ -79,6 +87,7 @@ class ListProduct extends AbstractProduct implements IdentityInterface
7987
* @param Data $urlHelper
8088
* @param array $data
8189
* @param OutputHelper|null $outputHelper
90+
* @param SpecialPriceBulkResolverInterface|null $specialPriceBulkResolver
8291
*/
8392
public function __construct(
8493
Context $context,
@@ -87,12 +96,15 @@ public function __construct(
8796
CategoryRepositoryInterface $categoryRepository,
8897
Data $urlHelper,
8998
array $data = [],
90-
?OutputHelper $outputHelper = null
99+
?OutputHelper $outputHelper = null,
100+
?SpecialPriceBulkResolverInterface $specialPriceBulkResolver = null
91101
) {
92102
$this->_catalogLayer = $layerResolver->get();
93103
$this->_postDataHelper = $postDataHelper;
94104
$this->categoryRepository = $categoryRepository;
95105
$this->urlHelper = $urlHelper;
106+
$this->specialPriceBulkResolver = $specialPriceBulkResolver ??
107+
ObjectManager::getInstance()->get(SpecialPriceBulkResolverInterface::class);
96108
$data['outputHelper'] = $outputHelper ?? ObjectManager::getInstance()->get(OutputHelper::class);
97109
parent::__construct(
98110
$context,
@@ -424,11 +436,21 @@ public function getProductPrice(Product $product)
424436
* (rendering happens in the scope of product list, but not single product)
425437
*
426438
* @return Render
439+
* @throws LocalizedException
427440
*/
428441
protected function getPriceRender()
429442
{
430-
return $this->getLayout()->getBlock('product.price.render.default')
431-
->setData('is_product_list', true);
443+
$block = $this->getLayout()->getBlock('product.price.render.default');
444+
$block->setData('is_product_list', true);
445+
446+
if ($this->specialPriceMap === null) {
447+
$this->specialPriceMap = $this->specialPriceBulkResolver->generateSpecialPriceMap(
448+
(int)$this->_storeManager->getStore()->getId(),
449+
$this->_getProductCollection()
450+
);
451+
}
452+
453+
return $block->setData('special_price_map', $this->specialPriceMap);
432454
}
433455

434456
/**
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\Catalog\Pricing\Price;
20+
21+
use Magento\Catalog\Api\Data\ProductInterface;
22+
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
23+
use Magento\Framework\App\ResourceConnection;
24+
use Magento\Framework\EntityManager\MetadataPool;
25+
use Magento\Framework\Session\SessionManagerInterface;
26+
use Magento\Framework\View\Element\Block\ArgumentInterface;
27+
28+
class SpecialPriceBulkResolver implements SpecialPriceBulkResolverInterface, ArgumentInterface
29+
{
30+
/**
31+
* @var ResourceConnection
32+
*/
33+
private ResourceConnection $resource;
34+
35+
/**
36+
* @var MetadataPool
37+
*/
38+
private MetadataPool $metadataPool;
39+
40+
/**
41+
* @var SessionManagerInterface
42+
*/
43+
private SessionManagerInterface $customerSession;
44+
45+
/**
46+
* @param ResourceConnection $resource
47+
* @param MetadataPool $metadataPool
48+
* @param SessionManagerInterface $customerSession
49+
*/
50+
public function __construct(
51+
ResourceConnection $resource,
52+
MetadataPool $metadataPool,
53+
SessionManagerInterface $customerSession
54+
) {
55+
$this->resource = $resource;
56+
$this->metadataPool = $metadataPool;
57+
$this->customerSession = $customerSession;
58+
}
59+
60+
/**
61+
* Determines if blocks have special prices
62+
*
63+
* @param int $storeId
64+
* @param AbstractCollection|null $productCollection
65+
* @return array
66+
* @throws \Exception
67+
*/
68+
public function generateSpecialPriceMap(int $storeId, ?AbstractCollection $productCollection): array
69+
{
70+
if (!$productCollection) {
71+
return [];
72+
}
73+
74+
$metadata = $this->metadataPool->getMetadata(ProductInterface::class);
75+
$connection = $this->resource->getConnection();
76+
$select = $connection->select()
77+
->from(
78+
['e' => $this->resource->getTableName('catalog_product_entity')]
79+
)
80+
->joinLeft(
81+
['link' => $this->resource->getTableName('catalog_product_super_link')],
82+
'link.parent_id = e.' . $metadata->getLinkField()
83+
)
84+
->joinLeft(
85+
['product_website' => $this->resource->getTableName('catalog_product_website')],
86+
'product_website.product_id = link.product_id'
87+
)
88+
->joinLeft(
89+
['price' => $this->resource->getTableName('catalog_product_index_price')],
90+
'price.entity_id = COALESCE(link.product_id, e.entity_id) AND price.website_id = ' . $storeId .
91+
' AND price.customer_group_id = ' . $this->customerSession->getCustomerGroupId()
92+
)
93+
->where('e.entity_id IN (' . implode(',', $productCollection->getAllIds()) . ')')
94+
->columns(
95+
[
96+
'link.product_id',
97+
'(price.final_price < price.price) AS hasSpecialPrice',
98+
'e.' . $metadata->getLinkField() . ' AS identifier',
99+
'e.entity_id'
100+
]
101+
);
102+
$data = $connection->fetchAll($select);
103+
$map = [];
104+
foreach ($data as $specialPriceInfo) {
105+
if (!isset($map[$specialPriceInfo['entity_id']])) {
106+
$map[$specialPriceInfo['entity_id']] = (bool) $specialPriceInfo['hasSpecialPrice'];
107+
} else {
108+
if ($specialPriceInfo['hasSpecialPrice'] > $map[$specialPriceInfo['entity_id']]) {
109+
$map[$specialPriceInfo['entity_id']] = true;
110+
}
111+
}
112+
113+
}
114+
115+
return $map;
116+
}
117+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
/************************************************************************
3+
*
4+
* Copyright 2023 Adobe
5+
* All Rights Reserved.
6+
*
7+
* NOTICE: All information contained herein is, and remains
8+
* the property of Adobe and its suppliers, if any. The intellectual
9+
* and technical concepts contained herein are proprietary to Adobe
10+
* and its suppliers and are protected by all applicable intellectual
11+
* property laws, including trade secret and copyright laws.
12+
* Dissemination of this information or reproduction of this material
13+
* is strictly forbidden unless prior written permission is obtained
14+
* from Adobe.
15+
* ************************************************************************
16+
*/
17+
declare(strict_types=1);
18+
19+
namespace Magento\Catalog\Pricing\Price;
20+
21+
use Magento\Eav\Model\Entity\Collection\AbstractCollection;
22+
23+
interface SpecialPriceBulkResolverInterface
24+
{
25+
public const DEFAULT_CACHE_LIFE_TIME = 31536000;
26+
27+
/**
28+
* Generate special price flag for entire product listing
29+
*
30+
* @param int $storeId
31+
* @param AbstractCollection|null $productCollection
32+
* @return array
33+
*/
34+
public function generateSpecialPriceMap(int $storeId, ?AbstractCollection $productCollection): array;
35+
}

app/code/Magento/Catalog/Pricing/Render/FinalPriceBox.php

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,18 @@ public function renderAmountMinimal()
153153
*/
154154
public function hasSpecialPrice()
155155
{
156-
$displayRegularPrice = $this->getPriceType(Price\RegularPrice::PRICE_CODE)->getAmount()->getValue();
157-
$displayFinalPrice = $this->getPriceType(Price\FinalPrice::PRICE_CODE)->getAmount()->getValue();
158-
return $displayFinalPrice < $displayRegularPrice;
156+
if ($this->isProductList()) {
157+
if (!$this->getData('special_price_map')) {
158+
return false;
159+
}
160+
161+
return (bool)$this->getData('special_price_map')[$this->saleableItem->getId()];
162+
} else {
163+
$displayRegularPrice = $this->getPriceType(Price\RegularPrice::PRICE_CODE)->getAmount()->getValue();
164+
$displayFinalPrice = $this->getPriceType(Price\FinalPrice::PRICE_CODE)->getAmount()->getValue();
165+
166+
return $displayFinalPrice < $displayRegularPrice;
167+
}
159168
}
160169

161170
/**

app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
2525
use Magento\Framework\Url\Helper\Data;
2626
use Magento\Framework\View\LayoutInterface;
27+
use Magento\Store\Api\Data\StoreInterface;
28+
use Magento\Store\Model\StoreManagerInterface;
2729
use PHPUnit\Framework\MockObject\MockObject;
2830
use PHPUnit\Framework\TestCase;
2931

@@ -139,6 +141,10 @@ protected function setUp(): void
139141
$this->context->expects($this->any())->method('getCartHelper')->willReturn($this->cartHelperMock);
140142
$this->context->expects($this->any())->method('getLayout')->willReturn($this->layoutMock);
141143
$this->context->expects($this->any())->method('getEventManager')->willReturn($eventManager);
144+
$storeManager = $this->createMock(StoreManagerInterface::class);
145+
$store = $this->createMock(StoreInterface::class);
146+
$storeManager->expects($this->any())->method('getStore')->willReturn($store);
147+
$this->context->expects($this->any())->method('getStoreManager')->willReturn($storeManager);
142148

143149
$this->block = $objectManager->getObject(
144150
ListProduct::class,
@@ -253,15 +259,14 @@ public function testGetAddToCartPostParams()
253259

254260
public function testSetIsProductListFlagOnGetProductPrice()
255261
{
256-
$this->renderer->expects($this->once())
262+
$this->renderer->expects($this->exactly(2))
257263
->method('setData')
258-
->with('is_product_list', true)
259264
->willReturnSelf();
260265
$this->layoutMock->expects($this->once())
261266
->method('getBlock')
262267
->with('product.price.render.default')
263268
->willReturn($this->renderer);
264-
269+
$this->block->setCollection($this->prodCollectionMock);
265270
$this->block->getProductPrice($this->productMock);
266271
}
267272
}

0 commit comments

Comments
 (0)