diff --git a/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php b/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php
index 23fc2026ab111..82a0086ad67ec 100644
--- a/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php
+++ b/app/code/Magento/Bundle/Block/Adminhtml/Sales/Order/Items/Renderer.php
@@ -100,6 +100,8 @@ public function getChildren($item)
}
/**
+ * Check if item can be shipped separately
+ *
* @param mixed $item
* @return bool
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -136,6 +138,8 @@ public function isShipmentSeparately($item = null)
}
/**
+ * Check if child items calculated
+ *
* @param mixed $item
* @return bool
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -174,6 +178,8 @@ public function isChildCalculated($item = null)
}
/**
+ * Retrieve selection attributes values
+ *
* @param mixed $item
* @return mixed|null
*/
@@ -191,6 +197,8 @@ public function getSelectionAttributes($item)
}
/**
+ * Retrieve order item options array
+ *
* @return array
*/
public function getOrderOptions()
@@ -212,6 +220,8 @@ public function getOrderOptions()
}
/**
+ * Retrieve order item
+ *
* @return mixed
*/
public function getOrderItem()
@@ -223,6 +233,8 @@ public function getOrderItem()
}
/**
+ * Get html info for item
+ *
* @param mixed $item
* @return string
*/
@@ -245,6 +257,8 @@ public function getValueHtml($item)
}
/**
+ * Check if we can show price info for this item
+ *
* @param object $item
* @return bool
*/
diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml
index ff26d67bd8378..12da960a9c6cf 100644
--- a/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml
+++ b/app/code/Magento/Bundle/view/adminhtml/templates/sales/invoice/create/items/renderer.phtml
@@ -28,8 +28,17 @@
+ getOrderItem()->getProductType() == \Magento\Catalog\Model\Product\Type::TYPE_BUNDLE) ?
+ !$_item->getOrderItem()->isShipSeparately() : !$_item->getOrderItem()->getParentItem()->isShipSeparately()
+ ?>
setPriceDataObject($_item) ?>
getOrderItem()->getParentItem()): ?>
+
getSelectionAttributes($_item) ?>
@@ -60,14 +69,14 @@
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
= $block->getColumnHtml($_item, 'price') ?>
|
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
= /* @escapeNotVerified */ __('Ordered') ?> |
@@ -116,7 +125,7 @@
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
canEditQty()) : ?>
getNodeById($rootId);
- if ($root && $rootId != \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
+ if ($root) {
$root->setIsVisible(true);
- } elseif ($root && $root->getId() == \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
- $root->setName(__('Root'));
+ if ($root->getId() == \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
+ $root->setName(__('Root'));
+ }
}
$this->_coreRegistry->register('root', $root);
@@ -162,6 +175,8 @@ public function getRoot($parentNodeCategory = null, $recursionLevel = 3)
}
/**
+ * Get Default Store Id
+ *
* @return int
*/
protected function _getDefaultStoreId()
@@ -170,6 +185,8 @@ protected function _getDefaultStoreId()
}
/**
+ * Get category collection
+ *
* @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
*/
public function getCategoryCollection()
@@ -227,6 +244,8 @@ public function getRootByIds($ids)
}
/**
+ * Get category node for tree
+ *
* @param mixed $parentNodeCategory
* @param int $recursionLevel
* @return Node
@@ -249,6 +268,8 @@ public function getNode($parentNodeCategory, $recursionLevel = 2)
}
/**
+ * Get category save url
+ *
* @param array $args
* @return string
*/
@@ -260,6 +281,8 @@ public function getSaveUrl(array $args = [])
}
/**
+ * Get category edit url
+ *
* @return string
*/
public function getEditUrl()
diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
index d3c84e69c9540..e296c8d3b8978 100644
--- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
+++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
@@ -58,22 +58,38 @@ public function build(Filter $filter): string
$conditionValue = $this->mapConditionValue($conditionType, $filter->getValue());
// NOTE: store scope was ignored intentionally to perform search across all stores
- $attributeSelect = $this->resourceConnection->getConnection()
- ->select()
- ->from(
- [$tableAlias => $attribute->getBackendTable()],
- $tableAlias . '.' . $attribute->getEntityIdField()
- )->where(
- $this->resourceConnection->getConnection()->prepareSqlCondition(
- $tableAlias . '.' . $attribute->getIdFieldName(),
- ['eq' => $attribute->getAttributeId()]
- )
- )->where(
- $this->resourceConnection->getConnection()->prepareSqlCondition(
- $tableAlias . '.value',
- [$conditionType => $conditionValue]
- )
- );
+ if ($conditionType == 'is_null') {
+ $entityResourceModel = $attribute->getEntity();
+ $attributeSelect = $this->resourceConnection->getConnection()
+ ->select()
+ ->from(
+ [Collection::MAIN_TABLE_ALIAS => $entityResourceModel->getEntityTable()],
+ Collection::MAIN_TABLE_ALIAS . '.' . $entityResourceModel->getEntityIdField()
+ )->joinLeft(
+ [$tableAlias => $attribute->getBackendTable()],
+ $tableAlias . '.' . $attribute->getEntityIdField() . '=' . Collection::MAIN_TABLE_ALIAS .
+ '.' . $entityResourceModel->getEntityIdField() . ' AND ' . $tableAlias . '.' .
+ $attribute->getIdFieldName() . '=' . $attribute->getAttributeId(),
+ ''
+ )->where($tableAlias . '.value is null');
+ } else {
+ $attributeSelect = $this->resourceConnection->getConnection()
+ ->select()
+ ->from(
+ [$tableAlias => $attribute->getBackendTable()],
+ $tableAlias . '.' . $attribute->getEntityIdField()
+ )->where(
+ $this->resourceConnection->getConnection()->prepareSqlCondition(
+ $tableAlias . '.' . $attribute->getIdFieldName(),
+ ['eq' => $attribute->getAttributeId()]
+ )
+ )->where(
+ $this->resourceConnection->getConnection()->prepareSqlCondition(
+ $tableAlias . '.value',
+ [$conditionType => $conditionValue]
+ )
+ );
+ }
return $this->resourceConnection
->getConnection()
@@ -86,6 +102,8 @@ public function build(Filter $filter): string
}
/**
+ * Get attribute entity by its code
+ *
* @param string $field
* @return Attribute
* @throws \Magento\Framework\Exception\LocalizedException
diff --git a/app/code/Magento/Catalog/Model/CategoryList.php b/app/code/Magento/Catalog/Model/CategoryList.php
index 790ea6b921fbe..e3318db505489 100644
--- a/app/code/Magento/Catalog/Model/CategoryList.php
+++ b/app/code/Magento/Catalog/Model/CategoryList.php
@@ -15,6 +15,9 @@
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
+/**
+ * Class for getting category list.
+ */
class CategoryList implements CategoryListInterface
{
/**
@@ -64,7 +67,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList(SearchCriteriaInterface $searchCriteria)
{
@@ -73,10 +76,11 @@ public function getList(SearchCriteriaInterface $searchCriteria)
$this->extensionAttributesJoinProcessor->process($collection);
$this->collectionProcessor->process($searchCriteria, $collection);
+ $collection->load();
$items = [];
- foreach ($collection->getAllIds() as $id) {
- $items[] = $this->categoryRepository->get($id);
+ foreach ($collection->getItems() as $category) {
+ $items[] = $this->categoryRepository->get($category->getId());
}
/** @var CategorySearchResultsInterface $searchResult */
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php b/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
index 2bb10d3b31a24..893000544a728 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
@@ -113,27 +113,28 @@ private function customizeAttributeCode($meta)
*/
private function customizeFrontendLabels($meta)
{
+ $labelConfigs = [];
+
foreach ($this->storeRepository->getList() as $store) {
$storeId = $store->getId();
if (!$storeId) {
continue;
}
-
- $meta['manage-titles']['children'] = [
- 'frontend_label[' . $storeId . ']' => $this->arrayManager->set(
- 'arguments/data/config',
- [],
- [
- 'formElement' => Input::NAME,
- 'componentType' => Field::NAME,
- 'label' => $store->getName(),
- 'dataType' => Text::NAME,
- 'dataScope' => 'frontend_label[' . $storeId . ']'
- ]
- ),
- ];
+ $labelConfigs['frontend_label[' . $storeId . ']'] = $this->arrayManager->set(
+ 'arguments/data/config',
+ [],
+ [
+ 'formElement' => Input::NAME,
+ 'componentType' => Field::NAME,
+ 'label' => $store->getName(),
+ 'dataType' => Text::NAME,
+ 'dataScope' => 'frontend_label[' . $storeId . ']'
+ ]
+ );
}
+ $meta['manage-titles']['children'] = $labelConfigs;
+
return $meta;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Repository.php b/app/code/Magento/Catalog/Model/Product/Option/Repository.php
index 9dc9695daffd1..bb4e247de32db 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Repository.php
@@ -14,6 +14,8 @@
use Magento\Framework\App\ObjectManager;
/**
+ * Product custom options repository
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Repository implements \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface
@@ -83,7 +85,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList($sku)
{
@@ -92,7 +94,7 @@ public function getList($sku)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getProductOptions(ProductInterface $product, $requiredOnly = false)
{
@@ -104,7 +106,7 @@ public function getProductOptions(ProductInterface $product, $requiredOnly = fal
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($sku, $optionId)
{
@@ -117,7 +119,7 @@ public function get($sku, $optionId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $entity)
{
@@ -126,7 +128,7 @@ public function delete(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $e
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function duplicate(
\Magento\Catalog\Api\Data\ProductInterface $product,
@@ -142,7 +144,7 @@ public function duplicate(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $option)
{
@@ -184,7 +186,7 @@ public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $opt
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteByIdentifier($sku, $optionId)
{
@@ -209,8 +211,8 @@ public function deleteByIdentifier($sku, $optionId)
/**
* Mark original values for removal if they are absent among new values
*
- * @param $newValues array
- * @param $originalValues \Magento\Catalog\Model\Product\Option\Value[]
+ * @param array $newValues
+ * @param \Magento\Catalog\Model\Product\Option\Value[] $originalValues
* @return array
*/
protected function markRemovedValues($newValues, $originalValues)
@@ -234,6 +236,8 @@ protected function markRemovedValues($newValues, $originalValues)
}
/**
+ * Get hydrator pool
+ *
* @return \Magento\Framework\EntityManager\HydratorPool
* @deprecated 101.0.0
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
index c4a2d60414a7b..0941aa2478935 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
@@ -28,6 +28,8 @@ public function __construct(
}
/**
+ * Perform action on relation/extension attribute
+ *
* @param object $entity
* @param array $arguments
* @return \Magento\Catalog\Api\Data\ProductInterface|object
@@ -35,6 +37,10 @@ public function __construct(
*/
public function execute($entity, $arguments = [])
{
+ if ($entity->getOptionsSaved()) {
+ return $entity;
+ }
+
$options = $entity->getOptions();
$optionIds = [];
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
index b9e629912a5b3..9cf1431317944 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
@@ -7,6 +7,10 @@
namespace Magento\Catalog\Model\ResourceModel;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
+use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
+use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
+use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
/**
* Catalog entity abstract model
@@ -37,16 +41,18 @@ abstract class AbstractResource extends \Magento\Eav\Model\Entity\AbstractEntity
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Model\Factory $modelFactory
* @param array $data
+ * @param UniqueValidationInterface|null $uniqueValidator
*/
public function __construct(
\Magento\Eav\Model\Entity\Context $context,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Model\Factory $modelFactory,
- $data = []
+ $data = [],
+ UniqueValidationInterface $uniqueValidator = null
) {
$this->_storeManager = $storeManager;
$this->_modelFactory = $modelFactory;
- parent::__construct($context, $data);
+ parent::__construct($context, $data, $uniqueValidator);
}
/**
@@ -86,9 +92,7 @@ protected function _isApplicableAttribute($object, $attribute)
/**
* Check whether attribute instance (attribute, backend, frontend or source) has method and applicable
*
- * @param AbstractAttribute|\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
- * |\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend
- * |\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource $instance
+ * @param AbstractAttribute|AbstractBackend|AbstractFrontend|AbstractSource $instance
* @param string $method
* @param array $args array of arguments
* @return boolean
@@ -112,6 +116,7 @@ protected function _isCallableAttributeInstance($instance, $method, $args)
/**
* Retrieve select object for loading entity attributes values
+ *
* Join attribute store value
*
* @param \Magento\Framework\DataObject $object
@@ -244,6 +249,7 @@ protected function _saveAttributeValue($object, $attribute, $value)
/**
* Check if attribute present for non default Store View.
+ *
* Prevent "delete" query locking in a case when nothing to delete
*
* @param AbstractAttribute $attribute
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
index 707ebbb2964cc..23f612582f42e 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
@@ -236,6 +236,8 @@ public function afterSave()
) {
$this->_indexerEavProcessor->markIndexerAsInvalid();
}
+
+ $this->_source = null;
return parent::afterSave();
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
index d71ec23881982..24174391be829 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
@@ -8,6 +8,7 @@
use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
/**
* Product entity resource model
@@ -101,6 +102,7 @@ class Product extends AbstractResource
* @param \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes
* @param array $data
* @param TableMaintainer|null $tableMaintainer
+ * @param UniqueValidationInterface|null $uniqueValidator
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -115,7 +117,8 @@ public function __construct(
\Magento\Eav\Model\Entity\TypeFactory $typeFactory,
\Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes,
$data = [],
- TableMaintainer $tableMaintainer = null
+ TableMaintainer $tableMaintainer = null,
+ UniqueValidationInterface $uniqueValidator = null
) {
$this->_categoryCollectionFactory = $categoryCollectionFactory;
$this->_catalogCategory = $catalogCategory;
@@ -127,7 +130,8 @@ public function __construct(
$context,
$storeManager,
$modelFactory,
- $data
+ $data,
+ $uniqueValidator
);
$this->connectionName = 'catalog';
$this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class);
@@ -289,7 +293,7 @@ protected function _afterSave(\Magento\Framework\DataObject $product)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete($object)
{
@@ -593,7 +597,7 @@ public function countAll()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function validate($object)
{
@@ -633,7 +637,7 @@ public function load($object, $entityId, $attributes = [])
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
* @since 101.0.0
*/
@@ -675,6 +679,8 @@ public function save(\Magento\Framework\Model\AbstractModel $object)
}
/**
+ * Retrieve entity manager object
+ *
* @return \Magento\Framework\EntityManager\EntityManager
*/
private function getEntityManager()
@@ -687,6 +693,8 @@ private function getEntityManager()
}
/**
+ * Retrieve ProductWebsiteLink object
+ *
* @deprecated 101.1.0
* @return ProductWebsiteLink
*/
@@ -696,6 +704,8 @@ private function getProductWebsiteLink()
}
/**
+ * Retrieve CategoryLink object
+ *
* @deprecated 101.1.0
* @return \Magento\Catalog\Model\ResourceModel\Product\CategoryLink
*/
@@ -710,9 +720,10 @@ private function getProductCategoryLink()
/**
* Extends parent method to be appropriate for product.
+ *
* Store id is required to correctly identify attribute value we are working with.
*
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.1.0
*/
protected function getAttributeRow($entity, $object, $attribute)
diff --git a/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php b/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php
new file mode 100644
index 0000000000000..dd750cfbc696e
--- /dev/null
+++ b/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php
@@ -0,0 +1,56 @@
+productRepository = $productRepository;
+ }
+
+ /**
+ * Update product 'has_options' and 'required_options' attributes after option save
+ *
+ * @param \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $subject
+ * @param \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option
+ *
+ * @return \Magento\Catalog\Api\Data\ProductCustomOptionInterface
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterSave(
+ \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $subject,
+ \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option
+ ) {
+ $product = $this->productRepository->get($option->getProductSku());
+ if (!$product->getHasOptions() ||
+ ($option->getIsRequire() && !$product->getRequiredOptions())) {
+ $product->setCanSaveCustomOptions(true);
+ $product->setOptionsSaved(true);
+ $currentOptions = array_filter($product->getOptions(), function ($iOption) use ($option) {
+ return $option->getOptionId() != $iOption->getOptionId();
+ });
+ $currentOptions[] = $option;
+ $product->setOptions($currentOptions);
+ $product->save();
+ }
+
+ return $option;
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
index 0d82ba3817df9..0082b376bc4a6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
@@ -30,6 +30,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
new file mode 100644
index 0000000000000..f7cd2e7076288
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
index 03a004e500aef..bf0762b4b0319 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
@@ -115,6 +115,27 @@
true
ProductAttributeFrontendLabel
+
+ attribute
+ boolean
+ global
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
attribute
text
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 50e3e5864f4c3..d136661e917cb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -35,6 +35,9 @@
EavStockItem
CustomAttributeCategoryIds
+
+ 100
+
ApiProductDescription
ApiProductShortDescription
@@ -146,6 +149,15 @@
EavStockItem
CustomAttributeProductAttribute
+
+ 50
+
+
+ 60
+
+
+ 70
+
api-simple-product-two
simple
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
index ef6fb99e88eed..14e714cb2b6b7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
@@ -13,7 +13,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
index 05be20b14acc0..ee6af87b8e2c5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
@@ -78,6 +78,7 @@
type="button" selector="#advanced_fieldset-wrapper"/>
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
index 0a1804aa284dc..697648cedb7ba 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
@@ -20,6 +20,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml
new file mode 100644
index 0000000000000..e159a4ce5c0b6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
index 337a3dd53f593..3f67e4b087cc4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
@@ -189,4 +189,9 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
index 178e58ef2d649..f35eb63ee0e0a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
@@ -16,6 +16,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index 6a4ac0d7683c7..4114b199715cb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -31,6 +31,8 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml
new file mode 100644
index 0000000000000..282331924bca3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
index b8b76524099f4..f78c0ad924954 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
@@ -93,7 +93,7 @@ public function testGetList()
$collection = $this->getMockBuilder(Collection::class)->disableOriginalConstructor()->getMock();
$collection->expects($this->once())->method('getSize')->willReturn($totalCount);
- $collection->expects($this->once())->method('getAllIds')->willReturn([$categoryIdFirst, $categoryIdSecond]);
+ $collection->expects($this->once())->method('getItems')->willReturn([$categoryFirst, $categorySecond]);
$this->collectionProcessorMock->expects($this->once())
->method('process')
diff --git a/app/code/Magento/Catalog/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml
index 2a5d60222e9f8..44cdd473bf74e 100644
--- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml
+++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml
index 2a5d60222e9f8..44cdd473bf74e 100644
--- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml
+++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
index 00a1580923a7b..ee67acd0ebd46 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
@@ -20,7 +20,7 @@
"categoryCheckboxTree": {
"dataUrl": "= $block->escapeUrl($block->getLoadTreeUrl()) ?>",
"divId": "= /* @noEscape */ $divId ?>",
- "rootVisible": = /* @noEscape */ $block->getRoot()->getIsVisible() ? 'true' : 'false' ?>,
+ "rootVisible": false,
"useAjax": = $block->escapeHtml($block->getUseAjax()) ?>,
"currentNodeId": = (int)$block->getCategoryId() ?>,
"jsFormObject": "= /* @noEscape */ $block->getJsFormObject() ?>",
@@ -28,7 +28,7 @@
"checked": "= $block->escapeHtml($block->getRoot()->getChecked()) ?>",
"allowdDrop": = /* @noEscape */ $block->getRoot()->getIsVisible() ? 'true' : 'false' ?>,
"rootId": = (int)$block->getRoot()->getId() ?>,
- "expanded": = (int)$block->getIsWasExpanded() ?>,
+ "expanded": true,
"categoryId": = (int)$block->getCategoryId() ?>,
"treeJson": = /* @noEscape */ $block->getTreeJson() ?>
}
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
index 93666470b1b2c..f448edc692ce2 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
@@ -302,6 +302,7 @@
}
//updateContent(url); //commented since ajax requests replaced with http ones to load a category
+ jQuery('#tree-div').find('.x-tree-node-el').first().remove();
}
jQuery(function () {
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
index dbe66ef1aecd3..69737b8a37c1c 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
@@ -160,7 +160,7 @@ jQuery(function()
loader: categoryLoader,
enableDD: false,
containerScroll: true,
- rootVisible: '= /* @escapeNotVerified */ $block->getRoot()->getIsVisible() ?>',
+ rootVisible: false,
useAjax: true,
currentNodeId: = (int) $block->getCategoryId() ?>,
addNodeTo: false
@@ -177,7 +177,7 @@ jQuery(function()
text: 'Psw',
draggable: false,
id: = (int) $block->getRoot()->getId() ?>,
- expanded: = (int) $block->getIsWasExpanded() ?>,
+ expanded: true,
category_id: = (int) $block->getCategoryId() ?>
};
diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product.php b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
index 52ab8d49ec546..75249e4907862 100644
--- a/app/code/Magento/CatalogImportExport/Model/Export/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Export/Product.php
@@ -351,6 +351,7 @@ class Product extends \Magento\ImportExport\Model\Export\Entity\AbstractEntity
/**
* Product constructor.
+ *
* @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
* @param \Magento\Eav\Model\Config $config
* @param \Magento\Framework\App\ResourceConnection $resource
@@ -941,15 +942,17 @@ protected function getExportData()
protected function loadCollection(): array
{
$data = [];
-
$collection = $this->_getEntityCollection();
foreach (array_keys($this->_storeIdToCode) as $storeId) {
+ $collection->setOrder('entity_id', 'asc');
+ $this->_prepareEntityCollection($collection);
$collection->setStoreId($storeId);
+ $collection->load();
foreach ($collection as $itemId => $item) {
$data[$itemId][$storeId] = $item;
}
+ $collection->clear();
}
- $collection->clear();
return $data;
}
diff --git a/app/code/Magento/CatalogRule/Model/Rule/Condition/ConditionsToSearchCriteriaMapper.php b/app/code/Magento/CatalogRule/Model/Rule/Condition/ConditionsToSearchCriteriaMapper.php
index 6d343fe149d21..fabe504fbe31c 100644
--- a/app/code/Magento/CatalogRule/Model/Rule/Condition/ConditionsToSearchCriteriaMapper.php
+++ b/app/code/Magento/CatalogRule/Model/Rule/Condition/ConditionsToSearchCriteriaMapper.php
@@ -71,6 +71,8 @@ public function mapConditionsToSearchCriteria(CombinedCondition $conditions): Se
}
/**
+ * Convert condition to filter group
+ *
* @param ConditionInterface $condition
* @return null|\Magento\Framework\Api\CombinedFilterGroup|\Magento\Framework\Api\Filter
* @throws InputException
@@ -89,6 +91,8 @@ private function mapConditionToFilterGroup(ConditionInterface $condition)
}
/**
+ * Convert combined condition to filter group
+ *
* @param Combine $combinedCondition
* @return null|\Magento\Framework\Api\CombinedFilterGroup
* @throws InputException
@@ -121,6 +125,8 @@ private function mapCombinedConditionToFilterGroup(CombinedCondition $combinedCo
}
/**
+ * Convert simple condition to filter group
+ *
* @param ConditionInterface $productCondition
* @return FilterGroup|Filter
* @throws InputException
@@ -139,6 +145,8 @@ private function mapSimpleConditionToFilterGroup(ConditionInterface $productCond
}
/**
+ * Convert simple condition with array value to filter group
+ *
* @param ConditionInterface $productCondition
* @return FilterGroup
* @throws InputException
@@ -161,6 +169,8 @@ private function processSimpleConditionWithArrayValue(ConditionInterface $produc
}
/**
+ * Get glue for multiple values by operator
+ *
* @param string $operator
* @return string
*/
@@ -211,6 +221,8 @@ private function reverseSqlOperatorInFilter(Filter $filter)
}
/**
+ * Convert filters array into combined filter group
+ *
* @param array $filters
* @param string $combinationMode
* @return FilterGroup
@@ -227,6 +239,8 @@ private function createCombinedFilterGroup(array $filters, string $combinationMo
}
/**
+ * Creating of filter object by filtering params
+ *
* @param string $field
* @param string $value
* @param string $conditionType
@@ -264,6 +278,7 @@ private function mapRuleOperatorToSQLCondition(string $ruleOperator): string
'!{}' => 'nlike', // does not contains
'()' => 'in', // is one of
'!()' => 'nin', // is not one of
+ '<=>' => 'is_null'
];
if (!array_key_exists($ruleOperator, $operatorsMap)) {
diff --git a/app/code/Magento/CatalogRule/Model/Rule/Condition/Product.php b/app/code/Magento/CatalogRule/Model/Rule/Condition/Product.php
index ab650c94a0f08..0db178b2a0a6d 100644
--- a/app/code/Magento/CatalogRule/Model/Rule/Condition/Product.php
+++ b/app/code/Magento/CatalogRule/Model/Rule/Condition/Product.php
@@ -4,12 +4,11 @@
* See COPYING.txt for license details.
*/
-/**
- * Catalog Rule Product Condition data model
- */
namespace Magento\CatalogRule\Model\Rule\Condition;
/**
+ * Catalog Rule Product Condition data model
+ *
* @method string getAttribute() Returns attribute code
*/
class Product extends \Magento\Rule\Model\Condition\Product\AbstractProduct
@@ -29,6 +28,9 @@ public function validate(\Magento\Framework\Model\AbstractModel $model)
$oldAttrValue = $model->getData($attrCode);
if ($oldAttrValue === null) {
+ if ($this->getOperator() === '<=>') {
+ return true;
+ }
return false;
}
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml
index fe4042e8a2e9f..b0c4f2d8a609f 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/CatalogPriceRuleActionGroup.xml
@@ -36,6 +36,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -77,4 +112,8 @@
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml
index 71bdfe0613bb7..5b75708d1ae0a 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Data/CatalogRuleData.xml
@@ -77,4 +77,21 @@
by_percent
96
+
+
+ CatalogPriceRule
+ Catalog Price Rule Description
+ 1
+
+ - 0
+ - 1
+ - 2
+ - 3
+
+
+ - 1
+
+ by_percent
+ 10
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml
index 7cfb5bf40be55..635260888e7fb 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Section/AdminNewCatalogPriceRuleSection.xml
@@ -41,6 +41,8 @@
+
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
new file mode 100644
index 0000000000000..053a8c33e640c
--- /dev/null
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ website
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
index 711d5a2da9ff5..9e47830debfc4 100644
--- a/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
+++ b/app/code/Magento/CatalogWidget/Block/Product/ProductsList.php
@@ -197,6 +197,7 @@ public function getCacheKeyInfo()
$this->httpContext->getValue(\Magento\Customer\Model\Context::CONTEXT_GROUP),
(int) $this->getRequest()->getParam($this->getData('page_var_name'), 1),
$this->getProductsPerPage(),
+ $this->getProductsCount(),
$conditions,
$this->json->serialize($this->getRequest()->getParams()),
$this->getTemplate(),
diff --git a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
index dc6e100ab1ad8..a789753795724 100644
--- a/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
+++ b/app/code/Magento/CatalogWidget/Test/Unit/Block/Product/ProductsListTest.php
@@ -167,6 +167,7 @@ public function testGetCacheKeyInfo()
'context_group',
1,
5,
+ 10,
'some_serialized_conditions',
json_encode('request_params'),
'test_template',
diff --git a/app/code/Magento/Checkout/Block/Cart/Sidebar.php b/app/code/Magento/Checkout/Block/Cart/Sidebar.php
index 92ba6bf2bbbb1..c5e309df3cad6 100644
--- a/app/code/Magento/Checkout/Block/Cart/Sidebar.php
+++ b/app/code/Magento/Checkout/Block/Cart/Sidebar.php
@@ -82,11 +82,14 @@ public function getConfig()
'baseUrl' => $this->getBaseUrl(),
'minicartMaxItemsVisible' => $this->getMiniCartMaxItemsCount(),
'websiteId' => $this->_storeManager->getStore()->getWebsiteId(),
- 'maxItemsToDisplay' => $this->getMaxItemsToDisplay()
+ 'maxItemsToDisplay' => $this->getMaxItemsToDisplay(),
+ 'storeId' => $this->_storeManager->getStore()->getId()
];
}
/**
+ * Get serialized config
+ *
* @return string
* @since 100.2.0
*/
@@ -96,6 +99,8 @@ public function getSerializedConfig()
}
/**
+ * Get image html template
+ *
* @return string
*/
public function getImageHtmlTemplate()
@@ -130,6 +135,7 @@ public function getShoppingCartUrl()
*
* @return string
* @codeCoverageIgnore
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getUpdateItemQtyUrl()
{
@@ -141,6 +147,7 @@ public function getUpdateItemQtyUrl()
*
* @return string
* @codeCoverageIgnore
+ * @SuppressWarnings(PHPMD.RequestAwareBlockMethod)
*/
public function getRemoveItemUrl()
{
@@ -210,6 +217,7 @@ private function getMiniCartMaxItemsCount()
/**
* Returns maximum cart items to display
+ *
* This setting regulates how many items will be displayed in minicart
*
* @return int
diff --git a/app/code/Magento/Checkout/CustomerData/Cart.php b/app/code/Magento/Checkout/CustomerData/Cart.php
index 01e91d75c02d9..169be4cc62f01 100644
--- a/app/code/Magento/Checkout/CustomerData/Cart.php
+++ b/app/code/Magento/Checkout/CustomerData/Cart.php
@@ -10,6 +10,8 @@
/**
* Cart source
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class Cart extends \Magento\Framework\DataObject implements SectionSourceInterface
{
@@ -98,7 +100,8 @@ public function getSectionData()
'items' => $this->getRecentItems(),
'extra_actions' => $this->layout->createBlock(\Magento\Catalog\Block\ShortcutButtons::class)->toHtml(),
'isGuestCheckoutAllowed' => $this->isGuestCheckoutAllowed(),
- 'website_id' => $this->getQuote()->getStore()->getWebsiteId()
+ 'website_id' => $this->getQuote()->getStore()->getWebsiteId(),
+ 'storeId' => $this->getQuote()->getStore()->getStoreId()
];
}
diff --git a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php
index d2bd680aa38f3..e0de45a3f0dea 100644
--- a/app/code/Magento/Checkout/Model/PaymentInformationManagement.php
+++ b/app/code/Magento/Checkout/Model/PaymentInformationManagement.php
@@ -118,7 +118,9 @@ public function savePaymentInformation(
$shippingAddress = $quote->getShippingAddress();
if ($shippingAddress && $shippingAddress->getShippingMethod()) {
$shippingRate = $shippingAddress->getShippingRateByCode($shippingAddress->getShippingMethod());
- $shippingAddress->setLimitCarrier($shippingRate->getCarrier());
+ $shippingAddress->setLimitCarrier(
+ $shippingRate ? $shippingRate->getCarrier() : $shippingAddress->getShippingMethod()
+ );
}
}
$this->paymentMethodManagement->set($cartId, $paymentMethod);
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml
index 0d3c6e419cc07..dcfb12fd4e965 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartProductSection.xml
@@ -26,6 +26,7 @@
parameterized="true"/>
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml
index ad2a43eb90c8c..6838824400b96 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingGuestInfoSection.xml
@@ -19,5 +19,9 @@
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
index a894f2fbb1af9..bdb02835c6276 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
@@ -13,6 +13,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml
new file mode 100644
index 0000000000000..3401369a8c749
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontProductNameMinicartOnCheckoutPageDifferentStoreViewsTest.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php
index 1c5224d007ec8..f69ced3b094c7 100644
--- a/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php
+++ b/app/code/Magento/Checkout/Test/Unit/Block/Cart/SidebarTest.php
@@ -144,7 +144,8 @@ public function testGetConfig()
'baseUrl' => $baseUrl,
'minicartMaxItemsVisible' => 3,
'websiteId' => 100,
- 'maxItemsToDisplay' => 8
+ 'maxItemsToDisplay' => 8,
+ 'storeId' => null
];
$valueMap = [
@@ -161,7 +162,7 @@ public function testGetConfig()
$this->urlBuilderMock->expects($this->exactly(4))
->method('getUrl')
->willReturnMap($valueMap);
- $this->storeManagerMock->expects($this->exactly(2))->method('getStore')->willReturn($storeMock);
+ $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock);
$storeMock->expects($this->once())->method('getBaseUrl')->willReturn($baseUrl);
$this->scopeConfigMock->expects($this->at(0))
diff --git a/app/code/Magento/Checkout/Test/Unit/CustomerData/CartTest.php b/app/code/Magento/Checkout/Test/Unit/CustomerData/CartTest.php
index 75e181cbabd08..e3e13cc5b1e69 100644
--- a/app/code/Magento/Checkout/Test/Unit/CustomerData/CartTest.php
+++ b/app/code/Magento/Checkout/Test/Unit/CustomerData/CartTest.php
@@ -113,7 +113,7 @@ public function testGetSectionData()
$storeMock = $this->createPartialMock(\Magento\Store\Model\System\Store::class, ['getWebsiteId']);
$storeMock->expects($this->once())->method('getWebsiteId')->willReturn($websiteId);
- $quoteMock->expects($this->once())->method('getStore')->willReturn($storeMock);
+ $quoteMock->expects($this->any())->method('getStore')->willReturn($storeMock);
$productMock = $this->createPartialMock(
\Magento\Catalog\Model\Product::class,
@@ -162,6 +162,7 @@ public function testGetSectionData()
'isGuestCheckoutAllowed' => 1,
'website_id' => $websiteId,
'subtotalAmount' => 200,
+ 'storeId' => null
];
$this->assertEquals($expectedResult, $this->model->getSectionData());
}
@@ -199,7 +200,7 @@ public function testGetSectionDataWithCompositeProduct()
$storeMock = $this->createPartialMock(\Magento\Store\Model\System\Store::class, ['getWebsiteId']);
$storeMock->expects($this->once())->method('getWebsiteId')->willReturn($websiteId);
- $quoteMock->expects($this->once())->method('getStore')->willReturn($storeMock);
+ $quoteMock->expects($this->any())->method('getStore')->willReturn($storeMock);
$this->checkoutCartMock->expects($this->once())->method('getSummaryQty')->willReturn($summaryQty);
$this->checkoutHelperMock->expects($this->once())
@@ -265,6 +266,7 @@ public function testGetSectionDataWithCompositeProduct()
'isGuestCheckoutAllowed' => 1,
'website_id' => $websiteId,
'subtotalAmount' => 200,
+ 'storeId' => null
];
$this->assertEquals($expectedResult, $this->model->getSectionData());
}
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js
index a2f8c8c56ff33..5e29fa209a641 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js
@@ -81,6 +81,7 @@ define([
maxItemsToDisplay: window.checkout.maxItemsToDisplay,
cart: {},
+ // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
/**
* @override
*/
@@ -101,12 +102,16 @@ define([
self.isLoading(true);
});
- if (cartData()['website_id'] !== window.checkout.websiteId) {
+ if (cartData().website_id !== window.checkout.websiteId ||
+ cartData().store_id !== window.checkout.storeId
+ ) {
customerData.reload(['cart'], false);
}
return this._super();
},
+ //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
+
isLoading: ko.observable(false),
initSidebar: initSidebar,
diff --git a/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml
new file mode 100644
index 0000000000000..2fa1b86a61572
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml b/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml
new file mode 100644
index 0000000000000..885310d9399ae
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml
index 8559334238d2f..ff6167ffc10e0 100644
--- a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml
@@ -31,6 +31,8 @@
+
+
@@ -99,6 +101,7 @@
+
@@ -111,6 +114,7 @@
+
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
index 06c041fabeb35..1a7b641070ad8 100644
--- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
+++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
@@ -28,4 +28,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml
index 82411faddfed7..eefaf5f3b539c 100644
--- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml
+++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml
@@ -38,4 +38,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php
index 2502b79921e99..e07879e93a6b4 100644
--- a/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php
+++ b/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php
@@ -15,6 +15,8 @@
use Magento\Framework\Pricing\PriceCurrencyInterface;
/**
+ * Confugurable product view type
+ *
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @api
@@ -276,6 +278,8 @@ protected function getOptionImages()
}
/**
+ * Collect price options
+ *
* @return array
*/
protected function getOptionPrices()
@@ -314,6 +318,11 @@ protected function getOptionPrices()
),
],
'tierPrices' => $tierPrices,
+ 'msrpPrice' => [
+ 'amount' => $this->localeFormat->getNumber(
+ $product->getMsrp()
+ ),
+ ],
];
}
return $prices;
diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
index f98075f2294cc..46f10608bc95e 100644
--- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
+++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
@@ -24,6 +24,7 @@
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @api
* @since 100.0.2
*/
@@ -1385,7 +1386,7 @@ function ($item) {
*/
private function getUsedProductsCacheKey($keyParts)
{
- return md5(implode('_', $keyParts));
+ return sha1(implode('_', $keyParts));
}
/**
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php
index 25d8412c91056..c5c2368720b98 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php
@@ -379,6 +379,9 @@ private function getExpectedArray($productId, $amount, $priceQty, $percentage):
'percentage' => $percentage,
],
],
+ 'msrpPrice' => [
+ 'amount' => null ,
+ ]
],
],
'priceFormat' => [],
diff --git a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
index 1df84d27a5c30..e732960421541 100644
--- a/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
+++ b/app/code/Magento/ConfigurableProduct/view/frontend/web/js/configurable.js
@@ -609,6 +609,13 @@ define([
} else {
$(this.options.slyOldPriceSelector).hide();
}
+
+ $(document).trigger('updateMsrpPriceBlock',
+ [
+ optionId,
+ this.options.spConfig.optionPrices
+ ]
+ );
},
/**
diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
index 3f2c7cda7608d..1bc6bb1da3680 100644
--- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
+++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
@@ -71,7 +71,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function _construct()
{
@@ -94,7 +94,7 @@ protected function _prepareCollection()
$quote = $this->getQuote();
if ($quote) {
- $collection = $quote->getItemsCollection(false);
+ $collection = $quote->getItemsCollection(true);
} else {
$collection = $this->_dataCollectionFactory->create();
}
@@ -106,7 +106,7 @@ protected function _prepareCollection()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function _prepareColumns()
{
@@ -144,7 +144,7 @@ protected function _prepareColumns()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getRowUrl($row)
{
@@ -152,7 +152,7 @@ public function getRowUrl($row)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getHeadersVisibility()
{
diff --git a/app/code/Magento/Customer/Block/Widget/Name.php b/app/code/Magento/Customer/Block/Widget/Name.php
index d50045f4a4092..6f1b051af7465 100644
--- a/app/code/Magento/Customer/Block/Widget/Name.php
+++ b/app/code/Magento/Customer/Block/Widget/Name.php
@@ -55,7 +55,7 @@ public function __construct(
}
/**
- * @return void
+ * @inheritdoc
*/
public function _construct()
{
@@ -245,10 +245,13 @@ public function getStoreLabel($attributeCode)
*/
public function getAttributeValidationClass($attributeCode)
{
- return $this->_addressHelper->getAttributeValidationClass($attributeCode);
+ $attributeMetadata = $this->_getAttribute($attributeCode);
+ return $attributeMetadata ? $attributeMetadata->getFrontendClass() : '';
}
/**
+ * Check if attribute is required
+ *
* @param string $attributeCode
* @return bool
*/
@@ -259,6 +262,8 @@ private function _isAttributeRequired($attributeCode)
}
/**
+ * Check if attribute is visible
+ *
* @param string $attributeCode
* @return bool
*/
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
index 7482193031091..0e31f0e0c7782 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
@@ -10,5 +10,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php b/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php
index 5cdcc6eb99af5..b752eaa111fa4 100644
--- a/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php
+++ b/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php
@@ -206,12 +206,15 @@ private function getElementsWithAutogeneratedName(Schema $schema, string $tableN
foreach ($tableData[$elementType] as $tableElementData) {
if ($tableElementData['type'] === 'foreign') {
$referenceTable = $schema->getTableByName($tableElementData['referenceTable']);
- $constraintName = $this->elementNameResolver->getFullFKName(
- $table,
- $table->getColumnByName($tableElementData['column']),
- $referenceTable,
- $referenceTable->getColumnByName($tableElementData['referenceColumn'])
- );
+ $column = $table->getColumnByName($tableElementData['column']);
+ $referenceColumn = $referenceTable->getColumnByName($tableElementData['referenceColumn']);
+ $constraintName = ($column !== false && $referenceColumn !== false) ?
+ $this->elementNameResolver->getFullFKName(
+ $table,
+ $column,
+ $referenceTable,
+ $referenceColumn
+ ) : null;
} else {
$constraintName = $this->elementNameResolver->getFullIndexName(
$table,
@@ -219,7 +222,9 @@ private function getElementsWithAutogeneratedName(Schema $schema, string $tableN
$tableElementData['type']
);
}
- $declaredStructure[$elementType][$constraintName] = true;
+ if ($constraintName) {
+ $declaredStructure[$elementType][$constraintName] = true;
+ }
}
}
diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
index 0522ea0432176..d0a5e8de53ae9 100644
--- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
+++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
@@ -10,6 +10,7 @@
use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
+use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
use Magento\Framework\App\Config\Element;
use Magento\Framework\DataObject;
use Magento\Framework\DB\Adapter\DuplicateException;
@@ -215,12 +216,21 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac
*/
protected $objectRelationProcessor;
+ /**
+ * @var UniqueValidationInterface
+ */
+ private $uniqueValidator;
+
/**
* @param Context $context
* @param array $data
+ * @param UniqueValidationInterface|null $uniqueValidator
*/
- public function __construct(Context $context, $data = [])
- {
+ public function __construct(
+ Context $context,
+ $data = [],
+ UniqueValidationInterface $uniqueValidator = null
+ ) {
$this->_eavConfig = $context->getEavConfig();
$this->_resource = $context->getResource();
$this->_attrSetEntity = $context->getAttributeSetEntity();
@@ -229,6 +239,8 @@ public function __construct(Context $context, $data = [])
$this->_universalFactory = $context->getUniversalFactory();
$this->transactionManager = $context->getTransactionManager();
$this->objectRelationProcessor = $context->getObjectRelationProcessor();
+ $this->uniqueValidator = $uniqueValidator ?:
+ ObjectManager::getInstance()->get(UniqueValidationInterface::class);
parent::__construct();
$properties = get_object_vars($this);
foreach ($data as $key => $value) {
@@ -488,6 +500,7 @@ public function addAttribute(AbstractAttribute $attribute, $object = null)
/**
* Get attributes by scope
*
+ * @param string $suffix
* @return array
*/
private function getAttributesByScope($suffix)
@@ -958,12 +971,8 @@ public function checkAttributeUniqueValue(AbstractAttribute $attribute, $object)
$data = $connection->fetchCol($select, $bind);
- $objectId = $object->getData($entityIdField);
- if ($objectId) {
- if (isset($data[0])) {
- return $data[0] == $objectId;
- }
- return true;
+ if ($object->getData($entityIdField)) {
+ return $this->uniqueValidator->validate($attribute, $object, $this, $entityIdField, $data);
}
return !count($data);
@@ -1972,7 +1981,8 @@ public function afterDelete(DataObject $object)
/**
* Load attributes for object
- * if the object will not pass all attributes for this entity type will be loaded
+ *
+ * If the object will not pass all attributes for this entity type will be loaded
*
* @param array $attributes
* @param AbstractEntity|null $object
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php b/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
index 0991b3f9f4b23..56188ab997b76 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
@@ -80,6 +80,8 @@ public function getOptionText($value)
}
/**
+ * Get option id.
+ *
* @param string $value
* @return null|string
*/
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php b/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php
new file mode 100644
index 0000000000000..b68e79d7b7d20
--- /dev/null
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php
@@ -0,0 +1,33 @@
+getData($entityLinkField);
+ }
+ return true;
+ }
+}
diff --git a/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php b/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
index cd2fe7477ca60..7f6dfa2a5e9ab 100644
--- a/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
+++ b/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
@@ -5,13 +5,19 @@
*/
namespace Magento\Eav\Model\ResourceModel;
+use Magento\Eav\Model\Config;
use Magento\Framework\DataObject;
+use Magento\Framework\DB\Select;
+use Magento\Framework\DB\Sql\UnionExpression;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\EntityManager\Operation\AttributeInterface;
use Magento\Framework\Model\Entity\ScopeInterface;
use Magento\Framework\Model\Entity\ScopeResolver;
use Psr\Log\LoggerInterface;
+/**
+ * EAV read handler
+ */
class ReadHandler implements AttributeInterface
{
/**
@@ -30,23 +36,21 @@ class ReadHandler implements AttributeInterface
private $logger;
/**
- * @var \Magento\Eav\Model\Config
+ * @var Config
*/
private $config;
/**
- * ReadHandler constructor.
- *
* @param MetadataPool $metadataPool
* @param ScopeResolver $scopeResolver
* @param LoggerInterface $logger
- * @param \Magento\Eav\Model\Config $config
+ * @param Config $config
*/
public function __construct(
MetadataPool $metadataPool,
ScopeResolver $scopeResolver,
LoggerInterface $logger,
- \Magento\Eav\Model\Config $config
+ Config $config
) {
$this->metadataPool = $metadataPool;
$this->scopeResolver = $scopeResolver;
@@ -86,6 +90,8 @@ private function getEntityAttributes(string $entityType, DataObject $entity): ar
}
/**
+ * Get context variables
+ *
* @param ScopeInterface $scope
* @return array
*/
@@ -99,6 +105,8 @@ protected function getContextVariables(ScopeInterface $scope)
}
/**
+ * Execute read handler
+ *
* @param string $entityType
* @param array $entityData
* @param array $arguments
@@ -129,33 +137,40 @@ public function execute($entityType, $entityData, $arguments = [])
}
}
if (count($attributeTables)) {
- $attributeTables = array_keys($attributeTables);
- foreach ($attributeTables as $attributeTable) {
+ $identifiers = null;
+ foreach ($attributeTables as $attributeTable => $attributeIds) {
$select = $connection->select()
->from(
['t' => $attributeTable],
['value' => 't.value', 'attribute_id' => 't.attribute_id']
)
- ->where($metadata->getLinkField() . ' = ?', $entityData[$metadata->getLinkField()]);
+ ->where($metadata->getLinkField() . ' = ?', $entityData[$metadata->getLinkField()])
+ ->where('attribute_id IN (?)', $attributeIds);
+ $attributeIdentifiers = [];
foreach ($context as $scope) {
//TODO: if (in table exists context field)
$select->where(
- $metadata->getEntityConnection()->quoteIdentifier($scope->getIdentifier()) . ' IN (?)',
+ $connection->quoteIdentifier($scope->getIdentifier()) . ' IN (?)',
$this->getContextVariables($scope)
- )->order('t.' . $scope->getIdentifier() . ' DESC');
+ );
+ $attributeIdentifiers[] = $scope->getIdentifier();
}
+ $attributeIdentifiers = array_unique($attributeIdentifiers);
+ $identifiers = array_intersect($identifiers ?? $attributeIdentifiers, $attributeIdentifiers);
$selects[] = $select;
}
- $unionSelect = new \Magento\Framework\DB\Sql\UnionExpression(
- $selects,
- \Magento\Framework\DB\Select::SQL_UNION_ALL
- );
- foreach ($connection->fetchAll($unionSelect) as $attributeValue) {
+ $this->applyIdentifierForSelects($selects, $identifiers);
+ $unionSelect = new UnionExpression($selects, Select::SQL_UNION_ALL, '( %s )');
+ $orderedUnionSelect = $connection->select();
+ $orderedUnionSelect->from(['u' => $unionSelect]);
+ $this->applyIdentifierForUnion($orderedUnionSelect, $identifiers);
+ $attributes = $connection->fetchAll($orderedUnionSelect);
+ foreach ($attributes as $attributeValue) {
if (isset($attributesMap[$attributeValue['attribute_id']])) {
$entityData[$attributesMap[$attributeValue['attribute_id']]] = $attributeValue['value'];
} else {
$this->logger->warning(
- "Attempt to load value of nonexistent EAV attribute '{$attributeValue['attribute_id']}'
+ "Attempt to load value of nonexistent EAV attribute '{$attributeValue['attribute_id']}'
for entity type '$entityType'."
);
}
@@ -163,4 +178,32 @@ public function execute($entityType, $entityData, $arguments = [])
}
return $entityData;
}
+
+ /**
+ * Apply identifiers column on select array
+ *
+ * @param Select[] $selects
+ * @param array $identifiers
+ */
+ private function applyIdentifierForSelects(array $selects, array $identifiers)
+ {
+ foreach ($selects as $select) {
+ foreach ($identifiers as $identifier) {
+ $select->columns($identifier, 't');
+ }
+ }
+ }
+
+ /**
+ * Apply identifiers order on union select
+ *
+ * @param Select $unionSelect
+ * @param array $identifiers
+ */
+ private function applyIdentifierForUnion(Select $unionSelect, array $identifiers)
+ {
+ foreach ($identifiers as $identifier) {
+ $unionSelect->order($identifier);
+ }
+ }
}
diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml
index 8e897b979d2f0..a4c89dcfab2af 100644
--- a/app/code/Magento/Eav/etc/di.xml
+++ b/app/code/Magento/Eav/etc/di.xml
@@ -8,6 +8,7 @@
+
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
index e4f5de46c4c86..270ca37e2d42c 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
@@ -12,12 +12,18 @@
use Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface;
use Magento\Elasticsearch\Model\Adapter\FieldType\Date as DateFieldType;
use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface;
+use Magento\Eav\Api\Data\AttributeOptionInterface;
/**
* Map product index data to search engine metadata
*/
class ProductDataMapper implements BatchDataMapperInterface
{
+ /**
+ * @var AttributeOptionInterface[]
+ */
+ private $attributeOptionsCache;
+
/**
* @var Builder
*/
@@ -95,6 +101,7 @@ public function __construct(
$this->excludedAttributes = array_merge($this->defaultExcludedAttributes, $excludedAttributes);
$this->additionalFieldsProvider = $additionalFieldsProvider;
$this->dataProvider = $dataProvider;
+ $this->attributeOptionsCache = [];
}
/**
@@ -272,7 +279,13 @@ private function isAttributeDate(Attribute $attribute): bool
private function getValuesLabels(Attribute $attribute, array $attributeValues): array
{
$attributeLabels = [];
- foreach ($attribute->getOptions() as $option) {
+
+ $options = $this->getAttributeOptions($attribute);
+ if (empty($options)) {
+ return $attributeLabels;
+ }
+
+ foreach ($options as $option) {
if (\in_array($option->getValue(), $attributeValues)) {
$attributeLabels[] = $option->getLabel();
}
@@ -281,6 +294,22 @@ private function getValuesLabels(Attribute $attribute, array $attributeValues):
return $attributeLabels;
}
+ /**
+ * Retrieve options for attribute
+ *
+ * @param Attribute $attribute
+ * @return array
+ */
+ private function getAttributeOptions(Attribute $attribute): array
+ {
+ if (!isset($this->attributeOptionsCache[$attribute->getId()])) {
+ $options = $attribute->getOptions() ?? [];
+ $this->attributeOptionsCache[$attribute->getId()] = $options;
+ }
+
+ return $this->attributeOptionsCache[$attribute->getId()];
+ }
+
/**
* Retrieve value for field. If field have only one value this method return it.
* Otherwise will be returned array of these values.
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php
index c7e2a4beabb5c..9e2659a757924 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php
@@ -126,7 +126,7 @@ public function getFields(array $context = []): array
foreach ($groups as $group) {
$groupPriceKey = $this->fieldNameResolver->getFieldName(
$priceAttribute,
- ['customerGroupId' => $group->getId(), 'websiteId' => $context['websiteId']]
+ array_merge($context, ['customerGroupId' => $group->getId()])
);
$allAttributes[$groupPriceKey] = [
'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_FLOAT),
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
index bcfb7f5565b86..eeb48f805bccf 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
@@ -8,10 +8,13 @@
use Magento\Framework\Search\Request\BucketInterface as RequestBucketInterface;
use Magento\Framework\Search\Dynamic\DataProviderInterface;
+/**
+ * Builder for term buckets.
+ */
class Term implements BucketBuilderInterface
{
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function build(
RequestBucketInterface $bucket,
@@ -19,13 +22,15 @@ public function build(
array $queryResult,
DataProviderInterface $dataProvider
) {
+ $buckets = $queryResult['aggregations'][$bucket->getName()]['buckets'] ?? [];
$values = [];
- foreach ($queryResult['aggregations'][$bucket->getName()]['buckets'] as $resultBucket) {
+ foreach ($buckets as $resultBucket) {
$values[$resultBucket['key']] = [
'value' => $resultBucket['key'],
'count' => $resultBucket['doc_count'],
];
}
+
return $values;
}
}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
index f1c3451482bab..e83c49941bcc2 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
@@ -5,11 +5,16 @@
*/
namespace Magento\Elasticsearch\SearchAdapter\Query\Builder;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Search\Request\Query\BoolExpression;
use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface;
+/**
+ * Builder for match query.
+ */
class Match implements QueryInterface
{
/**
@@ -23,24 +28,35 @@ class Match implements QueryInterface
private $fieldMapper;
/**
+ * @deprecated
+ * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
* @var PreprocessorInterface[]
*/
protected $preprocessorContainer;
+ /**
+ * @var ValueTransformerPool
+ */
+ private $valueTransformerPool;
+
/**
* @param FieldMapperInterface $fieldMapper
* @param PreprocessorInterface[] $preprocessorContainer
+ * @param ValueTransformerPool|null $valueTransformerPool
*/
public function __construct(
FieldMapperInterface $fieldMapper,
- array $preprocessorContainer
+ array $preprocessorContainer,
+ ValueTransformerPool $valueTransformerPool = null
) {
$this->fieldMapper = $fieldMapper;
$this->preprocessorContainer = $preprocessorContainer;
+ $this->valueTransformerPool = $valueTransformerPool ?? ObjectManager::getInstance()
+ ->get(ValueTransformerPool::class);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function build(array $selectQuery, RequestQueryInterface $requestQuery, $conditionType)
{
@@ -61,16 +77,14 @@ public function build(array $selectQuery, RequestQueryInterface $requestQuery, $
}
/**
+ * Prepare query.
+ *
* @param string $queryValue
* @param string $conditionType
* @return array
*/
protected function prepareQuery($queryValue, $conditionType)
{
- $queryValue = $this->escape($queryValue);
- foreach ($this->preprocessorContainer as $preprocessor) {
- $queryValue = $preprocessor->process($queryValue);
- }
$condition = $conditionType === BoolExpression::QUERY_CONDITION_NOT ?
self::QUERY_CONDITION_MUST_NOT : $conditionType;
return [
@@ -99,21 +113,34 @@ protected function buildQueries(array $matches, array $queryValue)
// Checking for quoted phrase \"phrase test\", trim escaped surrounding quotes if found
$count = 0;
- $value = preg_replace('#^\\\\"(.*)\\\\"$#m', '$1', $queryValue['value'], -1, $count);
+ $value = preg_replace('#^"(.*)"$#m', '$1', $queryValue['value'], -1, $count);
$condition = ($count) ? 'match_phrase' : 'match';
+ $attributesTypes = $this->fieldMapper->getAllAttributesTypes();
+ $transformedTypes = [];
foreach ($matches as $match) {
$resolvedField = $this->fieldMapper->getFieldName(
$match['field'],
['type' => FieldMapperInterface::TYPE_QUERY]
);
+ $valueTransformer = $this->valueTransformerPool->get($attributesTypes[$resolvedField]['type'] ?? 'text');
+ $valueTransformerHash = \spl_object_hash($valueTransformer);
+ if (!isset($transformedTypes[$valueTransformerHash])) {
+ $transformedTypes[$valueTransformerHash] = $valueTransformer->transform($value);
+ }
+ $transformedValue = $transformedTypes[$valueTransformerHash];
+ if (null === $transformedValue) {
+ //Value is incompatible with this field type.
+ continue;
+ }
+
$conditions[] = [
'condition' => $queryValue['condition'],
'body' => [
$condition => [
$resolvedField => [
- 'query' => $value,
- 'boost' => isset($match['boost']) ? $match['boost'] : 1,
+ 'query' => $transformedValue,
+ 'boost' => $match['boost'] ?? 1,
],
],
],
@@ -124,18 +151,15 @@ protected function buildQueries(array $matches, array $queryValue)
}
/**
- * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error.
- * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs.
- *
* Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
*
+ * @deprecated
+ * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
* @param string $value
* @return string
*/
protected function escape($value)
{
- $value = preg_replace('/@+|[@+-]+$/', '', $value);
-
$pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
$replace = '\\\$1';
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php
new file mode 100644
index 0000000000000..49eca6e9d82a6
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php
@@ -0,0 +1,44 @@
+dateFieldType = $dateFieldType;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function transform(string $value): ?string
+ {
+ try {
+ $formattedDate = $this->dateFieldType->formatDate(null, $value);
+ } catch (\Exception $e) {
+ $formattedDate = null;
+ }
+
+ return $formattedDate;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php
new file mode 100644
index 0000000000000..5e330076d3df7
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php
@@ -0,0 +1,24 @@
+preprocessors = $preprocessors;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function transform(string $value): string
+ {
+ $value = $this->escape($value);
+ foreach ($this->preprocessors as $preprocessor) {
+ $value = $preprocessor->process($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
+ *
+ * @param string $value
+ * @return string
+ */
+ private function escape(string $value): string
+ {
+ $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
+ $replace = '\\\$1';
+
+ return preg_replace($pattern, $replace, $value);
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php
new file mode 100644
index 0000000000000..c84ddc69cc7a8
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php
@@ -0,0 +1,22 @@
+transformers = $valueTransformers;
+ }
+
+ /**
+ * Get value transformer related to field type.
+ *
+ * @param string $fieldType
+ * @return ValueTransformerInterface
+ */
+ public function get(string $fieldType): ValueTransformerInterface
+ {
+ return $this->transformers[$fieldType] ?? $this->transformers['default'];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
index 8114feb09d35d..c8aa3db39bd5e 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
@@ -7,6 +7,8 @@
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
use Magento\Framework\Search\Request\Query\Match as MatchRequestQuery;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
@@ -23,46 +25,48 @@ class MatchTest extends \PHPUnit\Framework\TestCase
*/
protected function setUp()
{
+ $valueTransformerPoolMock = $this->createMock(ValueTransformerPool::class);
+ $valueTransformerMock = $this->createMock(ValueTransformerInterface::class);
+ $valueTransformerPoolMock->method('get')
+ ->willReturn($valueTransformerMock);
+ $valueTransformerMock->method('transform')
+ ->willReturnArgument(0);
+
$this->matchQueryBuilder = (new ObjectManager($this))->getObject(
MatchQueryBuilder::class,
[
'fieldMapper' => $this->getFieldMapper(),
'preprocessorContainer' => [],
+ 'valueTransformerPool' => $valueTransformerPoolMock,
]
);
}
/**
* Tests that method constructs a correct select query.
- * @see MatchQueryBuilder::build
*
- * @dataProvider queryValuesInvariantsProvider
- *
- * @param string $rawQueryValue
- * @param string $errorMessage
+ * @see MatchQueryBuilder::build
*/
- public function testBuild($rawQueryValue, $errorMessage)
+ public function testBuild()
{
- $this->assertSelectQuery(
- $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not'),
- $errorMessage
- );
- }
+ $rawQueryValue = 'query_value';
+ $selectQuery = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not');
- /**
- * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs.
- *
- * @return array
- */
- public function queryValuesInvariantsProvider()
- {
- return [
- ['query_value', 'Select query field must match simple raw query value.'],
- ['query_value+', 'Specifying a trailing plus sign causes InnoDB to report a syntax error.'],
- ['query_value-', 'Specifying a trailing minus sign causes InnoDB to report a syntax error.'],
- ['query_@value', 'The @ symbol is reserved for use by the @distance proximity search operator.'],
- ['query_value+@', 'The @ symbol is reserved for use by the @distance proximity search operator.'],
+ $expectedSelectQuery = [
+ 'bool' => [
+ 'must_not' => [
+ [
+ 'match' => [
+ 'some_field' => [
+ 'query' => $rawQueryValue,
+ 'boost' => 43,
+ ],
+ ],
+ ],
+ ],
+ ],
];
+ $this->assertEquals($expectedSelectQuery, $selectQuery);
}
/**
@@ -111,30 +115,6 @@ public function matchProvider()
];
}
- /**
- * @param array $selectQuery
- * @param string $errorMessage
- */
- private function assertSelectQuery($selectQuery, $errorMessage)
- {
- $expectedSelectQuery = [
- 'bool' => [
- 'must_not' => [
- [
- 'match' => [
- 'some_field' => [
- 'query' => 'query_value',
- 'boost' => 43,
- ],
- ],
- ],
- ],
- ],
- ];
-
- $this->assertEquals($expectedSelectQuery, $selectQuery, $errorMessage);
- }
-
/**
* Gets fieldMapper mock object.
*
diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml
index 7e219bb2f918f..05a67605ba0e6 100644
--- a/app/code/Magento/Elasticsearch/etc/di.xml
+++ b/app/code/Magento/Elasticsearch/etc/di.xml
@@ -421,4 +421,22 @@
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\DateTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\FloatTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\IntegerTransformer
+
+
+
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Query\Preprocessor\Stopwords
+ - Magento\Search\Adapter\Query\Preprocessor\Synonyms
+
+
+
diff --git a/app/code/Magento/Msrp/Helper/Data.php b/app/code/Magento/Msrp/Helper/Data.php
index b4ec34ebee19c..393383bb2e772 100644
--- a/app/code/Magento/Msrp/Helper/Data.php
+++ b/app/code/Magento/Msrp/Helper/Data.php
@@ -11,6 +11,7 @@
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
/**
* Msrp data helper
@@ -70,8 +71,7 @@ public function __construct(
}
/**
- * Check if can apply Minimum Advertise price to product
- * in specific visibility
+ * Check if can apply Minimum Advertise price to product in specific visibility
*
* @param int|Product $product
* @param int|null $visibility Check displaying price in concrete place (by default generally)
@@ -135,6 +135,8 @@ public function isShowPriceOnGesture($product)
}
/**
+ * Check if we should show MAP proce before order confirmation
+ *
* @param int|Product $product
* @return bool
*/
@@ -144,6 +146,8 @@ public function isShowBeforeOrderConfirm($product)
}
/**
+ * Check if any MAP price is larger than as low as value.
+ *
* @param int|Product $product
* @return bool|float
*/
@@ -155,10 +159,19 @@ public function isMinimalPriceLessMsrp($product)
$msrp = $product->getMsrp();
$price = $product->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE);
if ($msrp === null) {
- if ($product->getTypeId() !== \Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE) {
- return false;
- } else {
+ if ($product->getTypeId() === \Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE) {
$msrp = $product->getTypeInstance()->getChildrenMsrp($product);
+ } elseif ($product->getTypeId() === Configurable::TYPE_CODE) {
+ $prices = [];
+ foreach ($product->getTypeInstance()->getUsedProducts($product) as $item) {
+ if ($item->getMsrp() !== null) {
+ $prices[] = $item->getMsrp();
+ }
+ }
+
+ $msrp = $prices ? max($prices) : 0;
+ } else {
+ return false;
}
}
if ($msrp) {
diff --git a/app/code/Magento/Msrp/Test/Mftf/Data/MsrpSettingsData.xml b/app/code/Magento/Msrp/Test/Mftf/Data/MsrpSettingsData.xml
new file mode 100644
index 0000000000000..3922bb4868914
--- /dev/null
+++ b/app/code/Magento/Msrp/Test/Mftf/Data/MsrpSettingsData.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ EnableMAP
+
+
+ 1
+
+
+
+ DisableMAP
+
+
+ 0
+
+
diff --git a/app/code/Magento/Msrp/Test/Mftf/Metadata/msrp_settings-meta.xml b/app/code/Magento/Msrp/Test/Mftf/Metadata/msrp_settings-meta.xml
new file mode 100644
index 0000000000000..be91a548ad909
--- /dev/null
+++ b/app/code/Magento/Msrp/Test/Mftf/Metadata/msrp_settings-meta.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml
new file mode 100644
index 0000000000000..a874de3b223a2
--- /dev/null
+++ b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Msrp/composer.json b/app/code/Magento/Msrp/composer.json
index 6e7bf61063a2a..e3099aa2f14d6 100644
--- a/app/code/Magento/Msrp/composer.json
+++ b/app/code/Magento/Msrp/composer.json
@@ -11,6 +11,7 @@
"magento/module-downloadable": "*",
"magento/module-eav": "*",
"magento/module-grouped-product": "*",
+ "magento/module-configurable-product": "*",
"magento/module-store": "*",
"magento/module-tax": "*"
},
diff --git a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
index dd5abd433073d..a951c14cf4c70 100644
--- a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
+++ b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
@@ -20,8 +20,23 @@ $priceType = $block->getPrice();
/** @var $product \Magento\Catalog\Model\Product */
$product = $block->getSaleableItem();
$productId = $product->getId();
+
+$amount = 0;
+if ($product->getMsrp()) {
+ $amount = $product->getMsrp();
+} elseif ($product->getTypeId() === \Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE) {
+ $amount = $product->getTypeInstance()->getChildrenMsrp($product);
+} elseif ($product->getTypeId() === \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE) {
+ foreach ($product->getTypeInstance()->getUsedProducts($product) as $item) {
+ if ($item->getMsrp() !== null) {
+ $prices[] = $item->getMsrp();
+ }
+ }
+ $amount = $prices ? max($prices) : 0;
+}
+
$msrpPrice = $block->renderAmount(
- $priceType->getCustomAmount($product->getMsrp() ?: $product->getTypeInstance()->getChildrenMsrp($product)),
+ $priceType->getCustomAmount($amount),
[
'price_id' => $block->getPriceId() ? $block->getPriceId() : 'old-price-' . $productId,
'include_container' => false,
@@ -29,54 +44,56 @@ $msrpPrice = $block->renderAmount(
]
);
$priceElementIdPrefix = $block->getPriceElementIdPrefix() ? $block->getPriceElementIdPrefix() : 'product-price-';
-
-$addToCartUrl = '';
-if ($product->isSaleable()) {
- /** @var Magento\Catalog\Block\Product\AbstractProduct $addToCartUrlGenerator */
- $addToCartUrlGenerator = $block->getLayout()->getBlockSingleton('Magento\Catalog\Block\Product\AbstractProduct');
- $addToCartUrl = $addToCartUrlGenerator->getAddToCartUrl(
- $product,
- ['_query' => [
- \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED =>
- $this->helper('Magento\Framework\Url\Helper\Data')->getEncodedUrl(
- $addToCartUrlGenerator->getAddToCartUrl($product)
- ),
- ]]
- );
-}
?>
-getMsrp()): ?>
+
+
= /* @escapeNotVerified */ $msrpPrice ?>
+ = /* @escapeNotVerified */ $msrpPrice ?>
isShowPriceOnGesture()): ?>
getIdSuffix();
- $popupId = 'msrp-popup-' . $productId . $block->getRandomString(20);
- $data = ['addToCart' => [
- 'origin'=> 'msrp',
- 'popupId' => '#' . $popupId,
- 'productName' => $block->escapeJs($block->escapeHtml($product->getName())),
- 'productId' => $productId,
- 'productIdInput' => 'input[type="hidden"][name="product"]',
- 'realPrice' => $block->getRealPriceHtml(),
- 'isSaleable' => $product->isSaleable(),
- 'msrpPrice' => $msrpPrice,
- 'priceElementId' => $priceElementId,
- 'closeButtonId' => '#map-popup-close',
- 'addToCartUrl' => $addToCartUrl,
- 'paymentButtons' => '[data-label=or]'
- ]];
- if ($block->getRequest()->getFullActionName() === 'catalog_product_view') {
- $data['addToCart']['addToCartButton'] = '#product_addtocart_form [type=submit]';
- } else {
- $data['addToCart']['addToCartButton'] = sprintf(
- 'form:has(input[type="hidden"][name="product"][value="%s"]) button[type="submit"]',
- (int) $productId) . ',' .
- sprintf('.block.widget .price-box[data-product-id=%s]+.product-item-actions button.tocart',
- (int) $productId
- );
- }
+
+ $addToCartUrl = '';
+ if ($product->isSaleable()) {
+ /** @var Magento\Catalog\Block\Product\AbstractProduct $addToCartUrlGenerator */
+ $addToCartUrlGenerator = $block->getLayout()->getBlockSingleton('Magento\Catalog\Block\Product\AbstractProduct');
+ $addToCartUrl = $addToCartUrlGenerator->getAddToCartUrl(
+ $product,
+ ['_query' => [
+ \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED =>
+ $this->helper('Magento\Framework\Url\Helper\Data')->getEncodedUrl(
+ $addToCartUrlGenerator->getAddToCartUrl($product)
+ ),
+ ]]
+ );
+ }
+
+ $priceElementId = $priceElementIdPrefix . $productId . $block->getIdSuffix();
+ $popupId = 'msrp-popup-' . $productId . $block->getRandomString(20);
+ $data = ['addToCart' => [
+ 'origin'=> 'msrp',
+ 'popupId' => '#' . $popupId,
+ 'productName' => $block->escapeJs($block->escapeHtml($product->getName())),
+ 'productId' => $productId,
+ 'productIdInput' => 'input[type="hidden"][name="product"]',
+ 'realPrice' => $block->getRealPriceHtml(),
+ 'isSaleable' => $product->isSaleable(),
+ 'msrpPrice' => $msrpPrice,
+ 'priceElementId' => $priceElementId,
+ 'closeButtonId' => '#map-popup-close',
+ 'addToCartUrl' => $addToCartUrl,
+ 'paymentButtons' => '[data-label=or]'
+ ]];
+ if ($block->getRequest()->getFullActionName() === 'catalog_product_view') {
+ $data['addToCart']['addToCartButton'] = '#product_addtocart_form [type=submit]';
+ } else {
+ $data['addToCart']['addToCartButton'] = sprintf(
+ 'form:has(input[type="hidden"][name="product"][value="%s"]) button[type="submit"]',
+ (int) $productId . ',' .
+ sprintf('.block.widget .price-box[data-product-id=%s]+.product-item-actions button.tocart',
+ (int) $productId));
+ }
?>
isSaleable()) {
"productName": "= $block->escapeJs($block->escapeHtml($product->getName())) ?>",
"closeButtonId": "#map-popup-close"}}'>= /* @escapeNotVerified */ __("What's this?") ?>
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Msrp/view/base/web/js/msrp.js b/app/code/Magento/Msrp/view/base/web/js/msrp.js
index deeadd9b55b82..a0bd3ec132de6 100644
--- a/app/code/Magento/Msrp/view/base/web/js/msrp.js
+++ b/app/code/Magento/Msrp/view/base/web/js/msrp.js
@@ -4,11 +4,12 @@
*/
define([
'jquery',
+ 'Magento_Catalog/js/price-utils',
'underscore',
'jquery/ui',
'mage/dropdown',
'mage/template'
-], function ($) {
+], function ($, priceUtils, _) {
'use strict';
$.widget('mage.addToCart', {
@@ -24,7 +25,14 @@ define([
// Selectors
cartForm: '.form.map.checkout',
msrpLabelId: '#map-popup-msrp',
+ msrpPriceElement: '#map-popup-msrp .price-wrapper',
priceLabelId: '#map-popup-price',
+ priceElement: '#map-popup-price .price',
+ mapInfoLinks: '.map-show-info',
+ displayPriceElement: '.old-price.map-old-price .price-wrapper',
+ fallbackPriceElement: '.normal-price.map-fallback-price .price-wrapper',
+ displayPriceContainer: '.old-price.map-old-price',
+ fallbackPriceContainer: '.normal-price.map-fallback-price',
popUpAttr: '[data-role=msrp-popup-template]',
popupCartButtonId: '#map-popup-button',
paypalCheckoutButons: '[data-action=checkout-form-submit]',
@@ -59,9 +67,11 @@ define([
shadowHinter: 'popup popup-pointer'
},
popupOpened: false,
+ wasOpened: false,
/**
* Creates widget instance
+ *
* @private
*/
_create: function () {
@@ -73,10 +83,13 @@ define([
this.initTierPopup();
}
$(this.options.cartButtonId).on('click', this._addToCartSubmit.bind(this));
+ $(document).on('updateMsrpPriceBlock', this.onUpdateMsrpPrice.bind(this));
+ $(this.options.cartForm).on('submit', this._onSubmitForm.bind(this));
},
/**
* Init msrp popup
+ *
* @private
*/
initMsrpPopup: function () {
@@ -89,7 +102,7 @@ define([
$msrpPopup.find('button')
.on('click',
- this.handleMsrpAddToCart.bind(this))
+ this.handleMsrpAddToCart.bind(this))
.filter(this.options.popupCartButtonId)
.text($(this.options.addToCartButton).text());
@@ -104,6 +117,7 @@ define([
/**
* Init info popup
+ *
* @private
*/
initInfoPopup: function () {
@@ -212,8 +226,12 @@ define([
var options = this.tierOptions || this.options;
this.popUpOptions.position.of = $(event.target);
- this.$popup.find(this.options.msrpLabelId).html(options.msrpPrice);
- this.$popup.find(this.options.priceLabelId).html(options.realPrice);
+
+ if (!this.wasOpened) {
+ this.$popup.find(this.options.msrpLabelId).html(options.msrpPrice);
+ this.$popup.find(this.options.priceLabelId).html(options.realPrice);
+ this.wasOpened = true;
+ }
this.$popup.dropdownDialog(this.popUpOptions).dropdownDialog('open');
this._toggle(this.$popup);
@@ -223,6 +241,7 @@ define([
},
/**
+ * Toggle MAP popup visibility
*
* @param {HTMLElement} $elem
* @private
@@ -239,6 +258,7 @@ define([
},
/**
+ * Close MAP information popup
*
* @param {HTMLElement} $elem
*/
@@ -249,8 +269,10 @@ define([
/**
* Handler for addToCart action
+ *
+ * @param {Object} e
*/
- _addToCartSubmit: function () {
+ _addToCartSubmit: function (e) {
this.element.trigger('addToCart', this.element);
if (this.element.data('stop-processing')) {
@@ -266,9 +288,106 @@ define([
if (this.options.addToCartUrl) {
$('.mage-dropdown-dialog > .ui-dialog-content').dropdownDialog('close');
}
+
+ e.preventDefault();
$(this.options.cartForm).submit();
+ },
+ /**
+ * Call on event updatePrice. Proxy to updateMsrpPrice method.
+ *
+ * @param {Event} event
+ * @param {mixed} priceIndex
+ * @param {Object} prices
+ */
+ onUpdateMsrpPrice: function onUpdateMsrpPrice(event, priceIndex, prices) {
+
+ var defaultMsrp,
+ defaultPrice,
+ msrpPrice,
+ finalPrice;
+
+ defaultMsrp = _.chain(prices).map(function (price) {
+ return price.msrpPrice.amount;
+ }).reject(function (p) {
+ return p === null;
+ }).max().value();
+
+ defaultPrice = _.chain(prices).map(function (p) {
+ return p.finalPrice.amount;
+ }).min().value();
+
+ if (typeof priceIndex !== 'undefined') {
+ msrpPrice = prices[priceIndex].msrpPrice.amount;
+ finalPrice = prices[priceIndex].finalPrice.amount;
+
+ if (msrpPrice === null || msrpPrice <= finalPrice) {
+ this.updateNonMsrpPrice(priceUtils.formatPrice(finalPrice));
+ } else {
+ this.updateMsrpPrice(
+ priceUtils.formatPrice(finalPrice),
+ priceUtils.formatPrice(msrpPrice),
+ false);
+ }
+ } else {
+ this.updateMsrpPrice(
+ priceUtils.formatPrice(defaultPrice),
+ priceUtils.formatPrice(defaultMsrp),
+ true);
+ }
+ },
+
+ /**
+ * Update prices for configurable product with MSRP enabled
+ *
+ * @param {String} finalPrice
+ * @param {String} msrpPrice
+ * @param {Boolean} useDefaultPrice
+ */
+ updateMsrpPrice: function (finalPrice, msrpPrice, useDefaultPrice) {
+ var options = this.tierOptions || this.options;
+
+ $(this.options.fallbackPriceContainer).hide();
+ $(this.options.displayPriceContainer).show();
+ $(this.options.mapInfoLinks).show();
+
+ if (useDefaultPrice || !this.wasOpened) {
+ this.$popup.find(this.options.msrpLabelId).html(options.msrpPrice);
+ this.$popup.find(this.options.priceLabelId).html(options.realPrice);
+ $(this.options.displayPriceElement).html(msrpPrice);
+ this.wasOpened = true;
+ }
+
+ if (!useDefaultPrice) {
+ this.$popup.find(this.options.msrpPriceElement).html(msrpPrice);
+ this.$popup.find(this.options.priceElement).html(finalPrice);
+ $(this.options.displayPriceElement).html(msrpPrice);
+ }
+ },
+
+ /**
+ * Display non MAP price for irrelevant products
+ *
+ * @param {String} price
+ */
+ updateNonMsrpPrice: function (price) {
+ $(this.options.fallbackPriceElement).html(price);
+ $(this.options.displayPriceContainer).hide();
+ $(this.options.mapInfoLinks).hide();
+ $(this.options.fallbackPriceContainer).show();
+ },
+
+ /**
+ * Handler for submit form
+ *
+ * @private
+ */
+ _onSubmitForm: function () {
+ if ($(this.options.cartForm).valid()) {
+ $(this.options.cartButtonId).prop('disabled', true);
+ }
}
+
});
return $.mage.addToCart;
diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index bb81f9ebb475f..373d64afc8cc3 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -227,12 +227,12 @@ public function getCode($type, $code = '')
$codes = [
'condition_name' => [
'package_weight' => __('Weight vs. Destination'),
- 'package_value' => __('Price vs. Destination'),
+ 'package_value_with_discount' => __('Price vs. Destination'),
'package_qty' => __('# of Items vs. Destination'),
],
'condition_name_short' => [
'package_weight' => __('Weight (and above)'),
- 'package_value' => __('Order Subtotal (and above)'),
+ 'package_value_with_discount' => __('Order Subtotal (and above)'),
'package_qty' => __('# of Items (and above)'),
],
];
diff --git a/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php b/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php
new file mode 100644
index 0000000000000..070105846fdd8
--- /dev/null
+++ b/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php
@@ -0,0 +1,73 @@
+moduleDataSetup = $moduleDataSetup;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ $this->moduleDataSetup->getConnection()->startSetup();
+ $connection = $this->moduleDataSetup->getConnection();
+ $connection->update(
+ $this->moduleDataSetup->getTable('shipping_tablerate'),
+ ['condition_name' => 'package_value_with_discount'],
+ [new \Zend_Db_Expr('condition_name = \'package_value\'')]
+ );
+ $connection->update(
+ $this->moduleDataSetup->getTable('core_config_data'),
+ ['value' => 'package_value_with_discount'],
+ [
+ new \Zend_Db_Expr('value = \'package_value\''),
+ new \Zend_Db_Expr('path = \'carriers/tablerate/condition_name\'')
+ ]
+ );
+ $this->moduleDataSetup->getConnection()->endSetup();
+
+ $connection->endSetup();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/OfflineShipping/etc/db_schema.xml b/app/code/Magento/OfflineShipping/etc/db_schema.xml
index 0510ce9b9b8eb..5129e8a29b2a1 100644
--- a/app/code/Magento/OfflineShipping/etc/db_schema.xml
+++ b/app/code/Magento/OfflineShipping/etc/db_schema.xml
@@ -18,7 +18,7 @@
default="0" comment="Destination Region Id"/>
-
+
hasItemsCollection()) {
+ if ($this->hasItemsCollection() && $useCache) {
return $this->getData('items_collection');
}
- if (null === $this->_items) {
+ if (null === $this->_items || !$useCache) {
$this->_items = $this->_quoteItemCollectionFactory->create();
$this->extensionAttributesJoinProcessor->process($this->_items);
$this->_items->setQuote($this);
diff --git a/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php b/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php
new file mode 100644
index 0000000000000..19a7e03264d8a
--- /dev/null
+++ b/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php
@@ -0,0 +1,72 @@
+quoteRepository = $quoteRepository;
+ $this->checkoutSession = $checkoutSession;
+ }
+
+ /**
+ * Update store id in active quote after store view switching.
+ *
+ * @param StoreSwitcherInterface $subject
+ * @param string $result
+ * @param StoreInterface $fromStore store where we came from
+ * @param StoreInterface $targetStore store where to go to
+ * @param string $redirectUrl original url requested for redirect after switching
+ * @return string url to be redirected after switching
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterSwitch(
+ StoreSwitcherInterface $subject,
+ $result,
+ StoreInterface $fromStore,
+ StoreInterface $targetStore,
+ string $redirectUrl
+ ): string {
+ $quote = $this->checkoutSession->getQuote();
+ if ($quote->getIsActive()) {
+ $quote->setStoreId(
+ $targetStore->getId()
+ );
+ $quote->getItemsCollection(false);
+ $this->quoteRepository->save($quote);
+ }
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php b/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
index 1e999cb5e523e..804f0863d2d2a 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
@@ -77,7 +77,8 @@ protected function setUp()
'getAllVisibleItems',
'getBaseCurrencyCode',
'getQuoteCurrencyCode',
- 'getItemsQty'
+ 'getItemsQty',
+ 'collectTotals'
]);
$this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class);
$this->addressMock = $this->createPartialMock(
diff --git a/app/code/Magento/Quote/etc/frontend/di.xml b/app/code/Magento/Quote/etc/frontend/di.xml
index 125afb96f20fd..ecad94fbbc249 100644
--- a/app/code/Magento/Quote/etc/frontend/di.xml
+++ b/app/code/Magento/Quote/etc/frontend/di.xml
@@ -12,6 +12,9 @@
Magento\Checkout\Model\Session\Proxy
+
+
+
diff --git a/app/code/Magento/Quote/etc/sales.xml b/app/code/Magento/Quote/etc/sales.xml
index 3d54a6375c8d9..3db72a1226236 100644
--- a/app/code/Magento/Quote/etc/sales.xml
+++ b/app/code/Magento/Quote/etc/sales.xml
@@ -9,7 +9,7 @@
diff --git a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php
index d2be99757df47..6729fe722de56 100644
--- a/app/code/Magento/Rule/Model/Condition/AbstractCondition.php
+++ b/app/code/Magento/Rule/Model/Condition/AbstractCondition.php
@@ -106,8 +106,8 @@ public function getDefaultOperatorInputByType()
'string' => ['==', '!=', '>=', '>', '<=', '<', '{}', '!{}', '()', '!()'],
'numeric' => ['==', '!=', '>=', '>', '<=', '<', '()', '!()'],
'date' => ['==', '>=', '<='],
- 'select' => ['==', '!='],
- 'boolean' => ['==', '!='],
+ 'select' => ['==', '!=', '<=>'],
+ 'boolean' => ['==', '!=', '<=>'],
'multiselect' => ['{}', '!{}', '()', '!()'],
'grid' => ['()', '!()'],
];
@@ -137,6 +137,7 @@ public function getDefaultOperatorOptions()
'!{}' => __('does not contain'),
'()' => __('is one of'),
'!()' => __('is not one of'),
+ '<=>' => __('is undefined'),
];
}
return $this->_defaultOperatorOptions;
diff --git a/app/code/Magento/Rule/Model/Condition/Sql/Builder.php b/app/code/Magento/Rule/Model/Condition/Sql/Builder.php
index 6267e30a7a6d5..33e1bf97c3474 100644
--- a/app/code/Magento/Rule/Model/Condition/Sql/Builder.php
+++ b/app/code/Magento/Rule/Model/Condition/Sql/Builder.php
@@ -250,8 +250,30 @@ public function attachConditionToCollection(
$this->_joinTablesToCollection($collection, $combine);
$whereExpression = (string)$this->_getMappedSqlCombination($combine);
if (!empty($whereExpression)) {
- // Select ::where method adds braces even on empty expression
- $collection->getSelect()->where($whereExpression);
+ if (!empty($combine->getConditions())) {
+ $conditions = '';
+ $attributeField = '';
+ foreach ($combine->getConditions() as $condition) {
+ if ($condition->getData('attribute') === \Magento\Catalog\Api\Data\ProductInterface::SKU) {
+ $conditions = $condition->getData('value');
+ $attributeField = $condition->getMappedSqlField();
+ }
+ }
+
+ $collection->getSelect()->where($whereExpression);
+
+ if (!empty($conditions) && !empty($attributeField)) {
+ $conditions = explode(',', $conditions);
+ foreach ($conditions as &$condition) {
+ $condition = "'" . trim($condition) . "'";
+ }
+ $conditions = implode(', ', $conditions);
+ $collection->getSelect()->order("FIELD($attributeField, $conditions)");
+ }
+ } else {
+ // Select ::where method adds braces even on empty expression
+ $collection->getSelect()->where($whereExpression);
+ }
}
}
}
diff --git a/app/code/Magento/Rule/view/adminhtml/web/rules.js b/app/code/Magento/Rule/view/adminhtml/web/rules.js
index 8e36562ebd7fe..202337c39da35 100644
--- a/app/code/Magento/Rule/view/adminhtml/web/rules.js
+++ b/app/code/Magento/Rule/view/adminhtml/web/rules.js
@@ -101,6 +101,9 @@ define([
if (!elem.multiple) {
Event.observe(elem, 'change', this.hideParamInputField.bind(this, container));
+
+ this.changeVisibilityForValueRuleParam(elem);
+
}
Event.observe(elem, 'blur', this.hideParamInputField.bind(this, container));
}
@@ -262,6 +265,8 @@ define([
label.innerHTML = str != '' ? str : '...';
}
+ this.changeVisibilityForValueRuleParam(elem);
+
elem = Element.down(container, 'input.input-text');
if (elem) {
@@ -293,6 +298,23 @@ define([
this.shownElement = null;
},
+ changeVisibilityForValueRuleParam: function(elem) {
+ let parsedElementId = elem.id.split('__');
+ if (parsedElementId[2] != 'operator') {
+ return false;
+ }
+
+ let valueElement = jQuery('#' + parsedElementId[0] + '__' + parsedElementId[1] + '__value');
+
+ if(elem.value == '<=>') {
+ valueElement.closest('.rule-param').hide();
+ } else {
+ valueElement.closest('.rule-param').show();
+ }
+
+ return true;
+ },
+
addRuleNewChild: function (elem) {
var parent_id = elem.id.replace(/^.*__(.*)__.*$/, '$1');
var children_ul_id = elem.id.replace(/__/g, ':').replace(/[^:]*$/, 'children').replace(/:/g, '__');
diff --git a/app/code/Magento/Sales/Model/Service/InvoiceService.php b/app/code/Magento/Sales/Model/Service/InvoiceService.php
index 791274d3f23bd..ba6ae7eb14ba7 100644
--- a/app/code/Magento/Sales/Model/Service/InvoiceService.php
+++ b/app/code/Magento/Sales/Model/Service/InvoiceService.php
@@ -7,9 +7,14 @@
use Magento\Sales\Api\InvoiceManagementInterface;
use Magento\Sales\Model\Order;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Serialize\Serializer\Json;
+use Magento\Catalog\Model\Product\Type;
/**
* Class InvoiceService
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class InvoiceService implements InvoiceManagementInterface
{
@@ -58,6 +63,13 @@ class InvoiceService implements InvoiceManagementInterface
*/
protected $orderConverter;
+ /**
+ * Serializer interface instance.
+ *
+ * @var Json
+ */
+ private $serializer;
+
/**
* Constructor
*
@@ -68,6 +80,7 @@ class InvoiceService implements InvoiceManagementInterface
* @param \Magento\Sales\Model\Order\InvoiceNotifier $notifier
* @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
* @param \Magento\Sales\Model\Convert\Order $orderConverter
+ * @param Json|null $serializer
*/
public function __construct(
\Magento\Sales\Api\InvoiceRepositoryInterface $repository,
@@ -76,7 +89,8 @@ public function __construct(
\Magento\Framework\Api\FilterBuilder $filterBuilder,
\Magento\Sales\Model\Order\InvoiceNotifier $notifier,
\Magento\Sales\Api\OrderRepositoryInterface $orderRepository,
- \Magento\Sales\Model\Convert\Order $orderConverter
+ \Magento\Sales\Model\Convert\Order $orderConverter,
+ Json $serializer = null
) {
$this->repository = $repository;
$this->commentRepository = $commentRepository;
@@ -85,6 +99,7 @@ public function __construct(
$this->invoiceNotifier = $notifier;
$this->orderRepository = $orderRepository;
$this->orderConverter = $orderConverter;
+ $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
}
/**
@@ -172,13 +187,13 @@ private function prepareItemsQty(Order $order, array $qtys = [])
{
foreach ($order->getAllItems() as $orderItem) {
if (empty($qtys[$orderItem->getId()])) {
- $parentId = $orderItem->getParentItemId();
- if ($parentId && array_key_exists($parentId, $qtys)) {
- $qtys[$orderItem->getId()] = $qtys[$parentId];
+ if ($orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) {
+ $qtys[$orderItem->getId()] = $orderItem->getQtyOrdered() - $orderItem->getQtyInvoiced();
} else {
continue;
}
}
+
$this->prepareItemQty($orderItem, $qtys);
}
@@ -186,20 +201,27 @@ private function prepareItemsQty(Order $order, array $qtys = [])
}
/**
- * Prepare qty to invoice item.
+ * Prepare qty_invoiced for order item
*
- * @param Order\Item $orderItem
+ * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem
* @param array $qtys
- * @return void
*/
private function prepareItemQty(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, &$qtys)
{
+ $this->prepareBundleQty($orderItem, $qtys);
+
if ($orderItem->isDummy()) {
if ($orderItem->getHasChildren()) {
foreach ($orderItem->getChildrenItems() as $child) {
if (!isset($qtys[$child->getId()])) {
$qtys[$child->getId()] = $child->getQtyToInvoice();
}
+ $parentId = $orderItem->getParentItemId();
+ if ($parentId && array_key_exists($parentId, $qtys)) {
+ $qtys[$orderItem->getId()] = $qtys[$parentId];
+ } else {
+ continue;
+ }
}
} elseif ($orderItem->getParentItem()) {
$parent = $orderItem->getParentItem();
@@ -210,6 +232,26 @@ private function prepareItemQty(\Magento\Sales\Api\Data\OrderItemInterface $orde
}
}
+ /**
+ * Prepare qty to invoice for bundle products
+ *
+ * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem
+ * @param array $qtys
+ */
+ private function prepareBundleQty(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, &$qtys)
+ {
+ if ($orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) {
+ foreach ($orderItem->getChildrenItems() as $childItem) {
+ $bundleSelectionAttributes = $childItem->getProductOptionByCode('bundle_selection_attributes');
+ if (is_string($bundleSelectionAttributes)) {
+ $bundleSelectionAttributes = $this->serializer->unserialize($bundleSelectionAttributes);
+ }
+
+ $qtys[$childItem->getId()] = $qtys[$orderItem->getId()] * $bundleSelectionAttributes['qty'];
+ }
+ }
+ }
+
/**
* Check if order item can be invoiced.
*
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml
similarity index 64%
rename from app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml
rename to app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml
index c46dd612022fd..fcea25f997591 100644
--- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml
@@ -9,11 +9,11 @@
-
+
-
-
+
+
@@ -24,13 +24,4 @@
-
-
-
-
-
-
-
-
-
diff --git a/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml
deleted file mode 100644
index 76ff20813483e..0000000000000
--- a/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
- EnableRMAStorefront
-
-
- 1
-
-
-
- DisableRMAStorefront
-
-
- 0
-
-
diff --git a/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml b/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml
deleted file mode 100644
index 86226265dd146..0000000000000
--- a/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
- string
-
-
-
-
-
-
diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderProcessDataPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderProcessDataPage.xml
new file mode 100644
index 0000000000000..2041bf8f3c9ae
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderProcessDataPage.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml
index 32d94c3175807..ee546174d9680 100644
--- a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml
@@ -9,7 +9,6 @@
-
-
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml
index 11673f1f0fe26..beb566b20806c 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml
@@ -15,5 +15,6 @@
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml
index 53aeeb62c6b70..5c2ff296ebeee 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml
@@ -23,8 +23,10 @@
+
+
@@ -35,4 +37,4 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml
deleted file mode 100644
index fe8391cf3c28f..0000000000000
--- a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml
new file mode 100644
index 0000000000000..7c83f35468ce6
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php b/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php
new file mode 100644
index 0000000000000..c37ca276e0ee2
--- /dev/null
+++ b/app/code/Magento/SalesRule/Model/Quote/Address/Total/ShippingDiscount.php
@@ -0,0 +1,101 @@
+calculator = $calculator;
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @param Quote $quote
+ * @param ShippingAssignment $shippingAssignment
+ * @param Total $total
+ * @return ShippingDiscount
+ */
+ public function collect(Quote $quote, ShippingAssignment $shippingAssignment, Total $total): self
+ {
+ parent::collect($quote, $shippingAssignment, $total);
+
+ $address = $shippingAssignment->getShipping()->getAddress();
+ $this->calculator->reset($address);
+
+ $items = $shippingAssignment->getItems();
+ if (!count($items)) {
+ return $this;
+ }
+
+ $address->setShippingDiscountAmount(0);
+ $address->setBaseShippingDiscountAmount(0);
+ if ($address->getShippingAmount()) {
+ $this->calculator->processShippingAmount($address);
+ $total->addTotalAmount(DiscountCollector::COLLECTOR_TYPE_CODE, -$address->getShippingDiscountAmount());
+ $total->addBaseTotalAmount(
+ DiscountCollector::COLLECTOR_TYPE_CODE,
+ -$address->getBaseShippingDiscountAmount()
+ );
+ $total->setShippingDiscountAmount($address->getShippingDiscountAmount());
+ $total->setBaseShippingDiscountAmount($address->getBaseShippingDiscountAmount());
+
+ $this->calculator->prepareDescription($address);
+ $total->setDiscountDescription($address->getDiscountDescription());
+ $total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount());
+ $total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $total->getBaseDiscountAmount());
+
+ $address->setDiscountAmount($total->getDiscountAmount());
+ $address->setBaseDiscountAmount($total->getBaseDiscountAmount());
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @param \Magento\Quote\Model\Quote $quote
+ * @param \Magento\Quote\Model\Quote\Address\Total $total
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function fetch(Quote $quote, Total $total): array
+ {
+ $result = [];
+ $amount = $total->getDiscountAmount();
+
+ if ($amount != 0) {
+ $description = $total->getDiscountDescription() ?: '';
+ $result = [
+ 'code' => DiscountCollector::COLLECTOR_TYPE_CODE,
+ 'title' => strlen($description) ? __('Discount (%1)', $description) : __('Discount'),
+ 'value' => $amount
+ ];
+ }
+ return $result;
+ }
+}
diff --git a/app/code/Magento/SalesRule/Model/Quote/Discount.php b/app/code/Magento/SalesRule/Model/Quote/Discount.php
index 693a61b272f66..315ce874513a3 100644
--- a/app/code/Magento/SalesRule/Model/Quote/Discount.php
+++ b/app/code/Magento/SalesRule/Model/Quote/Discount.php
@@ -5,8 +5,13 @@
*/
namespace Magento\SalesRule\Model\Quote;
+/**
+ * Discount totals calculation model.
+ */
class Discount extends \Magento\Quote\Model\Quote\Address\Total\AbstractTotal
{
+ const COLLECTOR_TYPE_CODE = 'discount';
+
/**
* Discount calculation object
*
@@ -43,7 +48,7 @@ public function __construct(
\Magento\SalesRule\Model\Validator $validator,
\Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
) {
- $this->setCode('discount');
+ $this->setCode(self::COLLECTOR_TYPE_CODE);
$this->eventManager = $eventManager;
$this->calculator = $validator;
$this->storeManager = $storeManager;
@@ -124,21 +129,14 @@ public function collect(
}
}
- /** Process shipping amount discount */
- $address->setShippingDiscountAmount(0);
- $address->setBaseShippingDiscountAmount(0);
- if ($address->getShippingAmount()) {
- $this->calculator->processShippingAmount($address);
- $total->addTotalAmount($this->getCode(), -$address->getShippingDiscountAmount());
- $total->addBaseTotalAmount($this->getCode(), -$address->getBaseShippingDiscountAmount());
- $total->setShippingDiscountAmount($address->getShippingDiscountAmount());
- $total->setBaseShippingDiscountAmount($address->getBaseShippingDiscountAmount());
- }
-
$this->calculator->prepareDescription($address);
$total->setDiscountDescription($address->getDiscountDescription());
$total->setSubtotalWithDiscount($total->getSubtotal() + $total->getDiscountAmount());
$total->setBaseSubtotalWithDiscount($total->getBaseSubtotal() + $total->getBaseDiscountAmount());
+
+ $address->setDiscountAmount($total->getDiscountAmount());
+ $address->setBaseDiscountAmount($total->getBaseDiscountAmount());
+
return $this;
}
diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
index 59f24fa8b6e03..5e6f3847c8e31 100644
--- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
+++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
@@ -80,6 +80,8 @@ protected function _construct()
}
/**
+ * Map data for associated entities
+ *
* @param string $entityType
* @param string $objectField
* @throws \Magento\Framework\Exception\LocalizedException
@@ -114,6 +116,8 @@ protected function mapAssociatedEntities($entityType, $objectField)
}
/**
+ * Add website ids and customer group ids to rules data
+ *
* @return $this
* @throws \Exception
* @since 100.1.0
@@ -158,60 +162,15 @@ public function setValidationFilter(
$connection = $this->getConnection();
if (strlen($couponCode)) {
- $select->joinLeft(
- ['rule_coupons' => $this->getTable('salesrule_coupon')],
- $connection->quoteInto(
- 'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
- ),
- ['code']
- );
-
$noCouponWhereCondition = $connection->quoteInto(
- 'main_table.coupon_type = ? ',
+ 'main_table.coupon_type = ?',
\Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
);
-
- $autoGeneratedCouponCondition = [
- $connection->quoteInto(
- "main_table.coupon_type = ?",
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO
- ),
- $connection->quoteInto(
- "rule_coupons.type = ?",
- \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED
- ),
- ];
-
- $orWhereConditions = [
- "(" . implode($autoGeneratedCouponCondition, " AND ") . ")",
- $connection->quoteInto(
- '(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
- ),
- $connection->quoteInto(
- '(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
- ),
- ];
-
- $andWhereConditions = [
- $connection->quoteInto(
- 'rule_coupons.code = ?',
- $couponCode
- ),
- $connection->quoteInto(
- '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
- $this->_date->date()->format('Y-m-d')
- ),
- ];
-
- $orWhereCondition = implode(' OR ', $orWhereConditions);
- $andWhereCondition = implode(' AND ', $andWhereConditions);
+ $relatedRulesIds = $this->getCouponRelatedRuleIds($couponCode);
$select->where(
- $noCouponWhereCondition . ' OR ((' . $orWhereCondition . ') AND ' . $andWhereCondition . ')',
- null,
+ $noCouponWhereCondition . ' OR main_table.rule_id IN (?)',
+ $relatedRulesIds,
Select::TYPE_CONDITION
);
} else {
@@ -227,6 +186,75 @@ public function setValidationFilter(
return $this;
}
+ /**
+ * Get rules ids related to coupon code
+ *
+ * @param string $couponCode
+ * @return array
+ */
+ private function getCouponRelatedRuleIds(string $couponCode): array
+ {
+ $connection = $this->getConnection();
+ $select = $connection->select()->from(
+ ['main_table' => $this->getTable('salesrule')],
+ 'rule_id'
+ );
+ $select->joinLeft(
+ ['rule_coupons' => $this->getTable('salesrule_coupon')],
+ $connection->quoteInto(
+ 'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?',
+ \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON,
+ null
+ )
+ );
+
+ $autoGeneratedCouponCondition = [
+ $connection->quoteInto(
+ "main_table.coupon_type = ?",
+ \Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO
+ ),
+ $connection->quoteInto(
+ "rule_coupons.type = ?",
+ \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED
+ ),
+ ];
+
+ $orWhereConditions = [
+ "(" . implode($autoGeneratedCouponCondition, " AND ") . ")",
+ $connection->quoteInto(
+ '(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)',
+ \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
+ ),
+ $connection->quoteInto(
+ '(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)',
+ \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
+ ),
+ ];
+
+ $andWhereConditions = [
+ $connection->quoteInto(
+ 'rule_coupons.code = ?',
+ $couponCode
+ ),
+ $connection->quoteInto(
+ '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
+ $this->_date->date()->format('Y-m-d')
+ ),
+ ];
+
+ $orWhereCondition = implode(' OR ', $orWhereConditions);
+ $andWhereCondition = implode(' AND ', $andWhereConditions);
+
+ $select->where(
+ '(' . $orWhereCondition . ') AND ' . $andWhereCondition,
+ null,
+ Select::TYPE_CONDITION
+ );
+ $select->group('main_table.rule_id');
+
+ return $connection->fetchCol($select);
+ }
+
/**
* Filter collection by website(s), customer group(s) and date.
* Filter collection to only active rules.
@@ -366,6 +394,8 @@ public function addCustomerGroupFilter($customerGroupId)
}
/**
+ * Getter for _associatedEntitiesMap property
+ *
* @return array
* @deprecated 100.1.0
*/
@@ -380,6 +410,8 @@ private function getAssociatedEntitiesMap()
}
/**
+ * Getter for dateApplier property
+ *
* @return DateApplier
* @deprecated 100.1.0
*/
diff --git a/app/code/Magento/SalesRule/etc/sales.xml b/app/code/Magento/SalesRule/etc/sales.xml
index 3ab197d40b0df..d2db664224873 100644
--- a/app/code/Magento/SalesRule/etc/sales.xml
+++ b/app/code/Magento/SalesRule/etc/sales.xml
@@ -8,7 +8,8 @@
diff --git a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
index 46e794a1954cf..45eee0a4001d1 100644
--- a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
+++ b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
@@ -87,7 +87,7 @@ private function queryByPhrase($phrase)
{
$matchQuery = $this->fullTextSelect->getMatchQuery(
['synonyms' => 'synonyms'],
- $phrase,
+ $this->escapePhrase($phrase),
Fulltext::FULLTEXT_MODE_BOOLEAN
);
$query = $this->getConnection()->select()->from(
@@ -97,6 +97,18 @@ private function queryByPhrase($phrase)
return $this->getConnection()->fetchAll($query);
}
+ /**
+ * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error.
+ *
+ * @see https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html
+ * @param string $phrase
+ * @return string
+ */
+ private function escapePhrase(string $phrase): string
+ {
+ return preg_replace('/@+|[@+-]+$/', '', $phrase);
+ }
+
/**
* A private helper function to retrieve matching synonym groups per scope
*
diff --git a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml
index a7bf82588f7c7..0345c3f2949f4 100644
--- a/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml
+++ b/app/code/Magento/Shipping/Test/Mftf/Section/AdminShipmentItemsSection.xml
@@ -15,5 +15,6 @@
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml
index bee9a79abeb77..8004b750a4d1f 100644
--- a/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml
+++ b/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml
@@ -9,7 +9,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
-
+
diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
index 962ac8b171692..bd611d0cc1863 100644
--- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
+++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
@@ -493,7 +493,7 @@ define([
return '';
}
- $.each(config.options, function () {
+ $.each(config.options, function (index) {
var id,
type,
value,
@@ -523,6 +523,7 @@ define([
label = this.label ? this.label : '';
attr =
' id="' + controlId + '-item-' + id + '"' +
+ ' index="' + index + '"' +
' aria-checked="false"' +
' aria-describedby="' + controlId + '"' +
' tabindex="0"' +
@@ -745,6 +746,12 @@ define([
$widget._UpdatePrice();
}
+ $(document).trigger('updateMsrpPriceBlock',
+ [
+ parseInt($this.attr('index'), 10) + 1,
+ $widget.options.jsonConfig.optionPrices
+ ]);
+
$widget._loadMedia();
$input.trigger('change');
},
@@ -1229,7 +1236,10 @@ define([
}
imagesToUpdate = this._setImageIndex(imagesToUpdate);
- gallery.updateData(imagesToUpdate);
+
+ if (!_.isUndefined(gallery)) {
+ gallery.updateData(imagesToUpdate);
+ }
if (isInitial) {
$(this.options.mediaGallerySelector).AddFotoramaVideoEvents();
diff --git a/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php b/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php
index bad64260cf58a..939facd02c02d 100644
--- a/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php
+++ b/app/code/Magento/Tax/Model/Calculation/AbstractAggregateCalculator.php
@@ -7,6 +7,9 @@
use Magento\Tax\Api\Data\QuoteDetailsItemInterface;
+/**
+ * Abstract aggregate calculator.
+ */
abstract class AbstractAggregateCalculator extends AbstractCalculator
{
/**
@@ -106,11 +109,12 @@ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $
$rowTaxes = [];
$rowTaxesBeforeDiscount = [];
$appliedTaxes = [];
+ $rowTotalForTaxCalculation = $this->getPriceForTaxCalculation($item, $price) * $quantity;
//Apply each tax rate separately
foreach ($appliedRates as $appliedRate) {
$taxId = $appliedRate['id'];
$taxRate = $appliedRate['percent'];
- $rowTaxPerRate = $this->calculationTool->calcTaxAmount($rowTotal, $taxRate, false, false);
+ $rowTaxPerRate = $this->calculationTool->calcTaxAmount($rowTotalForTaxCalculation, $taxRate, false, false);
$deltaRoundingType = self::KEY_REGULAR_DELTA_ROUNDING;
if ($applyTaxAfterDiscount) {
$deltaRoundingType = self::KEY_TAX_BEFORE_DISCOUNT_DELTA_ROUNDING;
@@ -121,7 +125,10 @@ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $
//Handle discount
if ($applyTaxAfterDiscount) {
//TODO: handle originalDiscountAmount
- $taxableAmount = max($rowTotal - $discountAmount, 0);
+ $taxableAmount = max($rowTotalForTaxCalculation - $discountAmount, 0);
+ if ($taxableAmount && !$applyTaxAfterDiscount) {
+ $taxableAmount = $rowTotalForTaxCalculation;
+ }
$rowTaxAfterDiscount = $this->calculationTool->calcTaxAmount(
$taxableAmount,
$taxRate,
@@ -168,6 +175,26 @@ protected function calculateWithTaxNotInPrice(QuoteDetailsItemInterface $item, $
->setAppliedTaxes($appliedTaxes);
}
+ /**
+ * Get price for tax calculation.
+ *
+ * @param QuoteDetailsItemInterface $item
+ * @param float $price
+ * @return float
+ */
+ private function getPriceForTaxCalculation(QuoteDetailsItemInterface $item, float $price)
+ {
+ if ($item->getExtensionAttributes() && $item->getExtensionAttributes()->getPriceForTaxCalculation()) {
+ $priceForTaxCalculation = $this->calculationTool->round(
+ $item->getExtensionAttributes()->getPriceForTaxCalculation()
+ );
+ } else {
+ $priceForTaxCalculation = $price;
+ }
+
+ return $priceForTaxCalculation;
+ }
+
/**
* Round amount
*
diff --git a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php
index 0901e1b7bc78c..bff489ee50c2f 100644
--- a/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php
+++ b/app/code/Magento/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php
@@ -15,12 +15,17 @@
use Magento\Quote\Model\Quote\Item\AbstractItem;
use Magento\Store\Model\Store;
use Magento\Tax\Api\Data\QuoteDetailsInterfaceFactory;
+use Magento\Tax\Api\Data\QuoteDetailsItemInterface;
use Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory;
use Magento\Tax\Api\Data\TaxClassKeyInterface;
use Magento\Tax\Api\Data\TaxDetailsInterface;
use Magento\Tax\Api\Data\TaxDetailsItemInterface;
use Magento\Tax\Api\Data\QuoteDetailsInterface;
use Magento\Quote\Api\Data\ShippingAssignmentInterface;
+use Magento\Tax\Helper\Data as TaxHelper;
+use Magento\Framework\App\ObjectManager;
+use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterface;
+use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterfaceFactory;
/**
* Tax totals calculation model
@@ -129,6 +134,16 @@ class CommonTaxCollector extends AbstractTotal
*/
protected $quoteDetailsItemDataObjectFactory;
+ /**
+ * @var TaxHelper
+ */
+ private $taxHelper;
+
+ /**
+ * @var QuoteDetailsItemExtensionInterfaceFactory
+ */
+ private $quoteDetailsItemExtensionFactory;
+
/**
* Class constructor
*
@@ -139,6 +154,8 @@ class CommonTaxCollector extends AbstractTotal
* @param \Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory
* @param CustomerAddressFactory $customerAddressFactory
* @param CustomerAddressRegionFactory $customerAddressRegionFactory
+ * @param TaxHelper|null $taxHelper
+ * @param QuoteDetailsItemExtensionInterfaceFactory|null $quoteDetailsItemExtensionInterfaceFactory
*/
public function __construct(
\Magento\Tax\Model\Config $taxConfig,
@@ -147,7 +164,9 @@ public function __construct(
\Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $quoteDetailsItemDataObjectFactory,
\Magento\Tax\Api\Data\TaxClassKeyInterfaceFactory $taxClassKeyDataObjectFactory,
CustomerAddressFactory $customerAddressFactory,
- CustomerAddressRegionFactory $customerAddressRegionFactory
+ CustomerAddressRegionFactory $customerAddressRegionFactory,
+ TaxHelper $taxHelper = null,
+ QuoteDetailsItemExtensionInterfaceFactory $quoteDetailsItemExtensionInterfaceFactory = null
) {
$this->taxCalculationService = $taxCalculationService;
$this->quoteDetailsDataObjectFactory = $quoteDetailsDataObjectFactory;
@@ -156,6 +175,9 @@ public function __construct(
$this->quoteDetailsItemDataObjectFactory = $quoteDetailsItemDataObjectFactory;
$this->customerAddressFactory = $customerAddressFactory;
$this->customerAddressRegionFactory = $customerAddressRegionFactory;
+ $this->taxHelper = $taxHelper ?: ObjectManager::getInstance()->get(TaxHelper::class);
+ $this->quoteDetailsItemExtensionFactory = $quoteDetailsItemExtensionInterfaceFactory ?:
+ ObjectManager::getInstance()->get(QuoteDetailsItemExtensionInterfaceFactory::class);
}
/**
@@ -186,7 +208,7 @@ public function mapAddress(QuoteAddress $address)
* @param bool $priceIncludesTax
* @param bool $useBaseCurrency
* @param string $parentCode
- * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface
+ * @return QuoteDetailsItemInterface
*/
public function mapItem(
\Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory,
@@ -199,7 +221,7 @@ public function mapItem(
$sequence = 'sequence-' . $this->getNextIncrement();
$item->setTaxCalculationItemId($sequence);
}
- /** @var \Magento\Tax\Api\Data\QuoteDetailsItemInterface $itemDataObject */
+ /** @var QuoteDetailsItemInterface $itemDataObject */
$itemDataObject = $itemDataObjectFactory->create();
$itemDataObject->setCode($item->getTaxCalculationItemId())
->setQuantity($item->getQty())
@@ -215,12 +237,28 @@ public function mapItem(
if (!$item->getBaseTaxCalculationPrice()) {
$item->setBaseTaxCalculationPrice($item->getBaseCalculationPriceOriginal());
}
+
+ if ($this->taxHelper->applyTaxOnOriginalPrice()) {
+ $baseTaxCalculationPrice = $item->getBaseOriginalPrice();
+ } else {
+ $baseTaxCalculationPrice = $item->getBaseCalculationPriceOriginal();
+ }
+ $this->setPriceForTaxCalculation($itemDataObject, (float)$baseTaxCalculationPrice);
+
$itemDataObject->setUnitPrice($item->getBaseTaxCalculationPrice())
->setDiscountAmount($item->getBaseDiscountAmount());
} else {
if (!$item->getTaxCalculationPrice()) {
$item->setTaxCalculationPrice($item->getCalculationPriceOriginal());
}
+
+ if ($this->taxHelper->applyTaxOnOriginalPrice()) {
+ $taxCalculationPrice = $item->getOriginalPrice();
+ } else {
+ $taxCalculationPrice = $item->getCalculationPriceOriginal();
+ }
+ $this->setPriceForTaxCalculation($itemDataObject, (float)$taxCalculationPrice);
+
$itemDataObject->setUnitPrice($item->getTaxCalculationPrice())
->setDiscountAmount($item->getDiscountAmount());
}
@@ -230,6 +268,23 @@ public function mapItem(
return $itemDataObject;
}
+ /**
+ * Set price for tax calculation.
+ *
+ * @param QuoteDetailsItemInterface $quoteDetailsItem
+ * @param float $taxCalculationPrice
+ * @return void
+ */
+ private function setPriceForTaxCalculation(QuoteDetailsItemInterface $quoteDetailsItem, float $taxCalculationPrice)
+ {
+ $extensionAttributes = $quoteDetailsItem->getExtensionAttributes();
+ if (!$extensionAttributes) {
+ $extensionAttributes = $this->quoteDetailsItemExtensionFactory->create();
+ }
+ $extensionAttributes->setPriceForTaxCalculation($taxCalculationPrice);
+ $quoteDetailsItem->setExtensionAttributes($extensionAttributes);
+ }
+
/**
* Map item extra taxables
*
@@ -237,7 +292,7 @@ public function mapItem(
* @param AbstractItem $item
* @param bool $priceIncludesTax
* @param bool $useBaseCurrency
- * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface[]
+ * @return QuoteDetailsItemInterface[]
*/
public function mapItemExtraTaxables(
\Magento\Tax\Api\Data\QuoteDetailsItemInterfaceFactory $itemDataObjectFactory,
@@ -260,7 +315,7 @@ public function mapItemExtraTaxables(
} else {
$unitPrice = $extraTaxable[self::KEY_ASSOCIATED_TAXABLE_UNIT_PRICE];
}
- /** @var \Magento\Tax\Api\Data\QuoteDetailsItemInterface $itemDataObject */
+ /** @var QuoteDetailsItemInterface $itemDataObject */
$itemDataObject = $itemDataObjectFactory->create();
$itemDataObject->setCode($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_CODE])
->setType($extraTaxable[self::KEY_ASSOCIATED_TAXABLE_TYPE])
@@ -283,9 +338,9 @@ public function mapItemExtraTaxables(
* Add quote items
*
* @param ShippingAssignmentInterface $shippingAssignment
- * @param bool $useBaseCurrency
* @param bool $priceIncludesTax
- * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface[]
+ * @param bool $useBaseCurrency
+ * @return QuoteDetailsItemInterface[]
*/
public function mapItems(
ShippingAssignmentInterface $shippingAssignment,
@@ -361,10 +416,12 @@ public function populateAddressData(QuoteDetailsInterface $quoteDetails, QuoteAd
}
/**
+ * Get shipping data object.
+ *
* @param ShippingAssignmentInterface $shippingAssignment
* @param QuoteAddress\Total $total
* @param bool $useBaseCurrency
- * @return \Magento\Tax\Api\Data\QuoteDetailsItemInterface
+ * @return QuoteDetailsItemInterface
*/
public function getShippingDataObject(
ShippingAssignmentInterface $shippingAssignment,
@@ -379,7 +436,7 @@ public function getShippingDataObject(
$total->setBaseShippingTaxCalculationAmount($total->getBaseShippingAmount());
}
if ($total->getShippingTaxCalculationAmount() !== null) {
- /** @var \Magento\Tax\Api\Data\QuoteDetailsItemInterface $itemDataObject */
+ /** @var QuoteDetailsItemInterface $itemDataObject */
$itemDataObject = $this->quoteDetailsItemDataObjectFactory->create()
->setType(self::ITEM_TYPE_SHIPPING)
->setCode(self::ITEM_CODE_SHIPPING)
@@ -414,7 +471,7 @@ public function getShippingDataObject(
* Populate QuoteDetails object from quote address object
*
* @param ShippingAssignmentInterface $shippingAssignment
- * @param \Magento\Tax\Api\Data\QuoteDetailsItemInterface[] $itemDataObjects
+ * @param QuoteDetailsItemInterface[] $itemDataObjects
* @return \Magento\Tax\Api\Data\QuoteDetailsInterface
*/
protected function prepareQuoteDetails(ShippingAssignmentInterface $shippingAssignment, $itemDataObjects)
@@ -543,6 +600,7 @@ protected function processProductItems(
* Process applied taxes for items and quote
*
* @param QuoteAddress\Total $total
+ * @param ShippingAssignmentInterface $shippingAssignment
* @param array $itemsByType
* @return $this
*/
@@ -846,8 +904,9 @@ protected function saveAppliedTaxes()
}
/**
- * Increment and return counter. This function is intended to be used to generate temporary
- * id for an item.
+ * Increment and return counter.
+ *
+ * This function is intended to be used to generate temporary id for an item.
*
* @return int
*/
diff --git a/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml b/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml
index 887203a76fdad..4409ea0a21df6 100644
--- a/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml
+++ b/app/code/Magento/Tax/Test/Mftf/Data/TaxRateData.xml
@@ -106,4 +106,8 @@
0
0.1
+
+ 51
+ 6
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml b/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRatePage.xml
similarity index 63%
rename from app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml
rename to app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRatePage.xml
index 2a14f814eac16..26152d5497a98 100644
--- a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml
+++ b/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRatePage.xml
@@ -8,7 +8,7 @@
-
-
+
+
diff --git a/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRulePage.xml b/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRulePage.xml
new file mode 100644
index 0000000000000..c0e4958619c89
--- /dev/null
+++ b/app/code/Magento/Tax/Test/Mftf/Page/AdminEditTaxRulePage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml
index bfb082c145f07..e69bfbaebbfd9 100644
--- a/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml
+++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminConfigureTaxSection.xml
@@ -28,6 +28,8 @@
+
+
diff --git a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml
index 29c53242b90f6..46d92e30395e0 100644
--- a/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml
+++ b/app/code/Magento/Tax/Test/Mftf/Section/AdminTaxRulesSection.xml
@@ -33,5 +33,6 @@
+
diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml
new file mode 100644
index 0000000000000..732470d2558c7
--- /dev/null
+++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxCalcWithApplyTaxOnSettingTest.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Tax/Test/Unit/Model/Calculation/RowBaseAndTotalBaseCalculatorTestCase.php b/app/code/Magento/Tax/Test/Unit/Model/Calculation/RowBaseAndTotalBaseCalculatorTestCase.php
index cbd7ed46e38d7..2a7eeb27ee07e 100644
--- a/app/code/Magento/Tax/Test/Unit/Model/Calculation/RowBaseAndTotalBaseCalculatorTestCase.php
+++ b/app/code/Magento/Tax/Test/Unit/Model/Calculation/RowBaseAndTotalBaseCalculatorTestCase.php
@@ -9,6 +9,7 @@
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Tax\Model\Calculation\RowBaseCalculator;
use Magento\Tax\Model\Calculation\TotalBaseCalculator;
+use Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterface;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -66,6 +67,11 @@ class RowBaseAndTotalBaseCalculatorTestCase extends \PHPUnit\Framework\TestCase
*/
protected $taxDetailsItem;
+ /**
+ * @var \Magento\Tax\Api\Data\QuoteDetailsItemExtensionInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $quoteDetailsItemExtension;
+
/**
* initialize all mocks
*
@@ -101,7 +107,14 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
- $this->mockItem = $this->getMockBuilder(\Magento\Tax\Api\Data\QuoteDetailsItemInterface::class)->getMock();
+ $this->mockItem = $this->getMockBuilder(\Magento\Tax\Api\Data\QuoteDetailsItemInterface::class)
+ ->disableOriginalConstructor()->setMethods(['getExtensionAttributes', 'getUnitPrice'])
+ ->getMockForAbstractClass();
+ $this->quoteDetailsItemExtension = $this->getMockBuilder(QuoteDetailsItemExtensionInterface::class)
+ ->disableOriginalConstructor()->setMethods(['getPriceForTaxCalculation'])
+ ->getMockForAbstractClass();
+ $this->mockItem->expects($this->any())->method('getExtensionAttributes')
+ ->willReturn($this->quoteDetailsItemExtension);
$this->appliedTaxDataObjectFactory = $this->createPartialMock(
\Magento\Tax\Api\Data\AppliedTaxInterfaceFactory::class,
diff --git a/app/code/Magento/Tax/etc/extension_attributes.xml b/app/code/Magento/Tax/etc/extension_attributes.xml
index 90a5e6d2ecee3..41af1df836d6f 100644
--- a/app/code/Magento/Tax/etc/extension_attributes.xml
+++ b/app/code/Magento/Tax/etc/extension_attributes.xml
@@ -20,4 +20,7 @@
+
+
+
diff --git a/app/code/Magento/Tax/etc/sales.xml b/app/code/Magento/Tax/etc/sales.xml
index 64d29ece898de..15afd499bce3f 100644
--- a/app/code/Magento/Tax/etc/sales.xml
+++ b/app/code/Magento/Tax/etc/sales.xml
@@ -9,7 +9,7 @@
-
+
-
diff --git a/app/code/Magento/Translation/Model/Json/PreProcessor.php b/app/code/Magento/Translation/Model/Json/PreProcessor.php
index 5d46c3c8b0618..c178a324cb40b 100644
--- a/app/code/Magento/Translation/Model/Json/PreProcessor.php
+++ b/app/code/Magento/Translation/Model/Json/PreProcessor.php
@@ -6,6 +6,7 @@
namespace Magento\Translation\Model\Json;
+use Magento\Framework\App\Area;
use Magento\Framework\App\AreaList;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\TranslateInterface;
@@ -13,6 +14,7 @@
use Magento\Framework\View\Asset\PreProcessor\Chain;
use Magento\Framework\View\Asset\PreProcessorInterface;
use Magento\Framework\View\DesignInterface;
+use Magento\Backend\App\Area\FrontNameResolver;
use Magento\Translation\Model\Js\Config;
use Magento\Translation\Model\Js\DataProviderInterface;
@@ -83,7 +85,7 @@ public function process(Chain $chain)
$context = $chain->getAsset()->getContext();
$themePath = '*/*';
- $areaCode = \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE;
+ $areaCode = FrontNameResolver::AREA_CODE;
if ($context instanceof FallbackContext) {
$themePath = $context->getThemePath();
@@ -92,8 +94,10 @@ public function process(Chain $chain)
$this->viewDesign->setDesignTheme($themePath, $areaCode);
}
- $area = $this->areaList->getArea($areaCode);
- $area->load(\Magento\Framework\App\Area::PART_TRANSLATE);
+ if ($areaCode !== FrontNameResolver::AREA_CODE) {
+ $area = $this->areaList->getArea($areaCode);
+ $area->load(Area::PART_TRANSLATE);
+ }
$this->translate->setLocale($context->getLocale())->loadData($areaCode, true);
diff --git a/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php b/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php
index d9340e03dc996..cbeeefed6be6e 100644
--- a/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php
+++ b/app/code/Magento/Translation/Test/Unit/Model/Json/PreProcessorTest.php
@@ -8,39 +8,43 @@
use Magento\Translation\Model\Js\Config;
use Magento\Translation\Model\Js\DataProvider;
use Magento\Translation\Model\Json\PreProcessor;
+use Magento\Backend\App\Area\FrontNameResolver;
class PreProcessorTest extends \PHPUnit\Framework\TestCase
{
/**
* @var PreProcessor
*/
- protected $model;
+ private $model;
/**
* @var Config|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $configMock;
+ private $configMock;
/**
* @var DataProvider|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $dataProviderMock;
+ private $dataProviderMock;
/**
* @var \Magento\Framework\App\AreaList|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $areaListMock;
+ private $areaListMock;
/**
* @var \Magento\Framework\TranslateInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $translateMock;
+ private $translateMock;
/**
* @var \Magento\Framework\View\DesignInterface|\PHPUnit_Framework_MockObject_MockObject
*/
private $designMock;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->configMock = $this->createMock(\Magento\Translation\Model\Js\Config::class);
@@ -57,7 +61,14 @@ protected function setUp()
);
}
- public function testGetData()
+ /**
+ * Test 'process' method.
+ *
+ * @param array $data
+ * @param array $expects
+ * @dataProvider processDataProvider
+ */
+ public function testProcess(array $data, array $expects)
{
$chain = $this->createMock(\Magento\Framework\View\Asset\PreProcessor\Chain::class);
$asset = $this->createMock(\Magento\Framework\View\Asset\File::class);
@@ -66,8 +77,10 @@ public function testGetData()
$targetPath = 'path/js-translation.json';
$themePath = '*/*';
$dictionary = ['hello' => 'bonjour'];
- $areaCode = 'adminhtml';
+ $areaCode = $data['area_code'];
+
$area = $this->createMock(\Magento\Framework\App\Area::class);
+ $area->expects($expects['area_load'])->method('load')->willReturnSelf();
$chain->expects($this->once())
->method('getTargetAssetPath')
@@ -93,7 +106,7 @@ public function testGetData()
$this->designMock->expects($this->once())->method('setDesignTheme')->with($themePath, $areaCode);
- $this->areaListMock->expects($this->once())
+ $this->areaListMock->expects($expects['areaList_getArea'])
->method('getArea')
->with($areaCode)
->willReturn($area);
@@ -114,4 +127,33 @@ public function testGetData()
$this->model->process($chain);
}
+
+ /**
+ * Data provider for 'process' method test.
+ *
+ * @return array
+ */
+ public function processDataProvider()
+ {
+ return [
+ [
+ [
+ 'area_code' => FrontNameResolver::AREA_CODE
+ ],
+ [
+ 'areaList_getArea' => $this->never(),
+ 'area_load' => $this->never(),
+ ]
+ ],
+ [
+ [
+ 'area_code' => 'frontend'
+ ],
+ [
+ 'areaList_getArea' => $this->once(),
+ 'area_load' => $this->once(),
+ ]
+ ],
+ ];
+ }
}
diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js b/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js
index 3d02afcc40a9e..ce19899cd12cd 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/element/wysiwyg.js
@@ -18,6 +18,7 @@ define([
'use strict';
return Abstract.extend({
+ currentWysiwyg: undefined,
defaults: {
elementSelector: 'textarea',
suffixRegExpPattern: '${ $.wysiwygUniqueSuffix }',
@@ -53,6 +54,10 @@ define([
// disable editor completely after initialization is field is disabled
varienGlobalEvents.attachEventHandler('wysiwygEditorInitialized', function () {
+ if (!_.isUndefined(window.tinyMceEditors)) {
+ this.currentWysiwyg = window.tinyMceEditors[this.wysiwygId];
+ }
+
if (this.disabled()) {
this.setDisabled(true);
}
@@ -136,14 +141,9 @@ define([
}
/* eslint-disable no-undef */
- if (typeof wysiwyg !== 'undefined' && wysiwyg.activeEditor()) {
- if (wysiwyg && disabled) {
- wysiwyg.setEnabledStatus(false);
- wysiwyg.getPluginButtons().prop('disabled', 'disabled');
- } else if (wysiwyg) {
- wysiwyg.setEnabledStatus(true);
- wysiwyg.getPluginButtons().removeProp('disabled');
- }
+ if (!_.isUndefined(this.currentWysiwyg) && this.currentWysiwyg.activeEditor()) {
+ this.currentWysiwyg.setEnabledStatus(!disabled);
+ this.currentWysiwyg.getPluginButtons().prop('disabled', disabled);
}
}
});
diff --git a/app/code/Magento/Ui/view/base/web/js/grid/search/search.js b/app/code/Magento/Ui/view/base/web/js/grid/search/search.js
index 19536e7ff8c18..999e3262dbbdd 100644
--- a/app/code/Magento/Ui/view/base/web/js/grid/search/search.js
+++ b/app/code/Magento/Ui/view/base/web/js/grid/search/search.js
@@ -18,7 +18,7 @@ define([
return Element.extend({
defaults: {
template: 'ui/grid/search/search',
- placeholder: $t('Search by keyword'),
+ placeholder: 'Search by keyword',
label: $t('Keyword'),
value: '',
previews: [],
diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/bookmarks.html b/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/bookmarks.html
index 3ef64fd4b5371..36a3232c3e61a 100644
--- a/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/bookmarks.html
+++ b/app/code/Magento/Ui/view/base/web/templates/grid/controls/bookmarks/bookmarks.html
@@ -6,7 +6,7 @@
-->
|
|