Skip to content

Commit ac38244

Browse files
committed
ACP2E-1992: Configurable on sale products not visible in products carousel
- configurable products are now visible even if their children aren't
1 parent b4ec6dd commit ac38244

File tree

4 files changed

+219
-6
lines changed

4 files changed

+219
-6
lines changed

app/code/Magento/PageBuilder/Model/Catalog/ProductTotals.php

Lines changed: 82 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
use Magento\Catalog\Model\ResourceModel\Product\Collection;
1515
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
1616
use Magento\CatalogWidget\Model\Rule;
17+
use Magento\Framework\App\ObjectManager;
18+
use Magento\Framework\App\ResourceConnection;
19+
use Magento\Framework\EntityManager\MetadataPool;
1720
use Magento\Framework\Exception\LocalizedException;
1821
use Magento\Framework\Exception\NoSuchEntityException;
1922
use Magento\Rule\Model\Condition\Combine;
@@ -53,25 +56,43 @@ class ProductTotals
5356
*/
5457
private $categoryRepository;
5558

59+
/**
60+
* @var MetadataPool
61+
*/
62+
private MetadataPool $metadataPool;
63+
64+
/**
65+
* @var ResourceConnection
66+
*/
67+
private ResourceConnection $resource;
68+
69+
private ?Collection $parentsCollection = null;
70+
5671
/**
5772
* @param CollectionFactory $productCollectionFactory
5873
* @param Builder $sqlBuilder
5974
* @param Rule $rule
6075
* @param Conditions $conditionsHelper
6176
* @param CategoryRepositoryInterface $categoryRepository
77+
* @param MetadataPool|null $metadataPool
78+
* @param ResourceConnection|null $resource
6279
*/
6380
public function __construct(
6481
CollectionFactory $productCollectionFactory,
6582
Builder $sqlBuilder,
6683
Rule $rule,
6784
Conditions $conditionsHelper,
68-
CategoryRepositoryInterface $categoryRepository
85+
CategoryRepositoryInterface $categoryRepository,
86+
?MetadataPool $metadataPool = null,
87+
?ResourceConnection $resource = null
6988
) {
7089
$this->productCollectionFactory = $productCollectionFactory;
7190
$this->sqlBuilder = $sqlBuilder;
7291
$this->rule = $rule;
7392
$this->conditionsHelper = $conditionsHelper;
7493
$this->categoryRepository = $categoryRepository;
94+
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
95+
$this->resource = $resource ?: ObjectManager::getInstance()->get(ResourceConnection::class);
7596
}
7697

7798
/**
@@ -159,37 +180,83 @@ private function getProductCollection(string $conditions, bool $usePriceIndex =
159180
return $collection;
160181
}
161182

183+
/**
184+
* Get parent products that don't have stand-alone properties (e.g. price or special price)
185+
*
186+
* @param Collection $collection
187+
* @return Collection
188+
* @throws \Exception
189+
*/
190+
private function getParentProductsCollection(Collection $collection): Collection
191+
{
192+
if (!$this->parentsCollection) {
193+
$ids = $collection->getAllIds();
194+
$linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
195+
->getLinkField();
196+
$connection = $this->resource->getConnection();
197+
$productIds = $connection->fetchCol(
198+
$connection
199+
->select()
200+
->from(['e' => $collection->getTable('catalog_product_entity')], ['link_table.parent_id'])
201+
->joinInner(
202+
['link_table' => $collection->getTable('catalog_product_super_link')],
203+
'link_table.product_id = e.' . $linkField,
204+
[]
205+
)
206+
->where('link_table.product_id IN (?)', $ids)
207+
);
208+
209+
$parentProducts = $this->productCollectionFactory->create();
210+
$parentProducts->addIdFilter($productIds);
211+
$this->parentsCollection = $parentProducts;
212+
}
213+
214+
return $this->parentsCollection;
215+
}
216+
162217
/**
163218
* Retrieve count of all enabled products
164219
*
165220
* @param string $conditions
166221
* @return int number of enabled products
222+
* @throws LocalizedException
223+
* @throws \Exception
167224
*/
168225
private function getEnabledCount(string $conditions): int
169226
{
170227
$collection = $this->getProductCollection($conditions, true);
171228
$collection->addAttributeToFilter('status', Status::STATUS_ENABLED);
172-
return $collection->getSize();
229+
230+
$parentProducts = $this->getParentProductsCollection($collection);
231+
$parentProducts->addAttributeToFilter('status', Status::STATUS_ENABLED);
232+
233+
return $collection->getSize() + $parentProducts->getSize();
173234
}
174235

