diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
index 3f908663c8e5e..a247e6b09760b 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -916,7 +916,12 @@ public function addWebsiteFilter($websites = null)
}
$this->_productLimitationFilters['website_ids'] = $websites;
- $this->_applyProductLimitations();
+
+ if ($this->getStoreId() == Store::DEFAULT_STORE_ID) {
+ $this->_productLimitationJoinWebsite();
+ } else {
+ $this->_applyProductLimitations();
+ }
return $this;
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
index 2bf504369b8a7..9a55e48cfb1b4 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
@@ -38,6 +38,7 @@
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Framework\Validator\UniversalFactory;
+use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -93,6 +94,11 @@ class CollectionTest extends TestCase
*/
private $storeManager;
+ /**
+ * @var ProductLimitation|MockObject
+ */
+ private $productLimitationMock;
+
/**
* @var EntityFactory|MockObject
*/
@@ -192,7 +198,7 @@ protected function setUp(): void
$this->entityMock->expects($this->any())->method('getTable')->willReturnArgument(0);
$this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock);
- $productLimitationMock = $this->createMock(
+ $this->productLimitationMock = $this->createMock(
ProductLimitation::class
);
$productLimitationFactoryMock = $this->getMockBuilder(
@@ -201,7 +207,7 @@ protected function setUp(): void
->setMethods(['create'])->getMock();
$productLimitationFactoryMock->method('create')
- ->willReturn($productLimitationMock);
+ ->willReturn($this->productLimitationMock);
$this->collection = $this->objectManager->getObject(
Collection::class,
[
@@ -432,4 +438,44 @@ public function testGetNewEmptyItem()
$secondItem = $this->collection->getNewEmptyItem();
$this->assertEquals($firstItem, $secondItem);
}
+
+ /**
+ * Test to add website filter in admin area
+ */
+ public function testAddWebsiteFilterOnAdminStore(): void
+ {
+ $websiteIds = [2];
+ $websiteTable = 'catalog_product_website';
+ $joinCondition = 'join condition';
+ $this->productLimitationMock->expects($this->atLeastOnce())
+ ->method('offsetSet')
+ ->with('website_ids', $websiteIds);
+ $this->productLimitationMock->method('offsetExists')
+ ->with('website_ids')
+ ->willReturn(true);
+ $this->productLimitationMock->method('offsetGet')
+ ->with('website_ids')
+ ->willReturn($websiteIds);
+ $this->connectionMock->expects($this->once())
+ ->method('quoteInto')
+ ->with('product_website.website_id IN(?)', $websiteIds, 'int')
+ ->willReturn($joinCondition);
+ $this->selectMock->method('getPart')->with(Select::FROM)->willReturn([]);
+ /** @var AbstractEntity|MockObject $eavEntity */
+ $eavEntity = $this->createMock(AbstractEntity::class);
+ $eavEntity->method('getTable')
+ ->with('catalog_product_website')
+ ->willReturn($websiteTable);
+ $this->selectMock->expects($this->once())
+ ->method('join')
+ ->with(
+ ['product_website' => $websiteTable],
+ 'product_website.product_id = e.entity_id AND ' . $joinCondition,
+ []
+ );
+
+ $this->collection->setEntity($eavEntity);
+ $this->collection->setStoreId(Store::DEFAULT_STORE_ID);
+ $this->collection->addWebsiteFilter($websiteIds);
+ }
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
index 22bbc991a78e2..8218c22b8056d 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Deferred/Product.php
@@ -132,7 +132,7 @@ private function fetch() : array
$this->searchCriteriaBuilder->create(),
$this->attributeCodes,
false,
- true
+ false
);
/** @var \Magento\Catalog\Model\Product $product */
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
index 3e955ae303453..30be41072242b 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Product.php
@@ -89,7 +89,7 @@ public function getList(
$this->collectionPreProcessor->process($collection, $searchCriteria, $attributes, $context);
- if (!$isChildSearch) {
+ if ($isChildSearch) {
$visibilityIds = $isSearch
? $this->visibility->getVisibleInSearchIds()
: $this->visibility->getVisibleInCatalogIds();
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
index 22a83671f630a..84cdc4608148c 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/LinkProcessor.php
@@ -220,7 +220,7 @@ private function deleteProductsLinks(
if (!empty($linksToDelete) && Import::BEHAVIOR_APPEND === $importEntity->getBehavior()) {
foreach ($linksToDelete as $linkTypeId => $productIds) {
if (!empty($productIds)) {
- $whereLinkId = $importEntity->getConnection()->quoteInto('link_type_id', $linkTypeId);
+ $whereLinkId = $importEntity->getConnection()->quoteInto('link_type_id = ?', $linkTypeId);
$whereProductId = $importEntity->getConnection()->quoteInto(
'product_id IN (?)',
array_unique($productIds)
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
index 944710773123f..3e2301f34c4f8 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
@@ -6,8 +6,8 @@
namespace Magento\CatalogRule\Model\Indexer;
-use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
use Magento\CatalogRule\Model\Rule;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
@@ -18,6 +18,8 @@
*/
class ReindexRuleProduct
{
+ private const ADMIN_WEBSITE_ID = 0;
+
/**
* @var ResourceConnection
*/
@@ -38,22 +40,30 @@ class ReindexRuleProduct
*/
private $localeDate;
+ /**
+ * @var bool
+ */
+ private $useWebsiteTimezone;
+
/**
* @param ResourceConnection $resource
* @param ActiveTableSwitcher $activeTableSwitcher
* @param TableSwapper $tableSwapper
* @param TimezoneInterface $localeDate
+ * @param bool $useWebsiteTimezone
*/
public function __construct(
ResourceConnection $resource,
ActiveTableSwitcher $activeTableSwitcher,
TableSwapper $tableSwapper,
- TimezoneInterface $localeDate
+ TimezoneInterface $localeDate,
+ bool $useWebsiteTimezone = true
) {
$this->resource = $resource;
$this->activeTableSwitcher = $activeTableSwitcher;
$this->tableSwapper = $tableSwapper;
$this->localeDate = $localeDate;
+ $this->useWebsiteTimezone = $useWebsiteTimezone;
}
/**
@@ -95,18 +105,18 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
$actionOperator = $rule->getSimpleAction();
$actionAmount = $rule->getDiscountAmount();
$actionStop = $rule->getStopRulesProcessing();
+ $fromTimeInAdminTz = $this->parseDateByWebsiteTz((string)$rule->getFromDate(), self::ADMIN_WEBSITE_ID);
+ $toTimeInAdminTz = $this->parseDateByWebsiteTz((string)$rule->getToDate(), self::ADMIN_WEBSITE_ID);
$rows = [];
foreach ($websiteIds as $websiteId) {
- $scopeTz = new \DateTimeZone(
- $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId)
- );
- $fromTime = $rule->getFromDate()
- ? (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp()
- : 0;
- $toTime = $rule->getToDate()
- ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1
- : 0;
+ $fromTime = $this->useWebsiteTimezone
+ ? $this->parseDateByWebsiteTz((string)$rule->getFromDate(), (int)$websiteId)
+ : $fromTimeInAdminTz;
+ $toTime = $this->useWebsiteTimezone
+ ? $this->parseDateByWebsiteTz((string)$rule->getToDate(), (int)$websiteId)
+ + ($rule->getToDate() ? IndexBuilder::SECONDS_IN_DAY - 1 : 0)
+ : $toTimeInAdminTz;
foreach ($productIds as $productId => $validationByWebsite) {
if (empty($validationByWebsite[$websiteId])) {
@@ -140,4 +150,23 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
return true;
}
+
+ /**
+ * Parse date value by the timezone of the website
+ *
+ * @param string $date
+ * @param int $websiteId
+ * @return int
+ */
+ private function parseDateByWebsiteTz(string $date, int $websiteId): int
+ {
+ if (empty($date)) {
+ return 0;
+ }
+
+ $websiteTz = $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId);
+ $dateTime = new \DateTime($date, new \DateTimeZone($websiteTz));
+
+ return $dateTime->getTimestamp();
+ }
}
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
index 51869f1accbb3..ccc5352567ff0 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProductPrice.php
@@ -7,6 +7,7 @@
namespace Magento\CatalogRule\Model\Indexer;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
/**
@@ -39,25 +40,33 @@ class ReindexRuleProductPrice
*/
private $pricesPersistor;
+ /**
+ * @var bool
+ */
+ private $useWebsiteTimezone;
+
/**
* @param StoreManagerInterface $storeManager
* @param RuleProductsSelectBuilder $ruleProductsSelectBuilder
* @param ProductPriceCalculator $productPriceCalculator
* @param TimezoneInterface $localeDate
* @param RuleProductPricesPersistor $pricesPersistor
+ * @param bool $useWebsiteTimezone
*/
public function __construct(
StoreManagerInterface $storeManager,
RuleProductsSelectBuilder $ruleProductsSelectBuilder,
ProductPriceCalculator $productPriceCalculator,
TimezoneInterface $localeDate,
- RuleProductPricesPersistor $pricesPersistor
+ RuleProductPricesPersistor $pricesPersistor,
+ bool $useWebsiteTimezone = true
) {
$this->storeManager = $storeManager;
$this->ruleProductsSelectBuilder = $ruleProductsSelectBuilder;
$this->productPriceCalculator = $productPriceCalculator;
$this->localeDate = $localeDate;
$this->pricesPersistor = $pricesPersistor;
+ $this->useWebsiteTimezone = $useWebsiteTimezone;
}
/**
@@ -82,11 +91,9 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
$prevKey = null;
$storeGroup = $this->storeManager->getGroup($website->getDefaultGroupId());
- $currentDate = $this->localeDate->scopeDate($storeGroup->getDefaultStoreId(), null, true);
- $previousDate = (clone $currentDate)->modify('-1 day');
- $previousDate->setTime(23, 59, 59);
- $nextDate = (clone $currentDate)->modify('+1 day');
- $nextDate->setTime(0, 0, 0);
+ $dateInterval = $this->useWebsiteTimezone
+ ? $this->getDateInterval((int)$storeGroup->getDefaultStoreId())
+ : $this->getDateInterval(Store::DEFAULT_STORE_ID);
while ($ruleData = $productsStmt->fetch()) {
$ruleProductId = $ruleData['product_id'];
@@ -107,7 +114,7 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
/**
* Build prices for each day
*/
- foreach ([$previousDate, $currentDate, $nextDate] as $date) {
+ foreach ($dateInterval as $date) {
$time = $date->getTimestamp();
if (($ruleData['from_time'] == 0 ||
$time >= $ruleData['from_time']) && ($ruleData['to_time'] == 0 ||
@@ -157,4 +164,21 @@ public function execute(int $batchCount, ?int $productId = null, bool $useAdditi
return true;
}
+
+ /**
+ * Retrieve date sequence in store time zone
+ *
+ * @param int $storeId
+ * @return \DateTime[]
+ */
+ private function getDateInterval(int $storeId): array
+ {
+ $currentDate = $this->localeDate->scopeDate($storeId, null, true);
+ $previousDate = (clone $currentDate)->modify('-1 day');
+ $previousDate->setTime(23, 59, 59);
+ $nextDate = (clone $currentDate)->modify('+1 day');
+ $nextDate->setTime(0, 0, 0);
+
+ return [$previousDate, $currentDate, $nextDate];
+ }
}
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndContinueEditCatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndContinueEditCatalogPriceRuleActionGroup.xml
new file mode 100644
index 0000000000000..cf8499ca1b466
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/SaveAndContinueEditCatalogPriceRuleActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Clicks on Save and Continue Edit. Validates that the Success Message is present and correct on the Admin Catalog Price Rule creation/edit page.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
index 7f22634f9343d..230a6e3860950 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductPriceTest.php
@@ -64,7 +64,8 @@ protected function setUp(): void
$this->ruleProductsSelectBuilderMock,
$this->productPriceCalculatorMock,
$this->localeDate,
- $this->pricesPersistorMock
+ $this->pricesPersistorMock,
+ true
);
}
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
index ddb6a85ed614a..50f4eb0805ed2 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
@@ -21,6 +21,8 @@
class ReindexRuleProductTest extends TestCase
{
+ private const ADMIN_WEBSITE_ID = 0;
+
/**
* @var ReindexRuleProduct
*/
@@ -57,7 +59,8 @@ protected function setUp(): void
$this->resourceMock,
$this->activeTableSwitcherMock,
$this->tableSwapperMock,
- $this->localeDateMock
+ $this->localeDateMock,
+ true
);
}
@@ -85,6 +88,7 @@ public function testExecuteIfRuleWithoutWebsiteIds()
public function testExecute()
{
$websiteId = 3;
+ $adminTimeZone = 'America/Chicago';
$websiteTz = 'America/Los_Angeles';
$productIds = [
4 => [$websiteId => 1],
@@ -123,10 +127,11 @@ public function testExecute()
$ruleMock->expects($this->once())->method('getDiscountAmount')->willReturn(43);
$ruleMock->expects($this->once())->method('getStopRulesProcessing')->willReturn(true);
- $this->localeDateMock->expects($this->once())
- ->method('getConfigTimezone')
- ->with(ScopeInterface::SCOPE_WEBSITE, $websiteId)
- ->willReturn($websiteTz);
+ $this->localeDateMock->method('getConfigTimezone')
+ ->willReturnMap([
+ [ScopeInterface::SCOPE_WEBSITE, self::ADMIN_WEBSITE_ID, $adminTimeZone],
+ [ScopeInterface::SCOPE_WEBSITE, $websiteId, $websiteTz],
+ ]);
$batchRows = [
[
diff --git a/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNavigationArrowsActionGroup.xml b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNavigationArrowsActionGroup.xml
new file mode 100644
index 0000000000000..d0673dfc1adb7
--- /dev/null
+++ b/app/code/Magento/ProductVideo/Test/Mftf/ActionGroup/AssertProductVideoNavigationArrowsActionGroup.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+ Validates the navigation arrows on the Storefront Product page.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index e94d426ba7638..44c65c197f7db 100644
--- a/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/ProductVideo/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -14,5 +14,7 @@
+
+
diff --git a/app/code/Magento/ProductVideo/Test/Mftf/Test/VimeoVideoControlButtonsOnProductPageTest.xml b/app/code/Magento/ProductVideo/Test/Mftf/Test/VimeoVideoControlButtonsOnProductPageTest.xml
new file mode 100644
index 0000000000000..a60a5526498ce
--- /dev/null
+++ b/app/code/Magento/ProductVideo/Test/Mftf/Test/VimeoVideoControlButtonsOnProductPageTest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
index acaf2afeb6c26..bfd685543d7f7 100644
--- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
+++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
@@ -714,6 +714,7 @@ define([
}
$('.' + this.FTAR).addClass(this.isFullscreen ? 'fotorama__arr--shown' : 'fotorama__arr--hidden');
+ $('.' + this.FTVC).addClass('fotorama-show-control');
}
},
diff --git a/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php b/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
index 82c612c1a781d..094fac313d398 100644
--- a/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
+++ b/app/code/Magento/Sales/Model/ResourceModel/Order/Grid/Collection.php
@@ -5,16 +5,25 @@
*/
namespace Magento\Sales\Model\ResourceModel\Order\Grid;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy;
use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory;
use Magento\Framework\Event\ManagerInterface as EventManager;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult;
+use Magento\Sales\Model\ResourceModel\Order;
use Psr\Log\LoggerInterface as Logger;
/**
* Order grid collection
*/
-class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult
+class Collection extends SearchResult
{
+ /**
+ * @var TimezoneInterface
+ */
+ private $timeZone;
+
/**
* Initialize dependencies.
*
@@ -24,6 +33,7 @@ class Collection extends \Magento\Framework\View\Element\UiComponent\DataProvide
* @param EventManager $eventManager
* @param string $mainTable
* @param string $resourceModel
+ * @param TimezoneInterface|null $timeZone
*/
public function __construct(
EntityFactory $entityFactory,
@@ -31,9 +41,12 @@ public function __construct(
FetchStrategy $fetchStrategy,
EventManager $eventManager,
$mainTable = 'sales_order_grid',
- $resourceModel = \Magento\Sales\Model\ResourceModel\Order::class
+ $resourceModel = Order::class,
+ TimezoneInterface $timeZone = null
) {
parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel);
+ $this->timeZone = $timeZone ?: ObjectManager::getInstance()
+ ->get(TimezoneInterface::class);
}
/**
@@ -50,4 +63,20 @@ protected function _initSelect()
return $this;
}
+
+ /**
+ * @inheritDoc
+ */
+ public function addFieldToFilter($field, $condition = null)
+ {
+ if ($field === 'created_at') {
+ if (is_array($condition)) {
+ foreach ($condition as $key => $value) {
+ $condition[$key] = $this->timeZone->convertConfigTimeToUtc($value);
+ }
+ }
+ }
+
+ return parent::addFieldToFilter($field, $condition);
+ }
}
diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontAssertDropDownSearchSuggestionActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontAssertDropDownSearchSuggestionActionGroup.xml
new file mode 100644
index 0000000000000..226e30486251c
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/StoreFrontAssertDropDownSearchSuggestionActionGroup.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+ Fills the Storefront Quick Search field. Validates that the Search Suggestion is present
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByControlButtonsTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByControlButtonsTest.xml
new file mode 100644
index 0000000000000..b9c491705316c
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByControlButtonsTest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Search/view/frontend/web/js/form-mini.js b/app/code/Magento/Search/view/frontend/web/js/form-mini.js
index b8034fead76d0..df651feb89d45 100644
--- a/app/code/Magento/Search/view/frontend/web/js/form-mini.js
+++ b/app/code/Magento/Search/view/frontend/web/js/form-mini.js
@@ -253,6 +253,8 @@ define([
}
this.element.val(this.responseList.selected.find('.qs-option-name').text());
this.element.attr('aria-activedescendant', this.responseList.selected.attr('id'));
+ this._updateAriaHasPopup(true);
+ this.autoComplete.show();
}
break;
@@ -269,6 +271,8 @@ define([
}
this.element.val(this.responseList.selected.find('.qs-option-name').text());
this.element.attr('aria-activedescendant', this.responseList.selected.attr('id'));
+ this._updateAriaHasPopup(true);
+ this.autoComplete.show();
}
break;
default:
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/RelatedProduct/GetRelatedProductsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/RelatedProduct/GetRelatedProductsTest.php
index f2cf90c95de18..8575f1d33c435 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/RelatedProduct/GetRelatedProductsTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/RelatedProduct/GetRelatedProductsTest.php
@@ -190,4 +190,40 @@ private function assertRelatedProducts(array $relatedProducts): void
self::assertEquals($product['url_key'], $productExpectedData['url_key']);
}
}
+
+ /**
+ * Test query with disabled linked product in the default store
+ *
+ * @magentoApiDataFixture Magento/Catalog/_files/products_related_disabled_in_store.php
+ *
+ * @return void
+ */
+ public function testQueryDisableRelatedProductInStore(): void
+ {
+ $productSku = 'simple_with_related';
+ $query = <<graphQlQuery($query, [], '', ['Store' => 'default']);
+
+ self::assertArrayHasKey('products', $response);
+ self::assertArrayHasKey('items', $response['products']);
+ self::assertCount(1, $response['products']['items']);
+ self::assertArrayHasKey(0, $response['products']['items']);
+ self::assertArrayHasKey('related_products', $response['products']['items'][0]);
+ $relatedProducts = $response['products']['items'][0]['related_products'];
+ self::assertCount(0, $relatedProducts);
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store.php
new file mode 100644
index 0000000000000..b8e6d4702b44f
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store.php
@@ -0,0 +1,70 @@
+get(WebsiteRepositoryInterface::class);
+$baseWebsite = $websiteRepository->get('base');
+/** @var StoreManagerInterface $storeManager */
+$storeManager = $objectManager->get(StoreManagerInterface::class);
+$defaultAttributeSet = $objectManager->get(Config::class)->getEntityType(Product::ENTITY)->getDefaultAttributeSetId();
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+/** @var ProductInterfaceFactory $productInterfaceFactory */
+$productInterfaceFactory = $objectManager->get(ProductInterfaceFactory::class);
+
+/** @var Product $product */
+$product = $productInterfaceFactory->create();
+$product->setTypeId(Type::TYPE_SIMPLE)
+ ->setAttributeSetId($defaultAttributeSet)
+ ->setStoreId($storeManager->getDefaultStoreView()->getId())
+ ->setWebsiteIds([$baseWebsite->getId()])
+ ->setName('Simple Product')
+ ->setSku('simple')
+ ->setPrice(10)
+ ->setWeight(18)
+ ->setStockData(['use_config_manage_stock' => 0])
+ ->setVisibility(Visibility::VISIBILITY_BOTH)
+ ->setStatus(Status::STATUS_ENABLED);
+
+$simple = $productRepository->save($product);
+$simple->setStoreId($storeManager->getDefaultStoreView()->getId())
+->setStatus(Status::STATUS_DISABLED);
+$productRepository->save($simple);
+/** @var ProductLinkInterface $productLink */
+$productLink = $objectManager->create(ProductLinkInterface::class);
+$productLink->setSku('simple_with_related');
+$productLink->setLinkedProductSku('simple');
+$productLink->setPosition(1);
+$productLink->setLinkType('related');
+
+/** @var Product $product */
+$product = $productInterfaceFactory->create();
+$product->setTypeId(Type::TYPE_SIMPLE)
+ ->setAttributeSetId($defaultAttributeSet)
+ ->setStoreId($storeManager->getDefaultStoreView()->getId())
+ ->setWebsiteIds([$baseWebsite->getId()])
+ ->setName('Simple Product With Related Product')
+ ->setSku('simple_with_related')
+ ->setPrice(10)
+ ->setWeight(18)
+ ->setProductLinks([$productLink])
+ ->setStockData(['use_config_manage_stock' => 0])
+ ->setVisibility(Visibility::VISIBILITY_BOTH)
+ ->setStatus(Status::STATUS_ENABLED);
+
+$productRepository->save($product);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store_rollback.php
new file mode 100644
index 0000000000000..6ca27b71b9057
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/products_related_disabled_in_store_rollback.php
@@ -0,0 +1,33 @@
+get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->create(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+
+try {
+ $firstProduct = $productRepository->get('simple', false, null, true);
+ $productRepository->delete($firstProduct);
+} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
+ //Product already removed
+}
+
+try {
+ $secondProduct = $productRepository->get('simple_with_related', false, null, true);
+ $productRepository->delete($secondProduct);
+} catch (\Magento\Framework\Exception\NoSuchEntityException $exception) {
+ //Product already removed
+}
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
index 53e32483ee3d6..ceb07e3445c0e 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
@@ -1721,15 +1721,23 @@ public function testValidateUrlKeysMultipleStores()
}
/**
+ * Test import product with product links and empty value
+ *
+ * @param string $pathToFile
+ * @param bool $expectedResultCrossell
+ * @param bool $expectedResultUpsell
+ *
* @magentoDataFixture Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
* @magentoAppArea adminhtml
* @magentoDbIsolation enabled
* @magentoAppIsolation enabled
+ * @dataProvider getEmptyLinkedData
*/
- public function testProductLinksWithEmptyValue()
- {
- // import data from CSV file
- $pathToFile = __DIR__ . '/_files/products_to_import_with_product_links_with_empty_value.csv';
+ public function testProductLinksWithEmptyValue(
+ string $pathToFile,
+ bool $expectedResultCrossell,
+ bool $expectedResultUpsell
+ ): void {
$filesystem = BootstrapHelper::getObjectManager()->create(Filesystem::class);
$directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
@@ -1759,8 +1767,29 @@ public function testProductLinksWithEmptyValue()
$product = BootstrapHelper::getObjectManager()->create(Product::class);
$product->load($productId);
- $this->assertEmpty($product->getCrossSellProducts());
- $this->assertEmpty($product->getUpSellProducts());
+ $this->assertEquals(empty($product->getCrossSellProducts()), $expectedResultCrossell);
+ $this->assertEquals(empty($product->getUpSellProducts()), $expectedResultUpsell);
+ }
+
+ /**
+ * Get data for empty linked product
+ *
+ * @return array[]
+ */
+ public function getEmptyLinkedData(): array
+ {
+ return [
+ [
+ __DIR__ . '/_files/products_to_import_with_product_links_with_empty_value.csv',
+ true,
+ true,
+ ],
+ [
+ __DIR__ . '/_files/products_to_import_with_product_links_with_empty_data.csv',
+ false,
+ true,
+ ],
+ ];
}
/**
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links_with_empty_data.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links_with_empty_data.csv
new file mode 100644
index 0000000000000..d8812defa0828
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_product_links_with_empty_data.csv
@@ -0,0 +1,2 @@
+sku,crosssell_skus,crosssell_position,upsell_skus,upsell_position
+simple,,,__EMPTY__VALUE__,__EMPTY__VALUE__
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
index 5dde578a1341f..8ee35d747ea1a 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_with_product_links_data.php
@@ -18,7 +18,26 @@
$objectManager = Bootstrap::getObjectManager();
/** @var ProductRepositoryInterface $productRepository */
$productRepository = $objectManager->create(ProductRepositoryInterface::class);
-$product = $productRepository->get('simple_ms_1');
+/** @var \Magento\Catalog\Api\Data\ProductLinkInterface $productLink */
+$productCrosssellLink = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->create(\Magento\Catalog\Api\Data\ProductLinkInterface::class);
+$productCrosssellLink->setSku('simple');
+$productCrosssellLink->setLinkedProductSku('simple_ms_1');
+$productCrosssellLink->setPosition(2);
+$productCrosssellLink->setLinkType('crosssell');
+$productUpsellLink = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->create(\Magento\Catalog\Api\Data\ProductLinkInterface::class);
+$productUpsellLink->setSku('simple');
+$productUpsellLink->setLinkedProductSku('simple_ms_1');
+$productUpsellLink->setPosition(1);
+$productUpsellLink->setLinkType('upsell');
+$productRelatedLink = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->create(\Magento\Catalog\Api\Data\ProductLinkInterface::class);
+$productRelatedLink->setSku('simple');
+$productRelatedLink->setLinkedProductSku('simple_ms_1');
+$productRelatedLink->setPosition(3);
+$productRelatedLink->setLinkType('related');
+
$productModel = $objectManager->create(
\Magento\Catalog\Model\Product::class
);
@@ -51,10 +70,6 @@
true
)->setCategoryIds(
[333]
-)->setUpSellLinkData(
- [$product->getId() => ['position' => 1]]
-)->setCrossSellLinkData(
- [$product->getId() => ['position' => 2]]
-)->setRelatedLinkData(
- [$product->getId() => ['position' => 3]]
+)->setProductLinks(
+ [$productCrosssellLink, $productUpsellLink, $productRelatedLink]
)->save();
diff --git a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php
index d2d71ab880026..abf845aca0d88 100644
--- a/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php
+++ b/dev/tests/integration/testsuite/Magento/Sales/Model/ResourceModel/Order/Grid/CollectionTest.php
@@ -7,10 +7,26 @@
namespace Magento\Sales\Model\ResourceModel\Order\Grid;
+use Magento\Framework\ObjectManagerInterface;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\TestFramework\Helper\Bootstrap;
+use PHPUnit\Framework\TestCase;
-class CollectionTest extends \PHPUnit\Framework\TestCase
+class CollectionTest extends TestCase
{
+ /**
+ * @var ObjectManagerInterface
+ */
+ private $objectManager;
+
+ /**
+ * @inheritDoc
+ */
+ protected function setUp(): void
+ {
+ $this->objectManager = Bootstrap::getObjectManager();
+ }
+
/**
* Tests collection properties.
*
@@ -19,10 +35,8 @@ class CollectionTest extends \PHPUnit\Framework\TestCase
*/
public function testCollectionCreate(): void
{
- $objectManager = Bootstrap::getObjectManager();
-
/** @var Collection $gridCollection */
- $gridCollection = $objectManager->get(Collection::class);
+ $gridCollection = $this->objectManager->get(Collection::class);
$tableDescription = $gridCollection->getConnection()
->describeTable($gridCollection->getMainTable());
@@ -42,4 +56,25 @@ public function testCollectionCreate(): void
self::assertStringContainsString('main_table.', $mappedName);
}
}
+
+ /**
+ * Verifies that filter condition date is being converted to config timezone before select sql query
+ *
+ * @return void
+ */
+ public function testAddFieldToFilter(): void
+ {
+ $filterDate = "2021-01-19 00:00:00";
+ /** @var TimezoneInterface $timeZone */
+ $timeZone = $this->objectManager->get(TimezoneInterface::class);
+ /** @var Collection $gridCollection */
+ $gridCollection = $this->objectManager->get(Collection::class);
+ $convertedDate = $timeZone->convertConfigTimeToUtc($filterDate);
+
+ $collection = $gridCollection->addFieldToFilter('created_at', ['qteq' => $filterDate]);
+ $expectedSelect = "SELECT `main_table`.* FROM `sales_order_grid` AS `main_table` " .
+ "WHERE (((`main_table`.`created_at` = '{$convertedDate}')))";
+
+ $this->assertEquals($expectedSelect, $collection->getSelectSql(true));
+ }
}