Skip to content

Commit e9540e7

Browse files
merge magento-commerce/2.4-develop into magento-honey-badgers/MC-38927
2 parents de1ccb8 + 1c78cdb commit e9540e7

File tree

79 files changed

+3395
-223
lines changed

Some content is hidden

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

79 files changed

+3395
-223
lines changed

.github/stale.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ staleLabel: "stale issue"
3939
# Comment to post when marking as stale. Set to `false` to disable
4040
markComment: >
4141
This issue has been automatically marked as stale because it has not had
42-
recent activity. It will be closed after 14 days if no further activity occurs. Thank you
43-
for your contributions.
42+
recent activity. It will be closed after 14 days if no further activity occurs.
43+
Is this issue still relevant? If so, what is blocking it? Is there anything you can do to help move it forward?
44+
Thank you for your contributions!
4445
# Comment to post when removing the stale label.
4546
# unmarkComment: >
4647
# Your comment here.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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="AwsS3AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest" extends="AdminImportSimpleAndConfigurableProductsWithAssignedImagesTest">
12+
<annotations>
13+
<features value="AwsS3"/>
14+
<stories value="Import Products"/>
15+
<title value="S3 - Import Configurable Product With Simple Child Products With Images"/>
16+
<description value="Imports a .csv file containing a configurable product with 3 child simple products that
17+
have images. Verifies that products are imported successfully and that the images are attached to the
18+
products as expected."/>
19+
<severity value="MAJOR"/>
20+
<group value="importExport"/>
21+
<group value="remote_storage_aws_s3"/>
22+
<skip>
23+
<issueId value="MC-39280"/>
24+
</skip>
25+
</annotations>
26+
27+
<before>
28+
<!-- Enable AWS S3 Remote Storage -->
29+
<magentoCLI command="setup:config:set {{RemoteStorageAwsS3ConfigData.enable_options}}" stepKey="enableRemoteStorage"/>
30+
</before>
31+
32+
<after>
33+
<!-- Disable AWS S3 Remote Storage -->
34+
<magentoCLI command="setup:config:set {{RemoteStorageAwsS3ConfigData.disable_options}}" stepKey="disableRemoteStorage"/>
35+
</after>
36+
</test>
37+
</tests>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
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="AdminToggleSwitchDynamicPriceOnProductEditPageActionGroup">
12+
<waitForElementVisible selector="{{AdminProductFormBundleSection.dynamicPriceToggle}}" stepKey="waitForToggleDynamicPrice"/>
13+
<checkOption selector="{{AdminProductFormBundleSection.dynamicPriceToggle}}" stepKey="switchDynamicPriceToggle"/>
14+
</actionGroup>
15+
</actionGroups>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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+
<!-- Test XML Example -->
9+
<tests xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
10+
<test name="AdminBundleProductPriceValidationErrorDisappearedAfterSwitchToDynamicPriceTest">
11+
<annotations>
12+
<features value="Bundle"/>
13+
<stories value="Create/Edit bundle product in Admin"/>
14+
<title value="Assert error message for price field"/>
15+
<description value="Verify error message for price field is not visible when toggle Dynamic Price is disabled"/>
16+
<severity value="MAJOR"/>
17+
<testCaseId value="MC-40309"/>
18+
<useCaseId value="MC-30152"/>
19+
<group value="bundle"/>
20+
</annotations>
21+
22+
<before>
23+
<actionGroup ref="AdminLoginActionGroup" stepKey="loginAsAdmin"/>
24+
</before>
25+
<after>
26+
<actionGroup ref="AdminLogoutActionGroup" stepKey="logoutFromAdmin"/>
27+
</after>
28+
29+
<actionGroup ref="AdminOpenNewProductFormPageActionGroup" stepKey="openNewBundleProductPage">
30+
<argument name="productType" value="bundle"/>
31+
</actionGroup>
32+
<actionGroup ref="AdminToggleSwitchDynamicPriceOnProductEditPageActionGroup" stepKey="disableDynamicPrice"/>
33+
<actionGroup ref="AdminFillProductPriceFieldAndPressEnterOnProductEditPageActionGroup" stepKey="fillProductPriceField">
34+
<argument name="price" value="test"/>
35+
</actionGroup>
36+
<actionGroup ref="AssertAdminValidationErrorAppearedForPriceFieldOnProductEditPageActionGroup" stepKey="assertVisibleError">
37+
<argument name="errorMessage" value="Please enter a number 0 or greater in this field."/>
38+
</actionGroup>
39+
<actionGroup ref="AdminToggleSwitchDynamicPriceOnProductEditPageActionGroup" stepKey="enableDynamicPrice"/>
40+
<actionGroup ref="AssertAdminNoValidationErrorForPriceFieldOnProductEditPageActionGroup" stepKey="assertNotVisibleError"/>
41+
</test>
42+
</tests>

