Skip to content

Commit 179738e

Browse files
committed
ACP2E-2131: Product grid showing wrong thumbnail image
1 parent ca30c47 commit 179738e

File tree

3 files changed

+393
-0
lines changed

3 files changed

+393
-0
lines changed
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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\InventoryCatalog\Plugin\Catalog\Ui\Component\Listing\Columns;
9+
10+
use Magento\Catalog\Helper\Image;
11+
use Magento\Catalog\Ui\Component\Listing\Columns\Thumbnail;
12+
use Magento\Framework\App\Config\ScopeConfigInterface;
13+
use Magento\Framework\DataObject;
14+
use Magento\Framework\Exception\NoSuchEntityException;
15+
use Magento\Framework\UrlInterface;
16+
use Magento\Framework\View\Element\UiComponent\ContextInterface;
17+
use Magento\Store\Api\StoreWebsiteRelationInterface;
18+
use Magento\Store\Model\ScopeInterface;
19+
use Magento\Store\Model\StoreManagerInterface;
20+
21+
class ThumbnailPlugin
22+
{
23+
/**
24+
* @var ContextInterface
25+
*/
26+
private $context;
27+
28+
/**
29+
* @var Image
30+
*/
31+
private $imageHelper;
32+
33+
/**
34+
* @var UrlInterface
35+
*/
36+
private $urlBuilder;
37+
38+
/**
39+
* @var StoreManagerInterface
40+
*/
41+
private $storeManager;
42+
43+
/**
44+
* Core store config
45+
*
46+
* @var ScopeConfigInterface
47+
*/
48+
private $scopeConfig;
49+
50+
/**
51+
* @var StoreWebsiteRelationInterface
52+
*/
53+
private $storeWebsiteRelation;
54+
55+
/**
56+
* @param ContextInterface $context
57+
* @param Image $imageHelper
58+
* @param UrlInterface $urlBuilder
59+
* @param StoreManagerInterface $storeManager
60+
* @param ScopeConfigInterface $scopeConfig
61+
* @param StoreWebsiteRelationInterface $storeWebsiteRelation
62+
*/
63+
public function __construct(
64+
ContextInterface $context,
65+
Image $imageHelper,
66+
UrlInterface $urlBuilder,
67+
StoreManagerInterface $storeManager,
68+
ScopeConfigInterface $scopeConfig,
69+
StoreWebsiteRelationInterface $storeWebsiteRelation
70+
) {
71+
$this->context = $context;
72+
$this->imageHelper = $imageHelper;
73+
$this->urlBuilder = $urlBuilder;
74+
$this->storeManager = $storeManager;
75+
$this->scopeConfig = $scopeConfig;
76+
$this->storeWebsiteRelation = $storeWebsiteRelation;
77+
}
78+
79+
/**
80+
* In a multi-website arrangement, get ready the data source for the product thumbnail image.
81+
*
82+
* @param Thumbnail $subject
83+
* @param callable $proceed
84+
* @param array $dataSource
85+
* @return array
86+
*
87+
* @throws NoSuchEntityException
88+
*/
89+
public function aroundPrepareDataSource(
90+
Thumbnail $subject,
91+
callable $proceed,
92+
array $dataSource
93+
): array {
94+
if (count($this->storeManager->getWebsites()) === 1) {
95+
return $proceed($dataSource);
96+
}
97+
$allStoresByWebsitesIds = $this->getAllStoresByWebsiteIds();
98+
if (isset($dataSource['data']['items'])) {
99+
$fieldName = $subject->getData('name');
100+
foreach ($dataSource['data']['items'] as & $item) {
101+
$product = new DataObject($item);
102+
$this->setCurrentStore($product, $fieldName, $allStoresByWebsitesIds);
103+
$imageHelper = $this->imageHelper->init($product, 'product_listing_thumbnail');
104+
$item[$fieldName . '_src'] = $imageHelper->getUrl();
105+
$item[$fieldName . '_alt'] = $subject->getAlt($item) ?: $imageHelper->getLabel();
106+
$item[$fieldName . '_link'] = $this->urlBuilder->getUrl(
107+
'catalog/product/edit',
108+
['id' => $product->getEntityId(), 'store' => $this->context->getRequestParam('store')]
109+
);
110+
$origImageHelper = $this->imageHelper->init($product, 'product_listing_thumbnail_preview');
111+
$item[$fieldName . '_orig_src'] = $origImageHelper->getUrl();
112+
}
113+
}
114+
115+
return $dataSource;
116+
}
117+
118+
/**
119+
* Set the current store as per product storeId
120+
*
121+
* @param DataObject $product
122+
* @param String $fieldName
123+
* @param array $storesInWebsites
124+
* @throws NoSuchEntityException
125+
*/
126+
private function setCurrentStore(DataObject $product, string $fieldName, array $storesInWebsites): void
127+
{
128+
$productWebsites = $product->getWebsiteIds();
129+
if ($productWebsites) {
130+
foreach ($productWebsites as $websiteId) {
131+
foreach ($storesInWebsites[$websiteId] as $storeId) {
132+
$this->storeManager->setCurrentStore($storeId);
133+
if ($this->scopeConfig->getValue(
134+
"catalog/placeholder/{$fieldName}_placeholder",
135+
ScopeInterface::SCOPE_STORE,
136+
$storeId
137+
)) {
138+
break;
139+
}
140+
}
141+
}
142+
}
143+
}
144+
145+
/**
146+
* Get store ids against the websites
147+
*
148+
* @return array
149+
*/
150+
private function getAllStoresByWebsiteIds(): array
151+
{
152+
$soresInWebsites = [];
153+
foreach ($this->storeManager->getWebsites() as $website) {
154+
$store = $this->storeWebsiteRelation->getStoreByWebsiteId($website->getId());
155+
$soresInWebsites[$website->getId()] = $store;
156+
}
157+
return $soresInWebsites;
158+
}
159+
}
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
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\InventoryCatalog\Test\Unit\Plugin\Catalog\Ui\Component\Listing\Columns;
9+
10+
use Magento\Catalog\Helper\Image;
11+
use Magento\Catalog\Ui\Component\Listing\Columns\Thumbnail;
12+
use Magento\Framework\App\Config\ScopeConfigInterface;
13+
use Magento\Framework\DataObject;
14+
use Magento\Framework\Exception\NoSuchEntityException;
15+
use Magento\Framework\UrlInterface;
16+
use Magento\Framework\View\Element\UiComponent\ContextInterface;
17+
use Magento\InventoryCatalog\Plugin\Catalog\Ui\Component\Listing\Columns\ThumbnailPlugin;
18+
use Magento\Store\Api\Data\WebsiteInterface;
19+
use Magento\Store\Api\StoreWebsiteRelationInterface;
20+
use Magento\Store\Model\ScopeInterface;
21+
use Magento\Store\Model\Store;
22+
use Magento\Store\Model\StoreManagerInterface;
23+
use Magento\Store\Model\Website;
24+
use PHPUnit\Framework\TestCase;
25+
26+
/**
27+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
28+
*/
29+
class ThumbnailPluginTest extends TestCase
30+
{
31+
/**
32+
* @var ThumbnailPlugin
33+
*/
34+
private $plugin;
35+
36+
/**
37+
* @var ContextInterface
38+
*/
39+
private $context;
40+
41+
/**
42+
* @var Image
43+
*/
44+
private $imageHelper;
45+
46+
/**
47+
* @var UrlInterface
48+
*/
49+
private $urlBuilder;
50+
51+
/**
52+
* @var StoreManagerInterface
53+
*/
54+
private $storeManager;
55+
56+
/**
57+
* @var ScopeConfigInterface
58+
*/
59+
private $scopeConfig;
60+
61+
/**
62+
* @var StoreWebsiteRelationInterface
63+
*/
64+
private $storeWebsiteRelation;
65+
66+
protected function setUp(): void
67+
{
68+
$this->context = $this->getMockBuilder(ContextInterface::class)
69+
->getMockForAbstractClass();
70+
$this->imageHelper = $this->getMockBuilder(Image::class)
71+
->disableOriginalConstructor()
72+
->getMock();
73+
$this->urlBuilder = $this->getMockBuilder(UrlInterface::class)
74+
->getMockForAbstractClass();
75+
$this->storeManager = $this->getMockBuilder(StoreManagerInterface::class)
76+
->getMockForAbstractClass();
77+
$this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class)
78+
->getMockForAbstractClass();
79+
$this->storeWebsiteRelation = $this->getMockBuilder(StoreWebsiteRelationInterface::class)
80+
->getMockForAbstractClass();
81+
82+
$this->plugin = new ThumbnailPlugin(
83+
$this->context,
84+
$this->imageHelper,
85+
$this->urlBuilder,
86+
$this->storeManager,
87+
$this->scopeConfig,
88+
$this->storeWebsiteRelation
89+
);
90+
}
91+
92+
/**
93+
* Test a thumbnail placeholder image for the admin catalog products grid in a multi-website scenario.
94+
*
95+
* @return void
96+
* @throws NoSuchEntityException
97+
*/
98+
public function testAroundPrepareDataSource(): void
99+
{
100+
$storeId = 2;
101+
$fieldName = 'thumbnail';
102+
$dataSource = [
103+
'data' => [
104+
'totalRecords' => 1,
105+
'items' => [
106+
[
107+
'entity_id' => '1',
108+
'attribute_set_id' => '4',
109+
'type_id' => 'simple',
110+
'sku' => 'P1',
111+
'status' => '1',
112+
'websites' => [
113+
'2'
114+
],
115+
'website_ids' => [
116+
'2'
117+
]
118+
],
119+
],
120+
'showTotalRecords' => true
121+
]
122+
];
123+
$subject = $this->createMock(Thumbnail::class);
124+
$proceed = function () use ($dataSource) {
125+
return $dataSource;
126+
};
127+
128+
$websiteIds = [1, 2];
129+
$websites = [];
130+
foreach ($websiteIds as $websiteId) {
131+
$websites[] = $this->createConfiguredMock(
132+
Website::class,
133+
[
134+
'getId' => $websiteId
135+
]
136+
);
137+
}
138+
$this->storeWebsiteRelation->expects($this->atLeastOnce())
139+
->method('getStoreByWebsiteId')
140+
->withConsecutive([1], [2])
141+
->willReturnOnConsecutiveCalls([1], [2, 3]);
142+
143+
$this->storeManager->expects($this->exactly(2))
144+
->method('getWebsites')
145+
->willReturn($websites);
146+
147+
$product = new DataObject($dataSource['data']['items'][0]);
148+
149+
$subject->expects($this->atLeastOnce())->method('getData')->willReturn($fieldName);
150+
151+
$this->scopeConfig->expects($this->atLeastOnce())->method('getValue')
152+
->with("catalog/placeholder/{$fieldName}_placeholder", ScopeInterface::SCOPE_STORE, $storeId)
153+
->willReturn("stores/{$storeId}/test.jpg");
154+
155+
$storeMock = $this->createMock(Store::class);
156+
$storeMock->method('getId')->willReturn($storeId);
157+
$this->storeManager->expects($this->atLeastOnce())->method('setCurrentStore')->with($storeId);
158+
159+
$this->imageHelper->expects($this->atLeastOnce())->method('init')
160+
->withConsecutive([$product, 'product_listing_thumbnail'], [$product, 'product_listing_thumbnail_preview'])
161+
->willReturnOnConsecutiveCalls($this->imageHelper, $this->imageHelper);
162+
$this->imageHelper->expects($this->atLeastOnce())->method('getUrl')
163+
->willReturnMap([["http://magento.local/media/catalog/product/placeholder/stores/{$storeId}/test.jpg"]]);
164+
$this->imageHelper->expects($this->atLeastOnce())->method('getLabel')->willReturn('Image Label');
165+
166+
$this->context->expects($this->atLeastOnce())->method('getRequestParam')->with('store')->willReturn(null);
167+
$this->urlBuilder->expects($this->atLeastOnce())
168+
->method('getUrl')
169+
->with('catalog/product/edit', ['id' => $product['entity_id'], 'store' => null])
170+
->willReturn("http://magento.local/admin/catalog/product/edit/id/{$product['entity_id']}/");
171+
172+
$preparedDataSource = $this->plugin->aroundPrepareDataSource($subject, $proceed, $dataSource);
173+
174+
foreach ($preparedDataSource['data']['items'] as $item) {
175+
$this->assertEquals(
176+
"http://magento.local/media/catalog/product/placeholder/stores/{$storeId}/test.jpg",
177+
$item['thumbnail_src']
178+
);
179+
}
180+
}
181+
182+
/**
183+
* Test for product datasource in single (default) website scenario
184+
*
185+
* @return void
186+
* @throws NoSuchEntityException
187+
*/
188+
public function testAroundPrepareDataSourceForDefaultWebsiteOnly(): void
189+
{
190+
$dataSource = [
191+
'data' => [
192+
'totalRecords' => 1,
193+
'items' => [
194+
[
195+
'entity_id' => '1',
196+
'attribute_set_id' => '4',
197+
'type_id' => 'simple',
198+
'sku' => 'P1',
199+
'status' => '1',
200+
'websites' => [
201+
'1'
202+
],
203+
'website_ids' => [
204+
'1'
205+
]
206+
]
207+
],
208+
'showTotalRecords' => true
209+
]
210+
];
211+
$subject = $this->createMock(Thumbnail::class);
212+
$proceed = function () use ($dataSource) {
213+
return $dataSource;
214+
};
215+
216+
$websiteMock = $this->getMockForAbstractClass(WebsiteInterface::class);
217+
$this->storeManager->expects($this->once())
218+
->method('getWebsites')
219+
->willReturn([$websiteMock]);
220+
$this->assertEquals($dataSource, $this->plugin->aroundPrepareDataSource($subject, $proceed, $dataSource));
221+
}
222+
}

InventoryCatalog/etc/adminhtml/di.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
9+
<type name="Magento\Catalog\Ui\Component\Listing\Columns\Thumbnail">
10+
<plugin name="productThumbnailImagePlaceholder" type="Magento\InventoryCatalog\Plugin\Catalog\Ui\Component\Listing\Columns\ThumbnailPlugin"/>
11+
</type>
12+
</config>

0 commit comments

Comments
 (0)