Skip to content

Commit d143416

Browse files
committed
Merge remote-tracking branch 'commerce/2.4-develop' into platform-health
2 parents e557525 + 46679e7 commit d143416

File tree

238 files changed

+7394
-1431
lines changed

Some content is hidden

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

238 files changed

+7394
-1431
lines changed

.github/CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ For more detailed information on contribution please read our [beginners guide](
1717

1818
1. Contributions must adhere to the [Magento coding standards](https://devdocs.magento.com/guides/v2.4/coding-standards/bk-coding-standards.html).
1919
2. Pull requests (PRs) must be accompanied by a meaningful description of their purpose. Comprehensive descriptions increase the chances of a pull request being merged quickly and without additional clarification requests.
20-
3. Commits must be accompanied by meaningful commit messages. Please see the [Magento Pull Request Template](https://github.com/magento/magento2/blob/2.3-develop/.github/PULL_REQUEST_TEMPLATE.md) for more information.
20+
3. Commits must be accompanied by meaningful commit messages. Please see the [Magento Pull Request Template](https://github.com/magento/magento2/blob/HEAD/.github/PULL_REQUEST_TEMPLATE.md) for more information.
2121
4. PRs which include bug fixes must be accompanied with a step-by-step description of how to reproduce the bug.
2222
3. PRs which include new logic or new features must be submitted along with:
2323
* Unit/integration test coverage
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
11+
<test name="AdminAwsS3ImportSimpleProductImagesDuplicationTest" extends="AdminImportSimpleProductImagesDuplicationTest">
12+
<annotations>
13+
<features value="AwsS3"/>
14+
<stories value="Import Products"/>
15+
<title value="S3 - Duplicated images should not be created if the CSV file is imported more than once"/>
16+
<description value="Duplicated images should not be created if the CSV file is imported more than once"/>
17+
<severity value="MAJOR"/>
18+
<testCaseId value="MC-42986"/>
19+
<useCaseId value="MC-42330"/>
20+
<group value="catalog_import_export"/>
21+
<group value="remote_storage_aws_s3"/>
22+
<group value="remote_storage_disabled"/>
23+
</annotations>
24+
25+
<before>
26+
<!-- Locally Copy Import Files to Unique Media Import Directory -->
27+
<helper class="Magento\Catalog\Test\Mftf\Helper\LocalFileAssertions" method="createDirectory" stepKey="createDirectoryForImportImages">
28+
<argument name="path">pub/media/import/test_image_duplication</argument>
29+
</helper>
30+
<helper class="Magento\Catalog\Test\Mftf\Helper\LocalFileAssertions" method="copy" stepKey="copyProductBaseImage">
31+
<argument name="source">dev/tests/acceptance/tests/_data/{{placeholderBaseImage.file}}</argument>
32+
<argument name="destination">pub/media/import/test_image_duplication/{{placeholderBaseImage.file}}</argument>
33+
</helper>
34+
<helper class="Magento\Catalog\Test\Mftf\Helper\LocalFileAssertions" method="copy" stepKey="copyProductSmallImage">
35+
<argument name="source">dev/tests/acceptance/tests/_data/{{placeholderSmallImage.file}}</argument>
36+
<argument name="destination">pub/media/import/test_image_duplication/{{placeholderSmallImage.file}}</argument>
37+
</helper>
38+
<helper class="Magento\Catalog\Test\Mftf\Helper\LocalFileAssertions" method="copy" stepKey="copyProductThumbImage">
39+
<argument name="source">dev/tests/acceptance/tests/_data/{{placeholderThumbnailImage.file}}</argument>
40+
<argument name="destination">pub/media/import/test_image_duplication/{{placeholderThumbnailImage.file}}</argument>
41+
</helper>
42+
43+
<!-- Enable AWS S3 Remote Storage & Sync -->
44+
<magentoCLI command="setup:config:set {{RemoteStorageAwsS3ConfigData.enable_options}}" stepKey="enableRemoteStorage" after="copyProductThumbImage"/>
45+
<magentoCLI command="remote-storage:sync" timeout="120" stepKey="syncRemoteStorage" after="enableRemoteStorage"/>
46+
47+
<!-- Copy to Import Directory in AWS S3 -->
48+
<helper class="Magento\AwsS3\Test\Mftf\Helper\S3FileAssertions" method="createDirectory" stepKey="createDirectoryForImportFilesInS3" after="syncRemoteStorage">
49+
<argument name="path">var/import/images/test_image_duplication</argument>
50+
</helper>
51+
<helper class="Magento\AwsS3\Test\Mftf\Helper\S3FileAssertions" method="copy" stepKey="copyProductBaseImageS3" after="createDirectoryForImportFilesInS3">
52+
<argument name="source">media/import/test_image_duplication/{{placeholderBaseImage.file}}</argument>
53+
<argument name="destination">var/import/images/test_image_duplication/{{placeholderBaseImage.file}}</argument>
54+
</helper>
55+
<helper class="Magento\AwsS3\Test\Mftf\Helper\S3FileAssertions" method="copy" stepKey="copyProductSmallImageS3" after="copyProductBaseImageS3">
56+
<argument name="source">media/import/test_image_duplication/{{placeholderSmallImage.file}}</argument>
57+
<argument name="destination">var/import/images/test_image_duplication/{{placeholderSmallImage.file}}</argument>
58+
</helper>
59+
<helper class="Magento\AwsS3\Test\Mftf\Helper\S3FileAssertions" method="copy" stepKey="copyProductThumbImageS3" after="copyProductSmallImageS3">
60+
<argument name="source">media/import/test_image_duplication/{{placeholderThumbnailImage.file}}</argument>
61+
<argument name="destination">var/import/images/test_image_duplication/{{placeholderThumbnailImage.file}}</argument>
62+
</helper>
63+
</before>
64+
65+
<after>
66+
<!-- Delete S3 Data -->
67+
<remove keyForRemoval="deleteProductImageDirectory"/>
68+
<helper class="Magento\AwsS3\Test\Mftf\Helper\S3FileAssertions" method="deleteDirectory" stepKey="deleteImportFilesDirectoryS3" after="deleteCategory">
69+
<argument name="path">media/import/test_image_duplication</argument>
70+
</helper>
71+
<helper class="Magento\AwsS3\Test\Mftf\Helper\S3FileAssertions" method="deleteDirectory" stepKey="deleteImportImagesFilesDirectoryS3" after="deleteImportFilesDirectoryS3">
72+
<argument name="path">var/import/images/test_image_duplication</argument>
73+
</helper>
74+
75+
<!-- Disable AWS S3 Remote Storage & Delete Local Data -->
76+
<magentoCLI command="setup:config:set {{RemoteStorageAwsS3ConfigData.disable_options}}" stepKey="disableRemoteStorage" after="logoutFromAdmin"/>
77+
<helper class="Magento\Catalog\Test\Mftf\Helper\LocalFileAssertions" method="deleteDirectory" stepKey="deleteImportFilesDirectoryLocal" after="disableRemoteStorage">
78+
<argument name="path">pub/media/import/test_image_duplication</argument>
79+
</helper>
80+
</after>
81+
</test>
82+
</tests>

app/code/Magento/Backup/Model/Fs/Collection.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
7+
68
namespace Magento\Backup\Model\Fs;
79

810
use Magento\Framework\App\Filesystem\DirectoryList;
@@ -92,12 +94,17 @@ public function __construct(
9294
* Create .htaccess file and deny backups directory access from web
9395
*
9496
* @return void
97+
* @throws \Magento\Framework\Exception\FileSystemException
9598
*/
9699
protected function _hideBackupsForApache()
97100
{
98101
$filename = '.htaccess';
99-
if (!$this->_varDirectory->isFile($filename)) {
100-
$this->_varDirectory->writeFile($filename, 'deny from all');
102+
$driver = $this->_varDirectory->getDriver();
103+
$absolutePath = $driver->getAbsolutePath($this->_varDirectory->getAbsolutePath(), $filename);
104+
if (!$driver->isFile($absolutePath)) {
105+
$resource = $driver->fileOpen($absolutePath, 'w+');
106+
$driver->fileWrite($resource, 'deny from all');
107+
$driver->fileClose($resource);
101108
}
102109
}
103110

app/code/Magento/Backup/Test/Unit/Model/Fs/CollectionTest.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,15 @@ public function testConstructor()
3434
)->disableOriginalConstructor()
3535
->getMock();
3636
$backupData->expects($this->any())->method('getExtensions')->willReturn([]);
37-
37+
$driver = $this->getMockBuilder(
38+
Filesystem\DriverInterface::class
39+
)->disableOriginalConstructor()
40+
->getMock();
3841
$directoryWrite->expects($this->any())->method('create')->with('backups');
39-
$directoryWrite->expects($this->any())->method('getAbsolutePath')->with('backups');
42+
$directoryWrite->expects($this->any())->method('getAbsolutePath')->willReturn('');
43+
$directoryWrite->expects($this->at(3))->method('getAbsolutePath')->with('backups');
4044
$directoryWrite->expects($this->any())->method('isDirectory')->willReturn(true);
45+
$directoryWrite->expects($this->any())->method('getDriver')->willReturn($driver);
4146
$targetDirectory = $this->getMockBuilder(TargetDirectory::class)
4247
->disableOriginalConstructor()
4348
->getMock();

app/code/Magento/Catalog/Controller/Adminhtml/Product/Gallery/Upload.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,10 @@ public function execute()
108108

109109
$result['url'] = $this->productMediaConfig->getTmpMediaUrl($result['file']);
110110
$result['file'] = $result['file'] . '.tmp';
111-
} catch (\Exception $e) {
111+
} catch (LocalizedException $e) {
112112
$result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
113+
} catch (\Throwable $e) {
114+
$result = ['error' => 'Something went wrong while saving the file(s).', 'errorcode' => 0];
113115
}
114116

115117
/** @var \Magento\Framework\Controller\Result\Raw $response */

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,7 @@ public function getAvailableSortBy()
10371037
{
10381038
$available = $this->getData(self::KEY_AVAILABLE_SORT_BY);
10391039
if (empty($available)) {
1040-
return [];
1040+
return null;
10411041
}
10421042
if ($available && !is_array($available)) {
10431043
$available = explode(',', $available);

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -883,8 +883,8 @@ public function beforeSave()
883883

884884
$this->getTypeInstance()->beforeSave($this);
885885

886-
$hasOptions = $this->getData('has_options') === "1";
887-
$hasRequiredOptions = $this->getData('required_options') === "1";
886+
$hasOptions = $this->getData('has_options') === "1" && $this->isProductHasOptions();
887+
$hasRequiredOptions = $this->getData('required_options') === "1" && $this->isProductHasOptions();
888888

889889
/**
890890
* $this->_canAffectOptions - set by type instance only
@@ -934,6 +934,21 @@ public function beforeSave()
934934
parent::beforeSave();
935935
}
936936

937+
/**
938+
* Check based on options data
939+
*
940+
* @return bool
941+
*/
942+
private function isProductHasOptions() : bool
943+
{
944+
if ($this->getData('options') === null) {
945+
$result = true;
946+
} else {
947+
$result = is_array($this->getData('options')) && count($this->getData('options')) > 0;
948+
}
949+
return $result;
950+
}
951+
937952
/**
938953
* Check/set if options can be affected when saving product
939954
*

app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,16 @@
33
* Copyright © Magento, Inc. All rights reserved.
44
* See COPYING.txt for license details.
55
*/
6+
declare(strict_types=1);
67

78
namespace Magento\Catalog\Model\Product\Option\Type\File;
89

910
use Magento\Catalog\Model\Product;
10-
use Magento\Framework\App\Filesystem\DirectoryList;
1111
use Magento\Catalog\Model\Product\Exception as ProductException;
12+
use Magento\Framework\App\Filesystem\DirectoryList;
13+
use Magento\Framework\App\ObjectManager;
1214
use Magento\Framework\Exception\LocalizedException;
1315
use Magento\Framework\Math\Random;
14-
use Magento\Framework\App\ObjectManager;
1516
use Magento\MediaStorage\Model\File\Uploader;
1617

1718
/**
@@ -254,8 +255,12 @@ protected function initFilesystem()
254255

255256
// Directory listing and hotlink secure
256257
$path = $this->path . '/.htaccess';
257-
if (!$this->mediaDirectory->isFile($path)) {
258-
$this->mediaDirectory->writeFile($path, "Order deny,allow\nDeny from all");
258+
$driver = $this->mediaDirectory->getDriver();
259+
$absolutePath = $driver->getAbsolutePath($this->mediaDirectory->getAbsolutePath(), $path);
260+
if (!$driver->isFile($absolutePath)) {
261+
$resource = $driver->fileOpen($absolutePath, 'w+');
262+
$driver->fileWrite($resource, "Order deny,allow\nDeny from all");
263+
$driver->fileClose($resource);
259264
}
260265
}
261266

app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php

Lines changed: 59 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,22 @@
66

77
namespace Magento\Catalog\Model\Product\Option\Type\File;
88

9+
use Magento\Framework\App\Config\ScopeConfigInterface;
10+
use Magento\Framework\Exception\InputException;
11+
use Magento\Framework\Exception\LocalizedException;
12+
use Magento\Framework\File\Size;
13+
use Magento\Framework\Filesystem;
14+
use Magento\Framework\Filesystem\Io\File as IoFile;
15+
use Magento\MediaStorage\Helper\File\Storage\Database;
16+
use Magento\MediaStorage\Model\File\Validator\NotProtectedExtension;
17+
918
/**
1019
* Validator for existing files.
1120
*/
1221
class ValidatorInfo extends Validator
1322
{
1423
/**
15-
* @var \Magento\MediaStorage\Helper\File\Storage\Database
24+
* @var Database
1625
*/
1726
protected $coreFileStorageDatabase;
1827

@@ -36,24 +45,39 @@ class ValidatorInfo extends Validator
3645
*/
3746
protected $fileRelativePath;
3847

48+
/**
49+
* @var IoFile
50+
*/
51+
private $ioFile;
52+
/**
53+
* @var NotProtectedExtension
54+
*/
55+
private $fileValidator;
56+
3957
/**
4058
* Construct method
4159
*
42-
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
43-
* @param \Magento\Framework\Filesystem $filesystem
44-
* @param \Magento\Framework\File\Size $fileSize
45-
* @param \Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase
60+
* @param ScopeConfigInterface $scopeConfig
61+
* @param Filesystem $filesystem
62+
* @param Size $fileSize
63+
* @param Database $coreFileStorageDatabase
4664
* @param ValidateFactory $validateFactory
65+
* @param NotProtectedExtension $fileValidator
66+
* @param IoFile $ioFile
4767
*/
4868
public function __construct(
49-
\Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
50-
\Magento\Framework\Filesystem $filesystem,
51-
\Magento\Framework\File\Size $fileSize,
52-
\Magento\MediaStorage\Helper\File\Storage\Database $coreFileStorageDatabase,
53-
\Magento\Catalog\Model\Product\Option\Type\File\ValidateFactory $validateFactory
69+
ScopeConfigInterface $scopeConfig,
70+
Filesystem $filesystem,
71+
Size $fileSize,
72+
Database $coreFileStorageDatabase,
73+
ValidateFactory $validateFactory,
74+
NotProtectedExtension $fileValidator,
75+
IoFile $ioFile
5476
) {
5577
$this->coreFileStorageDatabase = $coreFileStorageDatabase;
5678
$this->validateFactory = $validateFactory;
79+
$this->fileValidator = $fileValidator;
80+
$this->ioFile = $ioFile;
5781
parent::__construct($scopeConfig, $filesystem, $fileSize);
5882
}
5983

@@ -94,27 +118,42 @@ public function validate($optionValue, $option)
94118
$validatorChain = $this->validateFactory->create();
95119
try {
96120
$validatorChain = $this->buildImageValidator($validatorChain, $option, $this->fileFullPath);
97-
} catch (\Magento\Framework\Exception\InputException $notImage) {
121+
} catch (InputException $notImage) {
98122
return false;
99123
}
100124

101-
$result = false;
102-
if ($validatorChain->isValid($this->fileFullPath, $optionValue['title'])) {
103-
$result = $this->rootDirectory->isReadable($this->fileRelativePath)
125+
if ($this->validatePath($optionValue) && $validatorChain->isValid($this->fileFullPath, $optionValue['title'])) {
126+
return $this->rootDirectory->isReadable($this->fileRelativePath)
104127
&& isset($optionValue['secret_key'])
105128
&& $this->buildSecretKey($this->fileRelativePath) == $optionValue['secret_key'];
106-
} elseif ($validatorChain->getErrors()) {
129+
} else {
107130
$errors = $this->getValidatorErrors($validatorChain->getErrors(), $optionValue, $option);
108-
109131
if (count($errors) > 0) {
110-
throw new \Magento\Framework\Exception\LocalizedException(__(implode("\n", $errors)));
132+
throw new LocalizedException(__(implode("\n", $errors)));
111133
}
112-
} else {
113-
throw new \Magento\Framework\Exception\LocalizedException(
134+
throw new LocalizedException(
114135
__("The product's required option(s) weren't entered. Make sure the options are entered and try again.")
115136
);
116137
}
117-
return $result;
138+
}
139+
140+
/**
141+
* Validate quote_path and order_path.
142+
*
143+
* @param array $optionValuePath
144+
* @return bool
145+
*/
146+
private function validatePath(array $optionValuePath): bool
147+
{
148+
foreach ([$optionValuePath['quote_path'], $optionValuePath['order_path']] as $path) {
149+
$pathInfo = $this->ioFile->getPathInfo($path);
150+
if (isset($pathInfo['extension'])) {
151+
if (!$this->fileValidator->isValid($pathInfo['extension'])) {
152+
return false;
153+
}
154+
}
155+
}
156+
return true;
118157
}
119158

120159
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
/**
4+
* Copyright © Magento, Inc. All rights reserved.
5+
* See COPYING.txt for license details.
6+
*/
7+
-->
8+
9+
<actionGroups xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
11+
<actionGroup name="AdminSetBackordersOnProductAdvancedInventoryActionGroup">
12+
<annotations>
13+
<description>Deselects the "Use Config Settings" checkbox and set the "Backorders" select value to required</description>
14+
</annotations>
15+
<arguments>
16+
<argument name="backorders" type="string" defaultValue="Allow Qty Below 0"/>
17+
</arguments>
18+
19+
<uncheckOption selector="{{AdminProductFormAdvancedInventorySection.useConfigSettingsForBackorders}}" stepKey="uncheckUseConfigSettings"/>
20+
<selectOption selector="{{AdminProductFormAdvancedInventorySection.backorders}}" userInput="{{backorders}}" stepKey="fillBackorders"/>
21+
</actionGroup>
22+
</actionGroups>

0 commit comments

Comments
 (0)