app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
use Magento\Catalog\Model\Config;
1313
use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction;
1414
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
15+
use Magento\Catalog\Model\Indexer\Category\Product;
16+
use Magento\Framework\App\DeploymentConfig;
1517
use Magento\Framework\App\ObjectManager;
1618
use Magento\Framework\DB\Adapter\AdapterInterface;
1719
use Magento\Framework\DB\Query\Generator as QueryGenerator;
@@ -63,6 +65,18 @@ class Full extends AbstractAction
6365
*/
6466
private $processManager;
6567

68+
/**
69+
* @var DeploymentConfig|null
70+
*/
71+
private $deploymentConfig;
72+
73+
/**
74+
* Deployment config path
75+
*
76+
* @var string
77+
*/
78+
private const DEPLOYMENT_CONFIG_INDEXER_BATCHES = 'indexer/batch_size/';
79+
6680
/**
6781
* @param ResourceConnection $resource
6882
* @param StoreManagerInterface $storeManager
@@ -73,7 +87,8 @@ class Full extends AbstractAction
7387
* @param MetadataPool|null $metadataPool
7488
* @param int|null $batchRowsCount
7589
* @param ActiveTableSwitcher|null $activeTableSwitcher
76-
* @param ProcessManager $processManager
90+
* @param ProcessManager|null $processManager
91+
* @param DeploymentConfig|null $deploymentConfig
7792
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
7893
*/
7994
public function __construct(
@@ -86,7 +101,8 @@ public function __construct(
86101
MetadataPool $metadataPool = null,
87102
$batchRowsCount = null,
88103
ActiveTableSwitcher $activeTableSwitcher = null,
89-
ProcessManager $processManager = null
104+
ProcessManager $processManager = null,
105+
?DeploymentConfig $deploymentConfig = null
90106
) {
91107
parent::__construct(
92108
$resource,
@@ -107,6 +123,7 @@ public function __construct(
107123
$this->batchRowsCount = $batchRowsCount;
108124
$this->activeTableSwitcher = $activeTableSwitcher ?: $objectManager->get(ActiveTableSwitcher::class);
109125
$this->processManager = $processManager ?: $objectManager->get(ProcessManager::class);
126+
$this->deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->get(DeploymentConfig::class);
110127
}
111128

112129
/**
@@ -266,6 +283,11 @@ private function reindexCategoriesBySelect(Select $basicSelect, $whereCondition,
266283
$columns = array_keys(
267284
$this->connection->describeTable($this->tableMaintainer->getMainTmpTable((int)$store->getId()))
268285
);
286+
287+
$this->batchRowsCount = $this->deploymentConfig->get(
288+
self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . Product::INDEXER_ID
289+
) ?? $this->batchRowsCount;
290+
269291
$this->batchSizeManagement->ensureBatchSize($this->connection, $this->batchRowsCount);
270292

271293
$select = $this->connection->select();

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,15 @@ private function getUrlBuilder()
152152
*/
153153
private function isDateWithDateInternal(array $optionValue): bool
154154
{
155-
return array_key_exists('date_internal', $optionValue) && array_key_exists('date', $optionValue);
155+
$hasDate = !empty($optionValue['day'])
156+
&& !empty($optionValue['month'])
157+
&& !empty($optionValue['year']);
158+
159+
$hasTime = !empty($optionValue['hour'])
160+
&& isset($optionValue['minute']);
161+
162+
$hasDateInternal = !empty($optionValue['date_internal']);
163+
164+
return $hasDateInternal && ($hasDate || $hasTime || !empty($optionValue['date']));
156165
}
157166
}

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

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
use Magento\Framework\Api\ImageContentValidatorInterface;
1818
use Magento\Framework\Api\ImageProcessorInterface;
1919
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
20+
use Magento\Framework\Api\SearchCriteriaInterface;
2021
use Magento\Framework\DB\Adapter\ConnectionException;
2122
use Magento\Framework\DB\Adapter\DeadlockException;
2223
use Magento\Framework\DB\Adapter\LockWaitException;
@@ -28,6 +29,8 @@
2829
use Magento\Framework\Exception\StateException;
2930
use Magento\Framework\Exception\TemporaryState\CouldNotSaveException as TemporaryCouldNotSaveException;
3031
use Magento\Framework\Exception\ValidatorException;
32+
use Magento\Store\Model\Store;
33+
use Magento\Catalog\Api\Data\EavAttributeInterface;
3134

3235
/**
3336
* @inheritdoc
@@ -514,9 +517,12 @@ public function save(ProductInterface $product, $saveOptions = false)
514517
{
515518
$assignToCategories = false;
516519
$tierPrices = $product->getData('tier_price');
520+
$productDataToChange = $product->getData();
517521

518522
try {
519-
$existingProduct = $product->getId() ? $this->getById($product->getId()) : $this->get($product->getSku());
523+
$existingProduct = $product->getId() ?
524+
$this->getById($product->getId()) :
525+
$this->get($product->getSku());
520526

521527
$product->setData(
522528
$this->resourceModel->getLinkField(),
@@ -548,7 +554,6 @@ public function save(ProductInterface $product, $saveOptions = false)
548554
$productDataArray['store_id'] = (int) $this->storeManager->getStore()->getId();
549555
}
550556
$product = $this->initializeProductData($productDataArray, empty($existingProduct));
551-
552557
$this->processLinks($product, $productLinks);
553558
if (isset($productDataArray['media_gallery'])) {
554559
$this->processMediaGallery($product, $productDataArray['media_gallery']['images']);
@@ -569,6 +574,42 @@ public function save(ProductInterface $product, $saveOptions = false)
569574
$product->setData('tier_price', $tierPrices);
570575
}
571576

577+
try {
578+
$stores = $product->getStoreIds();
579+
$websites = $product->getWebsiteIds();
580+
} catch (NoSuchEntityException $exception) {
581+
$stores = null;
582+
$websites = null;
583+
}
584+
585+
if (!empty($existingProduct) && is_array($stores) && is_array($websites)) {
586+
$hasDataChanged = false;
587+
$productAttributes = $product->getAttributes();
588+
if ($productAttributes !== null
589+
&& $product->getStoreId() !== Store::DEFAULT_STORE_ID
590+
&& (count($stores) > 1 || count($websites) === 1)
591+
) {
592+
foreach ($productAttributes as $attribute) {
593+
$attributeCode = $attribute->getAttributeCode();
594+
$value = $product->getData($attributeCode);
595+
if ($existingProduct->getData($attributeCode) === $value
596+
&& $attribute->getScope() !== EavAttributeInterface::SCOPE_GLOBAL_TEXT
597+
&& !is_array($value)
598+
&& $attribute->getData('frontend_input') !== 'media_image'
599+
&& !$attribute->isStatic()
600+
&& !array_key_exists($attributeCode, $productDataToChange)
601+
&& $value !== null
602+
) {
603+
$product->setData($attributeCode);
604+
$hasDataChanged = true;
605+
}
606+
}
607+
if ($hasDataChanged) {
608+
$product->setData('_edit_mode', true);
609+
}
610+
}
611+
}
612+
572613
$this->saveProduct($product);
573614
if ($assignToCategories === true && $product->getCategoryIds()) {
574615
$this->linkManagement->assignProductToCategories(
@@ -619,7 +660,7 @@ public function deleteById($sku)
619660
/**
620661
* @inheritdoc
621662
*/
622-
public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCriteria)
663+
public function getList(SearchCriteriaInterface $searchCriteria)
623664
{
624665
/** @var \Magento\Catalog\Model\ResourceModel\Product\Collection $collection */
625666
$collection = $this->collectionFactory->create();
@@ -628,6 +669,7 @@ public function getList(\Magento\Framework\Api\SearchCriteriaInterface $searchCr
628669
$collection->addAttributeToSelect('*');
629670
$collection->joinAttribute('status', 'catalog_product/status', 'entity_id', null, 'inner');
630671
$collection->joinAttribute('visibility', 'catalog_product/visibility', 'entity_id', null, 'inner');
672+
$this->joinPositionField($collection, $searchCriteria);
631673

632674
$this->collectionProcessor->process($searchCriteria, $collection);
633675

@@ -856,4 +898,36 @@ private function saveProduct($product): void
856898
);
857899
}
858900
}
901+
902+
/**
903+
* Join category position field to make sorting by position possible.
904+
*
905+
* @param Collection $collection
906+
* @param SearchCriteriaInterface $searchCriteria
907+
* @return void
908+
*/
909+
private function joinPositionField(
910+
Collection $collection,
911+
SearchCriteriaInterface $searchCriteria
912+
): void {
913+
$categoryIds = [[]];
914+
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
915+
foreach ($filterGroup->getFilters() as $filter) {
916+
if ($filter->getField() === 'category_id') {
917+
$categoryIds[] = explode(',', $filter->getValue());
918+
}
919+
}
920+
}
921+
$categoryIds = array_unique(array_merge(...$categoryIds));
922+
if (count($categoryIds) === 1) {
923+
$collection->joinField(
924+
'position',
925+
'catalog_category_product',
926+
'position',
927+
'product_id=entity_id',
928+
['category_id' => current($categoryIds)],
929+
'left'
930+
);
931+
}
932+
}
859933
}

