Skip to content

Commit b11da0f

Browse files
merge magento-commerce/2.4-develop into magento-honey-badgers/MC-38927
2 parents e9540e7 + c959471 commit b11da0f

File tree

50 files changed

+2209
-139
lines changed

Some content is hidden

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

50 files changed

+2209
-139
lines changed

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

+6-1
Original file line numberDiff line numberDiff line change
@@ -916,7 +916,12 @@ public function addWebsiteFilter($websites = null)
916916
}
917917

918918
$this->_productLimitationFilters['website_ids'] = $websites;
919-
$this->_applyProductLimitations();
919+
920+
if ($this->getStoreId() == Store::DEFAULT_STORE_ID) {
921+
$this->_productLimitationJoinWebsite();
922+
} else {
923+
$this->_applyProductLimitations();
924+
}
920925

921926
return $this;
922927
}

app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php

+48-2
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
3939
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
4040
use Magento\Framework\Validator\UniversalFactory;
41+
use Magento\Store\Model\Store;
4142
use Magento\Store\Model\StoreManagerInterface;
4243
use PHPUnit\Framework\MockObject\MockObject;
4344
use PHPUnit\Framework\TestCase;
@@ -93,6 +94,11 @@ class CollectionTest extends TestCase
9394
*/
9495
private $storeManager;
9596

97+
/**
98+
* @var ProductLimitation|MockObject
99+
*/
100+
private $productLimitationMock;
101+
96102
/**
97103
* @var EntityFactory|MockObject
98104
*/
@@ -192,7 +198,7 @@ protected function setUp(): void
192198
$this->entityMock->expects($this->any())->method('getTable')->willReturnArgument(0);
193199
$this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock);
194200

