Skip to content

Commit 4241b5e

Browse files
author
Prabhu Ram
committed
Merge remote-tracking branch 'mainline/2.4-develop' into honey-pr-bundle
2 parents ac10423 + 29bf891 commit 4241b5e

File tree

75 files changed

+3174
-364
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+3174
-364
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: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
use Magento\Framework\App\Request\DataPersistorInterface;
1616

1717
/**
18-
* Class Save
18+
* Product save controller
19+
*
1920
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2021
*/
2122
class Save extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface
@@ -141,10 +142,6 @@ public function execute()
141142
$canSaveCustomOptions = $product->getCanSaveCustomOptions();
142143
$product->save();
143144
$this->handleImageRemoveError($data, $product->getId());
144-
$this->categoryLinkManagement->assignProductToCategories(
145-
$product->getSku(),
146-
$product->getCategoryIds()
147-
);
148145
$productId = $product->getEntityId();
149146
$productAttributeSetId = $product->getAttributeSetId();
150147
$productTypeId = $product->getTypeId();

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

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Magento\Catalog\Api\Data\ProductInterface;
1111
use Magento\Catalog\Api\ProductLinkRepositoryInterface;
1212
use Magento\Catalog\Model\Product\Attribute\Backend\Media\EntryConverterPool;
13+
use Magento\Catalog\Model\Product\Attribute\Source\Status;
1314
use Magento\Catalog\Model\Product\Configuration\Item\Option\OptionInterface;
1415
use Magento\Framework\Api\AttributeValueFactory;
1516
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -202,7 +203,7 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements
202203
/**
203204
* Catalog product status
204205
*
205-
* @var \Magento\Catalog\Model\Product\Attribute\Source\Status
206+
* @var Status
206207
*/
207208
protected $_catalogProductStatus;
208209

