Skip to content

Commit 51a7125

Browse files
Merge branch '2.4-develop' into PR_2025_07_10_chittima
2 parents bd8a6f2 + 2a1e1e5 commit 51a7125

File tree

28 files changed

+931
-172
lines changed

28 files changed

+931
-172
lines changed

app/code/Magento/AwsS3/Driver/AwsS3.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,9 @@ private function createDirectoryRecursively(string $path): bool
188188
$parentDir = dirname($path);
189189

190190
while (!$this->isDirectory($parentDir)) {
191-
$this->createDirectoryRecursively($parentDir);
191+
if (!$this->createDirectoryRecursively($parentDir)) {
192+
return false;
193+
}
192194
}
193195

194196
if (!$this->isDirectory($path)) {

app/code/Magento/AwsS3/Test/Unit/Driver/AwsS3Test.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
/**
2020
* @see AwsS3
21+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
2122
*/
2223
class AwsS3Test extends TestCase
2324
{
@@ -497,6 +498,23 @@ public function testCreateDirectory(): void
497498
self::assertTrue($this->driver->createDirectory(self::URL . 'test/test2/'));
498499
}
499500

501+
/**
502+
* This test ensures that the method does not loop infinitely in case of an exception
503+
*
504+
* @return void
505+
* @throws FileSystemException
506+
* @throws \PHPUnit\Framework\MockObject\Exception
507+
*/
508+
public function testShouldFailSafelyIfUnableToCreateDirectory(): void
509+
{
510+
$this->adapterMock->expects(self::once())
511+
->method('createDirectory')
512+
->willThrowException($this->createMock(\League\Flysystem\FilesystemException::class))
513+
->with('test');
514+
515+
self::assertFalse($this->driver->createDirectory(self::URL . 'test/test2/'));
516+
}
517+
500518
public function testRename(): void
501519
{
502520
$this->adapterMock->expects(self::once())

app/code/Magento/Backend/Block/Store/Switcher.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,15 @@ public function getCurrentSelectionName()
457457
if ($this->getCurrentWebsiteName() !== '') {
458458
return $this->getCurrentWebsiteName();
459459
}
460+
461+
if (!$this->hasDefaultOption()) {
462+
$websites = $this->getWebsites();
463+
if (!empty($websites)) {
464+
$websiteArray = array_values($websites);
465+
return $websiteArray[0]->getName();
466+
}
467+
}
468+
460469
return $this->getDefaultSelectionName();
461470
}
462471

app/code/Magento/Bundle/Controller/Adminhtml/Product/Initialization/Helper/Plugin/Bundle.php

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
use Magento\Bundle\Api\Data\OptionInterfaceFactory as OptionFactory;
99
use Magento\Bundle\Api\Data\LinkInterfaceFactory as LinkFactory;
1010
use Magento\Catalog\Api\Data\ProductCustomOptionInterfaceFactory;
11+
use Magento\Catalog\Api\Data\ProductInterface;
1112
use Magento\Catalog\Api\ProductRepositoryInterface as ProductRepository;
1213
use Magento\Store\Model\StoreManagerInterface as StoreManager;
1314
use Magento\Framework\App\RequestInterface;
1415

1516
/**
16-
* Class Bundle
17+
* Plugin class to initialize Bundle product
18+
*
1719
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
1820
*/
1921
class Bundle
@@ -106,12 +108,14 @@ public function afterInitialize(
106108
$product->setBundleOptionsData($result['bundle_options']);
107109
}
108110

111+
if (!$result['bundle_selections']) {
112+
$this->resetBundleProductOptions($product);
113+
}
114+
109115
$this->processBundleOptionsData($product);
110116
$this->processDynamicOptionsData($product);
111117
} elseif (!$compositeReadonly) {
112-
$extension = $product->getExtensionAttributes();
113-
$extension->setBundleProductOptions([]);
114-
$product->setExtensionAttributes($extension);
118+
$this->resetBundleProductOptions($product);
115119
}
116120

117121
$affectProductSelections = (bool)$this->request->getPost('affect_bundle_product_selections');
@@ -120,6 +124,8 @@ public function afterInitialize(
120124
}
121125

122126
/**
127+
* Process Bundle Options Data
128+
*
123129
* @param \Magento\Catalog\Model\Product $product
124130
* @return void
125131
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -161,10 +167,11 @@ protected function processBundleOptionsData(\Magento\Catalog\Model\Product $prod
161167
$extension = $product->getExtensionAttributes();
162168
$extension->setBundleProductOptions($options);
163169
$product->setExtensionAttributes($extension);
164-
return;
165170
}
166171

167172
/**
173+
* Process Dynamic Options Data
174+
*
168175
* @param \Magento\Catalog\Model\Product $product
169176
* @return void
170177
*/
@@ -198,9 +205,10 @@ protected function processDynamicOptionsData(\Magento\Catalog\Model\Product $pro
198205
}
199206