195-
$productLimitationMock = $this->createMock(
201+
$this->productLimitationMock = $this->createMock(
196202
ProductLimitation::class
197203
);
198204
$productLimitationFactoryMock = $this->getMockBuilder(
@@ -201,7 +207,7 @@ protected function setUp(): void
201207
->setMethods(['create'])->getMock();
202208

203209
$productLimitationFactoryMock->method('create')
204-
->willReturn($productLimitationMock);
210+
->willReturn($this->productLimitationMock);
205211
$this->collection = $this->objectManager->getObject(
206212
Collection::class,
207213
[
@@ -432,4 +438,44 @@ public function testGetNewEmptyItem()
432438
$secondItem = $this->collection->getNewEmptyItem();
433439
$this->assertEquals($firstItem, $secondItem);
434440
}
441+
442+
/**
443+
* Test to add website filter in admin area
444+
*/
445+
public function testAddWebsiteFilterOnAdminStore(): void
446+
{
447+
$websiteIds = [2];
448+
$websiteTable = 'catalog_product_website';
449+
$joinCondition = 'join condition';
450+
$this->productLimitationMock->expects($this->atLeastOnce())
451+
->method('offsetSet')
452+
->with('website_ids', $websiteIds);
453+
$this->productLimitationMock->method('offsetExists')
454+
->with('website_ids')
455+
->willReturn(true);
456+
$this->productLimitationMock->method('offsetGet')
457+
->with('website_ids')
458+
->willReturn($websiteIds);
459+
$this->connectionMock->expects($this->once())
460+
->method('quoteInto')
461+
->with('product_website.website_id IN(?)', $websiteIds, 'int')
462+
->willReturn($joinCondition);
463+
$this->selectMock->method('getPart')->with(Select::FROM)->willReturn([]);
464+
/** @var AbstractEntity|MockObject $eavEntity */
465+
$eavEntity = $this->createMock(AbstractEntity::class);
466+
$eavEntity->method('getTable')
467+
->with('catalog_product_website')
468+
->willReturn($websiteTable);
469+
$this->selectMock->expects($this->once())
470+
->method('join')
471+
->with(
472+
['product_website' => $websiteTable],
473+
'product_website.product_id = e.entity_id AND ' . $joinCondition,
474+
[]
475+
);
476+
477+
$this->collection->setEntity($eavEntity);
478+
$this->collection->setStoreId(Store::DEFAULT_STORE_ID);
479+
$this->collection->addWebsiteFilter($websiteIds);
480+
}
435481
}

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ private function fetch() : array
132132
$this->searchCriteriaBuilder->create(),
133133
$this->attributeCodes,
134134
false,
135-
true
135+
false
136136
);
137137

138138
/** @var \Magento\Catalog\Model\Product $product */

app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public function getList(
8989

9090
$this->collectionPreProcessor->process($collection, $searchCriteria, $attributes, $context);
9191

92-
if (!$isChildSearch) {
92+
if ($isChildSearch) {
9393
$visibilityIds = $isSearch
9494
? $this->visibility->getVisibleInSearchIds()
9595
: $this->visibility->getVisibleInCatalogIds();

app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ private function deleteProductsLinks(
220220
if (!empty($linksToDelete) && Import::BEHAVIOR_APPEND === $importEntity->getBehavior()) {
221221
foreach ($linksToDelete as $linkTypeId => $productIds) {
222222
if (!empty($productIds)) {
223-
$whereLinkId = $importEntity->getConnection()->quoteInto('link_type_id', $linkTypeId);
223+
$whereLinkId = $importEntity->getConnection()->quoteInto('link_type_id = ?', $linkTypeId);
224224
$whereProductId = $importEntity->getConnection()->quoteInto(
225225
'product_id IN (?)',
226226
array_unique($productIds)

app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php

+40-11
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66

77
namespace Magento\CatalogRule\Model\Indexer;
88

9-
use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
109
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
10+
use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
1111
use Magento\CatalogRule\Model\Rule;
1212
use Magento\Framework\App\ResourceConnection;
1313
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
@@ -18,6 +18,8 @@
1818
*/
1919
class ReindexRuleProduct
2020
{
21+
private const ADMIN_WEBSITE_ID = 0;
22+
2123
/**
2224
* @var ResourceConnection
2325
*/
@@ -38,22 +40,30 @@ class ReindexRuleProduct
3840
*/
3941
private $localeDate;
4042

43+
/**
44+
* @var bool
45+
*/
46+
private $useWebsiteTimezone;
47+
4148
/**
4249
* @param ResourceConnection $resource
4350
* @param ActiveTableSwitcher $activeTableSwitcher
4451
* @param TableSwapper $tableSwapper
4552
* @param TimezoneInterface $localeDate
53+
* @param bool $useWebsiteTimezone
4654
*/
4755
public function __construct(
4856
ResourceConnection $resource,
4957
ActiveTableSwitcher $activeTableSwitcher,
5058
TableSwapper $tableSwapper,
51-
TimezoneInterface $localeDate
59+
TimezoneInterface $localeDate,
60+
bool $useWebsiteTimezone = true
5261
) {
5362
$this->resource = $resource;
5463
$this->activeTableSwitcher = $activeTableSwitcher;
5564
$this->tableSwapper = $tableSwapper;
5665
$this->localeDate = $localeDate;
66+
$this->useWebsiteTimezone = $useWebsiteTimezone;
5767
}
5868

5969
/**
@@ -95,18 +105,18 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
95105
$actionOperator = $rule->getSimpleAction();
96106
$actionAmount = $rule->getDiscountAmount();
97107
$actionStop = $rule->getStopRulesProcessing();
108+
$fromTimeInAdminTz = $this->parseDateByWebsiteTz((string)$rule->getFromDate(), self::ADMIN_WEBSITE_ID);
109+
$toTimeInAdminTz = $this->parseDateByWebsiteTz((string)$rule->getToDate(), self::ADMIN_WEBSITE_ID);
98110

99111
$rows = [];
100112
foreach ($websiteIds as $websiteId) {
101-
$scopeTz = new \DateTimeZone(
102-
$this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId)
103-
);
104-
$fromTime = $rule->getFromDate()
105-
? (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp()
106-
: 0;
107-
$toTime = $rule->getToDate()
108-
? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1
109-
: 0;
113+
$fromTime = $this->useWebsiteTimezone
114+
? $this->parseDateByWebsiteTz((string)$rule->getFromDate(), (int)$websiteId)
115+
: $fromTimeInAdminTz;
116+
$toTime = $this->useWebsiteTimezone
117+
? $this->parseDateByWebsiteTz((string)$rule->getToDate(), (int)$websiteId)
118+
+ ($rule->getToDate() ? IndexBuilder::SECONDS_IN_DAY - 1 : 0)
119+
: $toTimeInAdminTz;
110120

111121
foreach ($productIds as $productId => $validationByWebsite) {
112122
if (empty($validationByWebsite[$websiteId])) {
@@ -140,4 +150,23 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
140150

141151
return true;
142152
}
153+
154+
/**
155+
* Parse date value by the timezone of the website
156+
*
157+
* @param string $date
158+
* @param int $websiteId
159+
* @return int
160+
*/
161+
private function parseDateByWebsiteTz(string $date, int $websiteId): int
162+
{
163+
if (empty($date)) {
164+
return 0;
165+
}
166+
167+
$websiteTz = $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId);
168+
$dateTime = new \DateTime($date, new \DateTimeZone($websiteTz));
169+
170+
return $dateTime->getTimestamp();
171+
}
143172
}

app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php

+31-7
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
namespace Magento\CatalogRule\Model\Indexer;
88

99
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
10+
use Magento\Store\Model\Store;
1011
use Magento\Store\Model\StoreManagerInterface;
1112

1213
/**
@@ -39,25 +40,33 @@ class ReindexRuleProductPrice
3940
*/
4041
private $pricesPersistor;
4142

43+
/**
44+
* @var bool
45+
*/
46+
private $useWebsiteTimezone;
47+
4248
/**
4349
* @param StoreManagerInterface $storeManager
4450
* @param RuleProductsSelectBuilder $ruleProductsSelectBuilder
4551
* @param ProductPriceCalculator $productPriceCalculator
4652
* @param TimezoneInterface $localeDate
4753
* @param RuleProductPricesPersistor $pricesPersistor
54+
* @param bool $useWebsiteTimezone
4855
*/
4956
public function __construct(
5057
StoreManagerInterface $storeManager,
5158
RuleProductsSelectBuilder $ruleProductsSelectBuilder,
5259
ProductPriceCalculator $productPriceCalculator,
5360
TimezoneInterface $localeDate,
54-
RuleProductPricesPersistor $pricesPersistor
61+
RuleProductPricesPersistor $pricesPersistor,
62+
bool $useWebsiteTimezone = true
5563
) {
5664
$this->storeManager = $storeManager;
5765
$this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder;
5866
$this->productPriceCalculator = $productPriceCalculator;
5967
$this->localeDate = $localeDate;
6068
$this->pricesPersistor = $pricesPersistor;
69+
$this->useWebsiteTimezone = $useWebsiteTimezone;
6170
}
6271

6372
/**
@@ -82,11 +91,9 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
8291
$prevKey = null;
8392

8493
$storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId());
85-
$currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true);
86-
$previousDate = (clone $currentDate)->modify('-1 day');
87-
$previousDate->setTime(23, 59, 59);
88-
$nextDate = (clone $currentDate)->modify('+1 day');
89-
$nextDate->setTime(0, 0, 0);
94+
$dateInterval = $this->useWebsiteTimezone
95+
? $this->getDateInterval((int)$storeGroup->getDefaultStoreId())
96+
: $this->getDateInterval(Store::DEFAULT_STORE_ID);
9097

9198
while ($ruleData = $productsStmt->fetch()) {
9299
$ruleProductId = $ruleData['product_id'];
@@ -107,7 +114,7 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
107114
/**
108115
* Build prices for each day
109116
*/
110-
foreach ([$previousDate, $currentDate, $nextDate] as $date) {
117+
foreach ($dateInterval as $date) {
111118
$time = $date->getTimestamp();
112119
if (($ruleData['from_time'] == 0 ||
113120
$time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 ||
@@ -157,4 +164,21 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
157164

158165
return true;
159166
}
167+
168+
/**
169+
* Retrieve date sequence in store time zone
170+
*
171+
* @param int $storeId
172+
* @return \DateTime[]
173+
*/
174+
private function getDateInterval(int $storeId): array
175+
{
176+
$currentDate = $this->localeDate->scopeDate($storeId, null, true);
177+
$previousDate = (clone $currentDate)->modify('-1 day');
178+
$previousDate->setTime(23, 59, 59);
179+
$nextDate = (clone $currentDate)->modify('+1 day');
180+
$nextDate->setTime(0, 0, 0);
181+
182+
return [$previousDate, $currentDate, $nextDate];
183+
}
160184
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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="SaveAndContinueEditCatalogPriceRuleActionGroup">
12+
<annotations>
13+
<description>Clicks on Save and Continue Edit. Validates that the Success Message is present and correct on the Admin Catalog Price Rule creation/edit page.</description>
14+
</annotations>
15+
16+
<waitForElementVisible selector="{{AdminNewCatalogPriceRule.saveAndContinue}}" stepKey="waitForSaveAndContinueEditButton"/>
17+
<click selector="{{AdminNewCatalogPriceRule.saveAndContinue}}" stepKey="saveAndContinueEdit"/>
18+
<see selector="{{AdminCategoryMessagesSection.SuccessMessage}}" userInput="You saved the rule." stepKey="assertSuccess"/>
19+
</actionGroup>
20+
</actionGroups>

app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ protected function setUp(): void
6464
$this->ruleProductsSelectBuilderMock,
6565
$this->productPriceCalculatorMock,
6666
$this->localeDate,
67-
$this->pricesPersistorMock
67+
$this->pricesPersistorMock,
68+
true
6869
);
6970
}
7071

app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php

+10-5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
class ReindexRuleProductTest extends TestCase
2323
{
24+
private const ADMIN_WEBSITE_ID = 0;
25+
2426
/**
2527
* @var ReindexRuleProduct
2628
*/
@@ -57,7 +59,8 @@ protected function setUp(): void
5759
$this->resourceMock,
5860
$this->activeTableSwitcherMock,
5961
$this->tableSwapperMock,
60-
$this->localeDateMock
62+
$this->localeDateMock,
63+
true
6164
);
6265
}
6366

@@ -85,6 +88,7 @@ public function testExecuteIfRuleWithoutWebsiteIds()
8588
public function testExecute()
8689
{
8790
$websiteId = 3;
91+
$adminTimeZone = 'America/Chicago';
8892
$websiteTz = 'America/Los_Angeles';
8993
$productIds = [
9094
4 => [$websiteId => 1],
@@ -123,10 +127,11 @@ public function testExecute()
123127
$ruleMock->expects($this->once())->method('getDiscountAmount')->willReturn(43);
124128
$ruleMock->expects($this->once())->method('getStopRulesProcessing')->willReturn(true);
125129

126-
$this->localeDateMock->expects($this->once())
127-
->method('getConfigTimezone')
128-
->with(ScopeInterface::SCOPE_WEBSITE, $websiteId)
129-
->willReturn($websiteTz);
130+
$this->localeDateMock->method('getConfigTimezone')
131+
->willReturnMap([
132+
[ScopeInterface::SCOPE_WEBSITE, self::ADMIN_WEBSITE_ID, $adminTimeZone],
133+
[ScopeInterface::SCOPE_WEBSITE, $websiteId, $websiteTz],
134+
]);
130135

131136
$batchRows = [
132137
[

0 commit comments

Comments
 (0)