Skip to content

Commit fec5184

Browse files
Merge branch '2.4-develop' into PR-VK-2023-09-29
2 parents 73155e3 + 2f5faa7 commit fec5184

File tree

10 files changed

+343
-59
lines changed

10 files changed

+343
-59
lines changed

app/code/Magento/Catalog/Model/ProductRepository/MediaGalleryProcessor.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ public function __construct(
8282
* @throws InputException
8383
* @throws StateException
8484
* @throws LocalizedException
85+
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
8586
*/
8687
public function processMediaGallery(ProductInterface $product, array $mediaGalleryEntries) :void
8788
{
@@ -113,6 +114,9 @@ public function processMediaGallery(ProductInterface $product, array $mediaGalle
113114
// phpcs:ignore Magento2.Performance.ForeachArrayMerge
114115
$existingMediaGallery[$key] = array_merge($existingEntry, $updatedEntry);
115116
}
117+
} elseif (!empty($newEntries) && isset($existingEntry['value_id'])) {
118+
//avoid deleting an exiting image while adding a new one
119+
unset($existingMediaGallery[$key]);
116120
} elseif ($this->canRemoveImage($product, $existingEntry)) {
117121
//set the removed flag
118122
$existingEntry['removed'] = true;
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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\Catalog\Test\Unit\Model\ProductRepository;
9+
10+
use Magento\Catalog\Model\Product;
11+
use Magento\Catalog\Model\Product\Gallery\DeleteValidator;
12+
use Magento\Catalog\Model\Product\Gallery\Processor;
13+
use Magento\Catalog\Model\Product\Media\Config;
14+
use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor;
15+
use Magento\Framework\Api\Data\ImageContentInterface;
16+
use Magento\Framework\Api\Data\ImageContentInterfaceFactory;
17+
use Magento\Framework\Api\ImageContent;
18+
use Magento\Framework\Api\ImageProcessorInterface;
19+
use PHPUnit\Framework\MockObject\MockObject;
20+
use PHPUnit\Framework\TestCase;
21+
22+
class MediaGalleryProcessorTest extends TestCase
23+
{
24+
/**
25+
* @var MediaGalleryProcessor
26+
*/
27+
private $galleryProcessor;
28+
29+
/**
30+
* @var Processor|MockObject
31+
*/
32+
private $processorMock;
33+
34+
/**
35+
* @var ImageContentInterfaceFactory|MockObject
36+
*/
37+
private $contentFactoryMock;
38+
39+
/**
40+
* @var ImageProcessorInterface|MockObject
41+
*/
42+
private $imageProcessorMock;
43+
44+
/**
45+
* @var DeleteValidator|MockObject
46+
*/
47+
private $deleteValidatorMock;
48+
49+
/**
50+
* @var Product|MockObject
51+
*/
52+
private $productMock;
53+
54+
protected function setUp(): void
55+
{
56+
$this->processorMock = $this->getMockBuilder(Processor::class)
57+
->disableOriginalConstructor()
58+
->getMock();
59+
$this->contentFactoryMock = $this->getMockBuilder(ImageContentInterfaceFactory::class)
60+
->disableOriginalConstructor()
61+
->getMock();
62+
$this->imageProcessorMock = $this->getMockBuilder(ImageProcessorInterface::class)
63+
->disableOriginalConstructor()
64+
->getMock();
65+
$this->deleteValidatorMock = $this->getMockBuilder(DeleteValidator::class)
66+
->disableOriginalConstructor()
67+
->getMock();
68+
$this->productMock = $this->getMockBuilder(Product::class)
69+
->addMethods(['getMediaGallery'])
70+
->onlyMethods(['hasGalleryAttribute', 'getMediaConfig', 'getMediaAttributes'])
71+
->disableOriginalConstructor()
72+
->getMock();
73+
74+
$this->galleryProcessor = new MediaGalleryProcessor(
75+
$this->processorMock,
76+
$this->contentFactoryMock,
77+
$this->imageProcessorMock,
78+
$this->deleteValidatorMock
79+
);
80+
}
81+
82+
/**
83+
* The media gallery array should not have "removed" key while adding the new entry
84+
*
85+
* @return void
86+
*/
87+
public function testProcessMediaGallery(): void
88+
{
89+
$initialExitingEntry = [
90+
'value_id' => 5,
91+
"label" => "new_label_text",
92+
'file' => 'filename1',
93+
'position' => 10,
94+
'disabled' => false,
95+
'types' => ['image', 'small_image']
96+
];
97+
$newEntriesData = [
98+
'images' => [
99+
$initialExitingEntry,
100+
[
101+
'value_id' => null,
102+
'label' => "label_text",
103+
'position' => 10,
104+
'disabled' => false,
105+
'types' => ['image', 'small_image'],
106+
'content' => [
107+
'data' => [
108+
ImageContentInterface::NAME => 'filename',
109+
ImageContentInterface::TYPE => 'image/jpeg',
110+
ImageContentInterface::BASE64_ENCODED_DATA => 'encoded_content'
111+
]
112+
],
113+
'media_type' => 'media_type'
114+
]
115+
]
116+
];
117+
$newExitingEntriesData = [
118+
'images' => [
119+
$initialExitingEntry,
120+
[
121+
'value_id' => 6,
122+
"label" => "label_text2",
123+
'file' => 'filename2',
124+
'position' => 10,
125+
'disabled' => false,
126+
'types' => ['image', 'small_image']
127+
]
128+
]
129+
];
130+
$this->productMock->method('getMediaGallery')
131+
->willReturnOnConsecutiveCalls(
132+
$newExitingEntriesData['images'],
133+
$newExitingEntriesData['images']
134+
);
135+
$this->productMock->expects($this->any())
136+
->method('getMediaAttributes')
137+
->willReturn(["image" => "imageAttribute", "small_image" => "small_image_attribute"]);
138+
$this->productMock->method('hasGalleryAttribute')->willReturn(true);
139+
$mediaTmpPath = '/tmp';
140+
$absolutePath = '/a/b/filename.jpg';
141+
$this->processorMock->expects($this->once())->method('clearMediaAttribute')
142+
->with($this->productMock, ['image', 'small_image']);
143+
$mediaConfigMock = $this->getMockBuilder(Config::class)->disableOriginalConstructor()->getMock();
144+
$mediaConfigMock->expects($this->once())->method('getTmpMediaShortUrl')->with($absolutePath)
145+
->willReturn($mediaTmpPath . $absolutePath);
146+
$this->productMock->expects($this->once())->method('getMediaConfig')->willReturn($mediaConfigMock);
147+
//verify new entries
148+
$contentDataObject = $this->getMockBuilder(ImageContent::class)
149+
->disableOriginalConstructor()
150+
->setMethods(null)
151+
->getMock();
152+
$this->contentFactoryMock->expects($this->once())->method('create')->willReturn($contentDataObject);
153+
$this->imageProcessorMock->expects($this->once())->method('processImageContent')->willReturn($absolutePath);
154+
$imageFileUri = $mediaTmpPath . $absolutePath;
155+
$this->processorMock->expects($this->once())->method('addImage')
156+
->willReturnCallback(
157+
function ($product, $imageFileUri) use ($newEntriesData) {
158+
foreach ($product['media_gallery']['images'] as $entry) {
159+
if (isset($entry['value_id'])) {
160+
$this->assertArrayNotHasKey('removed', $entry);
161+
}
162+
}
163+
$this->productMock->setData('media_gallery', $newEntriesData);
164+
return $imageFileUri;
165+
}
166+
);
167+
$this->processorMock->expects($this->once())->method('updateImage')
168+
->with(
169+
$this->productMock,
170+
$imageFileUri,
171+
[
172+
'label' => 'label_text',
173+
'position' => 10,
174+
'disabled' => false,
175+
'media_type' => 'media_type',
176+
]
177+
);
178+
$this->galleryProcessor->processMediaGallery($this->productMock, $newEntriesData['images']);
179+
}
180+
}

app/code/Magento/CatalogUrlRewrite/Model/Category/Plugin/Category/Move.php

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
use Magento\Catalog\Model\CategoryFactory;
1010
use Magento\CatalogUrlRewrite\Model\CategoryUrlPathGenerator;
1111
use Magento\CatalogUrlRewrite\Model\Category\ChildrenCategoriesProvider;
12-
use Magento\Store\Model\Store;
12+
use Magento\Store\Model\StoreManagerInterface;
1313

1414
/**
1515
* Perform url updating for children categories.
@@ -31,19 +31,27 @@ class Move
3131
*/
3232
private $categoryFactory;
3333

34+
/**
35+
* @var StoreManagerInterface
36+
*/
37+
private $storeManager;
38+
3439
/**
3540
* @param CategoryUrlPathGenerator $categoryUrlPathGenerator
3641
* @param ChildrenCategoriesProvider $childrenCategoriesProvider
3742
* @param CategoryFactory $categoryFactory
43+
* @param StoreManagerInterface $storeManager
3844
*/
3945
public function __construct(
4046
CategoryUrlPathGenerator $categoryUrlPathGenerator,
4147
ChildrenCategoriesProvider $childrenCategoriesProvider,
42-
CategoryFactory $categoryFactory
48+
CategoryFactory $categoryFactory,
49+
StoreManagerInterface $storeManager
4350
) {
4451
$this->categoryUrlPathGenerator = $categoryUrlPathGenerator;
4552
$this->childrenCategoriesProvider = $childrenCategoriesProvider;
4653
$this->categoryFactory = $categoryFactory;
54+
$this->storeManager = $storeManager;
4755
}
4856

4957
/**
@@ -67,6 +75,7 @@ public function afterChangeParent(
6775
$categoryStoreId = $category->getStoreId();
6876
foreach ($category->getStoreIds() as $storeId) {
6977
$category->setStoreId($storeId);
78+
$this->removeObsoleteUrlPathEntries($category);
7079
$this->updateCategoryUrlKeyForStore($category);
7180
$category->unsUrlPath();
7281
$category->setUrlPath($this->categoryUrlPathGenerator->getUrlPath($category));
@@ -92,24 +101,13 @@ private function updateCategoryUrlKeyForStore(Category $category)
92101
$category->setUrlKey($item->getUrlKey());
93102
}
94103

95-
/**
96-
* Check is global scope.
97-
*
98-
* @param int|null $storeId
99-
* @return bool
100-
*/
101-
private function isGlobalScope($storeId)
102-
{
103-
return null === $storeId || $storeId == Store::DEFAULT_STORE_ID;
104-
}
105-
106104
/**
107105
* Updates url_path for child categories.
108106
*
109107
* @param Category $category
110108
* @return void
111109
*/
112-
private function updateUrlPathForChildren($category)
110+
private function updateUrlPathForChildren(Category $category): void
113111
{
114112
foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) {
115113
$childCategory->setStoreId($category->getStoreId());
@@ -118,4 +116,27 @@ private function updateUrlPathForChildren($category)
118116
$childCategory->getResource()->saveAttribute($childCategory, 'url_path');
119117
}
120118
}
119+
120+
/**
121+
* Clean obsolete entries
122+
*
123+
* @param Category $category
124+
* @return void
125+
*/
126+
private function removeObsoleteUrlPathEntries(Category $category): void
127+
{
128+
if ($this->storeManager->hasSingleStore()) {
129+
return;
130+
}
131+
$origPath = $category->getOrigData('path');
132+
$path = $category->getData('path');
133+
if ($origPath != null && $path != null && $origPath != $path) {
134+
$category->unsUrlPath();
135+
$category->getResource()->saveAttribute($category, 'url_path');
136+
foreach ($this->childrenCategoriesProvider->getChildren($category, true) as $childCategory) {
137+
$childCategory->unsUrlPath();
138+
$childCategory->getResource()->saveAttribute($childCategory, 'url_path');
139+
}
140+
}
141+
}
121142
}

0 commit comments

Comments
 (0)