200207
/**
208+
* Build product link
209+
*
201210
* @param \Magento\Catalog\Model\Product $product
202211
* @param array $linkData
203-
*
204212
* @return \Magento\Bundle\Api\Data\LinkInterface
205213
*/
206214
private function buildLink(
@@ -228,4 +236,18 @@ private function buildLink(
228236

229237
return $link;
230238
}
239+
240+
/**
241+
* Resets bundle product options inside product extension attributes
242+
*
243+
* @param ProductInterface $product
244+
* @return void
245+
*/
246+
private function resetBundleProductOptions(ProductInterface $product) : void
247+
{
248+
$extension = $product->getExtensionAttributes();
249+
$extension->setBundleProductOptions([]);
250+
$product->setExtensionAttributes($extension);
251+
$product->setDropOptions(true);
252+
}
231253
}

app/code/Magento/Bundle/Model/Product/SaveHandler.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,9 @@ public function execute($entity, $arguments = [])
9696
/** @var OptionInterface[] $bundleProductOptions */
9797
$bundleProductOptions = $entity->getExtensionAttributes()->getBundleProductOptions() ?: [];
9898
//Only processing bundle products.
99-
if ($entity->getTypeId() !== Type::TYPE_CODE || empty($bundleProductOptions)) {
99+
if ($entity->getTypeId() !== Type::TYPE_CODE
100+
|| (empty($bundleProductOptions) && !$entity->getDropOptions())
101+
) {
100102
return $entity;
101103
}
102104

app/code/Magento/Bundle/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/Plugin/BundleTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,13 @@ public function testAfterInitializeIfBundleSelectionsAndCustomOptionsExist()
173173
->method('setBundleOptionsData')
174174
->with($this->bundleOptionsCleaned);
175175
$this->productMock->expects($this->never())->method('setBundleSelectionsData');
176+
$extensionAttribute = $this->getMockBuilder(ProductExtensionInterface::class)
177+
->disableOriginalConstructor()
178+
->addMethods(['setBundleProductOptions'])
179+
->getMockForAbstractClass();
180+
$extensionAttribute->expects($this->once())->method('setBundleProductOptions')->with([]);
181+
$this->productMock->expects($this->once())->method('getExtensionAttributes')->willReturn($extensionAttribute);
182+
$this->productMock->expects($this->once())->method('setExtensionAttributes')->with($extensionAttribute);
176183
$this->productMock->expects($this->once())->method('getPriceType')->willReturn(2);
177184
$this->productMock->expects($this->any())->method('getOptionsReadonly')->willReturn(true);
178185
$this->productMock->expects($this->once())->method('setCanSaveBundleSelections')->with(false);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ public function execute()
143143
$this->validateProductAttributes($attributesData);
144144
$this->publish($attributesData, $websiteRemoveData, $websiteAddData, $storeId, $websiteId, $productIds);
145145
$this->messageManager->addSuccessMessage(__('Message is added to queue'));
146+
$this->attributeHelper->setProductIds([]);
146147
} catch (LocalizedException $e) {
147148
$this->messageManager->addErrorMessage($e->getMessage());
148149
} catch (\Exception $e) {

app/code/Magento/CatalogGraphQl/Model/Resolver/Categories.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
namespace Magento\CatalogGraphQl\Model\Resolver;
99

1010
use Magento\Catalog\Api\Data\CategoryInterface;
11+
use Magento\Catalog\Model\Product\Visibility;
1112
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
1213
use Magento\CatalogGraphQl\Model\AttributesJoiner;
1314
use Magento\CatalogGraphQl\Model\Category\Hydrator as CategoryHydrator;
@@ -99,8 +100,12 @@ public function resolve(Field $field, $context, ResolveInfo $info, ?array $value
99100
}
100101
/** @var \Magento\Catalog\Model\Product $product */
101102
$product = $value['model'];
102-
$storeId = $this->storeManager->getStore()->getId();
103-
$categoryIds = $this->productCategories->getCategoryIdsByProduct((int)$product->getId(), (int)$storeId);
103+
if ($product->getVisibility() == Visibility::VISIBILITY_NOT_VISIBLE && in_array('orders', $info->path)) {
104+
$categoryIds = $product->getCategoryIds();
105+
} else {
106+
$storeId = $this->storeManager->getStore()->getId();
107+
$categoryIds = $this->productCategories->getCategoryIdsByProduct((int)$product->getId(), (int)$storeId);
108+
}
104109
$collection = $this->collectionFactory->create();
105110
return $this->valueFactory->create(
106111
function () use ($categoryIds, $info, $collection) {
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\CatalogGraphQl\Test\Unit\Model\Resolver;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\Product\Visibility;
12+
use Magento\Catalog\Model\ResourceModel\Category\CollectionFactory;
13+
use Magento\CatalogGraphQl\Model\AttributesJoiner;
14+
use Magento\CatalogGraphQl\Model\Category\Hydrator as CategoryHydrator;
15+
use Magento\CatalogGraphQl\Model\Resolver\Categories;
16+
use Magento\CatalogGraphQl\Model\Resolver\Product\ProductCategories;
17+
use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\CustomAttributesFlattener;
18+
use Magento\Framework\GraphQl\Config\Element\Field;
19+
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
20+
use Magento\Framework\GraphQl\Query\Resolver\ValueFactory;
21+
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
22+
use Magento\Store\Api\Data\StoreInterface;
23+
use Magento\Store\Model\StoreManagerInterface;
24+
use PHPUnit\Framework\MockObject\Exception;
25+
use PHPUnit\Framework\TestCase;
26+
use PHPUnit\Framework\MockObject\MockObject;
27+
28+
/**
29+
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
30+
*/
31+
class CategoriesTests extends TestCase
32+
{
33+
/**
34+
* @var CollectionFactory|MockObject
35+
*/
36+
private CollectionFactory $collectionFactory;
37+
38+
/**
39+
* @var AttributesJoiner|MockObject
40+
*/
41+
private AttributesJoiner $attributesJoiner;
42+
43+
/**
44+
* @var CustomAttributesFlattener|MockObject
45+
*/
46+
private CustomAttributesFlattener $customAttributesFlattener;
47+
48+
/**
49+
* @var ValueFactory|MockObject
50+
*/
51+
private ValueFactory $valueFactory;
52+
53+
/**
54+
* @var CategoryHydrator|MockObject
55+
*/
56+
private CategoryHydrator $categoryHydrator;
57+
58+
/**
59+
* @var ProductCategories|MockObject
60+
*/
61+
private ProductCategories $productCategories;
62+
63+
/**
64+
* @var StoreManagerInterface|MockObject
65+
*/
66+
private StoreManagerInterface $storeManager;
67+
68+
/**
69+
* @var Categories
70+
*/
71+
private Categories $categoriesResolver;
72+
73+
/**
74+
* @inheritDoc
75+
*/
76+
protected function setUp(): void
77+
{
78+
$this->collectionFactory = $this->createMock(CollectionFactory::class);
79+
$this->attributesJoiner = $this->createMock(AttributesJoiner::class);
80+
$this->customAttributesFlattener = $this->createMock(CustomAttributesFlattener::class);
81+
$this->valueFactory = $this->createMock(ValueFactory::class);
82+
$this->categoryHydrator = $this->createMock(CategoryHydrator::class);
83+
$this->productCategories = $this->createMock(ProductCategories::class);
84+
$this->storeManager = $this->createMock(StoreManagerInterface::class);
85+
86+
$this->categoriesResolver = new Categories(
87+
$this->collectionFactory,
88+
$this->attributesJoiner,
89+
$this->customAttributesFlattener,
90+
$this->valueFactory,
91+
$this->categoryHydrator,
92+
$this->productCategories,
93+
$this->storeManager
94+
);
95+
}
96+
97+
/**
98+
* @return void
99+
* @throws Exception
100+
*/
101+
public function testResolveWithHiddenProduct(): void
102+
{
103+
$categoryId = 1;
104+
$field = $this->createMock(Field::class);
105+
$context = $this->createMock(ContextInterface::class);
106+
$info = $this->createMock(ResolveInfo::class);
107+
$info->path = [
108+
'orders',
109+
'customers'
110+
];
111+
$product = $this->createMock(Product::class);
112+
$product->expects($this->once())
113+
->method('getVisibility')
114+
->willReturn(Visibility::VISIBILITY_NOT_VISIBLE);
115+
$product->expects($this->once())->method('getCategoryIds')->willReturn([$categoryId]);
116+
$value = [
117+
'model' => $product
118+
];
119+
$args = [];
120+
$this->collectionFactory->expects($this->once())->method('create');
121+
122+
$this->categoriesResolver->resolve($field, $context, $info, $value, $args);
123+
}
124+
125+
/**
126+
* @return void
127+
* @throws Exception
128+
*/
129+
public function testResolveWithVisibleProduct(): void
130+
{
131+
$categoryId = $storeId = $productId = 1;
132+
$field = $this->createMock(Field::class);
133+
$context = $this->createMock(ContextInterface::class);
134+
$info = $this->createMock(ResolveInfo::class);
135+
$info->path = [
136+
'orders',
137+
'customers'
138+
];
139+
$product = $this->createMock(Product::class);
140+
$product->expects($this->once())
141+
->method('getVisibility')
142+
->willReturn(Visibility::VISIBILITY_IN_CATALOG);
143+
$product->expects($this->once())->method('getId')->willReturn([$productId]);
144+
$value = [
145+
'model' => $product
146+
];
147+
$args = [];
148+
$store = $this->createMock(StoreInterface::class);
149+
$store->expects($this->once())->method('getId')->willReturn($storeId);
150+
$this->storeManager->expects($this->once())->method('getStore')->willReturn($store);
151+
$this->productCategories->expects($this->once())
152+
->method('getCategoryIdsByProduct')
153+
->with($productId, $storeId)
154+
->willReturn([$categoryId]);
155+
$this->collectionFactory->expects($this->once())->method('create');
156+
157+
$this->categoriesResolver->resolve($field, $context, $info, $value, $args);
158+
}
159+
}

0 commit comments

Comments
 (0)