Skip to content

Commit 5154cda

Browse files
committed
MC-37006: Adding/removing disabled products to Magento flushes categories cache
1 parent a025033 commit 5154cda

File tree

6 files changed

+200
-45
lines changed

6 files changed

+200
-45
lines changed

app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace Magento\Catalog\Controller\Adminhtml\Product\Initialization;
88

99
use Magento\Backend\Helper\Js;
10+
use Magento\Catalog\Api\Data\CategoryLinkInterfaceFactory;
1011
use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory as CustomOptionFactory;
1112
use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory as ProductLinkFactory;
1213
use Magento\Catalog\Api\Data\ProductLinkTypeInterface;
@@ -115,6 +116,11 @@ class Helper
115116
*/
116117
private $dateTimeFilter;
117118

119+
/**
120+
* @var CategoryLinkInterfaceFactory
121+
*/
122+
private $categoryLinkFactory;
123+
118124
/**
119125
* Constructor
120126
*
@@ -132,6 +138,7 @@ class Helper
132138
* @param FormatInterface|null $localeFormat
133139
* @param ProductAuthorization|null $productAuthorization
134140
* @param DateTimeFilter|null $dateTimeFilter
141+
* @param CategoryLinkInterfaceFactory|null $categoryLinkFactory
135142
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
136143
*/
137144
public function __construct(
@@ -148,7 +155,8 @@ public function __construct(
148155
AttributeFilter $attributeFilter = null,
149156
FormatInterface $localeFormat = null,
150157
?ProductAuthorization $productAuthorization = null,
151-
?DateTimeFilter $dateTimeFilter = null
158+
?DateTimeFilter $dateTimeFilter = null,
159+
?CategoryLinkInterfaceFactory $categoryLinkFactory = null
152160
) {
153161
$this->request = $request;
154162
$this->storeManager = $storeManager;
@@ -166,6 +174,7 @@ public function __construct(
166174
$this->localeFormat = $localeFormat ?: $objectManager->get(FormatInterface::class);
167175
$this->productAuthorization = $productAuthorization ?? $objectManager->get(ProductAuthorization::class);
168176
$this->dateTimeFilter = $dateTimeFilter ?? $objectManager->get(DateTimeFilter::class);
177+
$this->categoryLinkFactory = $categoryLinkFactory ?? $objectManager->get(CategoryLinkInterfaceFactory::class);
169178
}
170179

171180
/**
@@ -238,6 +247,7 @@ public function initializeFromData(Product $product, array $productData)
238247

239248
$product = $this->setProductLinks($product);
240249
$product = $this->fillProductOptions($product, $productOptions);
250+
$this->setCategoryLinks($product);
241251

242252
$product->setCanSaveCustomOptions(
243253
!empty($productData['affect_product_custom_options']) && !$product->getOptionsReadonly()
@@ -484,4 +494,30 @@ function ($valueData) {
484494

485495
return $product->setOptions($customOptions);
486496
}
497+
498+
/**
499+
* Set category links based on initialized category ids
500+
*
501+
* @param Product $product
502+
*/
503+
private function setCategoryLinks(Product $product): void
504+
{
505+
$extensionAttributes = $product->getExtensionAttributes();
506+
$categoryLinks = [];
507+
foreach ((array) $extensionAttributes->getCategoryLinks() as $categoryLink) {
508+
$categoryLinks[$categoryLink->getCategoryId()] = $categoryLink;
509+
}
510+
511+
$newCategoryLinks = [];
512+
foreach ($product->getCategoryIds() as $categoryId) {
513+
$categoryLink = $categoryLinks[$categoryId] ??
514+
$this->categoryLinkFactory->create()
515+
->setCategoryId($categoryId)
516+
->setPosition(0);
517+
$newCategoryLinks[] = $categoryLink;
518+
}
519+
520+
$extensionAttributes->setCategoryLinks(!empty($newCategoryLinks) ? $newCategoryLinks : null);
521+
$product->setExtensionAttributes($extensionAttributes);
522+
}
487523
}

app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,10 +141,6 @@ public function execute()
141141
$canSaveCustomOptions = $product->getCanSaveCustomOptions();
142142
$product->save();
143143
$this->handleImageRemoveError($data, $product->getId());
144-
$this->categoryLinkManagement->assignProductToCategories(
145-
$product->getSku(),
146-
$product->getCategoryIds()
147-
);
148144
$productId = $product->getEntityId();
149145
$productAttributeSetId = $product->getAttributeSetId();
150146
$productTypeId = $product->getTypeId();

app/code/Magento/Catalog/Model/Product.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2367,14 +2367,15 @@ public function getIdentities()
23672367
{
23682368
$identities = [self::CACHE_TAG . '_' . $this->getId()];
23692369

2370-
if (!$this->isObjectNew() || $this->getStatus() == Status::STATUS_ENABLED) {
2370+
$isStatusChanged = $this->getOrigData(self::STATUS) != $this->getData(self::STATUS) && !$this->isObjectNew();
2371+
if ($isStatusChanged || $this->getStatus() == Status::STATUS_ENABLED) {
23712372
if ($this->getIsChangedCategories()) {
23722373
foreach ($this->getAffectedCategoryIds() as $categoryId) {
23732374
$identities[] = self::CACHE_PRODUCT_CATEGORY_TAG . '_' . $categoryId;
23742375
}
23752376
}
23762377

2377-
if (($this->getOrigData('status') != $this->getData('status')) || $this->isStockStatusChanged()) {
2378+
if ($isStatusChanged || $this->isStockStatusChanged()) {
23782379
foreach ($this->getCategoryIds() as $categoryId) {
23792380
$identities[] = self::CACHE_PRODUCT_CATEGORY_TAG . '_' . $categoryId;
23802381
}

app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/HelperTest.php

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77

88
namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Initialization;
99

10+
use Magento\Catalog\Api\Data\CategoryLinkInterface;
11+
use Magento\Catalog\Api\Data\CategoryLinkInterfaceFactory;
1012
use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory;
13+
use Magento\Catalog\Api\Data\ProductExtensionInterface;
1114
use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory;
1215
use Magento\Catalog\Api\Data\ProductLinkTypeInterface;
1316
use Magento\Catalog\Api\ProductRepositoryInterface as ProductRepository;
@@ -122,20 +125,16 @@ protected function setUp(): void
122125
{
123126
$this->objectManager = new ObjectManager($this);
124127
$this->productLinkFactoryMock = $this->getMockBuilder(ProductLinkInterfaceFactory::class)
125-
->setMethods(['create'])
126-
->disableOriginalConstructor()
127-
->getMock();
128-
$this->productRepositoryMock = $this->getMockBuilder(ProductRepository::class)
128+
->onlyMethods(['create'])
129129
->disableOriginalConstructor()
130130
->getMock();
131+
$this->productRepositoryMock = $this->createMock(ProductRepository::class);
131132
$this->requestMock = $this->getMockBuilder(RequestInterface::class)
132133
->setMethods(['getPost'])
133134
->getMockForAbstractClass();
134-
$this->storeManagerMock = $this->getMockBuilder(StoreManagerInterface::class)
135-
->getMockForAbstractClass();
136-
$this->stockFilterMock = $this->getMockBuilder(StockDataFilter::class)
137-
->disableOriginalConstructor()
138-
->getMock();
135+
$this->storeManagerMock = $this->createMock(StoreManagerInterface::class);
136+
$this->stockFilterMock = $this->createMock(StockDataFilter::class);
137+
139138
$this->productMock = $this->getMockBuilder(Product::class)
140139
->setMethods(
141140
[
@@ -150,30 +149,32 @@ protected function setUp(): void
150149
)
151150
->disableOriginalConstructor()
152151
->getMockForAbstractClass();
152+
$productExtensionAttributes = $this->createMock(ProductExtensionInterface::class);
153+
$this->productMock->setExtensionAttributes($productExtensionAttributes);
154+
153155
$this->customOptionFactoryMock = $this->getMockBuilder(ProductCustomOptionInterfaceFactory::class)
154156
->disableOriginalConstructor()
155-
->setMethods(['create'])
156-
->getMock();
157-
$this->productLinksMock = $this->getMockBuilder(ProductLinks::class)
158-
->disableOriginalConstructor()
159-
->getMock();
160-
$this->linkTypeProviderMock = $this->getMockBuilder(LinkTypeProvider::class)
161-
->disableOriginalConstructor()
157+
->onlyMethods(['create'])
162158
->getMock();
159+
$this->productLinksMock = $this->createMock(ProductLinks::class);
160+
$this->linkTypeProviderMock = $this->createMock(LinkTypeProvider::class);
163161
$this->productLinksMock->expects($this->any())
164162
->method('initializeLinks')
165163
->willReturn($this->productMock);
166-
$this->attributeFilterMock = $this->getMockBuilder(AttributeFilter::class)
167-
->setMethods(['prepareProductAttributes'])
168-
->disableOriginalConstructor()
169-
->getMock();
170-
$this->localeFormatMock = $this->getMockBuilder(Format::class)
171-
->setMethods(['getNumber'])
172-
->disableOriginalConstructor()
173-
->getMock();
164+
$this->attributeFilterMock = $this->createMock(AttributeFilter::class);
165+
$this->localeFormatMock = $this->createMock(Format::class);
174166

175167
$this->dateTimeFilterMock = $this->createMock(DateTime::class);
176168

169+
$categoryLinkFactoryMock = $this->getMockBuilder(CategoryLinkInterfaceFactory::class)
170+
->onlyMethods(['create'])
171+
->disableOriginalConstructor()
172+
->getMock();
173+
$categoryLinkFactoryMock->method('create')
174+
->willReturnCallback(function() {
175+
return $this->createMock(CategoryLinkInterface::class);
176+
});
177+
177178
$this->helper = $this->objectManager->getObject(
178179
Helper::class,
179180
[
@@ -187,13 +188,12 @@ protected function setUp(): void
187188
'linkTypeProvider' => $this->linkTypeProviderMock,
188189
'attributeFilter' => $this->attributeFilterMock,
189190
'localeFormat' => $this->localeFormatMock,
190-
'dateTimeFilter' => $this->dateTimeFilterMock
191+
'dateTimeFilter' => $this->dateTimeFilterMock,
192+
'categoryLinkFactory' => $categoryLinkFactoryMock,
191193
]
192194
);
193195

194-
$this->linkResolverMock = $this->getMockBuilder(Resolver::class)
195-
->disableOriginalConstructor()
196-
->getMock();
196+
$this->linkResolverMock = $this->createMock(Resolver::class);
197197
$helperReflection = new \ReflectionClass(get_class($this->helper));
198198
$resolverProperty = $helperReflection->getProperty('linkResolver');
199199
$resolverProperty->setAccessible(true);

app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -831,52 +831,70 @@ public function getIdentitiesProvider(): array
831831
'is_changed_categories' => true
832832
],
833833
],
834-
'status change only' => [
834+
'category change for disabled product' => [
835+
[0 => 'cat_p_1'],
836+
['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => Status::STATUS_DISABLED],
837+
[
838+
'id' => 1,
839+
'name' => 'value',
840+
'category_ids' => [2],
841+
'status' => Status::STATUS_DISABLED,
842+
'affected_category_ids' => [1, 2],
843+
'is_changed_categories' => true
844+
],
845+
],
846+
'status change to disabled' => [
835847
[0 => 'cat_p_1', 1 => 'cat_c_p_7'],
836848
['id' => 1, 'name' => 'value', 'category_ids' => [7], 'status' => Status::STATUS_ENABLED],
837849
['id' => 1, 'name' => 'value', 'category_ids' => [7], 'status' => Status::STATUS_DISABLED],
838850
],
851+
'status change to enabled' => [
852+
[0 => 'cat_p_1', 1 => 'cat_c_p_7'],
853+
['id' => 1, 'name' => 'value', 'category_ids' => [7], 'status' => Status::STATUS_DISABLED],
854+
['id' => 1, 'name' => 'value', 'category_ids' => [7], 'status' => Status::STATUS_ENABLED],
855+
],
839856
'status changed, category unassigned' => $this->getStatusAndCategoryChangesData(),
840857
'no status changes' => [
841858
[0 => 'cat_p_1'],
842859
['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => Status::STATUS_ENABLED],
843860
['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => Status::STATUS_ENABLED],
844861
],
845-
'no stock status changes' => [
862+
'no stock status changes' => $this->getNoStockStatusChangesData($extensionAttributesMock),
863+
'no stock status data 1' => [
846864
[0 => 'cat_p_1'],
847865
['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => Status::STATUS_ENABLED],
848866
[
849867
'id' => 1,
850868
'name' => 'value',
851869
'category_ids' => [1],
852870
'status' => Status::STATUS_ENABLED,
853-
'stock_data' => ['is_in_stock' => true],
854871
ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY => $extensionAttributesMock,
855872
],
856873
],
857-
'no stock status data 1' => [
874+
'no stock status data 2' => [
858875
[0 => 'cat_p_1'],
859876
['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => Status::STATUS_ENABLED],
860877
[
861878
'id' => 1,
862879
'name' => 'value',
863880
'category_ids' => [1],
864881
'status' => Status::STATUS_ENABLED,
865-
ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY => $extensionAttributesMock,
882+
'stock_data' => ['is_in_stock' => true],
866883
],
867884
],
868-
'no stock status data 2' => [
885+
'stock status changes for enabled product' => $this->getStatusStockProviderData($extensionAttributesMock),
886+
'stock status changes for disabled product' => [
869887
[0 => 'cat_p_1'],
870-
['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => Status::STATUS_ENABLED],
888+
['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => Status::STATUS_DISABLED],
871889
[
872890
'id' => 1,
873891
'name' => 'value',
874892
'category_ids' => [1],
875-
'status' => Status::STATUS_ENABLED,
876-
'stock_data' => ['is_in_stock' => true],
893+
'status' => Status::STATUS_DISABLED,
894+
'stock_data' => ['is_in_stock' => false],
895+
ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY => $extensionAttributesMock,
877896
],
878897
],
879-
'stock status changes' => $this->getStatusStockProviderData($extensionAttributesMock),
880898
];
881899
}
882900

@@ -899,6 +917,26 @@ private function getStatusAndCategoryChangesData(): array
899917
];
900918
}
901919

920+
/**
921+
* @param MockObject $extensionAttributesMock
922+
* @return array
923+
*/
924+
private function getNoStockStatusChangesData($extensionAttributesMock): array
925+
{
926+
return [
927+
[0 => 'cat_p_1'],
928+
['id' => 1, 'name' => 'value', 'category_ids' => [1], 'status' => Status::STATUS_ENABLED],
929+
[
930+
'id' => 1,
931+
'name' => 'value',
932+
'category_ids' => [1],
933+
'status' => Status::STATUS_ENABLED,
934+
'stock_data' => ['is_in_stock' => true],
935+
ExtensibleDataInterface::EXTENSION_ATTRIBUTES_KEY => $extensionAttributesMock,
936+
],
937+
];
938+
}
939+
902940
/**
903941
* @return array
904942
*/

0 commit comments

Comments
 (0)