diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php
new file mode 100644
index 0000000000000..774edcfeb6b64
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php
@@ -0,0 +1,156 @@
+objectManager = new ObjectManager($this);
+
+ $this->attribute = $this->getMockBuilder(ProductAttributeInterface::class)
+ ->setMethods(['usesSource'])
+ ->getMockForAbstractClass();
+ $this->context = $this->createMock(ContextInterface::class);
+ $this->uiComponentFactory = $this->createMock(UiComponentFactory::class);
+ $this->column = $this->getMockForAbstractClass(ColumnInterface::class);
+ $this->uiComponentFactory->method('create')
+ ->willReturn($this->column);
+
+ $this->columnFactory = $this->objectManager->getObject(ColumnFactory::class, [
+ 'componentFactory' => $this->uiComponentFactory
+ ]);
+ }
+
+ /**
+ * Tests the create method will return correct object.
+ *
+ * @return void
+ */
+ public function testCreatedObject(): void
+ {
+ $this->context->method('getRequestParam')
+ ->with(FilterModifier::FILTER_MODIFIER, [])
+ ->willReturn([]);
+
+ $object = $this->columnFactory->create($this->attribute, $this->context);
+ $this->assertEquals(
+ $this->column,
+ $object,
+ 'Object must be the same which the ui component factory creates.'
+ );
+ }
+
+ /**
+ * Tests create method with not filterable in grid attribute.
+ *
+ * @param array $filterModifiers
+ * @param null|string $filter
+ *
+ * @return void
+ * @dataProvider filterModifiersProvider
+ */
+ public function testCreateWithNotFilterableInGridAttribute(array $filterModifiers, ?string $filter): void
+ {
+ $componentFactoryArgument = [
+ 'data' => [
+ 'config' => [
+ 'label' => __(null),
+ 'dataType' => 'text',
+ 'add_field' => true,
+ 'visible' => null,
+ 'filter' => $filter,
+ 'component' => 'Magento_Ui/js/grid/columns/column',
+ ],
+ ],
+ 'context' => $this->context,
+ ];
+
+ $this->context->method('getRequestParam')
+ ->with(FilterModifier::FILTER_MODIFIER, [])
+ ->willReturn($filterModifiers);
+ $this->attribute->method('getIsFilterableInGrid')
+ ->willReturn(false);
+ $this->attribute->method('getAttributeCode')
+ ->willReturn('color');
+
+ $this->uiComponentFactory->expects($this->once())
+ ->method('create')
+ ->with($this->anything(), $this->anything(), $componentFactoryArgument);
+
+ $this->columnFactory->create($this->attribute, $this->context);
+ }
+
+ /**
+ * Filter modifiers data provider.
+ *
+ * @return array
+ */
+ public function filterModifiersProvider(): array
+ {
+ return [
+ 'without' => [
+ 'filter_modifiers' => [],
+ 'filter' => null,
+ ],
+ 'with' => [
+ 'filter_modifiers' => [
+ 'color' => [
+ 'condition_type' => 'notnull',
+ ],
+ ],
+ 'filter' => 'text',
+ ],
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
index 1903bcd144831..ea6b1fd47a0a5 100644
--- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
+++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Ui\Component;
+use Magento\Ui\Component\Filters\FilterModifier;
+
/**
* Column Factory
*
@@ -60,13 +62,15 @@ public function __construct(\Magento\Framework\View\Element\UiComponentFactory $
*/
public function create($attribute, $context, array $config = [])
{
+ $filterModifiers = $context->getRequestParam(FilterModifier::FILTER_MODIFIER, []);
+
$columnName = $attribute->getAttributeCode();
$config = array_merge([
'label' => __($attribute->getDefaultFrontendLabel()),
'dataType' => $this->getDataType($attribute),
'add_field' => true,
'visible' => $attribute->getIsVisibleInGrid(),
- 'filter' => ($attribute->getIsFilterableInGrid())
+ 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers))
? $this->getFilterType($attribute->getFrontendInput())
: null,
], $config);
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
index 2a4d2ff52d479..00132c6ad89e8 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
@@ -389,6 +389,9 @@ private function addAdvancedPriceLink()
'componentType' => Container::NAME,
'component' => 'Magento_Ui/js/form/components/button',
'template' => 'ui/form/components/button/container',
+ 'imports' => [
+ 'childError' => $this->scopeName . '.advanced_pricing_modal.advanced-pricing:error',
+ ],
'actions' => [
[
'targetName' => $this->scopeName . '.advanced_pricing_modal',
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml
index 65fabfe25e817..2c351a12af72e 100644
--- a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml
@@ -11,13 +11,12 @@
-
-
+
@@ -37,7 +36,7 @@
-
+
@@ -51,10 +50,18 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php
index bee334596e990..f2bf3116af9e4 100644
--- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php
+++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php
@@ -7,14 +7,15 @@
*/
namespace Magento\ConfigurableProduct\Model\Product\Type\Configurable;
+use Magento\Catalog\Model\Product;
+
+/**
+ * Class Price for configurable product
+ */
class Price extends \Magento\Catalog\Model\Product\Type\Price
{
/**
- * Get product final price
- *
- * @param float $qty
- * @param \Magento\Catalog\Model\Product $product
- * @return float
+ * @inheritdoc
*/
public function getFinalPrice($qty, $product)
{
@@ -22,7 +23,10 @@ public function getFinalPrice($qty, $product)
return $product->getCalculatedFinalPrice();
}
if ($product->getCustomOption('simple_product') && $product->getCustomOption('simple_product')->getProduct()) {
- $finalPrice = parent::getFinalPrice($qty, $product->getCustomOption('simple_product')->getProduct());
+ /** @var Product $simpleProduct */
+ $simpleProduct = $product->getCustomOption('simple_product')->getProduct();
+ $simpleProduct->setCustomerGroupId($product->getCustomerGroupId());
+ $finalPrice = parent::getFinalPrice($qty, $simpleProduct);
} else {
$priceInfo = $product->getPriceInfo();
$finalPrice = $priceInfo->getPrice('final_price')->getAmount()->getValue();
@@ -35,7 +39,7 @@ public function getFinalPrice($qty, $product)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getPrice($product)
{
@@ -48,6 +52,7 @@ public function getPrice($product)
}
}
}
+
return 0;
}
}
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php
index 64b9b3776442a..ab52d4eb86021 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php
@@ -6,22 +6,47 @@
namespace Magento\ConfigurableProduct\Test\Unit\Model\Product\Type\Configurable;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Configuration\Item\Option;
+use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price as ConfigurablePrice;
+use Magento\Framework\Event\ManagerInterface;
+use Magento\Framework\Pricing\Amount\AmountInterface;
+use Magento\Framework\Pricing\Price\PriceInterface;
+use Magento\Framework\Pricing\PriceInfo\Base as PriceInfoBase;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use PHPUnit\Framework\MockObject\MockObject;
class PriceTest extends \PHPUnit\Framework\TestCase
{
- /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price */
+ /**
+ * @var ObjectManagerHelper
+ */
+ protected $objectManagerHelper;
+
+ /**
+ * @var ConfigurablePrice
+ */
protected $model;
- /** @var ObjectManagerHelper */
- protected $objectManagerHelper;
+ /**
+ * @var ManagerInterface|MockObject
+ */
+ private $eventManagerMock;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->objectManagerHelper = new ObjectManagerHelper($this);
+ $this->eventManagerMock = $this->createPartialMock(
+ ManagerInterface::class,
+ ['dispatch']
+ );
$this->model = $this->objectManagerHelper->getObject(
- \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price::class
+ ConfigurablePrice::class,
+ ['eventManager' => $this->eventManagerMock]
);
}
@@ -29,29 +54,29 @@ public function testGetFinalPrice()
{
$finalPrice = 10;
$qty = 1;
- $configurableProduct = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
- ->disableOriginalConstructor()
- ->setMethods(['getCustomOption', 'getPriceInfo', 'setFinalPrice', '__wakeUp'])
- ->getMock();
- $customOption = $this->getMockBuilder(\Magento\Catalog\Model\Product\Configuration\Item\Option::class)
+
+ /** @var Product|MockObject $configurableProduct */
+ $configurableProduct = $this->getMockBuilder(Product::class)
->disableOriginalConstructor()
- ->setMethods(['getProduct'])
+ ->setMethods(['getCustomOption', 'getPriceInfo', 'setFinalPrice'])
->getMock();
- $priceInfo = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo\Base::class)
+ /** @var PriceInfoBase|MockObject $priceInfo */
+ $priceInfo = $this->getMockBuilder(PriceInfoBase::class)
->disableOriginalConstructor()
->setMethods(['getPrice'])
->getMock();
- $price = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class)
+ /** @var PriceInterface|MockObject $price */
+ $price = $this->getMockBuilder(PriceInterface::class)
->disableOriginalConstructor()
->getMock();
- $amount = $this->getMockBuilder(\Magento\Framework\Pricing\Amount\AmountInterface::class)
+ /** @var AmountInterface|MockObject $amount */
+ $amount = $this->getMockBuilder(AmountInterface::class)
->disableOriginalConstructor()
->getMock();
$configurableProduct->expects($this->any())
->method('getCustomOption')
->willReturnMap([['simple_product', false], ['option_ids', false]]);
- $customOption->expects($this->never())->method('getProduct');
$configurableProduct->expects($this->once())->method('getPriceInfo')->willReturn($priceInfo);
$priceInfo->expects($this->once())->method('getPrice')->with('final_price')->willReturn($price);
$price->expects($this->once())->method('getAmount')->willReturn($amount);
@@ -60,4 +85,60 @@ public function testGetFinalPrice()
$this->assertEquals($finalPrice, $this->model->getFinalPrice($qty, $configurableProduct));
}
+
+ public function testGetFinalPriceWithSimpleProduct()
+ {
+ $finalPrice = 10;
+ $qty = 1;
+ $customerGroupId = 1;
+
+ /** @var Product|MockObject $configurableProduct */
+ $configurableProduct = $this->createPartialMock(
+ Product::class,
+ ['getCustomOption', 'setFinalPrice', 'getCustomerGroupId']
+ );
+ /** @var Option|MockObject $customOption */
+ $customOption = $this->createPartialMock(
+ Option::class,
+ ['getProduct']
+ );
+ /** @var Product|MockObject $simpleProduct */
+ $simpleProduct = $this->createPartialMock(
+ Product::class,
+ ['setCustomerGroupId', 'setFinalPrice', 'getPrice', 'getTierPrice', 'getData', 'getCustomOption']
+ );
+
+ $configurableProduct->method('getCustomOption')
+ ->willReturnMap([
+ ['simple_product', $customOption],
+ ['option_ids', false]
+ ]);
+ $configurableProduct->method('getCustomerGroupId')->willReturn($customerGroupId);
+ $configurableProduct->expects($this->atLeastOnce())
+ ->method('setFinalPrice')
+ ->with($finalPrice)
+ ->willReturnSelf();
+ $customOption->method('getProduct')->willReturn($simpleProduct);
+ $simpleProduct->expects($this->atLeastOnce())
+ ->method('setCustomerGroupId')
+ ->with($customerGroupId)
+ ->willReturnSelf();
+ $simpleProduct->method('getPrice')->willReturn($finalPrice);
+ $simpleProduct->method('getTierPrice')->with($qty)->willReturn($finalPrice);
+ $simpleProduct->expects($this->atLeastOnce())
+ ->method('setFinalPrice')
+ ->with($finalPrice)
+ ->willReturnSelf();
+ $simpleProduct->method('getData')->with('final_price')->willReturn($finalPrice);
+ $simpleProduct->method('getCustomOption')->with('option_ids')->willReturn(false);
+ $this->eventManagerMock->expects($this->once())
+ ->method('dispatch')
+ ->with('catalog_product_get_final_price', ['product' => $simpleProduct, 'qty' => $qty]);
+
+ $this->assertEquals(
+ $finalPrice,
+ $this->model->getFinalPrice($qty, $configurableProduct),
+ 'The final price calculation is wrong'
+ );
+ }
}
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml
index efd3a8c6b8cad..d30fc1e5a2a35 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml
@@ -17,4 +17,9 @@
+
+
+
+
+
diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/button.js b/app/code/Magento/Ui/view/base/web/js/form/components/button.js
index df85af5824d92..bfc2eb2b8852b 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/components/button.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/components/button.js
@@ -45,7 +45,8 @@ define([
.observe([
'visible',
'disabled',
- 'title'
+ 'title',
+ 'childError'
]);
},
diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/button.html b/app/code/Magento/Ui/view/base/web/templates/form/element/button.html
index a8b6c3a858956..766df35f5764b 100644
--- a/app/code/Magento/Ui/view/base/web/templates/form/element/button.html
+++ b/app/code/Magento/Ui/view/base/web/templates/form/element/button.html
@@ -11,3 +11,15 @@
attr="'data-index': index">
+
+
+
+
+
+
+ This element contains invalid data. Please resolve this before saving.
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php
index 2ec573b6459da..e1bb094e7fc39 100644
--- a/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php
+++ b/app/code/Magento/UrlRewrite/Model/StoreSwitcher/RewriteUrl.php
@@ -81,7 +81,12 @@ public function switch(StoreInterface $fromStore, StoreInterface $targetStore, s
$existingRewrite = $this->urlFinder->findOneByData([
UrlRewrite::REQUEST_PATH => $urlPath
]);
- if ($existingRewrite) {
+ $currentRewrite = $this->urlFinder->findOneByData([
+ UrlRewrite::REQUEST_PATH => $urlPath,
+ UrlRewrite::STORE_ID => $targetStore->getId(),
+ ]);
+
+ if ($existingRewrite && !$currentRewrite) {
/** @var \Magento\Framework\App\Response\Http $response */
$targetUrl = $targetStore->getBaseUrl();
}
diff --git a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php
index 234f0aae6a3ea..676433c0a1e6c 100644
--- a/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php
+++ b/dev/tests/integration/testsuite/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/Data/AssociatedProductsTest.php
@@ -5,7 +5,19 @@
*/
namespace Magento\ConfigurableProduct\Ui\DataProvider\Product\Form\Modifier\Data;
-class AssociatedProductsTest extends \PHPUnit\Framework\TestCase
+use Magento\Framework\View\Element\UiComponentFactory;
+use Magento\Ui\Component\Filters\FilterModifier;
+use Magento\Framework\View\Element\UiComponent\ContextInterface;
+use Magento\ConfigurableProduct\Ui\DataProvider\Product\Form\Modifier\ConfigurablePanel;
+use Magento\Framework\App\RequestInterface;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * AssociatedProductsTest
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class AssociatedProductsTest extends TestCase
{
/**
* @var \Magento\Framework\ObjectManagerInterface $objectManager
@@ -17,7 +29,10 @@ class AssociatedProductsTest extends \PHPUnit\Framework\TestCase
*/
private $registry;
- public function setUp()
+ /**
+ * @inheritdoc
+ */
+ public function setUp(): void
{
$this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
$this->registry = $this->objectManager->get(\Magento\Framework\Registry::class);
@@ -64,6 +79,53 @@ public function testGetProductMatrix($interfaceLocale)
}
}
+ /**
+ * Tests configurable product won't appear in product listing.
+ *
+ * Tests configurable product won't appear in configurable associated product listing if its options attribute
+ * is not filterable in grid.
+ *
+ * @return void
+ * @magentoDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+ * @magentoAppArea adminhtml
+ */
+ public function testAddManuallyConfigurationsWithNotFilterableInGridAttribute(): void
+ {
+ /** @var RequestInterface $request */
+ $request = $this->objectManager->get(RequestInterface::class);
+ $request->setParams([
+ FilterModifier::FILTER_MODIFIER => [
+ 'test_configurable' => [
+ 'condition_type' => 'notnull',
+ ],
+ ],
+ 'attributes_codes' => [
+ 'test_configurable'
+ ],
+ ]);
+ $context = $this->objectManager->create(ContextInterface::class, ['request' => $request]);
+ /** @var UiComponentFactory $uiComponentFactory */
+ $uiComponentFactory = $this->objectManager->get(UiComponentFactory::class);
+ $uiComponent = $uiComponentFactory->create(
+ ConfigurablePanel::ASSOCIATED_PRODUCT_LISTING,
+ null,
+ ['context' => $context]
+ );
+
+ foreach ($uiComponent->getChildComponents() as $childUiComponent) {
+ $childUiComponent->prepare();
+ }
+
+ $dataSource = $uiComponent->getDataSourceData();
+ $skus = array_column($dataSource['data']['items'], 'sku');
+
+ $this->assertNotContains(
+ 'configurable',
+ $skus,
+ 'Only products with specified attribute should be in product list'
+ );
+ }
+
/**
* @return array
*/