175236
/**
176237
* Retrieve count of all disabled products
177238
*
178239
* @param string $conditions
179240
* @return int number of disabled products
241+
* @throws \Exception
180242
*/
181243
private function getDisabledCount(string $conditions): int
182244
{
183245
$collection = $this->getProductCollection($conditions, false);
184246
$collection->addAttributeToFilter('status', Status::STATUS_DISABLED);
185-
return $collection->getSize();
247+
248+
$parentProducts = $this->getParentProductsCollection($collection);
249+
$parentProducts->addAttributeToFilter('status', Status::STATUS_DISABLED);
250+
251+
return $collection->getSize() + $parentProducts->getSize();
186252
}
187253

188254
/**
189255
* Retrieve count of all not visible individually products
190256
*
191257
* @param string $conditions
192258
* @return int number of products not visible individually
259+
* @throws \Exception
193260
*/
194261
private function getNotVisibleCount(string $conditions): int
195262
{
@@ -202,7 +269,18 @@ private function getNotVisibleCount(string $conditions): int
202269
Visibility::VISIBILITY_IN_SEARCH
203270
]
204271
);
205-
return $collection->getSize();
272+
273+
$parentProducts = $this->getParentProductsCollection($collection);
274+
$parentProducts->addAttributeToFilter('status', Status::STATUS_ENABLED);
275+
$parentProducts->addAttributeToFilter(
276+
'visibility',
277+
[
278+
Visibility::VISIBILITY_NOT_VISIBLE,
279+
Visibility::VISIBILITY_IN_SEARCH
280+
]
281+
);
282+
283+
return $collection->getSize() + $parentProducts->getSize();
206284
}
207285

