diff --git a/vendor/magento/module-catalog/Block/Product/ListProduct.php b/vendor/magento/module-catalog/Block/Product/ListProduct.php
index 5b8cb6a1919..ab50e83e967 100644
--- a/vendor/magento/module-catalog/Block/Product/ListProduct.php
+++ b/vendor/magento/module-catalog/Block/Product/ListProduct.php
@@ -43,15 +43,11 @@ class ListProduct extends AbstractProduct implements IdentityInterface
protected $_defaultToolbarBlock = Toolbar::class;
/**
- * Product Collection
- *
* @var AbstractCollection
*/
protected $_productCollection;
/**
- * Catalog layer
- *
* @var Layer
*/
protected $_catalogLayer;
@@ -427,8 +423,10 @@ class ListProduct extends AbstractProduct implements IdentityInterface
*/
protected function getPriceRender()
{
- return $this->getLayout()->getBlock('product.price.render.default')
- ->setData('is_product_list', true);
+ $block = $this->getLayout()->getBlock('product.price.render.default');
+ $block->setData('is_product_list', true);
+
+ return $block->setData('product_list', $this->_productCollection);
}
/**
diff --git a/vendor/magento/module-configurable-product/Pricing/Price/SpecialPriceBulkResolver.php b/vendor/magento/module-configurable-product/Pricing/Price/SpecialPriceBulkResolver.php
new file mode 100644
index 00000000000..611aa15109f
--- /dev/null
+++ b/vendor/magento/module-configurable-product/Pricing/Price/SpecialPriceBulkResolver.php
@@ -0,0 +1,212 @@
+resource = $resource;
+ $this->metadataPool = $metadataPool;
+ $this->cache = $cache;
+ $this->cacheLifeTime = $cacheLifeTime;
+ }
+
+ /**
+ * Determines if blocks have special prices
+ *
+ * @param int $storeId
+ * @param AbstractCollection|null $productCollection
+ * @return array
+ * @throws \Exception
+ */
+ public function generateSpecialPriceMap(int $storeId, ?AbstractCollection $productCollection): array
+ {
+ if (!$productCollection) {
+ return [];
+ }
+ $cacheKey = $this->getCacheKey($storeId, $productCollection);
+ $cachedData = $this->getCachedData($cacheKey);
+ if ($cachedData === null) {
+ $configurableProducts = $this->filterConfigurableProducts($productCollection);
+ $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
+ $connection = $this->resource->getConnection();
+ $select = $connection->select()
+ ->from(
+ ['link' => $this->resource->getTableName('catalog_product_super_link')]
+ )
+ ->joinInner(
+ ['e' => $this->resource->getTableName('catalog_product_entity')],
+ 'link.parent_id = e.' . $metadata->getLinkField()
+ )
+ ->joinInner(
+ ['product_website' => $this->resource->getTableName('catalog_product_website')],
+ 'product_website.product_id = link.product_id'
+ )
+ ->joinLeft(
+ ['price' => $this->resource->getTableName('catalog_product_index_price')],
+ 'price.entity_id = link.product_id AND price.website_id = ' . $storeId .
+ ' AND price.customer_group_id = 0'
+ )
+ ->where('e.entity_id IN (' . implode(',', $configurableProducts) . ')')
+ ->columns(
+ [
+ 'link.product_id',
+ '(price.final_price < price.price) AS hasSpecialPrice',
+ 'e.' . $metadata->getLinkField() . ' AS identifier',
+ 'e.entity_id'
+ ]
+ );
+ $data = $connection->fetchAssoc($select);
+ $map = [];
+ foreach ($data as $specialPriceInfo) {
+ if (!isset($map[$specialPriceInfo['entity_id']])) {
+ $map[$specialPriceInfo['entity_id']] = (bool) $specialPriceInfo['hasSpecialPrice'];
+ } else {
+ if ($specialPriceInfo['hasSpecialPrice'] > $map[$specialPriceInfo['entity_id']]) {
+ $map[$specialPriceInfo['entity_id']] = true;
+ }
+ }
+
+ }
+ $this->saveCachedData($cacheKey, $map, array_column($data, 'identifier'));
+
+ return $map;
+ }
+
+ return $cachedData;
+ }
+
+ /**
+ * Returns only configurable product ids
+ *
+ * @param AbstractCollection $productCollection
+ * @return array
+ */
+ private function filterConfigurableProducts(AbstractCollection $productCollection): array
+ {
+ $configurableProductIds = [];
+ /** @var Product $product */
+ foreach ($productCollection->getItems() as $product) {
+ if ($product->getTypeId() == Configurable::TYPE_CODE) {
+ $configurableProductIds[] = $product->getEntityId();
+ }
+ }
+
+ return $configurableProductIds;
+ }
+
+ /**
+ * Generate cache key
+ *
+ * @param int $storeId
+ * @param AbstractCollection $productCollection
+ * @return string
+ */
+ private function getCacheKey(int $storeId, AbstractCollection $productCollection): string
+ {
+ $keyParts = $productCollection->getAllIds();
+ $keyParts[] = 'store_id_' . $storeId;
+
+ return hash('sha256', implode('_', $keyParts));
+ }
+
+ /**
+ * Retrieve potential cached data
+ *
+ * @param string $cacheKey
+ * @return array|null
+ */
+ private function getCachedData(string $cacheKey): ?array
+ {
+ $data = $this->cache->load($cacheKey);
+ if (!$data) {
+ return null;
+ }
+
+ return json_decode($data, true);
+ }
+
+ /**
+ * Store data in cache
+ *
+ * @param string $cacheKey
+ * @param array $data
+ * @param array $productTags
+ * @return bool
+ */
+ private function saveCachedData(string $cacheKey, array $data, array $productTags): bool
+ {
+ $tags = [
+ Category::CACHE_TAG,
+ Product::CACHE_TAG,
+ 'price'
+ ];
+ $productTags = array_unique($productTags);
+ foreach ($productTags as $tag) {
+ $tags[] = Configurable::TYPE_CODE . '_' . $tag;
+ }
+
+ return $this->cache->save(json_encode($data), $cacheKey, $tags, $this->cacheLifeTime);
+ }
+}
diff --git a/vendor/magento/module-configurable-product/Pricing/Render/FinalPriceBox.php b/vendor/magento/module-configurable-product/Pricing/Render/FinalPriceBox.php
index b48a987c235..3582f1d8176 100644
--- a/vendor/magento/module-configurable-product/Pricing/Render/FinalPriceBox.php
+++ b/vendor/magento/module-configurable-product/Pricing/Render/FinalPriceBox.php
@@ -9,6 +9,7 @@ use Magento\Catalog\Model\Product\Pricing\Renderer\SalableResolverInterface;
use Magento\Catalog\Pricing\Price\FinalPrice;
use Magento\Catalog\Pricing\Price\MinimalPriceCalculatorInterface;
use Magento\Catalog\Pricing\Price\RegularPrice;
+use Magento\ConfigurableProduct\Pricing\Price\SpecialPriceBulkResolver;
use Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsProviderInterface;
use Magento\Framework\Pricing\Price\PriceInterface;
use Magento\Framework\Pricing\Render\RendererPool;
@@ -23,7 +24,17 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox
/**
* @var ConfigurableOptionsProviderInterface
*/
- private $configurableOptionsProvider;
+ private ConfigurableOptionsProviderInterface $configurableOptionsProvider;
+
+ /**
+ * @var SpecialPriceBulkResolver
+ */
+ private SpecialPriceBulkResolver $specialPriceBulkResolver;
+
+ /**
+ * @var array
+ */
+ private array $specialPriceMap = [];
/**
* @param Context $context
@@ -33,17 +44,19 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox
* @param SalableResolverInterface $salableResolver
* @param MinimalPriceCalculatorInterface $minimalPriceCalculator
* @param ConfigurableOptionsProviderInterface $configurableOptionsProvider
+ * @param SpecialPriceBulkResolver $specialPriceBulkResolver
* @param array $data
*/
public function __construct(
- Context $context,
- SaleableInterface $saleableItem,
- PriceInterface $price,
- RendererPool $rendererPool,
- SalableResolverInterface $salableResolver,
- MinimalPriceCalculatorInterface $minimalPriceCalculator,
+ Context $context,
+ SaleableInterface $saleableItem,
+ PriceInterface $price,
+ RendererPool $rendererPool,
+ SalableResolverInterface $salableResolver,
+ MinimalPriceCalculatorInterface $minimalPriceCalculator,
ConfigurableOptionsProviderInterface $configurableOptionsProvider,
- array $data = []
+ SpecialPriceBulkResolver $specialPriceBulkResolver,
+ array $data = []
) {
parent::__construct(
$context,
@@ -56,23 +69,40 @@ class FinalPriceBox extends \Magento\Catalog\Pricing\Render\FinalPriceBox
);
$this->configurableOptionsProvider = $configurableOptionsProvider;
+ $this->specialPriceBulkResolver = $specialPriceBulkResolver;
}
/**
* Define if the special price should be shown
*
* @return bool
+ * @throws \Exception
*/
public function hasSpecialPrice()
{
- $product = $this->getSaleableItem();
- foreach ($this->configurableOptionsProvider->getProducts($product) as $subProduct) {
- $regularPrice = $subProduct->getPriceInfo()->getPrice(RegularPrice::PRICE_CODE)->getValue();
- $finalPrice = $subProduct->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue();
- if ($finalPrice < $regularPrice) {
- return true;
+ if ($this->isProductList()) {
+ if ($this->getData('product_list') === null) {
+ return false;
+ }
+
+ if (empty($this->specialPriceMap)) {
+ $this->specialPriceMap = $this->specialPriceBulkResolver->generateSpecialPriceMap(
+ $this->saleableItem->getStoreId(),
+ $this->getData('product_list')
+ );
+ }
+
+ return $this->specialPriceMap[$this->saleableItem->getId()];
+ } else {
+ $product = $this->getSaleableItem();
+ foreach ($this->configurableOptionsProvider->getProducts($product) as $subProduct) {
+ $regularPrice = $subProduct->getPriceInfo()->getPrice(RegularPrice::PRICE_CODE)->getValue();
+ $finalPrice = $subProduct->getPriceInfo()->getPrice(FinalPrice::PRICE_CODE)->getValue();
+ if ($finalPrice < $regularPrice) {
+ return true;
+ }
}
+ return false;
}
- return false;
}
}
diff --git a/vendor/magento/module-configurable-product/etc/di.xml b/vendor/magento/module-configurable-product/etc/di.xml
index 16e3aaafc9c..7ce4d50df81 100644
--- a/vendor/magento/module-configurable-product/etc/di.xml
+++ b/vendor/magento/module-configurable-product/etc/di.xml
@@ -283,4 +283,9 @@
+
+
+ Magento\Framework\App\Cache\Type\Collection
+
+
diff --git a/vendor/magento/module-configurable-product/view/base/templates/product/price/final_price.phtml b/vendor/magento/module-configurable-product/view/base/templates/product/price/final_price.phtml
index a6dc6c81998..c1238139d01 100644
--- a/vendor/magento/module-configurable-product/view/base/templates/product/price/final_price.phtml
+++ b/vendor/magento/module-configurable-product/view/base/templates/product/price/final_price.phtml
@@ -4,7 +4,7 @@
* See COPYING.txt for license details.
*/
-/** @var \Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox$block */
+/** @var \Magento\ConfigurableProduct\Pricing\Render\FinalPriceBox $block */
/** @var \Magento\Framework\Pricing\Price\PriceInterface $priceModel */
$priceModel = $block->getPriceType('regular_price');
/** @var \Magento\Framework\Pricing\Price\PriceInterface $finalPriceModel */
@@ -23,24 +23,24 @@ $schema = ($block->getZone() == 'item_view') ? true : false;
?>
-isProductList() && $block->hasSpecialPrice()) : ?>
+hasSpecialPrice()): ?>
= /* @noEscape */ $block->renderAmount($priceModel->getAmount(), [
'display_label' => __('Regular Price'),
'price_id' => $block->getPriceId('old-price-' . $idSuffix),
'price_type' => 'oldPrice',
'include_container' => true,
- 'skip_adjustments' => true
+ 'skip_adjustments' => false
]); ?>
-showMinimalPrice()) : ?>
- getUseLinkForAsLowAs()) :?>
+showMinimalPrice()): ?>
+ getUseLinkForAsLowAs()): ?>
= /* @noEscape */ $block->renderAmountMinimal() ?>
-
+
= /* @noEscape */ $block->renderAmountMinimal() ?>