app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BatchSizeCalculator.php

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66

77
namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price;
88

9+
use Magento\Framework\App\DeploymentConfig;
10+
use Magento\Framework\App\ObjectManager;
11+
use Magento\Catalog\Model\Indexer\Product\Price\Processor;
12+
913
/**
1014
* Ensure that size of index MEMORY table is enough for configured rows count in batch.
1115
*/
@@ -26,17 +30,34 @@ class BatchSizeCalculator
2630
*/
2731
private $batchSizeAdjusters;
2832

33+
/**
34+
* @var DeploymentConfig|null
35+
*/
36+
private $deploymentConfig;
37+
38+
/**
39+
* Deployment config path
40+
*
41+
* @var string
42+
*/
43+
private const DEPLOYMENT_CONFIG_INDEXER_BATCHES = 'indexer/batch_size/';
44+
2945
/**
3046
* BatchSizeCalculator constructor.
3147
* @param array $batchRowsCount
3248
* @param array $estimators
3349
* @param array $batchSizeAdjusters
3450
*/
35-
public function __construct(array $batchRowsCount, array $estimators, array $batchSizeAdjusters)
36-
{
51+
public function __construct(
52+
array $batchRowsCount,
53+
array $estimators,
54+
array $batchSizeAdjusters,
55+
?DeploymentConfig $deploymentConfig = null
56+
) {
3757
$this->batchRowsCount = $batchRowsCount;
3858
$this->estimators = $estimators;
3959
$this->batchSizeAdjusters = $batchSizeAdjusters;
60+
$this->deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->get(DeploymentConfig::class);
4061
}
4162

4263
/**
@@ -50,9 +71,18 @@ public function __construct(array $batchRowsCount, array $estimators, array $bat
5071
*/
5172
public function estimateBatchSize(\Magento\Framework\DB\Adapter\AdapterInterface $connection, $indexerTypeId)
5273
{
53-
$batchRowsCount = isset($this->batchRowsCount[$indexerTypeId])
54-
? $this->batchRowsCount[$indexerTypeId]
55-
: $this->batchRowsCount['default'];
74+
$batchRowsCount = $this->deploymentConfig->get(
75+
self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . Processor::INDEXER_ID . '/' . $indexerTypeId,
76+
$batchRowsCount = $this->deploymentConfig->get(
77+
self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . Processor::INDEXER_ID . '/' . 'default'
78+
)
79+
);
80+
81+
if (is_null($batchRowsCount)) {
82+
$batchRowsCount = isset($this->batchRowsCount[$indexerTypeId])
83+
? $this->batchRowsCount[$indexerTypeId]
84+
: $this->batchRowsCount['default'];
85+
}
5686

5787
/** @var \Magento\Framework\Indexer\BatchSizeManagementInterface $calculator */
5888
$calculator = isset($this->estimators[$indexerTypeId])

0 commit comments

Comments
 (0)