208286
/**
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
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\PageBuilder\Test\Unit\Model\Catalog;
9+
10+
use Magento\Catalog\Api\CategoryRepositoryInterface;
11+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
12+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
13+
use Magento\CatalogWidget\Model\Rule;
14+
use Magento\Framework\App\ResourceConnection;
15+
use Magento\Framework\DB\Adapter\AdapterInterface;
16+
use Magento\Framework\DB\Select;
17+
use Magento\Framework\EntityManager\EntityMetadataInterface;
18+
use Magento\Framework\EntityManager\MetadataPool;
19+
use Magento\PageBuilder\Model\Catalog\ProductTotals;
20+
use Magento\Rule\Model\Condition\Combine;
21+
use Magento\Rule\Model\Condition\Sql\Builder;
22+
use Magento\Widget\Helper\Conditions;
23+
use PHPUnit\Framework\MockObject\MockObject;
24+
use PHPUnit\Framework\TestCase;
25+
26+
class ProductTotalsTest extends TestCase
27+
{
28+
/**
29+
* @var CollectionFactory|MockObject
30+
*/
31+
private CollectionFactory $productCollectionFactory;
32+
33+
/**
34+
* @var Builder|MockObject
35+
*/
36+
private Builder $sqlBuilder;
37+
38+
/**
39+
* @var Rule|MockObject
40+
*/
41+
private Rule $rule;
42+
43+
/**
44+
* @var Conditions|MockObject
45+
*/
46+
private Conditions $conditionsHelper;
47+
48+
/**
49+
* @var CategoryRepositoryInterface|MockObject
50+
*/
51+
private CategoryRepositoryInterface $categoryRepository;
52+
53+
/**
54+
* @var MetadataPool|MockObject
55+
*/
56+
private MetadataPool $metadataPool;
57+
58+
/**
59+
* @var ResourceConnection|MockObject
60+
*/
61+
private ResourceConnection $resource;
62+
63+
/**
64+
* @var ProductTotals|MockObject
65+
*/
66+
private ProductTotals $productTotals;
67+
68+
/**
69+
* @inheritdoc
70+
*/
71+
protected function setUp(): void
72+
{
73+
$this->productCollectionFactory = $this->createMock(CollectionFactory::class);
74+
$this->sqlBuilder = $this->createMock(Builder::class);
75+
$this->rule = $this->createMock(Rule::class);
76+
$this->conditionsHelper = $this->createMock(Conditions::class);
77+
$this->categoryRepository = $this->createMock(CategoryRepositoryInterface::class);
78+
$this->metadataPool = $this->createMock(MetadataPool::class);
79+
$this->resource = $this->createMock(ResourceConnection::class);
80+
81+
$this->productTotals = new ProductTotals(
82+
$this->productCollectionFactory,
83+
$this->sqlBuilder,
84+
$this->rule,
85+
$this->conditionsHelper,
86+
$this->categoryRepository,
87+
$this->metadataPool,
88+
$this->resource
89+
);
90+
91+
parent::setUp();
92+
}
93+
94+
/**
95+
* @return void
96+
* @throws \Magento\Framework\Exception\LocalizedException
97+
* @throws \Zend_Db_Select_Exception
98+
*/
99+
public function testGetProductTotals(): void
100+
{
101+
$collection = $this->createMock(Collection::class);
102+
$collection->expects($this->exactly(3))->method('distinct');
103+
$collection->expects($this->any())->method('addAttributeToFilter');
104+
$collection->expects($this->once())->method('getAllIds');
105+
$select = $this->createMock(Select::class);
106+
$select->expects($this->any())->method('joinLeft')->willReturn($select);
107+
108+
$collection->expects($this->exactly(3))->method('getSelect')->willReturn($select);
109+
$this->productCollectionFactory->expects($this->exactly(4))
110+
->method('create')
111+
->willReturn($collection);
112+
113+
$entityMeta = $this->createMock(EntityMetadataInterface::class);
114+
$entityMeta->expects($this->once())->method('getLinkField')->willReturn('row_id');
115+
$this->metadataPool->expects($this->exactly(1))
116+
->method('getMetadata')
117+
->with(\Magento\Catalog\Api\Data\ProductInterface::class)
118+
->willReturn($entityMeta);
119+
120+
$parentSelect = $this->createMock(Select::class);
121+
$parentSelect->expects($this->any())->method('from')->willReturn($parentSelect);
122+
$parentSelect->expects($this->any())->method('joinInner')->willReturn($parentSelect);
123+
$db = $this->createMock(AdapterInterface::class);
124+
$db->expects($this->once())->method('select')->willReturn($parentSelect);
125+
$db->expects($this->once())->method('fetchCol')->willReturn([1]);
126+
$this->resource->expects($this->once())->method('getConnection')->willReturn($db);
127+
128+
$this->conditionsHelper->expects($this->exactly(3))->method('decode')->willReturn([]);
129+
$this->rule->expects($this->exactly(3))->method('loadPost');
130+
$combine = $this->createMock(Combine::class);
131+
$this->rule->expects($this->exactly(3))->method('getConditions')->willReturn($combine);
132+
133+
$this->productTotals->getProductTotals('{conditions}');
134+
}
135+
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@
184184
<arguments>
185185
<argument name="productCollectionFactory" xsi:type="object">pageBuilderProductCollectionFactory</argument>
186186
</arguments>
187-
<plugin name="pagebuilder_product_list" type="Magento\PageBuilder\Plugin\Catalog\Block\Product\ProductsListPlugin" />
187+
<plugin name="pagebuilder_product_list" type="Magento\PageBuilder\Plugin\Catalog\Block\Product\ProductsListPlugin" sortOrder="1"/>
188188
</type>
189189
<type name="Magento\Catalog\Helper\Output">
190190
<arguments>

app/code/Magento/PageBuilder/view/frontend/templates/catalog/product/widget/content/carousel.phtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use Magento\Framework\App\Action\Action;
1717
* @var \Magento\Framework\Escaper $escaper
1818
*/
1919
?>
20-
<?php if ($exist = ($block->getProductCollection() && $block->getProductCollection()->getSize())): ?>
20+
<?php if ($exist = ($block->getProductCollection() && $block->getProductCollection()->count())): ?>
2121
<?php
2222
$type = 'widget-product-carousel';
2323

0 commit comments

Comments
 (0)