Skip to content

Commit e5b3588

Browse files
Merge branch '2.4-develop' into spartans_pr_21042025
2 parents d45dc4e + 520f9e3 commit e5b3588

File tree

48 files changed

+1473
-571
lines changed

Some content is hidden

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

48 files changed

+1473
-571
lines changed

app/code/Magento/AdvancedPricingImportExport/Controller/Adminhtml/Export/GetFilter.php

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,33 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2014 Adobe
4+
* All Rights Reserved.
55
*/
6+
67
namespace Magento\AdvancedPricingImportExport\Controller\Adminhtml\Export;
78

9+
use Magento\Backend\App\Action\Context;
810
use Magento\Framework\App\Action\HttpGetActionInterface;
911
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
1012
use Magento\ImportExport\Controller\Adminhtml\Export as ExportController;
1113
use Magento\Framework\Controller\ResultFactory;
1214
use Magento\AdvancedPricingImportExport\Model\Export\AdvancedPricing as ExportAdvancedPricing;
1315
use Magento\Catalog\Model\Product as CatalogProduct;
16+
use Magento\ImportExport\Model\Export\EntityFiltersProviderInterface;
1417

1518
class GetFilter extends ExportController implements HttpGetActionInterface, HttpPostActionInterface
1619
{
20+
/**
21+
* @param Context $context
22+
* @param EntityFiltersProviderInterface $filtersProvider
23+
*/
24+
public function __construct(
25+
Context $context,
26+
private readonly EntityFiltersProviderInterface $filtersProvider
27+
) {
28+
parent::__construct($context);
29+
}
30+
1731
/**
1832
* Get grid-filter of entity attributes action.
1933
*
@@ -34,9 +48,7 @@ public function execute()
3448
/** @var $export \Magento\ImportExport\Model\Export */
3549
$export = $this->_objectManager->create(\Magento\ImportExport\Model\Export::class);
3650
$export->setData($data);
37-
$attrFilterBlock->prepareCollection(
38-
$export->filterAttributeCollection($export->getEntityAttributeCollection())
39-
);
51+
$attrFilterBlock->prepareCollection($this->filtersProvider->getFilters($export));
4052
return $resultLayout;
4153
} catch (\Exception $e) {
4254
$this->messageManager->addErrorMessage($e->getMessage());

app/code/Magento/AdvancedPricingImportExport/composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"require": {
88
"php": "~8.2.0||~8.3.0||~8.4.0",
99
"magento/framework": "*",
10+
"magento/module-backend": "*",
1011
"magento/module-catalog": "*",
1112
"magento/module-catalog-import-export": "*",
1213
"magento/module-catalog-inventory": "*",

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0"?>
22
<!--
33
/**
4-
* Copyright © Magento, Inc. All rights reserved.
5-
* See COPYING.txt for license details.
4+
* Copyright 2015 Adobe
5+
* All Rights Reserved.
66
*/
77
-->
88
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
@@ -19,4 +19,11 @@
1919
</arguments>
2020
</type>
2121
<preference for="Magento\ImportExport\Controller\Adminhtml\Export\GetFilter" type="Magento\AdvancedPricingImportExport\Controller\Adminhtml\Export\GetFilter" />
22+
<type name="Magento\ImportExport\Model\Export\EntityFiltersProvider">
23+
<arguments>
24+
<argument name="providers" xsi:type="array">
25+
<item name="advanced_pricing" xsi:type="object">Magento\CatalogImportExport\Model\Export\EntityFiltersProvider</item>
26+
</argument>
27+
</arguments>
28+
</type>
2229
</config>

app/code/Magento/BundleImportExport/Test/Unit/Model/Import/Product/Type/BundleTest.php

Lines changed: 21 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
11
<?php
22
/**
3-
* Copyright © Magento, Inc. All rights reserved.
4-
* See COPYING.txt for license details.
3+
* Copyright 2015 Adobe
4+
* All Rights Reserved.
55
*/
66
declare(strict_types=1);
77

88
namespace Magento\BundleImportExport\Test\Unit\Model\Import\Product\Type;
99

1010
use Magento\BundleImportExport\Model\Import\Product\Type\Bundle;
1111
use Magento\Catalog\Api\Data\ProductInterface;
12+
use Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection as ProductAttributeCollection;
13+
use Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory as ProductAttributeCollectionFactory;
1214
use Magento\CatalogImportExport\Model\Import\Product;
13-
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection;
14-
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory;
15+
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory as AttributeSetCollectionFactory;
1516
use Magento\Framework\App\ResourceConnection;
1617
use Magento\Framework\App\ScopeInterface;
1718
use Magento\Framework\App\ScopeResolverInterface;
@@ -34,46 +35,41 @@ class BundleTest extends AbstractImportTestCase
3435
/**
3536
* @var Bundle
3637
*/
37-
protected $bundle;
38+
private $bundle;
3839

3940
/**
4041
* @var ResourceConnection|MockObject
4142
*/
42-
protected $resource;
43+
private $resource;
4344

4445
/**
4546
* @var Select|MockObject
4647
*/
47-
protected $select;
48+
private $select;
4849

4950
/**
5051
* @var Product|MockObject
5152
*/
52-
protected $entityModel;
53+
private $entityModel;
5354

5455
/**
5556
* @var []
5657
*/
57-
protected $params;
58+
private $params;
5859

5960
/** @var AdapterInterface|MockObject
6061
*/
61-
protected $connection;
62+
private $connection;
6263

6364
/**
64-
* @var MockObject
65+
* @var AttributeSetCollectionFactory|MockObject
6566
*/
66-
protected $attrSetColFac;
67+
private $attrSetColFac;
6768

6869
/**
69-
* @var MockObject
70+
* @var ProductAttributeCollectionFactory|MockObject
7071
*/
71-
protected $prodAttrColFac;
72-
73-
/**
74-
* @var Collection|MockObject
75-
*/
76-
protected $setCollection;
72+
private $prodAttrColFac;
7773

7874
/**
7975
* @var ScopeResolverInterface|MockObject
@@ -179,36 +175,17 @@ protected function setUp(): void
179175
);
180176
$this->resource->expects($this->any())->method('getConnection')->willReturn($this->connection);
181177
$this->resource->expects($this->any())->method('getTableName')->willReturn('tableName');
182-
$this->attrSetColFac = $this->createPartialMock(
183-
CollectionFactory::class,
184-
['create']
185-
);
186-
$this->setCollection = $this->createPartialMock(
187-
Collection::class,
188-
['setEntityTypeFilter']
189-
);
190-
$this->attrSetColFac->expects($this->any())->method('create')->willReturn(
191-
$this->setCollection
192-
);
193-
$this->setCollection->expects($this->any())
194-
->method('setEntityTypeFilter')
195-
->willReturn([]);
196-
$this->prodAttrColFac = $this->createPartialMock(
197-
\Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory::class,
198-
['create']
199-
);
200-
$attrCollection =
201-
$this->createMock(\Magento\Catalog\Model\ResourceModel\Product\Attribute\Collection::class);
202-
$attrCollection->expects($this->any())->method('addFieldToFilter')->willReturn([]);
178+
$this->attrSetColFac = $this->createMock(AttributeSetCollectionFactory::class);
179+
$this->prodAttrColFac = $this->createMock(ProductAttributeCollectionFactory::class);
180+
$attrCollection = $this->createMock(ProductAttributeCollection::class);
181+
$attrCollection->expects($this->any())->method('addFieldToFilter')->willReturnSelf();
182+
$attrCollection->expects($this->any())->method('getItems')->willReturn([]);
203183
$this->prodAttrColFac->expects($this->any())->method('create')->willReturn($attrCollection);
204184
$this->params = [
205185
0 => $this->entityModel,
206186
1 => 'bundle'
207187
];
208-
$this->scopeResolver = $this->getMockBuilder(ScopeResolverInterface::class)
209-
->disableOriginalConstructor()
210-
->onlyMethods(['getScope'])
211-
->getMockForAbstractClass();
188+
$this->scopeResolver = $this->createMock(ScopeResolverInterface::class);
212189

213190
$objects = [
214191
[
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model;
9+
10+
use Magento\Framework\Lock\LockManagerInterface;
11+
12+
class ProductMutex implements ProductMutexInterface
13+
{
14+
private const LOCK_PREFIX = 'product_mutex_';
15+
16+
private const LOCK_TIMEOUT = 60;
17+
18+
/**
19+
* @param LockManagerInterface $lockManager
20+
* @param int $lockWaitTimeout
21+
*/
22+
public function __construct(
23+
private readonly LockManagerInterface $lockManager,
24+
private readonly int $lockWaitTimeout = self::LOCK_TIMEOUT
25+
) {
26+
}
27+
28+
/**
29+
* @inheritdoc
30+
*/
31+
public function execute(string $sku, callable $callable, ...$args): mixed
32+
{
33+
if ($this->lockManager->lock(self::LOCK_PREFIX . $sku, $this->lockWaitTimeout)) {
34+
try {
35+
$result = $callable(...$args);
36+
} finally {
37+
$this->lockManager->unlock(self::LOCK_PREFIX . $sku);
38+
}
39+
} else {
40+
throw new ProductMutexException(
41+
__('Could not acquire lock for SKU %1', $sku)
42+
);
43+
}
44+
return $result;
45+
}
46+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model;
9+
10+
use Magento\Framework\Exception\StateException;
11+
12+
class ProductMutexException extends StateException
13+
{
14+
15+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Model;
9+
10+
/**
11+
* Prevents race conditions during concurrent product save operations.
12+
*/
13+
interface ProductMutexInterface
14+
{
15+
/**
16+
* Acquires a lock for SKU, executes callable and releases the lock after.
17+
*
18+
* @param string $sku
19+
* @param callable $callable
20+
* @param array $args
21+
* @return mixed
22+
* @throws ProductMutexException
23+
*/
24+
public function execute(string $sku, callable $callable, ...$args): mixed;
25+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Catalog\Plugin;
9+
10+
use Magento\Catalog\Api\Data\ProductInterface;
11+
use Magento\Catalog\Api\ProductRepositoryInterface;
12+
use Magento\Catalog\Model\Product;
13+
use Magento\Catalog\Model\ProductMutexException;
14+
use Magento\Catalog\Model\ProductMutexInterface;
15+
use Magento\Framework\Exception\CouldNotSaveException;
16+
17+
class ProductRepositorySaveOperationSynchronizer
18+
{
19+
/**
20+
* @param ProductMutexInterface $productMutex
21+
*/
22+
public function __construct(
23+
private readonly ProductMutexInterface $productMutex
24+
) {
25+
}
26+
27+
/**
28+
* Synchronizes product save operations to avoid data corruption from concurrent requests.
29+
*
30+
* @param ProductRepositoryInterface $subject
31+
* @param callable $proceed
32+
* @param Product $product
33+
* @param mixed $saveOptions
34+
* @return ProductInterface
35+
* @throws CouldNotSaveException
36+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
37+
*/
38+
public function aroundSave(
39+
ProductRepositoryInterface $subject,
40+
callable $proceed,
41+
ProductInterface $product,
42+
mixed $saveOptions = false
43+
): ProductInterface {
44+
try {
45+
return $this->productMutex->execute((string) $product->getSku(), $proceed, $product, $saveOptions);
46+
} catch (ProductMutexException $e) {
47+
throw new CouldNotSaveException(
48+
__('The product was unable to be saved. Please try again.'),
49+
$e
50+
);
51+
}
52+
}
53+
}

0 commit comments

Comments
 (0)