diff --git a/app/code/Magento/Bundle/Model/Product/Price.php b/app/code/Magento/Bundle/Model/Product/Price.php
index 4d65e1df627aa..83bfcbbabc253 100644
--- a/app/code/Magento/Bundle/Model/Product/Price.php
+++ b/app/code/Magento/Bundle/Model/Product/Price.php
@@ -183,8 +183,9 @@ public function getFinalPrice($qty, $product)
$finalPrice = $this->_applyOptionsPrice($product, $qty, $finalPrice);
$finalPrice += $this->getTotalBundleItemsPrice($product, $qty);
+ $finalPrice = max(0, $finalPrice);
$product->setFinalPrice($finalPrice);
- return max(0, $product->getData('final_price'));
+ return $finalPrice;
}
/**
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Main.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Main.php
index 8fe925252dc42..4ad87969f0489 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Main.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Main.php
@@ -70,11 +70,11 @@ protected function _prepareForm()
}
$this->_coreRegistry->register('attribute_type_hidden_fields', $_hiddenFields);
- $this->_eventManager->dispatch('product_attribute_form_build_main_tab', ['form' => $form]);
-
$frontendInputValues = array_merge($frontendInputElm->getValues(), $additionalTypes);
$frontendInputElm->setValues($frontendInputValues);
+ $this->_eventManager->dispatch('product_attribute_form_build_main_tab', ['form' => $form]);
+
return $this;
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
index 5409d2cf73b88..2917708741475 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
@@ -88,7 +88,7 @@ protected function createActionPage($title = null)
/** @var \Magento\Backend\Model\View\Result\Page $resultPage */
$resultPage = $this->resultPageFactory->create();
if ($this->getRequest()->getParam('popup')) {
- if ($this->getRequest()->getParam('product_tab') == 'variations') {
+ if ($this->getRequest()->getParam('product_tab') === 'variations') {
$resultPage->addHandle(['popup', 'catalog_product_attribute_edit_product_tab_variations_popup']);
} else {
$resultPage->addHandle(['popup', 'catalog_product_attribute_edit_popup']);
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/StockDataFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/StockDataFilter.php
index 7ad9c3f4d888d..805800c7bc2ac 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/StockDataFilter.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/StockDataFilter.php
@@ -16,7 +16,7 @@ class StockDataFilter
/**
* The greatest value which could be stored in CatalogInventory Qty field
*/
- const MAX_QTY_VALUE = 99999999.9999;
+ const MAX_QTY_VALUE = 99999999;
/**
* @var ScopeConfigInterface
diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php
index d1f91a90e105c..8769df70dbb7a 100644
--- a/app/code/Magento/Catalog/Model/Product.php
+++ b/app/code/Magento/Catalog/Model/Product.php
@@ -142,11 +142,6 @@ class Product extends \Magento\Catalog\Model\AbstractModel implements
*/
protected $optionInstance;
- /**
- * @var bool
- */
- protected $optionsInitialized = false;
-
/**
* @var array
*/
@@ -1901,6 +1896,7 @@ public function addOption(Product\Option $option)
{
$options = (array)$this->getData('options');
$options[] = $option;
+ $option->setProduct($this);
$this->setData('options', $options);
return $this;
}
diff --git a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php
index c97d660f5a829..38032a1c2c5d2 100644
--- a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php
+++ b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/Form/Modifier/AdvancedInventory.php
@@ -5,6 +5,7 @@
*/
namespace Magento\CatalogInventory\Ui\DataProvider\Product\Form\Modifier;
+use Magento\Catalog\Controller\Adminhtml\Product\Initialization\StockDataFilter;
use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\CatalogInventory\Api\StockRegistryInterface;
@@ -213,6 +214,7 @@ private function prepareMeta()
'validation' => [
'validate-number' => true,
'validate-digits' => true,
+ 'less-than-equals-to' => StockDataFilter::MAX_QTY_VALUE,
],
'imports' => [
'handleChanges' => '${$.provider}:data.product.stock_data.is_qty_decimal',
diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
index 309db72472c37..dea1bc2f4c20a 100644
--- a/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
+++ b/app/code/Magento/CatalogInventory/view/adminhtml/ui_component/product_form.xml
@@ -96,6 +96,7 @@
-
- true
- true
+ - 99999999
- 200
- [GLOBAL]
diff --git a/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/qty-validator-changer.js b/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/qty-validator-changer.js
index b10711671d502..b14527c9c9ec6 100644
--- a/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/qty-validator-changer.js
+++ b/app/code/Magento/CatalogInventory/view/adminhtml/web/js/components/qty-validator-changer.js
@@ -21,6 +21,7 @@ define([
this.validation['validate-number'] = !isDigits;
this.validation['validate-digits'] = isDigits;
+ this.validation['less-than-equals-to'] = isDigits ? 99999999 : 99999999.9999;
this.validate();
}
});
diff --git a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php
index bff84a9236564..8c46242abb110 100644
--- a/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php
+++ b/app/code/Magento/CatalogSearch/Model/Layer/Filter/Attribute.php
@@ -84,6 +84,12 @@ protected function _getItemsData()
->getProductCollection();
$optionsFacetedData = $productCollection->getFacetedData($attribute->getAttributeCode());
+ if (count($optionsFacetedData) === 0
+ && $this->getAttributeIsFilterable($attribute) !== static::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS
+ ) {
+ return $this->itemDataBuilder->build();
+ }
+
$productSize = $productCollection->getSize();
$options = $attribute->getFrontend()
@@ -100,9 +106,8 @@ protected function _getItemsData()
: 0;
// Check filter type
if (
- $count === 0
- && $this->getAttributeIsFilterable($attribute) === static::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS
- && !$this->isOptionReducesResults($count, $productSize)
+ $this->getAttributeIsFilterable($attribute) === static::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS
+ && (!$this->isOptionReducesResults($count, $productSize) || $count === 0)
) {
continue;
}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
index 1d3749f4a95a9..9a1e4bbcf1b4c 100644
--- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
@@ -5,6 +5,7 @@
*/
namespace Magento\CatalogSearch\Model\ResourceModel\Fulltext;
+use Magento\CatalogSearch\Model\Search\RequestGenerator;
use Magento\Framework\DB\Select;
use Magento\Framework\Exception\StateException;
use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage;
@@ -398,14 +399,17 @@ public function getFacetedData($field)
$this->_renderFilters();
$result = [];
$aggregations = $this->searchResult->getAggregations();
- $bucket = $aggregations->getBucket($field . '_bucket');
- if ($bucket) {
- foreach ($bucket->getValues() as $value) {
- $metrics = $value->getMetrics();
- $result[$metrics['value']] = $metrics;
+ // This behavior is for case with empty object when we got EmptyRequestDataException
+ if (null !== $aggregations) {
+ $bucket = $aggregations->getBucket($field . RequestGenerator::BUCKET_SUFFIX);
+ if ($bucket) {
+ foreach ($bucket->getValues() as $value) {
+ $metrics = $value->getMetrics();
+ $result[$metrics['value']] = $metrics;
+ }
+ } else {
+ throw new StateException(__('Bucket does not exist'));
}
- } else {
- throw new StateException(__('Bucket do not exists'));
}
return $result;
}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php b/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php
index 98facef4679c2..d4f371e965e07 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/TableMapper.php
@@ -17,6 +17,9 @@
use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface;
use Magento\Store\Model\StoreManagerInterface;
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class TableMapper
{
/**
@@ -108,7 +111,7 @@ private function getMappingData(FilterInterface $filter)
&& in_array($attribute->getFrontendInput(), ['select', 'multiselect'], true)
) {
$table = $this->resource->getTableName('catalog_product_index_eav');
- $alias = $field . '_filter';
+ $alias = $field . RequestGenerator::FILTER_SUFFIX;
$mapOn = sprintf(
'search_index.entity_id = %1$s.entity_id AND %1$s.attribute_id = %2$d AND %1$s.store_id = %3$d',
$alias,
@@ -118,7 +121,7 @@ private function getMappingData(FilterInterface $filter)
$mappedFields = [];
} elseif ($attribute->getBackendType() === AbstractAttribute::TYPE_STATIC) {
$table = $attribute->getBackendTable();
- $alias = $field . '_filter';
+ $alias = $field . RequestGenerator::FILTER_SUFFIX;
$mapOn = 'search_index.entity_id = ' . $alias . '.entity_id';
$mappedFields = null;
}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php
index fae8db405ea3c..4afe01f845f94 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Layer/Filter/AttributeTest.php
@@ -6,6 +6,7 @@
namespace Magento\CatalogSearch\Test\Unit\Model\Layer\Filter;
+use Magento\Catalog\Model\Layer\Filter\AbstractFilter;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
@@ -104,9 +105,6 @@ protected function setUp()
->disableOriginalConstructor()
->setMethods(['getAttributeCode', 'getFrontend', 'getIsFilterable'])
->getMock();
- $this->attribute->expects($this->atLeastOnce())
- ->method('getFrontend')
- ->will($this->returnValue($this->frontend));
$this->request = $this->getMockBuilder('\Magento\Framework\App\RequestInterface')
->setMethods(['getParam'])
@@ -143,6 +141,9 @@ public function testApplyFilter()
$this->attribute->expects($this->exactly(2))
->method('getAttributeCode')
->will($this->returnValue($attributeCode));
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getFrontend')
+ ->will($this->returnValue($this->frontend));
$this->target->setAttributeModel($this->attribute);
@@ -202,6 +203,9 @@ public function testGetItemsWithApply()
$this->attribute->expects($this->exactly(2))
->method('getAttributeCode')
->will($this->returnValue($attributeCode));
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getFrontend')
+ ->will($this->returnValue($this->frontend));
$this->target->setAttributeModel($this->attribute);
@@ -283,6 +287,9 @@ public function testGetItemsWithoutApply()
$this->attribute->expects($this->exactly(2))
->method('getAttributeCode')
->will($this->returnValue($attributeCode));
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getFrontend')
+ ->will($this->returnValue($this->frontend));
$this->target->setAttributeModel($this->attribute);
@@ -329,6 +336,105 @@ public function testGetItemsWithoutApply()
$this->assertEquals($expectedFilterItems, $result);
}
+ /**
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testGetItemsOnlyWithResults()
+ {
+ $attributeCode = 'attributeCode';
+ $selectedOptions = [
+ [
+ 'label' => 'selectedOptionLabel1',
+ 'value' => 'selectedOptionValue1',
+ ],
+ [
+ 'label' => 'selectedOptionLabel2',
+ 'value' => 'selectedOptionValue2',
+ ],
+ ];
+ $facetedData = [
+ 'selectedOptionValue1' => ['count' => 10],
+ 'selectedOptionValue2' => ['count' => 0],
+ ];
+ $builtData = [
+ [
+ 'label' => $selectedOptions[0]['label'],
+ 'value' => $selectedOptions[0]['value'],
+ 'count' => $facetedData[$selectedOptions[0]['value']]['count'],
+ ],
+ ];
+
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getAttributeCode')
+ ->willReturn($attributeCode);
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getIsFilterable')
+ ->willReturn(AbstractFilter::ATTRIBUTE_OPTIONS_ONLY_WITH_RESULTS);
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getFrontend')
+ ->will($this->returnValue($this->frontend));
+
+ $this->target->setAttributeModel($this->attribute);
+
+ $this->frontend->expects($this->once())
+ ->method('getSelectOptions')
+ ->willReturn($selectedOptions);
+
+ $this->fulltextCollection->expects($this->once())
+ ->method('getFacetedData')
+ ->willReturn($facetedData);
+ $this->fulltextCollection->expects($this->once())
+ ->method('getSize')
+ ->will($this->returnValue(50));
+
+ $this->itemDataBuilder->expects($this->once())
+ ->method('addItemData')
+ ->with(
+ $selectedOptions[0]['label'],
+ $selectedOptions[0]['value'],
+ $facetedData[$selectedOptions[0]['value']]['count']
+ )
+ ->will($this->returnSelf());
+
+ $this->itemDataBuilder->expects($this->once())
+ ->method('build')
+ ->willReturn($builtData);
+
+ $expectedFilterItems = [
+ $this->createFilterItem(0, $builtData[0]['label'], $builtData[0]['value'], $builtData[0]['count']),
+ ];
+ $result = $this->target->getItems();
+
+ $this->assertEquals($expectedFilterItems, $result);
+ }
+
+ /**
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function testGetItemsIfFacetedDataIsEmpty()
+ {
+ $attributeCode = 'attributeCode';
+
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getAttributeCode')
+ ->willReturn($attributeCode);
+ $this->attribute->expects($this->atLeastOnce())
+ ->method('getIsFilterable')
+ ->willReturn(0);
+
+ $this->target->setAttributeModel($this->attribute);
+
+ $this->fulltextCollection->expects($this->once())
+ ->method('getFacetedData')
+ ->willReturn([]);
+
+ $this->itemDataBuilder->expects($this->once())
+ ->method('build')
+ ->willReturn([]);
+
+ $this->assertEquals([], $this->target->getItems());
+ }
+
/**
* @param int $index
* @param string $label
diff --git a/app/code/Magento/ConfigurableProduct/Observer/HideUnsupportedAttributeTypes.php b/app/code/Magento/ConfigurableProduct/Observer/HideUnsupportedAttributeTypes.php
new file mode 100644
index 0000000000000..4ccbab7e1af05
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Observer/HideUnsupportedAttributeTypes.php
@@ -0,0 +1,68 @@
+supportedTypes = $supportedTypes;
+ $this->request = $request;
+ }
+
+ /**
+ * @param \Magento\Framework\Event\Observer $observer
+ * @return void
+ */
+ public function execute(EventObserver $observer)
+ {
+ if (!$this->isVariationsPopupUsed()) {
+ return;
+ }
+
+ /** @var \Magento\Framework\Data\Form $form */
+ $form = $observer->getForm();
+
+ $filteredValues = [];
+ /** @var \Magento\Framework\Data\Form\Element\Select $frontendInput */
+ $frontendInput = $form->getElement('frontend_input');
+ foreach ($frontendInput->getValues() as $frontendValue) {
+ if (in_array($frontendValue['value'], $this->supportedTypes, true)) {
+ $filteredValues[] = $frontendValue;
+ }
+ }
+ $frontendInput->setValues($filteredValues);
+ }
+
+ /**
+ * @return bool
+ */
+ private function isVariationsPopupUsed()
+ {
+ $popup = $this->request->getParam('popup');
+ $productTab = $this->request->getParam('product_tab') === 'variations';
+ return $popup && $productTab;
+ }
+}
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Observer/HideUnsupportedAttributeTypesTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Observer/HideUnsupportedAttributeTypesTest.php
new file mode 100644
index 0000000000000..a6f15699d417e
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Observer/HideUnsupportedAttributeTypesTest.php
@@ -0,0 +1,174 @@
+objectManager = new ObjectManager($this);
+ }
+
+ /**
+ * @return void
+ */
+ public function testExecuteWhenBlockNotPassed()
+ {
+ $target = $this->createTarget($this->createRequestMock(false));
+ $event = $this->createEventMock();
+ $this->assertEquals(null, $target->execute($event));
+ }
+
+ /**
+ * @param RequestInterface|MockObject $request
+ * @param array $supportedTypes
+ * @return HideUnsupportedAttributeTypes
+ */
+ private function createTarget(MockObject $request, array $supportedTypes = [])
+ {
+ return $this->objectManager->getObject(
+ HideUnsupportedAttributeTypes::class,
+ [
+ 'request' => $request,
+ 'supportedTypes' => $supportedTypes
+ ]
+ );
+ }
+
+ private function createRequestMock($popup, $productTab = 'variations')
+ {
+ $request = $this->getMockBuilder(RequestInterface::class)
+ ->setMethods(['getParam'])
+ ->getMockForAbstractClass();
+ $request->method('getParam')
+ ->willReturnCallback(
+ function ($name) use ($popup, $productTab) {
+ switch ($name) {
+ case 'popup':
+ return $popup;
+ case 'product_tab':
+ return $productTab;
+ default:
+ return null;
+ }
+ }
+ );
+ return $request;
+ }
+
+ /**
+ * @param MockObject|null $form
+ * @return EventObserver|MockObject
+ * @internal param null|MockObject $block
+ */
+ private function createEventMock(MockObject $form = null)
+ {
+ $event = $this->getMockBuilder(EventObserver::class)
+ ->setMethods(['getForm', 'getBlock'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $event->expects($this->any())
+ ->method('getForm')
+ ->willReturn($form);
+ return $event;
+ }
+
+ /**
+ * @dataProvider executeDataProvider
+ */
+ public function testExecuteWithDefaultTypes(array $supportedTypes, array $originalValues, array $expectedValues)
+ {
+ $target = $this->createTarget($this->createRequestMock(true), $supportedTypes);
+ $event = $this->createEventMock($this->createForm($originalValues, $expectedValues));
+ $this->assertEquals(null, $target->execute($event));
+ }
+
+ public function executeDataProvider()
+ {
+ return [
+ 'testWithDefaultTypes' => [
+ 'supportedTypes' => ['select'],
+ 'originalValues' => [
+ $this->createFrontendInputValue('text2', 'Text2'),
+ $this->createFrontendInputValue('select', 'Select'),
+ $this->createFrontendInputValue('text', 'Text'),
+ $this->createFrontendInputValue('multiselect', 'Multiselect'),
+ $this->createFrontendInputValue('text3', 'Text3'),
+ ],
+ 'expectedValues' => [
+ $this->createFrontendInputValue('select', 'Select'),
+ ],
+ ],
+ 'testWithCustomTypes' => [
+ 'supportedTypes' => ['select', 'custom_type', 'second_custom_type'],
+ 'originalValues' => [
+ $this->createFrontendInputValue('custom_type', 'CustomType'),
+ $this->createFrontendInputValue('text2', 'Text2'),
+ $this->createFrontendInputValue('select', 'Select'),
+ $this->createFrontendInputValue('text', 'Text'),
+ $this->createFrontendInputValue('second_custom_type', 'SecondCustomType'),
+ $this->createFrontendInputValue('multiselect', 'Multiselect'),
+ $this->createFrontendInputValue('text3', 'Text3'),
+ ],
+ 'expectedValues' => [
+ $this->createFrontendInputValue('custom_type', 'CustomType'),
+ $this->createFrontendInputValue('select', 'Select'),
+ $this->createFrontendInputValue('second_custom_type', 'SecondCustomType'),
+ ],
+ ]
+ ];
+ }
+
+ private function createFrontendInputValue($value, $label)
+ {
+ return ['value' => $value, 'label' => $label];
+ }
+
+ private function createForm(array $originalValues = [], array $expectedValues = [])
+ {
+ $form = $this->getMockBuilder(\Magento\Framework\Data\Form::class)
+ ->setMethods(['getElement'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $frontendInput = $this->getMockBuilder(\Magento\Framework\Data\Form\Element\Select::class)
+ ->setMethods(['getValues', 'setValues'])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $frontendInput->expects($this->once())
+ ->method('getValues')
+ ->willReturn($originalValues);
+ $frontendInput->expects($this->once())
+ ->method('setValues')
+ ->with($expectedValues)
+ ->willReturnSelf();
+ $form->expects($this->once())
+ ->method('getElement')
+ ->with('frontend_input')
+ ->willReturn($frontendInput);
+ return $form;
+ }
+}
diff --git a/app/code/Magento/ConfigurableProduct/etc/adminhtml/events.xml b/app/code/Magento/ConfigurableProduct/etc/adminhtml/events.xml
new file mode 100644
index 0000000000000..88df2d60a7dd4
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/etc/adminhtml/events.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml
index 992642ddc0871..c57194c664bd1 100644
--- a/app/code/Magento/ConfigurableProduct/etc/di.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/di.xml
@@ -151,4 +151,11 @@
Magento\Framework\App\Cache\Type\Collection
+
+
+
+ - select
+
+
+
diff --git a/app/code/Magento/Downloadable/Model/Product/Type.php b/app/code/Magento/Downloadable/Model/Product/Type.php
index 8b385cf74bd38..64c00d208b86f 100644
--- a/app/code/Magento/Downloadable/Model/Product/Type.php
+++ b/app/code/Magento/Downloadable/Model/Product/Type.php
@@ -164,10 +164,11 @@ public function getLinks($product)
*/
public function hasLinks($product)
{
- if ($product->hasData('links_exist')) {
- return $product->getData('links_exist');
+ $hasLinks = $product->getData('links_exist');
+ if (null === $hasLinks) {
+ $hasLinks = (count($this->getLinks($product)) > 0);
}
- return count($this->getLinks($product)) > 0;
+ return $hasLinks;
}
/**
diff --git a/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeTest.php b/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeTest.php
index 9ee0351e64d13..c4be8f5d5aee1 100644
--- a/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeTest.php
+++ b/app/code/Magento/Downloadable/Test/Unit/Model/Product/TypeTest.php
@@ -17,6 +17,7 @@ class TypeTest extends \PHPUnit_Framework_TestCase
* @var \Magento\Downloadable\Model\Product\Type
*/
private $target;
+
/**
* @var TypeHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
@@ -83,6 +84,7 @@ protected function setUp()
'getDownloadableData',
'setTypeHasOptions',
'setLinksExist',
+ 'getDownloadableLinks',
'__wakeup',
],
[],
@@ -144,4 +146,12 @@ public function testBeforeSave()
{
$this->target->beforeSave($this->product);
}
+
+ public function testHasLinks()
+ {
+ $this->product->expects($this->exactly(2))
+ ->method('getDownloadableLinks')
+ ->willReturn(['link1', 'link2']);
+ $this->assertTrue($this->target->hasLinks($this->product));
+ }
}
diff --git a/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php b/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php
index c988700ee94b3..1050280a46300 100644
--- a/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php
+++ b/app/code/Magento/Eav/Model/ResourceModel/CreateHandler.php
@@ -14,6 +14,7 @@
/**
* Class CreateHandler
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CreateHandler implements AttributeInterface
{
@@ -87,6 +88,7 @@ protected function getAttributes($entityType)
* @return array
* @throws \Exception
* @throws \Magento\Framework\Exception\ConfigurationMismatchException
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function execute($entityType, $entityData, $arguments = [])
{
@@ -100,6 +102,7 @@ public function execute($entityType, $entityData, $arguments = [])
continue;
}
if (isset($entityData[$attribute->getAttributeCode()])
+ && !is_array($entityData[$attribute->getAttributeCode()])
&& !$attribute->isValueEmpty($entityData[$attribute->getAttributeCode()])
) {
$this->attributePersistor->registerInsert(
diff --git a/app/code/Magento/Swatches/etc/di.xml b/app/code/Magento/Swatches/etc/di.xml
index 26c86388231e9..525456d1f9eb6 100644
--- a/app/code/Magento/Swatches/etc/di.xml
+++ b/app/code/Magento/Swatches/etc/di.xml
@@ -21,6 +21,14 @@
+
+
+
+ - swatch_visual
+ - swatch_text
+
+
+
diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js
index 3ebdc66edf3ba..555c10ee04b70 100644
--- a/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js
+++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/rules.js
@@ -703,27 +703,21 @@ define([
],
"less-than-equals-to": [
function(value, params) {
- if ($.isNumeric($(params).val()) && $.isNumeric(value)) {
- this.lteToVal = $(params).val();
- return parseFloat(value) <= parseFloat($(params).val());
+ if ($.isNumeric(params) && $.isNumeric(value)) {
+ return parseFloat(value) <= parseFloat(params);
}
return true;
},
- function() {
- return 'Please enter a value less than or equal to %s.'.replace('%s', this.lteToVal);
- }
+ $.mage.__('Please enter a value less than or equal to {0}.')
],
"greater-than-equals-to": [
function(value, params) {
- if ($.isNumeric($(params).val()) && $.isNumeric(value)) {
- this.gteToVal = $(params).val();
- return parseFloat(value) >= parseFloat($(params).val());
+ if ($.isNumeric(params) && $.isNumeric(value)) {
+ return parseFloat(value) >= parseFloat(params);
}
return true;
},
- function() {
- return 'Please enter a value greater than or equal to %s.'.replace('%s', this.gteToVal);
- }
+ $.mage.__('Please enter a value greater than or equal to {0}.')
],
"validate-emails": [
function(value) {
diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml
index b9e5f6e317e62..6fcef33132603 100644
--- a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/CreateBundleProductEntityTest.xml
@@ -195,8 +195,8 @@
Yes
dynamic-8
20
- m/d/Y -1 day
- m/d/Y +3 days
+ M j, Y -1 day
+ M j, Y +3 days
default_dynamic
catalogProductSimple::product_100_dollar,catalogProductSimple::product_40_dollar
bundle_default
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Section/Variations/Config/Matrix.php b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Section/Variations/Config/Matrix.php
index ed8cbc800b2e6..893d01f7ccb92 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Section/Variations/Config/Matrix.php
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/Block/Adminhtml/Product/Edit/Section/Variations/Config/Matrix.php
@@ -79,7 +79,7 @@ class Matrix extends Form
*
* @var string
*/
- protected $deleteVariation = '[data-bind*="deleteRecord"]';
+ protected $deleteVariation = '[data-bind*="Remove Product"]';
/**
* Choose a different Product button selector.
@@ -193,8 +193,14 @@ public function getTemplateBlock()
public function deleteVariations()
{
- $variations = $this->_rootElement->getElements($this->variationRow, Locator::SELECTOR_XPATH);
- foreach (array_reverse($variations) as $variation) {
+ $rowLocator = sprintf($this->variationRowByNumber, 1);
+ $variationText = '';
+ while ($this->_rootElement->find($rowLocator, Locator::SELECTOR_XPATH)->isVisible()) {
+ $variation = $this->_rootElement->find($rowLocator, Locator::SELECTOR_XPATH);
+ if ($variationText == $variation->getText()) {
+ throw new \Exception("Failed to delete configurable product variation");
+ }
+ $variationText = $variation->getText();
$variation->find($this->actionMenu)->hover();
$variation->find($this->actionMenu)->click();
$variation->find($this->deleteVariation)->click();
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php
index 6390f1d3c995c..fda3851989ea8 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/Adapter/Mysql/AdapterTest.php
@@ -364,6 +364,7 @@ public function advancedSearchDataProvider()
}
/**
+ * @magentoDataFixture Magento/Framework/Search/_files/filterable_attribute.php
* @magentoConfigFixture current_store catalog/search/engine mysql
*/
public function testCustomFilterableAttribute()
@@ -384,12 +385,46 @@ public function testCustomFilterableAttribute()
$this->requestBuilder->bind('select_attribute', $selectOptions->getLastItem()->getId());
$this->requestBuilder->bind('multiselect_attribute', $multiselectOptions->getLastItem()->getId());
- $this->requestBuilder->bind('price.from', 9);
- $this->requestBuilder->bind('price.to', 12);
+ $this->requestBuilder->bind('price.from', 98);
+ $this->requestBuilder->bind('price.to', 100);
$this->requestBuilder->bind('category_ids', 2);
$this->requestBuilder->setRequestName('filterable_custom_attributes');
$queryResponse = $this->executeQuery();
$this->assertEquals(1, $queryResponse->count());
}
+
+ /**
+ * Advanced search request using date product attribute
+ *
+ * @param $rangeFilter
+ * @param $expectedRecordsCount
+ * @magentoDataFixture Magento/Framework/Search/_files/date_attribute.php
+ * @magentoConfigFixture current_store catalog/search/engine mysql
+ * @dataProvider dateDataProvider
+ */
+ public function testAdvancedSearchDateField($rangeFilter, $expectedRecordsCount)
+ {
+ array_walk($rangeFilter, function (&$item) {
+ if (!empty($item)) {
+ $item = gmdate('c', strtotime($item)) . 'Z';
+ }
+ });
+ $this->requestBuilder->bind('date.from', $rangeFilter['from']);
+ $this->requestBuilder->bind('date.to', $rangeFilter['to']);
+ $this->requestBuilder->setRequestName('advanced_search_date_field');
+
+ $queryResponse = $this->executeQuery();
+ $this->assertEquals($expectedRecordsCount, $queryResponse->count());
+ }
+
+ public function dateDataProvider()
+ {
+ return [
+ [['from' => '2000-01-01', 'to' => '2000-01-01'], 1], //Y-m-d
+ [['from' => '2000-01-01', 'to' => ''], 1],
+ [['from' => '1999-12-31', 'to' => '2000-01-01'], 1],
+ [['from' => '2000-02-01', 'to' => ''], 0],
+ ];
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/date_attribute.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/date_attribute.php
new file mode 100644
index 0000000000000..606a53acbfd72
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/date_attribute.php
@@ -0,0 +1,59 @@
+create(
+ 'Magento\Catalog\Setup\CategorySetup',
+ ['resourceName' => 'catalog_setup']
+);
+
+/** @var $selectAttribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
+$dateAttribute = $objectManager->create(
+ 'Magento\Catalog\Model\ResourceModel\Eav\Attribute'
+);
+$dateAttribute->setData(
+ [
+ 'attribute_code' => 'date_attribute',
+ 'entity_type_id' => $installer->getEntityTypeId('catalog_product'),
+ 'is_global' => 1,
+ 'is_filterable' => 1,
+ 'backend_type' => 'datetime',
+ 'frontend_input' => 'date',
+ 'frontend_label' => 'Test Date',
+ ]
+);
+$dateAttribute->save();
+/* Assign attribute to attribute set */
+$installer->addAttributeToGroup('catalog_product', 'Default', 'General', $dateAttribute->getId());
+
+/** @var $product \Magento\Catalog\Model\Product */
+$product = $objectManager->create('Magento\Catalog\Model\Product');
+$product
+ ->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
+ ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default'))
+ ->setWebsiteIds([1])
+ ->setName('Simple Product with date attribute')
+ ->setSku('simple_product_with_date_attribute')
+ ->setPrice(1)
+ ->setCategoryIds([2])
+ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH)
+ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED)
+ ->setStockData(['use_config_manage_stock' => 1, 'qty' => 5, 'is_in_stock' => 1])
+ ->save();
+
+$objectManager->get('Magento\Catalog\Model\Product\Action')
+ ->updateAttributes(
+ [$product->getId()],
+ [
+ $dateAttribute->getAttributeCode() => '01/01/2000' // m/d/Y
+ ],
+ $product->getStoreId()
+ );
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/date_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/date_attribute_rollback.php
new file mode 100644
index 0000000000000..2f59c100ca3b1
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/date_attribute_rollback.php
@@ -0,0 +1,35 @@
+create(
+ 'Magento\Catalog\Setup\CategorySetup',
+ ['resourceName' => 'catalog_setup']
+);
+$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var $product \Magento\Catalog\Model\Product */
+$product = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create('Magento\Catalog\Model\Product');
+$product = $product->loadByAttribute('sku', 'simple_product_with_date_attribute');
+if ($product->getId()) {
+ $product->delete();
+}
+
+/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
+$attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
+ 'Magento\Catalog\Model\ResourceModel\Eav\Attribute'
+);
+$attribute->loadByCode($installer->getEntityTypeId('catalog_product'), 'date_attribute');
+if ($attribute->getId()) {
+ $attribute->delete();
+}
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute.php
index 3103d25cc1fb5..6a2e967560987 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attribute.php
@@ -81,7 +81,7 @@
)->setSku(
'simple_product_' . $option->getId()
)->setPrice(
- 10
+ 99
)->setCategoryIds(
[2]
)->setVisibility(
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml
index 76e044afcba49..0cdfa11c3b6b5 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/requests.xml
@@ -366,4 +366,25 @@
0
10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 0
+ 10
+
diff --git a/lib/internal/Magento/Framework/Search/Request/Cleaner.php b/lib/internal/Magento/Framework/Search/Request/Cleaner.php
index 7d2ad5173fd6b..bceeedc79df9a 100644
--- a/lib/internal/Magento/Framework/Search/Request/Cleaner.php
+++ b/lib/internal/Magento/Framework/Search/Request/Cleaner.php
@@ -57,7 +57,7 @@ public function clean(array $requestData)
$this->clear();
if (empty($requestData['queries']) && empty($requestData['filters'])) {
- throw new EmptyRequestDataException(new Phrase('Request query and filter is not set'));
+ throw new EmptyRequestDataException(new Phrase('Request query and filters are not set'));
}
return $requestData;
diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/Request/CleanerTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/Request/CleanerTest.php
index 2d3368345b910..41346379f1ebc 100644
--- a/lib/internal/Magento/Framework/Search/Test/Unit/Request/CleanerTest.php
+++ b/lib/internal/Magento/Framework/Search/Test/Unit/Request/CleanerTest.php
@@ -338,7 +338,7 @@ public function testCleanQueryNotExists()
/**
* @expectedException \Magento\Framework\Search\Request\EmptyRequestDataException
- * @expectedExceptionMessage Request query and filter is not set
+ * @expectedExceptionMessage Request query and filters are not set
*/
public function testCleanEmptyQueryAndFilter()
{