Skip to content

Commit ca47f51

Browse files
committed
Merge branch 'ACP2E-1992' of https://github.com/magento-l3/magento2ce into L3-PR-2023-09-13
2 parents 563a8a5 + c37a231 commit ca47f51

File tree

6 files changed

+269
-6
lines changed

6 files changed

+269
-6
lines changed

app/code/Magento/CatalogWidget/Block/Product/ProductsList.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -324,15 +324,26 @@ protected function _beforeToHtml()
324324
*/
325325
public function createCollection()
326326
{
327-
/** @var $collection Collection */
328-
$collection = $this->productCollectionFactory->create();
327+
$collection = $this->getBaseCollection();
328+
329+
$collection->setVisibility($this->catalogProductVisibility->getVisibleInCatalogIds());
330+
331+
return $collection;
332+
}
329333

334+
/**
335+
* Prepare and return product collection without visibility filter
336+
*
337+
* @return Collection
338+
* @throws LocalizedException
339+
*/
340+
public function getBaseCollection(): Collection
341+
{
342+
$collection = $this->productCollectionFactory->create();
330343
if ($this->getData('store_id') !== null) {
331344
$collection->setStoreId($this->getData('store_id'));
332345
}
333346

334-
$collection->setVisibility($this->catalogProductVisibility->getVisibleInCatalogIds());
335-
336347
/**
337348
* Change sorting attribute to entity_id because created_at can be the same for products fastly created
338349
* one by one and sorting by created_at is indeterministic in this case.

app/code/Magento/CatalogWidget/view/frontend/templates/product/widget/content/grid.phtml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use Magento\Framework\App\Action\Action;
1515
// phpcs:disable Magento2.Templates.ThisInTemplate.FoundHelper
1616
// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis
1717
?>
18-
<?php if ($exist = ($block->getProductCollection() && $block->getProductCollection()->getSize())): ?>
18+
<?php if ($exist = ($block->getProductCollection() && $block->getProductCollection()->count())): ?>
1919
<?php
2020
$type = 'widget-product-grid';
2121

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
/**
3+
* Copyright © Magento, Inc. All rights reserved.
4+
* See COPYING.txt for license details.
5+
*/
6+
7+
declare(strict_types=1);
8+
9+
namespace Magento\ConfigurableProduct\Plugin\CatalogWidget\Block\Product;
10+
11+
use Magento\Catalog\Model\Product;
12+
use Magento\Catalog\Model\Product\Visibility;
13+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
14+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
15+
use Magento\CatalogWidget\Block\Product\ProductsList;
16+
use Magento\Framework\App\ResourceConnection;
17+
use Magento\Framework\EntityManager\MetadataPool;
18+
use Magento\Framework\Exception\LocalizedException;
19+
20+
class ProductsListPlugin
21+
{
22+
/**
23+
* @var CollectionFactory
24+
*/
25+
private CollectionFactory $productCollectionFactory;
26+
27+
/**
28+
* @var Visibility
29+
*/
30+
private Visibility $catalogProductVisibility;
31+
32+
/**
33+
* @var ResourceConnection
34+
*/
35+
private ResourceConnection $resource;
36+
37+
/**
38+
* @var MetadataPool
39+
*/
40+
private MetadataPool $metadataPool;
41+
42+
/**
43+
* @param CollectionFactory $productCollectionFactory
44+
* @param Visibility $catalogProductVisibility
45+
* @param ResourceConnection $resource
46+
* @param MetadataPool $metadataPool
47+
*/
48+
public function __construct(
49+
CollectionFactory $productCollectionFactory,
50+
Visibility $catalogProductVisibility,
51+
ResourceConnection $resource,
52+
MetadataPool $metadataPool
53+
) {
54+
$this->productCollectionFactory = $productCollectionFactory;
55+
$this->catalogProductVisibility = $catalogProductVisibility;
56+
$this->resource = $resource;
57+
$this->metadataPool = $metadataPool;
58+
}
59+
60+
/**
61+
* Adds configurable products to the item list if child products are already part of the collection
62+
*
63+
* @param ProductsList $subject
64+
* @param Collection $result
65+
* @return Collection
66+
* @throws LocalizedException
67+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
68+
*/
69+
public function afterCreateCollection(ProductsList $subject, Collection $result): Collection
70+
{
71+
$notVisibleCollection = $subject->getBaseCollection();
72+
$currentIds = $result->getAllIds();
73+
$searchProducts = array_merge($currentIds, $notVisibleCollection->getAllIds());
74+
75+
if (!empty($searchProducts)) {
76+
$linkField = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class)
77+
->getLinkField();
78+
$connection = $this->resource->getConnection();
79+
$productIds = $connection->fetchCol(
80+
$connection
81+
->select()
82+
->from(['e' => $this->resource->getTableName('catalog_product_entity')], ['link_table.parent_id'])
83+
->joinInner(
84+
['link_table' => $this->resource->getTableName('catalog_product_super_link')],
85+
'link_table.product_id = e.' . $linkField,
86+
[]
87+
)
88+
->where('link_table.product_id IN (?)', $searchProducts)
89+
);
90+
91+
$configurableProductCollection = $this->productCollectionFactory->create();
92+
$configurableProductCollection->setVisibility($this->catalogProductVisibility->getVisibleInCatalogIds());
93+
$configurableProductCollection->addIdFilter($productIds);
94+
95+
/** @var Product $item */
96+
foreach ($configurableProductCollection->getItems() as $item) {
97+
if (false === in_array($item->getId(), $currentIds)) {
98+
$result->addItem($item->load($item->getId()));
99+
}
100+
}
101+
}
102+
103+
return $result;
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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\ConfigurableProduct\Test\Unit\Plugin\CatalogWidget\Block\Product;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\Product\Visibility;
12+
use Magento\Catalog\Model\ResourceModel\Product\Collection;
13+
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
14+
use Magento\CatalogWidget\Block\Product\ProductsList;
15+
use Magento\ConfigurableProduct\Plugin\CatalogWidget\Block\Product\ProductsListPlugin;
16+
use Magento\Framework\App\ResourceConnection;
17+
use Magento\Framework\DB\Adapter\AdapterInterface;
18+
use Magento\Framework\EntityManager\EntityMetadataInterface;
19+
use Magento\Framework\EntityManager\MetadataPool;
20+
use PHPUnit\Framework\TestCase;
21+
use PHPUnit\Framework\MockObject\MockObject;
22+
use Magento\Framework\DB\Select;
23+
use Magento\Framework\DataObject;
24+
25+
/**
26+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
27+
*/
28+
class ProductListPluginTest extends TestCase
29+
{
30+
/**
31+
* @var CollectionFactory|MockObject
32+
*/
33+
protected CollectionFactory $productCollectionFactory;
34+
35+
/**
36+
* @var Visibility|MockObject
37+
*/
38+
protected Visibility $catalogProductVisibility;
39+
40+
/**
41+
* @var ResourceConnection|MockObject
42+
*/
43+
protected ResourceConnection $resource;
44+
45+
/**
46+
* @var MetadataPool
47+
*/
48+
protected MetadataPool $metadataPool;
49+
50+
/**
51+
* @var ProductsListPlugin
52+
*/
53+
protected ProductsListPlugin $plugin;
54+
55+
protected function setUp(): void
56+
{
57+
$this->productCollectionFactory = $this->createMock(CollectionFactory::class);
58+
$this->catalogProductVisibility = $this->createMock(Visibility::class);
59+
$this->resource = $this->createMock(ResourceConnection::class);
60+
$this->metadataPool = $this->createMock(MetadataPool::class);
61+
62+
$this->plugin = new ProductsListPlugin(
63+
$this->productCollectionFactory,
64+
$this->catalogProductVisibility,
65+
$this->resource,
66+
$this->metadataPool
67+
);
68+
69+
parent::setUp();
70+
}
71+
72+
/**
73+
* @return void
74+
* @throws \Magento\Framework\Exception\LocalizedException
75+
*/
76+
public function testAfterCreateCollectionNoCount(): void
77+
{
78+
$subject = $this->createMock(ProductsList::class);
79+
$baseCollection = $this->createMock(Collection::class);
80+
$baseCollection->expects($this->once())->method('getAllIds')->willReturn([]);
81+
$subject->expects($this->once())->method('getBaseCollection')->willReturn($baseCollection);
82+
$result = $this->createMock(Collection::class);
83+
$result->expects($this->once())->method('getAllIds')->willReturn([]);
84+
85+
$this->assertSame($result, $this->plugin->afterCreateCollection($subject, $result));
86+
}
87+
88+
/**
89+
* @return void
90+
* @throws \Magento\Framework\Exception\LocalizedException
91+
*/
92+
public function testAfterCreateCollectionSuccess(): void
93+
{
94+
$linkField = 'entity_id';
95+
$baseCollection = $this->createMock(Collection::class);
96+
$baseCollection->expects($this->once())->method('getAllIds')->willReturn([2]);
97+
$subject = $this->createMock(ProductsList::class);
98+
$subject->expects($this->once())->method('getBaseCollection')->willReturn($baseCollection);
99+
100+
$result = $this->createMock(Collection::class);
101+
$result->expects($this->once())->method('getAllIds')->willReturn([1]);
102+
$result->expects($this->once())->method('addItem');
103+
$entity = $this->createMock(EntityMetadataInterface::class);
104+
$entity->expects($this->once())->method('getLinkField')->willReturn($linkField);
105+
$this->metadataPool->expects($this->once())
106+
->method('getMetadata')
107+
->with(\Magento\Catalog\Api\Data\ProductInterface::class)
108+
->willReturn($entity);
109+
110+
$select = $this->createMock(Select::class);
111+
$select->expects($this->once())
112+
->method('from')
113+
->with(['e' => 'catalog_product_entity'], ['link_table.parent_id'])
114+
->willReturn($select);
115+
$select->expects($this->once())
116+
->method('joinInner')
117+
->with(
118+
['link_table' => 'catalog_product_super_link'],
119+
'link_table.product_id = e.' . $linkField,
120+
[]
121+
)->willReturn($select);
122+
$select->expects($this->once())->method('where')->with('link_table.product_id IN (?)', [1, 2]);
123+
$connection = $this->createMock(AdapterInterface::class);
124+
$connection->expects($this->once())->method('select')->willReturn($select);
125+
$connection->expects($this->once())->method('fetchCol')->willReturn([2]);
126+
$this->resource->expects($this->once())->method('getConnection')->willReturn($connection);
127+
$this->resource->expects($this->exactly(2))
128+
->method('getTableName')
129+
->withConsecutive(['catalog_product_entity'], ['catalog_product_super_link'])
130+
->willReturnOnConsecutiveCalls('catalog_product_entity', 'catalog_product_super_link');
131+
132+
$collection = $this->createMock(Collection::class);
133+
$this->productCollectionFactory->expects($this->once())->method('create')->willReturn($collection);
134+
$this->catalogProductVisibility->expects($this->once())->method('getVisibleInCatalogIds');
135+
$collection->expects($this->once())->method('setVisibility');
136+
$collection->expects($this->once())->method('addIdFilter');
137+
$product = $this->createMock(Product::class);
138+
$product->expects($this->once())->method('load')->willReturn($product);
139+
$collection->expects($this->once())->method('getItems')->willReturn([$product]);
140+
141+
$this->plugin->afterCreateCollection($subject, $result);
142+
}
143+
}

app/code/Magento/ConfigurableProduct/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"magento/module-product-video": "*",
2727
"magento/module-configurable-sample-data": "*",
2828
"magento/module-product-links-sample-data": "*",
29-
"magento/module-tax": "*"
29+
"magento/module-tax": "*",
30+
"magento/module-catalog-widget": "*"
3031
},
3132
"type": "magento2-module",
3233
"license": [

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@
2121
<preference for="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\OptionsSelectBuilderInterface" type="Magento\ConfigurableProduct\Model\ResourceModel\Product\Indexer\Price\OptionsSelectBuilder" />
2222
<preference for="Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsFilterInterface" type="Magento\ConfigurableProduct\Pricing\Price\ConfigurableOptionsCompositeFilter" />
2323

24+
<type name="Magento\CatalogWidget\Block\Product\ProductsList">
25+
<plugin name="configurable_product_widget_product_list" type="Magento\ConfigurableProduct\Plugin\CatalogWidget\Block\Product\ProductsListPlugin" sortOrder="2"/>
26+
</type>
2427
<type name="Magento\CatalogInventory\Model\Quote\Item\QuantityValidator\Initializer\Option">
2528
<plugin name="configurable_product" type="Magento\ConfigurableProduct\Model\Quote\Item\QuantityValidator\Initializer\Option\Plugin\ConfigurableProduct" sortOrder="50" />
2629
</type>

0 commit comments

Comments
 (0)