@@ -408,7 +409,7 @@ public function __construct(
408409
\Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory,
409410
\Magento\Catalog\Model\Product\OptionFactory $catalogProductOptionFactory,
410411
\Magento\Catalog\Model\Product\Visibility $catalogProductVisibility,
411-
\Magento\Catalog\Model\Product\Attribute\Source\Status $catalogProductStatus,
412+
Status $catalogProductStatus,
412413
\Magento\Catalog\Model\Product\Media\Config $catalogProductMediaConfig,
413414
Product\Type $catalogProductType,
414415
\Magento\Framework\Module\Manager $moduleManager,
@@ -668,7 +669,7 @@ public function getTypeId()
668669
public function getStatus()
669670
{
670671
$status = $this->_getData(self::STATUS);
671-
return $status !== null ? $status : \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED;
672+
return $status !== null ? $status : Status::STATUS_ENABLED;
672673
}
673674

674675
/**
@@ -1033,7 +1034,7 @@ public function priceReindexCallback()
10331034
*/
10341035
public function eavReindexCallback()
10351036
{
1036-
if ($this->isObjectNew() || $this->isDataChanged($this)) {
1037+
if ($this->isObjectNew() || $this->isDataChanged()) {
10371038
$this->_productEavIndexerProcessor->reindexRow($this->getEntityId());
10381039
}
10391040
}
@@ -1103,7 +1104,7 @@ public function afterDeleteCommit()
11031104
protected function _afterLoad()
11041105
{
11051106
if (!$this->hasData(self::STATUS)) {
1106-
$this->setData(self::STATUS, \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED);
1107+
$this->setData(self::STATUS, Status::STATUS_ENABLED);
11071108
}
11081109
parent::_afterLoad();
11091110
return $this;
@@ -1179,7 +1180,7 @@ public function getTierPrice($qty = null)
11791180
/**
11801181
* Get formatted by currency product price
11811182
*
1182-
* @return array|double
1183+
* @return array|double
11831184
* @since 102.0.6
11841185
*/
11851186
public function getFormattedPrice()
@@ -1780,7 +1781,7 @@ public function isSaleable()
17801781
*/
17811782
public function isInStock()
17821783
{
1783-
return $this->getStatus() == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED;
1784+
return $this->getStatus() == Status::STATUS_ENABLED;
17841785
}
17851786

17861787
/**
@@ -2341,7 +2342,7 @@ public function getProductEntitiesInfo($columns = null)
23412342
*/
23422343
public function isDisabled()
23432344
{
2344-
return $this->getStatus() == \Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_DISABLED;
2345+
return $this->getStatus() == Status::STATUS_DISABLED;
23452346
}
23462347

23472348
/**
@@ -2357,6 +2358,22 @@ public function getImage()
23572358
return parent::getImage();
23582359
}
23592360

2361+
/**
2362+
* Get identities for related to product categories
2363+
*
2364+
* @param array $categoryIds
2365+
* @return array
2366+
*/
2367+
private function getProductCategoryIdentities(array $categoryIds): array
2368+
{
2369+
$identities = [];
2370+
foreach ($categoryIds as $categoryId) {
2371+
$identities[] = self::CACHE_PRODUCT_CATEGORY_TAG . '_' . $categoryId;
2372+
}
2373+
2374+
return $identities;
2375+
}
2376+
23602377
/**
23612378
* Get identities
23622379
*
@@ -2365,17 +2382,24 @@ public function getImage()
23652382
public function getIdentities()
23662383
{
23672384
$identities = [self::CACHE_TAG . '_' . $this->getId()];
2368-
if ($this->getIsChangedCategories()) {
2369-
foreach ($this->getAffectedCategoryIds() as $categoryId) {
2370-
$identities[] = self::CACHE_PRODUCT_CATEGORY_TAG . '_' . $categoryId;
2385+
2386+
$isStatusChanged = $this->getOrigData(self::STATUS) != $this->getData(self::STATUS) && !$this->isObjectNew();
2387+
if ($isStatusChanged || $this->getStatus() == Status::STATUS_ENABLED) {
2388+
if ($this->getIsChangedCategories()) {
2389+
$identities = array_merge(
2390+
$identities,
2391+
$this->getProductCategoryIdentities($this->getAffectedCategoryIds())
2392+
);
23712393
}
2372-
}
23732394

2374-
if (($this->getOrigData('status') != $this->getData('status')) || $this->isStockStatusChanged()) {
2375-
foreach ($this->getCategoryIds() as $categoryId) {
2376-
$identities[] = self::CACHE_PRODUCT_CATEGORY_TAG . '_' . $categoryId;
2395+
if ($isStatusChanged || $this->isStockStatusChanged()) {
2396+
$identities = array_merge(
2397+
$identities,
2398+
$this->getProductCategoryIdentities($this->getCategoryIds())
2399+
);
23772400
}
23782401
}
2402+
23792403
if ($this->_appState->getAreaCode() == \Magento\Framework\App\Area::AREA_FRONTEND) {
23802404
$identities[] = self::CACHE_TAG;
23812405
}

app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@
3333
<actionGroup ref="AdminSwitchIndexerToActionModeActionGroup" stepKey="switchProductCategory">
3434
<argument name="indexerValue" value="catalog_product_category"/>
3535
</actionGroup>
36+
<actionGroup ref="AdminSwitchIndexerToActionModeActionGroup" stepKey="switchCatalogSearch">
37+
<argument name="indexerValue" value="catalogsearch_fulltext"/>
38+
</actionGroup>
3639
</before>
3740

3841
<after>

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

Lines changed: 28 additions & 26 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;
@@ -125,17 +128,13 @@ protected function setUp(): void
125128
->setMethods(['create'])
126129
->disableOriginalConstructor()
127130
->getMock();
128-
$this->productRepositoryMock = $this->getMockBuilder(ProductRepository::class)
129-
->disableOriginalConstructor()
130-
->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,34 @@ protected function setUp(): void
150149
)
151150
->disableOriginalConstructor()
152151
->getMockForAbstractClass();
152+
$productExtensionAttributes = $this->getMockBuilder(ProductExtensionInterface::class)
153+
->setMethods(['getCategoryLinks', 'setCategoryLinks'])
154+
->getMockForAbstractClass();
155+
$this->productMock->setExtensionAttributes($productExtensionAttributes);
156+
153157
$this->customOptionFactoryMock = $this->getMockBuilder(ProductCustomOptionInterfaceFactory::class)
154158
->disableOriginalConstructor()
155159
->setMethods(['create'])
156160
->getMock();
157-
$this->productLinksMock = $this->getMockBuilder(ProductLinks::class)
158-
->disableOriginalConstructor()
159-
->getMock();
160-
$this->linkTypeProviderMock = $this->getMockBuilder(LinkTypeProvider::class)
161-
->disableOriginalConstructor()
162-
->getMock();
161+
$this->productLinksMock = $this->createMock(ProductLinks::class);
162+
$this->linkTypeProviderMock = $this->createMock(LinkTypeProvider::class);
163163
$this->productLinksMock->expects($this->any())
164164
->method('initializeLinks')
165165
->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();
166+
$this->attributeFilterMock = $this->createMock(AttributeFilter::class);
167+
$this->localeFormatMock = $this->createMock(Format::class);
174168

175169
$this->dateTimeFilterMock = $this->createMock(DateTime::class);
176170

171+
$categoryLinkFactoryMock = $this->getMockBuilder(CategoryLinkInterfaceFactory::class)
172+
->setMethods(['create'])
173+
->disableOriginalConstructor()
174+
->getMock();
175+
$categoryLinkFactoryMock->method('create')
176+
->willReturnCallback(function () {
177+
return $this->createMock(CategoryLinkInterface::class);
178+
});
179+
177180
$this->helper = $this->objectManager->getObject(
178181
Helper::class,
179182
[
@@ -187,13 +190,12 @@ protected function setUp(): void
187190
'linkTypeProvider' => $this->linkTypeProviderMock,
188191
'attributeFilter' => $this->attributeFilterMock,
189192
'localeFormat' => $this->localeFormatMock,
190-
'dateTimeFilter' => $this->dateTimeFilterMock
193+
'dateTimeFilter' => $this->dateTimeFilterMock,
194+
'categoryLinkFactory' => $categoryLinkFactoryMock,
191195
]
192196
);
193197

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

0 commit comments

Comments
 (0)