-
= $block->escapeHtml(__('Please type the letters and numbers below')) ?>
+
+
+ = $block->escapeHtml(__('Please type the letters and numbers below')) ?>
+
-
+
data-validate="{required:true}"
+ id="captcha_= $block->escapeHtmlAttr($block->getFormId()) ?>"
+ autocomplete="off"/>
- isCaseSensitive()) :?>
-
- = $block->escapeHtml(__('Attention : Captcha is case sensitive.'), ['strong']) ?>
-
+ isCaseSensitive()):?>
+
= /* @noEscape */ $note ?>
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/action/refresh.js b/app/code/Magento/Captcha/view/frontend/web/js/action/refresh.js
index c82278269adee..030a990ce2a8f 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/action/refresh.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/action/refresh.js
@@ -4,18 +4,21 @@
*/
define([
- 'mage/storage'
-], function (storage) {
+ 'jquery', 'mage/url'
+], function ($, urlBuilder) {
'use strict';
return function (refreshUrl, formId, imageSource) {
- return storage.post(
- refreshUrl,
- JSON.stringify({
+ return $.ajax({
+ url: urlBuilder.build(refreshUrl),
+ type: 'POST',
+ async: false,
+ data: JSON.stringify({
'formId': formId
}),
- false
- ).done(
+ global: false,
+ contentType: 'application/json'
+ }).done(
function (response) {
if (response.imgSrc) {
imageSource(response.imgSrc);
diff --git a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
index d79c42a711565..55f3782e78f73 100644
--- a/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
+++ b/app/code/Magento/Captcha/view/frontend/web/js/view/checkout/defaultCaptcha.js
@@ -21,6 +21,7 @@ define([
},
dataScope: 'global',
currentCaptcha: null,
+ subscribedFormIds: [],
/**
* @return {*}
@@ -56,7 +57,7 @@ define([
*/
checkCustomerData: function (formId, captchaData, captcha) {
if (!_.isEmpty(captchaData) &&
- !_.isEmpty(captchaData)[formId] &&
+ !_.isEmpty(captchaData[formId]) &&
captchaData[formId].timestamp > captcha.timestamp
) {
if (!captcha.isRequired() && captchaData[formId].isRequired) {
@@ -74,9 +75,12 @@ define([
* @param {Object} captcha
*/
subscribeCustomerData: function (formId, captcha) {
- customerData.get('captcha').subscribe(function (captchaData) {
- this.checkCustomerData(formId, captchaData, captcha);
- }.bind(this));
+ if (this.subscribedFormIds.includes(formId) === false) {
+ this.subscribedFormIds.push(formId);
+ customerData.get('captcha').subscribe(function (captchaData) {
+ this.checkCustomerData(formId, captchaData, captcha);
+ }.bind(this));
+ }
},
/**
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
index 89239a2e3e608..35f30ed8d3d6b 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
@@ -61,7 +61,7 @@ public function __construct(
\Magento\Framework\Data\FormFactory $formFactory,
Yesno $yesNo,
Data $eavData,
- array $disableScopeChangeList = ['sku'],
+ array $disableScopeChangeList = [],
array $data = []
) {
$this->_yesNo = $yesNo;
diff --git a/app/code/Magento/Catalog/Block/Navigation.php b/app/code/Magento/Catalog/Block/Navigation.php
index 1d4ad5a03619e..be42f9df49656 100644
--- a/app/code/Magento/Catalog/Block/Navigation.php
+++ b/app/code/Magento/Catalog/Block/Navigation.php
@@ -152,6 +152,8 @@ public function getCacheKeyInfo()
$shortCacheId = array_values($shortCacheId);
$shortCacheId = implode('|', $shortCacheId);
+ // md5() here is not for cryptographic use.
+ // phpcs:ignore Magento2.Security.InsecureFunction
$shortCacheId = md5($shortCacheId);
$cacheId['category_path'] = $this->getCurrentCategoryKey();
diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php
index b181a5392905b..89e81989ab885 100644
--- a/app/code/Magento/Catalog/Block/Product/ListProduct.php
+++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php
@@ -461,7 +461,7 @@ private function initializeProductCollection()
// if the product is associated with any category
if ($categories->count()) {
// show products from this category
- $this->setCategoryId(current($categories->getIterator())->getId());
+ $this->setCategoryId($categories->getIterator()->current()->getId());
}
}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options.php b/app/code/Magento/Catalog/Block/Product/View/Options.php
index c457b20cd0904..26c6bab809927 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options.php
@@ -60,6 +60,11 @@ class Options extends \Magento\Framework\View\Element\Template
*/
protected $_catalogData;
+ /**
+ * @var \Magento\Framework\Stdlib\ArrayUtils
+ */
+ private $arrayUtils;
+
/**
* @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Framework\Pricing\Helper\Data $pricingHelper
@@ -93,7 +98,7 @@ public function __construct(
* Retrieve product object
*
* @return Product
- * @throws \LogicExceptions
+ * @throws \LogicException
*/
public function getProduct()
{
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Date.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Date.php
index af921959f8e27..0f8c978de649c 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Date.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Date.php
@@ -168,7 +168,10 @@ public function getTimeHtml()
$dayPartHtml = $this->_getHtmlSelect(
'day_part'
)->setOptions(
- ['am' => __('AM'), 'pm' => __('PM')]
+ [
+ 'am' => $this->escapeHtml(__('AM')),
+ 'pm' => $this->escapeHtml(__('PM'))
+ ]
)->getHtml();
}
$hoursHtml = $this->_getSelectFromToHtml('hour', $hourStart, $hourEnd);
diff --git a/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php b/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
index c5c08a0552f42..93732b1b52f0c 100644
--- a/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
+++ b/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
@@ -153,6 +153,7 @@ public function getCurrentProductData()
$this->productRenderCollectorComposite
->collect($product, $productRender);
$data = $this->hydrator->extract($productRender);
+ $data['is_available'] = $product->isAvailable();
$currentProductData = [
'items' => [
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
index d5fed1782bc62..7a6b71db9dde3 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute.php
@@ -126,6 +126,8 @@ protected function generateCode($label)
);
$validatorAttrCode = new \Zend_Validate_Regex(['pattern' => '/^[a-z][a-z_0-9]{0,29}[a-z0-9]$/']);
if (!$validatorAttrCode->isValid($code)) {
+ // md5() here is not for cryptographic use.
+ // phpcs:ignore Magento2.Security.InsecureFunction
$code = 'attr_' . ($code ?: substr(md5(time()), 0, 8));
}
return $code;
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
index 4ca9d4b0d0606..440716e456910 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
@@ -185,6 +185,9 @@ public function execute()
}
$attributeId = $this->getRequest()->getParam('attribute_id');
+ if (!empty($data['attribute_id']) && $data['attribute_id'] != $attributeId) {
+ $attributeId = $data['attribute_id'];
+ }
/** @var ProductAttributeInterface $model */
$model = $this->attributeFactory->create();
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php
index 1d6939acacfd0..81ab67bdf26dc 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilter.php
@@ -102,6 +102,6 @@ private function isAttributeShouldNotBeUpdated(Product $product, array $useDefau
{
$considerUseDefaultsAttribute = !isset($useDefaults[$attribute]) || $useDefaults[$attribute] === '1';
- return ($value === '' && $considerUseDefaultsAttribute && !$product->getData($attribute));
+ return ($value === '' && $considerUseDefaultsAttribute && ($product->getData($attribute) === null));
}
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
index c779c01cd7d71..0edd439230600 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
@@ -9,9 +9,12 @@
namespace Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Backend\App\Action\Context;
+use Magento\Backend\Model\View\Result\Redirect;
use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Exception\LocalizedException;
use Magento\Ui\Component\MassAction\Filter;
@@ -20,7 +23,7 @@
/**
* Class \Magento\Catalog\Controller\Adminhtml\Product\MassDelete
*/
-class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product implements HttpPostActionInterface
+class MassDelete extends Product implements HttpPostActionInterface
{
/**
* Massactions filter
@@ -49,8 +52,8 @@ class MassDelete extends \Magento\Catalog\Controller\Adminhtml\Product implement
* @param Builder $productBuilder
* @param Filter $filter
* @param CollectionFactory $collectionFactory
- * @param ProductRepositoryInterface $productRepository
- * @param LoggerInterface $logger
+ * @param ProductRepositoryInterface|null $productRepository
+ * @param LoggerInterface|null $logger
*/
public function __construct(
Context $context,
@@ -63,20 +66,23 @@ public function __construct(
$this->filter = $filter;
$this->collectionFactory = $collectionFactory;
$this->productRepository = $productRepository ?:
- \Magento\Framework\App\ObjectManager::getInstance()->create(ProductRepositoryInterface::class);
+ ObjectManager::getInstance()->create(ProductRepositoryInterface::class);
$this->logger = $logger ?:
- \Magento\Framework\App\ObjectManager::getInstance()->create(LoggerInterface::class);
+ ObjectManager::getInstance()->create(LoggerInterface::class);
parent::__construct($context, $productBuilder);
}
/**
* Mass Delete Action
*
- * @return \Magento\Backend\Model\View\Result\Redirect
+ * @return Redirect
+ * @throws LocalizedException
*/
public function execute()
{
$collection = $this->filter->getCollection($this->collectionFactory->create());
+ $collection->addMediaGalleryData();
+
$productDeleted = 0;
$productDeletedError = 0;
/** @var \Magento\Catalog\Model\Product $product */
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
index 77c9cfcd40f05..035b50f3ada50 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
@@ -12,6 +12,9 @@
use Magento\Catalog\Controller\Adminhtml\Product;
use Magento\Framework\App\ObjectManager;
use Magento\Store\Model\StoreManagerInterface;
+use Magento\UrlRewrite\Model\Exception\UrlAlreadyExistsException;
+use Magento\CatalogUrlRewrite\Model\Product\Validator as ProductUrlRewriteValidator;
+use Magento\CatalogUrlRewrite\Model\ProductUrlPathGenerator;
/**
* Product validate
@@ -57,6 +60,16 @@ class Validate extends Product implements HttpPostActionInterface, HttpGetAction
*/
private $storeManager;
+ /**
+ * @var ProductUrlPathGenerator
+ */
+ private $productUrlPathGenerator;
+
+ /**
+ * @var ProductUrlRewriteValidator
+ */
+ private $productUrlRewriteValidator;
+
/**
* @param Action\Context $context
* @param Builder $productBuilder
@@ -65,6 +78,8 @@ class Validate extends Product implements HttpPostActionInterface, HttpGetAction
* @param \Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory
* @param \Magento\Framework\View\LayoutFactory $layoutFactory
* @param \Magento\Catalog\Model\ProductFactory $productFactory
+ * @param ProductUrlRewriteValidator $productUrlRewriteValidator
+ * @param ProductUrlPathGenerator $productUrlPathGenerator
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
@@ -73,7 +88,9 @@ public function __construct(
\Magento\Catalog\Model\Product\Validator $productValidator,
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
\Magento\Framework\View\LayoutFactory $layoutFactory,
- \Magento\Catalog\Model\ProductFactory $productFactory
+ \Magento\Catalog\Model\ProductFactory $productFactory,
+ ProductUrlRewriteValidator $productUrlRewriteValidator,
+ ProductUrlPathGenerator $productUrlPathGenerator
) {
$this->_dateFilter = $dateFilter;
$this->productValidator = $productValidator;
@@ -81,6 +98,8 @@ public function __construct(
$this->resultJsonFactory = $resultJsonFactory;
$this->layoutFactory = $layoutFactory;
$this->productFactory = $productFactory;
+ $this->productUrlRewriteValidator = $productUrlRewriteValidator;
+ $this->productUrlPathGenerator = $productUrlPathGenerator;
}
/**
@@ -130,11 +149,22 @@ public function execute()
$resource->getAttribute('news_from_date')->setMaxValue($product->getNewsToDate());
$resource->getAttribute('custom_design_from')->setMaxValue($product->getCustomDesignTo());
+ if (!$product->getUrlKey()) {
+ $urlKey = $this->productUrlPathGenerator->getUrlKey($product);
+ $product->setUrlKey($urlKey);
+ }
+ $this->productUrlRewriteValidator->validateUrlKeyConflicts($product);
$this->productValidator->validate($product, $this->getRequest(), $response);
} catch (\Magento\Eav\Model\Entity\Attribute\Exception $e) {
$response->setError(true);
$response->setAttribute($e->getAttributeCode());
$response->setMessages([$e->getMessage()]);
+ } catch (UrlAlreadyExistsException $e) {
+ $this->messageManager->addExceptionMessage($e);
+ $layout = $this->layoutFactory->create();
+ $layout->initMessages();
+ $response->setError(true);
+ $response->setHtmlMessage($layout->getMessagesBlock()->getGroupedHtml());
} catch (\Magento\Framework\Exception\LocalizedException $e) {
$response->setError(true);
$response->setMessages([$e->getMessage()]);
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 e296c8d3b8978..438618d89b43f 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
@@ -64,11 +64,11 @@ public function build(Filter $filter): string
->select()
->from(
[Collection::MAIN_TABLE_ALIAS => $entityResourceModel->getEntityTable()],
- Collection::MAIN_TABLE_ALIAS . '.' . $entityResourceModel->getEntityIdField()
+ Collection::MAIN_TABLE_ALIAS . '.' . $attribute->getEntityIdField()
)->joinLeft(
[$tableAlias => $attribute->getBackendTable()],
$tableAlias . '.' . $attribute->getEntityIdField() . '=' . Collection::MAIN_TABLE_ALIAS .
- '.' . $entityResourceModel->getEntityIdField() . ' AND ' . $tableAlias . '.' .
+ '.' . $attribute->getEntityIdField() . ' AND ' . $tableAlias . '.' .
$attribute->getIdFieldName() . '=' . $attribute->getAttributeId(),
''
)->where($tableAlias . '.value is null');
diff --git a/app/code/Magento/Catalog/Model/Attribute/ScopeOverriddenValue.php b/app/code/Magento/Catalog/Model/Attribute/ScopeOverriddenValue.php
index cf194615b1f3b..e58383f7d9bef 100644
--- a/app/code/Magento/Catalog/Model/Attribute/ScopeOverriddenValue.php
+++ b/app/code/Magento/Catalog/Model/Attribute/ScopeOverriddenValue.php
@@ -45,6 +45,11 @@ class ScopeOverriddenValue
*/
private $resourceConnection;
+ /**
+ * @var FilterBuilder
+ */
+ private $filterBuilder;
+
/**
* ScopeOverriddenValue constructor.
* @param AttributeRepository $attributeRepository
diff --git a/app/code/Magento/Catalog/Model/Category.php b/app/code/Magento/Catalog/Model/Category.php
index 538a721d356d7..981a3d81f0e7d 100644
--- a/app/code/Magento/Catalog/Model/Category.php
+++ b/app/code/Magento/Catalog/Model/Category.php
@@ -1159,7 +1159,10 @@ public function reindex()
*/
public function afterDeleteCommit()
{
- $this->reindex();
+ if ($this->getIsActive() || $this->getDeletedChildrenIds()) {
+ $this->reindex();
+ }
+
return parent::afterDeleteCommit();
}
diff --git a/app/code/Magento/Catalog/Model/Category/AttributeRepository.php b/app/code/Magento/Catalog/Model/Category/AttributeRepository.php
index bde5e08d90994..3243bf718e663 100644
--- a/app/code/Magento/Catalog/Model/Category/AttributeRepository.php
+++ b/app/code/Magento/Catalog/Model/Category/AttributeRepository.php
@@ -24,6 +24,11 @@ class AttributeRepository implements CategoryAttributeRepositoryInterface
*/
protected $eavAttributeRepository;
+ /**
+ * @var \Magento\Eav\Model\Config
+ */
+ private $eavConfig;
+
/**
* @param \Magento\Framework\Api\SearchCriteriaBuilder $searchCriteriaBuilder
* @param \Magento\Framework\Api\FilterBuilder $filterBuilder
diff --git a/app/code/Magento/Catalog/Model/Category/FileInfo.php b/app/code/Magento/Catalog/Model/Category/FileInfo.php
index f5aec60b2fcc0..adaf9304d8b3c 100644
--- a/app/code/Magento/Catalog/Model/Category/FileInfo.php
+++ b/app/code/Magento/Catalog/Model/Category/FileInfo.php
@@ -5,12 +5,15 @@
*/
namespace Magento\Catalog\Model\Category;
+use Magento\Catalog\Model\Category\Media\PathResolverFactory;
+use Magento\Catalog\Model\Category\Media\PathResolverInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\File\Mime;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\WriteInterface;
use Magento\Framework\Filesystem\Directory\ReadInterface;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Filesystem\ExtendedDriverInterface;
use Magento\Store\Model\StoreManagerInterface;
/**
@@ -121,11 +124,15 @@ private function getPubDirectory()
*/
public function getMimeType($fileName)
{
- $filePath = $this->getFilePath($fileName);
- $absoluteFilePath = $this->getMediaDirectory()->getAbsolutePath($filePath);
-
- $result = $this->mime->getMimeType($absoluteFilePath);
- return $result;
+ if ($this->getMediaDirectory()->getDriver() instanceof ExtendedDriverInterface) {
+ return $this->mediaDirectory->getDriver()->getMetadata($fileName)['mimetype'];
+ } else {
+ return $this->mime->getMimeType(
+ $this->getMediaDirectory()->getAbsolutePath(
+ $this->getFilePath($fileName)
+ )
+ );
+ }
}
/**
diff --git a/app/code/Magento/Catalog/Model/Config.php b/app/code/Magento/Catalog/Model/Config.php
index c4ff12bbf0f94..3ced479057e0f 100644
--- a/app/code/Magento/Catalog/Model/Config.php
+++ b/app/code/Magento/Catalog/Model/Config.php
@@ -44,6 +44,11 @@ class Config extends \Magento\Eav\Model\Config
*/
protected $_productTypesById;
+ /**
+ * @var array
+ */
+ private $_productTypesByName;
+
/**
* Array of attributes codes needed for product load
*
@@ -175,16 +180,6 @@ public function __construct(
);
}
- /**
- * Initialize resource model
- *
- * @return void
- */
- protected function _construct()
- {
- $this->_init(\Magento\Catalog\Model\ResourceModel\Config::class);
- }
-
/**
* Set store id
*
diff --git a/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php b/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php
index 0ae128b34d348..aecb3da8fd3d5 100644
--- a/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php
+++ b/app/code/Magento/Catalog/Model/Config/CatalogMediaConfig.php
@@ -16,8 +16,8 @@ class CatalogMediaConfig
{
private const XML_PATH_CATALOG_MEDIA_URL_FORMAT = 'web/url/catalog_media_url_format';
- const IMAGE_OPTIMIZATION_PARAMETERS = 'image_optimization_parameters';
- const HASH = 'hash';
+ public const IMAGE_OPTIMIZATION_PARAMETERS = 'image_optimization_parameters';
+ public const HASH = 'hash';
/**
* @var ScopeConfigInterface
@@ -41,10 +41,16 @@ public function __construct(ScopeConfigInterface $scopeConfig)
*/
public function getMediaUrlFormat($scopeType = ScopeConfigInterface::SCOPE_TYPE_DEFAULT, $scopeCode = null): string
{
- return $this->scopeConfig->getValue(
- CatalogMediaConfig::XML_PATH_CATALOG_MEDIA_URL_FORMAT,
+ $value = $this->scopeConfig->getValue(
+ self::XML_PATH_CATALOG_MEDIA_URL_FORMAT,
$scopeType,
$scopeCode
);
+
+ if ($value === null) {
+ return self::HASH;
+ }
+
+ return (string)$value;
}
}
diff --git a/app/code/Magento/Catalog/Model/CustomOptions/CustomOption.php b/app/code/Magento/Catalog/Model/CustomOptions/CustomOption.php
index df9961518f479..655602cd8e7be 100644
--- a/app/code/Magento/Catalog/Model/CustomOptions/CustomOption.php
+++ b/app/code/Magento/Catalog/Model/CustomOptions/CustomOption.php
@@ -17,6 +17,11 @@
class CustomOption extends AbstractExtensibleModel implements CustomOptionInterface
{
+ /**
+ * @var FileProcessor
+ */
+ private $fileProcessor;
+
/**
* @param Context $context
* @param Registry $registry
diff --git a/app/code/Magento/Catalog/Model/Design.php b/app/code/Magento/Catalog/Model/Design.php
index fed18a5a60913..c22de4f6da488 100644
--- a/app/code/Magento/Catalog/Model/Design.php
+++ b/app/code/Magento/Catalog/Model/Design.php
@@ -3,12 +3,20 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Model;
use Magento\Catalog\Model\Category\Attribute\LayoutUpdateManager as CategoryLayoutManager;
use Magento\Catalog\Model\Product\Attribute\LayoutUpdateManager as ProductLayoutManager;
use Magento\Framework\App\ObjectManager;
-use \Magento\Framework\TranslateInterface;
+use Magento\Framework\Data\Collection\AbstractDb;
+use Magento\Framework\DataObject;
+use Magento\Framework\Model\Context;
+use Magento\Framework\Model\ResourceModel\AbstractResource;
+use Magento\Framework\Registry;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Framework\TranslateInterface;
+use Magento\Framework\View\DesignInterface;
/**
* Catalog Custom Category design Model
@@ -28,12 +36,12 @@ class Design extends \Magento\Framework\Model\AbstractModel
/**
* Design package instance
*
- * @var \Magento\Framework\View\DesignInterface
+ * @var DesignInterface
*/
protected $_design = null;
/**
- * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
+ * @var TimezoneInterface
*/
protected $_localeDate;
@@ -53,12 +61,12 @@ class Design extends \Magento\Framework\Model\AbstractModel
private $productLayoutUpdates;
/**
- * @param \Magento\Framework\Model\Context $context
- * @param \Magento\Framework\Registry $registry
- * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
- * @param \Magento\Framework\View\DesignInterface $design
- * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
- * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
+ * @param Context $context
+ * @param Registry $registry
+ * @param TimezoneInterface $localeDate
+ * @param DesignInterface $design
+ * @param AbstractResource|null $resource
+ * @param AbstractDb|null $resourceCollection
* @param array $data
* @param TranslateInterface|null $translator
* @param CategoryLayoutManager|null $categoryLayoutManager
@@ -66,12 +74,12 @@ class Design extends \Magento\Framework\Model\AbstractModel
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Framework\Model\Context $context,
- \Magento\Framework\Registry $registry,
- \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
- \Magento\Framework\View\DesignInterface $design,
- \Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
- \Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
+ Context $context,
+ Registry $registry,
+ TimezoneInterface $localeDate,
+ DesignInterface $design,
+ AbstractResource $resource = null,
+ AbstractDb $resourceCollection = null,
array $data = [],
TranslateInterface $translator = null,
?CategoryLayoutManager $categoryLayoutManager = null,
@@ -104,7 +112,7 @@ public function applyCustomDesign($design)
* Get custom layout settings
*
* @param Category|Product $object
- * @return \Magento\Framework\DataObject
+ * @return DataObject
*/
public function getDesignSettings($object)
{
@@ -134,14 +142,17 @@ public function getDesignSettings($object)
* Extract custom layout settings from category or product object
*
* @param Category|Product $object
- * @return \Magento\Framework\DataObject
+ * @return DataObject
*/
protected function _extractSettings($object)
{
- $settings = new \Magento\Framework\DataObject();
+ $settings = new DataObject();
if (!$object) {
return $settings;
}
+ $settings->setPageLayout($object->getPageLayout());
+ $settings->setLayoutUpdates((array)$object->getCustomLayoutUpdate());
+
$date = $object->getCustomDesignDate();
if (array_key_exists(
'from',
@@ -155,28 +166,28 @@ protected function _extractSettings($object)
$date['to']
)
) {
- $settings->setCustomDesign(
- $object->getCustomDesign()
- )->setPageLayout(
- $object->getPageLayout()
- )->setLayoutUpdates(
- (array)$object->getCustomLayoutUpdate()
- );
+ if ($object->getCustomDesign()) {
+ $settings->setCustomDesign($object->getCustomDesign());
+ }
+ if ($object->getCustomLayout()) {
+ $settings->setPageLayout($object->getCustomLayout());
+ }
if ($object instanceof Category) {
$this->categoryLayoutUpdates->extractCustomSettings($object, $settings);
} elseif ($object instanceof Product) {
$this->productLayoutUpdates->extractCustomSettings($object, $settings);
}
}
+
return $settings;
}
/**
* Merge custom design settings
*
- * @param \Magento\Framework\DataObject $categorySettings
- * @param \Magento\Framework\DataObject $productSettings
- * @return \Magento\Framework\DataObject
+ * @param DataObject $categorySettings
+ * @param DataObject $productSettings
+ * @return DataObject
*/
protected function _mergeSettings($categorySettings, $productSettings)
{
@@ -190,6 +201,21 @@ protected function _mergeSettings($categorySettings, $productSettings)
$update = array_merge($categorySettings->getLayoutUpdates(), $productSettings->getLayoutUpdates());
$categorySettings->setLayoutUpdates($update);
}
+ if ($categorySettings->getPageLayoutHandles()) {
+ $handles = [];
+ foreach ($categorySettings->getPageLayoutHandles() as $key => $value) {
+ $handles[$key] = [
+ 'handle' => 'catalog_category_view',
+ 'value' => $value,
+ ];
+ }
+ $categorySettings->setPageLayoutHandles($handles);
+ }
+ if ($productSettings->getPageLayoutHandles()) {
+ $handle = array_merge($categorySettings->getPageLayoutHandles(), $productSettings->getPageLayoutHandles());
+ $categorySettings->setPageLayoutHandles($handle);
+ }
+
return $categorySettings;
}
}
diff --git a/app/code/Magento/Catalog/Model/ImageUploader.php b/app/code/Magento/Catalog/Model/ImageUploader.php
index 6aff6488164f9..0b987f05d162c 100644
--- a/app/code/Magento/Catalog/Model/ImageUploader.php
+++ b/app/code/Magento/Catalog/Model/ImageUploader.php
@@ -3,12 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Model;
use Magento\Framework\App\Filesystem\DirectoryList;
-use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\File\Uploader;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\File\Name;
use Magento\Framework\Filesystem;
use Magento\Framework\Filesystem\Directory\WriteInterface;
@@ -211,7 +211,7 @@ public function moveFileFromTmp($imageName, $returnRelativePath = false)
$baseTmpImagePath = $this->getFilePath($baseTmpPath, $imageName);
try {
- $this->coreFileStorageDatabase->copyFile(
+ $this->coreFileStorageDatabase->renameFile(
$baseTmpImagePath,
$baseImagePath
);
diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/Dynamic/Manual.php b/app/code/Magento/Catalog/Model/Layer/Filter/Dynamic/Manual.php
index bfa4613127830..ee452b08c43ee 100644
--- a/app/code/Magento/Catalog/Model/Layer/Filter/Dynamic/Manual.php
+++ b/app/code/Magento/Catalog/Model/Layer/Filter/Dynamic/Manual.php
@@ -18,6 +18,41 @@ class Manual implements AlgorithmInterface
{
const XML_PATH_RANGE_MAX_INTERVALS = 'catalog/layered_navigation/price_range_max_intervals';
+ /**
+ * @var Algorithm
+ */
+ private $algorithm;
+
+ /**
+ * @var \Magento\Catalog\Model\Layer
+ */
+ private $layer;
+
+ /**
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
+ /**
+ * @var Render
+ */
+ private $render;
+
+ /**
+ * @var Registry
+ */
+ private $coreRegistry;
+
+ /**
+ * @var Range
+ */
+ private $range;
+
+ /**
+ * @var Price
+ */
+ private $resource;
+
/**
* @param Algorithm $algorithm
* @param Resolver $layerResolver
diff --git a/app/code/Magento/Catalog/Model/Product.php b/app/code/Magento/Catalog/Model/Product.php
index 82d252acd9909..355d5bd36e7d2 100644
--- a/app/code/Magento/Catalog/Model/Product.php
+++ b/app/code/Magento/Catalog/Model/Product.php
@@ -828,9 +828,6 @@ public function getStoreIds()
if (!$this->hasStoreIds()) {
$storeIds = [];
if ($websiteIds = $this->getWebsiteIds()) {
- if (!$this->isObjectNew() && $this->_storeManager->isSingleStoreMode()) {
- $websiteIds = array_keys($websiteIds);
- }
foreach ($websiteIds as $websiteId) {
$websiteStores = $this->_storeManager->getWebsite($websiteId)->getStoreIds();
$storeIds[] = $websiteStores;
@@ -877,15 +874,17 @@ public function getAttributes($groupId = null, $skipSuper = false)
*/
public function beforeSave()
{
- $this->setTypeHasOptions(false);
- $this->setTypeHasRequiredOptions(false);
- $this->setHasOptions(false);
- $this->setRequiredOptions(false);
+ if ($this->getData('has_options') === null) {
+ $this->setHasOptions(false);
+ }
+ if ($this->getData('required_options') === null) {
+ $this->setRequiredOptions(false);
+ }
$this->getTypeInstance()->beforeSave($this);
- $hasOptions = false;
- $hasRequiredOptions = false;
+ $hasOptions = $this->getData('has_options') === "1";
+ $hasRequiredOptions = $this->getData('required_options') === "1";
/**
* $this->_canAffectOptions - set by type instance only
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php
index 9cb2ac0145898..32a417a935e81 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/TierPrice/SaveHandler.php
@@ -86,9 +86,11 @@ public function execute($entity, $arguments = [])
$identifierField = $this->metadataPoll->getMetadata(ProductInterface::class)->getLinkField();
$priceRows = array_filter($priceRows);
$productId = (int) $entity->getData($identifierField);
+ $pricesStored = $this->getPricesStored($priceRows);
+ $pricesMerged = $this->mergePrices($priceRows, $pricesStored);
// prepare and save data
- foreach ($priceRows as $data) {
+ foreach ($pricesMerged as $data) {
$isPriceWebsiteGlobal = (int)$data['website_id'] === 0;
if ($isGlobal === $isPriceWebsiteGlobal
|| !empty($data['price_qty'])
@@ -109,4 +111,51 @@ public function execute($entity, $arguments = [])
return $entity;
}
+
+ /**
+ * Merge prices
+ *
+ * @param array $prices
+ * @param array $pricesStored
+ * @return array
+ */
+ private function mergePrices(array $prices, array $pricesStored): array
+ {
+ if (!$pricesStored) {
+ return $prices;
+ }
+ $pricesId = [];
+ $pricesStoredId = [];
+ foreach ($prices as $price) {
+ if (isset($price['price_id'])) {
+ $pricesId[$price['price_id']] = $price;
+ }
+ }
+ foreach ($pricesStored as $price) {
+ if (isset($price['price_id'])) {
+ $pricesStoredId[$price['price_id']] = $price;
+ }
+ }
+ $pricesAdd = array_diff_key($pricesStoredId, $pricesId);
+ foreach ($pricesAdd as $price) {
+ $prices[] = $price;
+ }
+ return $prices;
+ }
+
+ /**
+ * Get stored prices
+ *
+ * @param array $prices
+ * @return array
+ */
+ private function getPricesStored(array $prices): array
+ {
+ $pricesStored = [];
+ $price = reset($prices);
+ if (isset($price['product_id']) && $price['product_id']) {
+ $pricesStored = $this->tierPriceResource->loadPriceData($price['product_id']);
+ }
+ return $pricesStored;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
index 4d148f078ae48..a562b3203490c 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
@@ -126,6 +126,7 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib
$attribute->setAttributeId($existingModel->getAttributeId());
$attribute->setIsUserDefined($existingModel->getIsUserDefined());
$attribute->setFrontendInput($existingModel->getFrontendInput());
+ $attribute->setBackendModel($existingModel->getBackendModel());
$this->updateDefaultFrontendLabel($attribute, $existingModel);
} else {
diff --git a/app/code/Magento/Catalog/Model/Product/Authorization.php b/app/code/Magento/Catalog/Model/Product/Authorization.php
index 4022eb34e65e3..3394b65f9eeb5 100644
--- a/app/code/Magento/Catalog/Model/Product/Authorization.php
+++ b/app/code/Magento/Catalog/Model/Product/Authorization.php
@@ -129,7 +129,7 @@ private function hasProductChanged(ProductModel $product, ?array $oldProduct = n
//No new value
continue;
}
- if (!in_array($newValue, $oldValues, true)) {
+ if ($newValue !== null && !in_array($newValue, $oldValues, true)) {
return true;
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php
index 68d0877c6cd66..d8cfb2eff264a 100644
--- a/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php
+++ b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php
@@ -16,7 +16,7 @@
class ItemResolverComposite implements ItemResolverInterface
{
/** @var string[] */
- private $itemResolvers = [];
+ private $itemResolvers;
/** @var ItemResolverInterface[] */
private $itemResolversInstances = [];
diff --git a/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php b/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php
index 6a1392d776d31..edee9aef508de 100644
--- a/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Gallery/UpdateHandler.php
@@ -88,9 +88,6 @@ protected function processDeletedImages($product, array &$images)
foreach ($images as $image) {
if (!empty($image['removed'])) {
if (!empty($image['value_id'])) {
- if (preg_match('/\.\.(\\\|\/)/', $image['file'])) {
- continue;
- }
$recordsToDelete[] = $image['value_id'];
if (!in_array($image['file'], $imagesToNotDelete)) {
$imagesToDelete[] = $image['file'];
@@ -116,7 +113,8 @@ protected function processDeletedImages($product, array &$images)
private function canDeleteImage(string $file): bool
{
$catalogPath = $this->mediaConfig->getBaseMediaPath();
- return $this->mediaDirectory->isFile($catalogPath . $file)
+ $filePath = $this->mediaDirectory->getRelativePath($catalogPath . $file);
+ return $this->mediaDirectory->isFile($filePath)
&& $this->resourceModel->countImageUses($file) <= 1;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Repository.php b/app/code/Magento/Catalog/Model/Product/Option/Repository.php
index bb4e247de32db..bb0f0c4792235 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Repository.php
@@ -158,6 +158,7 @@ public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $opt
$option->setData('product_id', $product->getData($metadata->getLinkField()));
$option->setData('store_id', $product->getStoreId());
+ $backedOptions = $option->getValues();
if ($option->getOptionId()) {
$options = $product->getOptions();
if (!$options) {
@@ -174,6 +175,9 @@ public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $opt
}
$originalValues = $persistedOption->getValues();
$newValues = $option->getData('values');
+ if (!$newValues) {
+ $newValues = $this->getOptionValues($option);
+ }
if ($newValues) {
if (isset($originalValues)) {
$newValues = $this->markRemovedValues($newValues, $originalValues);
@@ -182,6 +186,8 @@ public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $opt
}
}
$option->save();
+ // Required for API response data consistency
+ $option->setValues($backedOptions);
return $option;
}
@@ -249,4 +255,28 @@ private function getHydratorPool()
}
return $this->hydratorPool;
}
+
+ /**
+ * Get Option values from property
+ *
+ * Gets Option values stored in property, modifies for needed format and clears the property
+ *
+ * @param \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option
+ * @return array|null
+ */
+ private function getOptionValues(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $option): ?array
+ {
+ if ($option->getValues() === null) {
+ return null;
+ }
+
+ $optionValues = [];
+
+ foreach ($option->getValues() as $optionValue) {
+ $optionValues[] = $optionValue->getData();
+ }
+ $option->setValues(null);
+
+ return $optionValues;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
index 9cb6cda4d0a09..121ff2a5db9b2 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
@@ -7,26 +7,47 @@
namespace Magento\Catalog\Model\Product\Option;
+use Magento\Catalog\Api\Data\ProductCustomOptionInterface;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface as OptionRepository;
+use Magento\Catalog\Model\Product\Option;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\EntityManager\Operation\ExtensionInterface;
+use Magento\Catalog\Model\ResourceModel\Product\Relation;
+use Magento\Framework\Exception\CouldNotSaveException;
/**
- * Class SaveHandler
+ * SaveHandler for product option
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class SaveHandler implements ExtensionInterface
{
+ /**
+ * @var string[]
+ */
+ private $compositeProductTypes = ['grouped', 'configurable', 'bundle'];
+
/**
* @var OptionRepository
*/
protected $optionRepository;
+ /**
+ * @var Relation
+ */
+ private $relation;
+
/**
* @param OptionRepository $optionRepository
+ * @param Relation|null $relation
*/
public function __construct(
- OptionRepository $optionRepository
+ OptionRepository $optionRepository,
+ ?Relation $relation = null
) {
$this->optionRepository = $optionRepository;
+ $this->relation = $relation ?: ObjectManager::getInstance()->get(Relation::class);
}
/**
@@ -34,7 +55,7 @@ public function __construct(
*
* @param object $entity
* @param array $arguments
- * @return \Magento\Catalog\Api\Data\ProductInterface|object
+ * @return ProductInterface|object
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function execute($entity, $arguments = [])
@@ -47,20 +68,19 @@ public function execute($entity, $arguments = [])
$optionIds = [];
if ($options) {
- $optionIds = array_map(function ($option) {
- /** @var \Magento\Catalog\Model\Product\Option $option */
+ $optionIds = array_map(function (Option $option) {
return $option->getOptionId();
}, $options);
}
- /** @var \Magento\Catalog\Api\Data\ProductInterface $entity */
+ /** @var ProductInterface $entity */
foreach ($this->optionRepository->getProductOptions($entity) as $option) {
if (!in_array($option->getOptionId(), $optionIds)) {
$this->optionRepository->delete($option);
}
}
if ($options) {
- $this->processOptionsSaving($options, (bool)$entity->dataHasChangedFor('sku'), (string)$entity->getSku());
+ $this->processOptionsSaving($options, (bool)$entity->dataHasChangedFor('sku'), $entity);
}
return $entity;
@@ -71,15 +91,43 @@ public function execute($entity, $arguments = [])
*
* @param array $options
* @param bool $hasChangedSku
- * @param string $newSku
+ * @param ProductInterface $product
+ * @return void
+ * @throws CouldNotSaveException
*/
- private function processOptionsSaving(array $options, bool $hasChangedSku, string $newSku)
+ private function processOptionsSaving(array $options, bool $hasChangedSku, ProductInterface $product): void
{
+ $isProductHasRelations = $this->isProductHasRelations($product);
+ /** @var ProductCustomOptionInterface $option */
foreach ($options as $option) {
+ if (!$isProductHasRelations && $option->getIsRequire()) {
+ $message = 'Required custom options cannot be added to a simple product'
+ . ' that is a part of a composite product.';
+ throw new CouldNotSaveException(__($message));
+ }
+
if ($hasChangedSku && $option->hasData('product_sku')) {
- $option->setProductSku($newSku);
+ $option->setProductSku($product->getSku());
}
$this->optionRepository->save($option);
}
}
+
+ /**
+ * Check if product doesn't belong to composite product
+ *
+ * @param ProductInterface $product
+ * @return bool
+ */
+ private function isProductHasRelations(ProductInterface $product): bool
+ {
+ $result = true;
+ if (!in_array($product->getId(), $this->compositeProductTypes)
+ && $this->relation->getRelationsByChildren([$product->getId()])
+ ) {
+ $result = false;
+ }
+
+ return $result;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
index 934ff48045097..f227e14e6af45 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorFile.php
@@ -213,7 +213,7 @@ public function validate($processingParams, $option)
}
}
- $fileHash = md5($tmpDirectory->readFile($tmpDirectory->getRelativePath($fileInfo['tmp_name'])));
+ $fileHash = hash('sha256', $tmpDirectory->readFile($tmpDirectory->getRelativePath($fileInfo['tmp_name'])));
$userValue = [
'type' => $fileInfo['type'],
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
index 100ad37273cff..43bbe6f673385 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/File/ValidatorInfo.php
@@ -125,7 +125,7 @@ public function validate($optionValue, $option)
*/
protected function buildSecretKey($fileRelativePath)
{
- return substr(md5($this->rootDirectory->readFile($fileRelativePath)), 0, 20);
+ return substr(hash('sha256', $this->rootDirectory->readFile($fileRelativePath)), 0, 20);
}
/**
diff --git a/app/code/Magento/Catalog/Model/Product/Price/SpecialPriceStorage.php b/app/code/Magento/Catalog/Model/Product/Price/SpecialPriceStorage.php
index 83a2d1340794c..a0f6fcf315c7f 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/SpecialPriceStorage.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/SpecialPriceStorage.php
@@ -6,12 +6,22 @@
namespace Magento\Catalog\Model\Product\Price;
+use Magento\Catalog\Api\Data\SpecialPriceInterface;
+use Magento\Catalog\Api\Data\SpecialPriceInterfaceFactory;
+use Magento\Catalog\Api\SpecialPriceStorageInterface;
+use Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor;
+use Magento\Catalog\Model\Product\Price\Validation\Result;
+use Magento\Catalog\Model\ProductIdLocatorInterface;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Catalog\Helper\Data;
+use Magento\Store\Api\StoreRepositoryInterface;
/**
* Special price storage presents efficient price API and is used to retrieve, update or delete special prices.
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class SpecialPriceStorage implements \Magento\Catalog\Api\SpecialPriceStorageInterface
+class SpecialPriceStorage implements SpecialPriceStorageInterface
{
/**
* @var \Magento\Catalog\Api\SpecialPriceInterface
@@ -19,52 +29,59 @@ class SpecialPriceStorage implements \Magento\Catalog\Api\SpecialPriceStorageInt
private $specialPriceResource;
/**
- * @var \Magento\Catalog\Api\Data\SpecialPriceInterfaceFactory
+ * @var SpecialPriceInterfaceFactory
*/
private $specialPriceFactory;
/**
- * @var \Magento\Catalog\Model\ProductIdLocatorInterface
+ * @var ProductIdLocatorInterface
*/
private $productIdLocator;
/**
- * @var \Magento\Store\Api\StoreRepositoryInterface
+ * @var StoreRepositoryInterface
*/
private $storeRepository;
/**
- * @var \Magento\Catalog\Model\Product\Price\Validation\Result
+ * @var Result
*/
private $validationResult;
/**
- * @var \Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor
+ * @var InvalidSkuProcessor
*/
private $invalidSkuProcessor;
/**
* @var array
*/
- private $allowedProductTypes = [];
+ private $allowedProductTypes;
+
+ /**
+ * @var Data
+ */
+ private $catalogData;
/**
* @param \Magento\Catalog\Api\SpecialPriceInterface $specialPriceResource
- * @param \Magento\Catalog\Api\Data\SpecialPriceInterfaceFactory $specialPriceFactory
- * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
- * @param \Magento\Store\Api\StoreRepositoryInterface $storeRepository
- * @param \Magento\Catalog\Model\Product\Price\Validation\Result $validationResult
- * @param \Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor $invalidSkuProcessor
- * @param array $allowedProductTypes [optional]
+ * @param SpecialPriceInterfaceFactory $specialPriceFactory
+ * @param ProductIdLocatorInterface $productIdLocator
+ * @param StoreRepositoryInterface $storeRepository
+ * @param Result $validationResult
+ * @param InvalidSkuProcessor $invalidSkuProcessor
+ * @param array $allowedProductTypes
+ * @param Data|null $catalogData
*/
public function __construct(
\Magento\Catalog\Api\SpecialPriceInterface $specialPriceResource,
- \Magento\Catalog\Api\Data\SpecialPriceInterfaceFactory $specialPriceFactory,
- \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
- \Magento\Store\Api\StoreRepositoryInterface $storeRepository,
- \Magento\Catalog\Model\Product\Price\Validation\Result $validationResult,
- \Magento\Catalog\Model\Product\Price\Validation\InvalidSkuProcessor $invalidSkuProcessor,
- array $allowedProductTypes = []
+ SpecialPriceInterfaceFactory $specialPriceFactory,
+ ProductIdLocatorInterface $productIdLocator,
+ StoreRepositoryInterface $storeRepository,
+ Result $validationResult,
+ InvalidSkuProcessor $invalidSkuProcessor,
+ array $allowedProductTypes = [],
+ ?Data $catalogData = null
) {
$this->specialPriceResource = $specialPriceResource;
$this->specialPriceFactory = $specialPriceFactory;
@@ -73,10 +90,11 @@ public function __construct(
$this->validationResult = $validationResult;
$this->invalidSkuProcessor = $invalidSkuProcessor;
$this->allowedProductTypes = $allowedProductTypes;
+ $this->catalogData = $catalogData ?: ObjectManager::getInstance()->get(Data::class);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get(array $skus)
{
@@ -85,7 +103,7 @@ public function get(array $skus)
$prices = [];
foreach ($rawPrices as $rawPrice) {
- /** @var \Magento\Catalog\Api\Data\SpecialPriceInterface $price */
+ /** @var SpecialPriceInterface $price */
$price = $this->specialPriceFactory->create();
$sku = isset($rawPrice['sku'])
? $rawPrice['sku']
@@ -102,7 +120,7 @@ public function get(array $skus)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function update(array $prices)
{
@@ -113,7 +131,7 @@ public function update(array $prices)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(array $prices)
{
@@ -140,52 +158,14 @@ private function retrieveValidPrices(array $prices)
foreach ($prices as $key => $price) {
if (!$price->getSku() || in_array($price->getSku(), $failedSkus)) {
- $this->validationResult->addFailedItem(
- $key,
- __(
- 'The product that was requested doesn\'t exist. Verify the product and try again. '
- . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.',
- [
- 'SKU' => $price->getSku(),
- 'storeId' => $price->getStoreId(),
- 'priceFrom' => $price->getPriceFrom(),
- 'priceTo' => $price->getPriceTo()
- ]
- ),
- [
- 'SKU' => $price->getSku(),
- 'storeId' => $price->getStoreId(),
- 'priceFrom' => $price->getPriceFrom(),
- 'priceTo' => $price->getPriceTo()
- ]
- );
+ $errorMessage = 'The product that was requested doesn\'t exist. Verify the product and try again. '
+ . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.';
+ $this->addFailedItemPrice($price, $key, $errorMessage, []);
}
+ $this->checkStore($price, $key);
$this->checkPrice($price, $key);
$this->checkDate($price, $price->getPriceFrom(), 'Price From', $key);
$this->checkDate($price, $price->getPriceTo(), 'Price To', $key);
- try {
- $this->storeRepository->getById($price->getStoreId());
- } catch (NoSuchEntityException $e) {
- $this->validationResult->addFailedItem(
- $key,
- __(
- 'Requested store is not found. '
- . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.',
- [
- 'SKU' => $price->getSku(),
- 'storeId' => $price->getStoreId(),
- 'priceFrom' => $price->getPriceFrom(),
- 'priceTo' => $price->getPriceTo()
- ]
- ),
- [
- 'SKU' => $price->getSku(),
- 'storeId' => $price->getStoreId(),
- 'priceFrom' => $price->getPriceFrom(),
- 'priceTo' => $price->getPriceTo()
- ]
- );
- }
}
foreach ($this->validationResult->getFailedRowIds() as $id) {
@@ -195,77 +175,95 @@ private function retrieveValidPrices(array $prices)
return $prices;
}
+ /**
+ * Check that store exists and is global when price scope is global and otherwise add error to aggregator.
+ *
+ * @param SpecialPriceInterface $price
+ * @param int $key
+ * @return void
+ */
+ private function checkStore(SpecialPriceInterface $price, int $key): void
+ {
+ if ($this->catalogData->isPriceGlobal() && $price->getStoreId() !== 0) {
+ $errorMessage = 'Could not change non global Price when price scope is global. '
+ . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.';
+ $this->addFailedItemPrice($price, $key, $errorMessage, []);
+ }
+
+ try {
+ $this->storeRepository->getById($price->getStoreId());
+ } catch (NoSuchEntityException $e) {
+ $errorMessage = 'Requested store is not found. '
+ . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.';
+ $this->addFailedItemPrice($price, $key, $errorMessage, []);
+ }
+ }
+
/**
* Check that date value is correct and add error to aggregator if it contains incorrect data.
*
- * @param \Magento\Catalog\Api\Data\SpecialPriceInterface $price
+ * @param SpecialPriceInterface $price
* @param string $value
* @param string $label
* @param int $key
* @return void
*/
- private function checkDate(\Magento\Catalog\Api\Data\SpecialPriceInterface $price, $value, $label, $key)
+ private function checkDate(SpecialPriceInterface $price, $value, $label, $key)
{
if ($value && !$this->isCorrectDateValue($value)) {
- $this->validationResult->addFailedItem(
- $key,
- __(
- 'Invalid attribute %label = %priceTo. '
- . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.',
- [
- 'label' => $label,
- 'SKU' => $price->getSku(),
- 'storeId' => $price->getStoreId(),
- 'priceFrom' => $price->getPriceFrom(),
- 'priceTo' => $price->getPriceTo()
- ]
- ),
- [
- 'label' => $label,
- 'SKU' => $price->getSku(),
- 'storeId' => $price->getStoreId(),
- 'priceFrom' => $price->getPriceFrom(),
- 'priceTo' => $price->getPriceTo()
- ]
- );
+ $errorMessage = 'Invalid attribute %label = %priceTo. '
+ . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.';
+ $this->addFailedItemPrice($price, $key, $errorMessage, ['label' => $label]);
}
}
/**
- * Check that provided price value is not empty and not lower then zero and add error to aggregator if price
+ * Check price.
+ *
+ * Verify that provided price value is not empty and not lower then zero and add error to aggregator if price
* contains not valid data.
*
- * @param \Magento\Catalog\Api\Data\SpecialPriceInterface $price
+ * @param SpecialPriceInterface $price
* @param int $key
* @return void
*/
- private function checkPrice(\Magento\Catalog\Api\Data\SpecialPriceInterface $price, $key)
+ private function checkPrice(SpecialPriceInterface $price, int $key): void
{
if (null === $price->getPrice() || $price->getPrice() < 0) {
- $this->validationResult->addFailedItem(
- $key,
- __(
- 'Invalid attribute Price = %price. '
- . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.',
- [
- 'price' => $price->getPrice(),
- 'SKU' => $price->getSku(),
- 'storeId' => $price->getStoreId(),
- 'priceFrom' => $price->getPriceFrom(),
- 'priceTo' => $price->getPriceTo()
- ]
- ),
- [
- 'price' => $price->getPrice(),
- 'SKU' => $price->getSku(),
- 'storeId' => $price->getStoreId(),
- 'priceFrom' => $price->getPriceFrom(),
- 'priceTo' => $price->getPriceTo()
- ]
- );
+ $errorMessage = 'Invalid attribute Price = %price. '
+ . 'Row ID: SKU = %SKU, Store ID: %storeId, Price From: %priceFrom, Price To: %priceTo.';
+ $this->addFailedItemPrice($price, $key, $errorMessage, ['price' => $price->getPrice()]);
}
}
+ /**
+ * Adds failed item price to validation result
+ *
+ * @param SpecialPriceInterface $price
+ * @param int $key
+ * @param string $message
+ * @param array $firstParam
+ * @return void
+ */
+ private function addFailedItemPrice(
+ SpecialPriceInterface $price,
+ int $key,
+ string $message,
+ array $firstParam
+ ): void {
+ $additionalInfo = [];
+ if ($firstParam) {
+ $additionalInfo = array_merge($additionalInfo, $firstParam);
+ }
+
+ $additionalInfo['SKU'] = $price->getSku();
+ $additionalInfo['storeId'] = $price->getStoreId();
+ $additionalInfo['priceFrom'] = $price->getPriceFrom();
+ $additionalInfo['priceTo'] = $price->getPriceTo();
+
+ $this->validationResult->addFailedItem($key, __($message, $additionalInfo), $additionalInfo);
+ }
+
/**
* Retrieve SKU by product ID.
*
diff --git a/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php b/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php
index a2e151c1c9624..61f64e7c46958 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/TierPriceFactory.php
@@ -56,6 +56,16 @@ class TierPriceFactory
*/
private $customerGroupsByCode = [];
+ /**
+ * @var \Magento\Framework\Api\SearchCriteriaBuilder
+ */
+ private $searchCriteriaBuilder;
+
+ /**
+ * @var \Magento\Framework\Api\FilterBuilder
+ */
+ private $filterBuilder;
+
/**
* TierPriceBuilder constructor.
*
diff --git a/app/code/Magento/Catalog/Model/Product/Price/Validation/InvalidSkuProcessor.php b/app/code/Magento/Catalog/Model/Product/Price/Validation/InvalidSkuProcessor.php
index 744fa76ff69b6..b124da7b18d6a 100644
--- a/app/code/Magento/Catalog/Model/Product/Price/Validation/InvalidSkuProcessor.php
+++ b/app/code/Magento/Catalog/Model/Product/Price/Validation/InvalidSkuProcessor.php
@@ -11,6 +11,16 @@
*/
class InvalidSkuProcessor
{
+ /**
+ * @var \Magento\Catalog\Model\ProductIdLocatorInterface
+ */
+ private $productIdLocator;
+
+ /**
+ * @var \Magento\Catalog\Api\ProductRepositoryInterface
+ */
+ private $productRepository;
+
/**
* @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
* @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
diff --git a/app/code/Magento/Catalog/Model/Product/Type.php b/app/code/Magento/Catalog/Model/Product/Type.php
index d7dc74e0d0cc3..c2603d475e7d9 100644
--- a/app/code/Magento/Catalog/Model/Product/Type.php
+++ b/app/code/Magento/Catalog/Model/Product/Type.php
@@ -127,6 +127,8 @@ public function factory($product)
$typeModel = $this->_productTypePool->get($typeModelName);
$typeModel->setConfig($types[$typeId]);
+ $typeModel->setTypeId($typeId);
+
return $typeModel;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
index 19f6461d44b6a..6c08c493e803e 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
@@ -504,21 +504,17 @@ public function processFileQueue()
/** @var $uploader \Zend_File_Transfer_Adapter_Http */
$uploader = $queueOptions['uploader'] ?? null;
$isUploaded = false;
- if ($uploader && $uploader->isValid()) {
+ if ($uploader && $uploader->isValid($src)) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$path = pathinfo($dst, PATHINFO_DIRNAME);
$uploader = $this->uploaderFactory->create(['fileId' => $src]);
$uploader->setFilesDispersion(false);
$uploader->setAllowRenameFiles(true);
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$isUploaded = $uploader->save($path, pathinfo($dst, PATHINFO_FILENAME));
}
if (empty($src) || empty($dst) || !$isUploaded) {
- /**
- * @todo: show invalid option
- */
- if (isset($queueOptions['option'])) {
- $queueOptions['option']->setIsValid(false);
- }
throw new \Magento\Framework\Exception\LocalizedException(
__('The file upload failed. Try to upload again.')
);
@@ -756,6 +752,9 @@ protected function _removeNotApplicableAttributes($product)
*/
public function beforeSave($product)
{
+ if (!$product->getTypeId()) {
+ $product->setTypeId($this->_typeId);
+ }
$this->_removeNotApplicableAttributes($product);
$product->canAffectOptions(true);
return $this;
diff --git a/app/code/Magento/Catalog/Model/Product/Type/Price.php b/app/code/Magento/Catalog/Model/Product/Type/Price.php
index 206f3dba0ee60..8956d1d4c95a9 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/Price.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/Price.php
@@ -298,7 +298,7 @@ public function getTierPrice($qty, $product)
$custGroup = $this->_getCustomerGroupId($product);
if ($qty) {
- $prevQty = 1;
+ $prevQty = 0;
$prevPrice = $product->getPrice();
$prevGroup = $allGroupsId;
diff --git a/app/code/Magento/Catalog/Model/ProductIdLocator.php b/app/code/Magento/Catalog/Model/ProductIdLocator.php
index 2d382164f2649..00a4a9751907d 100644
--- a/app/code/Magento/Catalog/Model/ProductIdLocator.php
+++ b/app/code/Magento/Catalog/Model/ProductIdLocator.php
@@ -124,7 +124,7 @@ public function retrieveProductIdsBySkus(array $skus)
private function truncateToLimit()
{
if (count($this->idsBySku) > $this->idsLimit) {
- $this->idsBySku = array_slice($this->idsBySku, round($this->idsLimit / -2));
+ $this->idsBySku = array_slice($this->idsBySku, $this->idsLimit * -1, null, true);
}
}
}
diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php
index 5e1de230239b9..fdee74c9559c7 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository.php
@@ -9,6 +9,7 @@
use Magento\Catalog\Api\CategoryLinkManagementInterface;
use Magento\Catalog\Api\Data\ProductExtension;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Attribute\ScopeOverriddenValue;
use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap;
use Magento\Catalog\Model\ProductRepository\MediaGalleryProcessor;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
@@ -181,6 +182,11 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa
*/
private $linkManagement;
+ /**
+ * @var ScopeOverriddenValue
+ */
+ private $scopeOverriddenValue;
+
/**
* ProductRepository constructor.
* @param ProductFactory $productFactory
@@ -208,6 +214,7 @@ class ProductRepository implements \Magento\Catalog\Api\ProductRepositoryInterfa
* @param int $cacheLimit [optional]
* @param ReadExtensions $readExtensions
* @param CategoryLinkManagementInterface $linkManagement
+ * @param ScopeOverriddenValue|null $scopeOverriddenValue
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
@@ -236,7 +243,8 @@ public function __construct(
\Magento\Framework\Serialize\Serializer\Json $serializer = null,
$cacheLimit = 1000,
ReadExtensions $readExtensions = null,
- CategoryLinkManagementInterface $linkManagement = null
+ CategoryLinkManagementInterface $linkManagement = null,
+ ?ScopeOverriddenValue $scopeOverriddenValue = null
) {
$this->productFactory = $productFactory;
$this->collectionFactory = $collectionFactory;
@@ -263,6 +271,8 @@ public function __construct(
->get(ReadExtensions::class);
$this->linkManagement = $linkManagement ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(CategoryLinkManagementInterface::class);
+ $this->scopeOverriddenValue = $scopeOverriddenValue ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(ScopeOverriddenValue::class);
}
/**
@@ -599,6 +609,12 @@ public function save(ProductInterface $product, $saveOptions = false)
&& !$attribute->isStatic()
&& !array_key_exists($attributeCode, $productDataToChange)
&& $value !== null
+ && !$this->scopeOverriddenValue->containsValue(
+ ProductInterface::class,
+ $product,
+ $attributeCode,
+ $product->getStoreId()
+ )
) {
$product->setData($attributeCode);
$hasDataChanged = true;
@@ -914,7 +930,8 @@ private function joinPositionField(
foreach ($searchCriteria->getFilterGroups() as $filterGroup) {
foreach ($filterGroup->getFilters() as $filter) {
if ($filter->getField() === 'category_id') {
- $categoryIds[] = explode(',', $filter->getValue());
+ $filterValue = $filter->getValue();
+ $categoryIds[] = is_array($filterValue) ? $filterValue : explode(',', $filterValue);
}
}
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
index ed2df0f10ac3b..18f38f7d2bc24 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
@@ -232,7 +232,10 @@ protected function _beforeDelete(\Magento\Framework\DataObject $object)
*/
protected function _afterDelete(DataObject $object)
{
- $this->indexerProcessor->markIndexerAsInvalid();
+ if ($object->getIsActive() || $object->getDeletedChildrenIds()) {
+ $this->indexerProcessor->markIndexerAsInvalid();
+ }
+
return parent::_afterDelete($object);
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/MediaImageDeleteProcessor.php b/app/code/Magento/Catalog/Model/ResourceModel/MediaImageDeleteProcessor.php
new file mode 100644
index 0000000000000..f49ddef01ca74
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/MediaImageDeleteProcessor.php
@@ -0,0 +1,146 @@
+imageConfig = $imageConfig;
+ $this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
+ $this->imageProcessor = $imageProcessor;
+ $this->productGallery = $productGallery;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Process $product data and remove image from gallery after product delete
+ *
+ * @param DataObject $product
+ * @return void
+ */
+ public function execute(DataObject $product): void
+ {
+ try {
+ $productImages = $product->getMediaGalleryImages();
+ foreach ($productImages as $image) {
+ $imageFile = $image->getFile();
+ if ($imageFile) {
+ $this->deleteProductImage($image, $product, $imageFile);
+ }
+ }
+ } catch (Exception $e) {
+ $this->logger->critical($e);
+ }
+ }
+
+ /**
+ * Check if image exists and is not used by any other products
+ *
+ * @param string $file
+ * @return bool
+ */
+ private function canDeleteImage(string $file): bool
+ {
+ return $this->productGallery->countImageUses($file) <= 1;
+ }
+
+ /**
+ * Delete the physical image if it's existed and not used by other products
+ *
+ * @param string $imageFile
+ * @param string $filePath
+ * @throws FileSystemException
+ */
+ private function deletePhysicalImage(string $imageFile, string $filePath): void
+ {
+ if ($this->canDeleteImage($imageFile)) {
+ $this->mediaDirectory->delete($filePath);
+ }
+ }
+
+ /**
+ * Remove product image
+ *
+ * @param DataObject $image
+ * @param ProductInterface $product
+ * @param string $imageFile
+ */
+ private function deleteProductImage(
+ DataObject $image,
+ ProductInterface $product,
+ string $imageFile
+ ): void {
+ $catalogPath = $this->imageConfig->getBaseMediaPath();
+ $filePath = $catalogPath . $imageFile;
+
+ if ($this->mediaDirectory->isFile($filePath)) {
+ try {
+ $this->productGallery->deleteGallery($image->getValueId());
+ $this->imageProcessor->removeImage($product, $imageFile);
+ $this->deletePhysicalImage($imageFile, $filePath);
+ } catch (Exception $e) {
+ $this->logger->critical($e);
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
index b174e4beb6353..b3c50015d9dbf 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
@@ -100,6 +100,11 @@ class Product extends AbstractResource
*/
private $eavAttributeManagement;
+ /**
+ * @var MediaImageDeleteProcessor
+ */
+ private $mediaImageDeleteProcessor;
+
/**
* @param \Magento\Eav\Model\Entity\Context $context
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -114,6 +119,7 @@ class Product extends AbstractResource
* @param TableMaintainer|null $tableMaintainer
* @param UniqueValidationInterface|null $uniqueValidator
* @param AttributeManagementInterface|null $eavAttributeManagement
+ * @param MediaImageDeleteProcessor|null $mediaImageDeleteProcessor
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -129,7 +135,8 @@ public function __construct(
$data = [],
TableMaintainer $tableMaintainer = null,
UniqueValidationInterface $uniqueValidator = null,
- AttributeManagementInterface $eavAttributeManagement = null
+ AttributeManagementInterface $eavAttributeManagement = null,
+ ?MediaImageDeleteProcessor $mediaImageDeleteProcessor = null
) {
$this->_categoryCollectionFactory = $categoryCollectionFactory;
$this->_catalogCategory = $catalogCategory;
@@ -148,6 +155,8 @@ public function __construct(
$this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class);
$this->eavAttributeManagement = $eavAttributeManagement
?? ObjectManager::getInstance()->get(AttributeManagementInterface::class);
+ $this->mediaImageDeleteProcessor = $mediaImageDeleteProcessor
+ ?? ObjectManager::getInstance()->get(MediaImageDeleteProcessor::class);
}
/**
@@ -819,4 +828,16 @@ protected function getAttributeRow($entity, $object, $attribute)
$data['store_id'] = $object->getStoreId();
return $data;
}
+
+ /**
+ * After delete entity process
+ *
+ * @param DataObject $object
+ * @return $this
+ */
+ protected function _afterDelete(DataObject $object)
+ {
+ $this->mediaImageDeleteProcessor->execute($object);
+ return parent::_afterDelete($object);
+ }
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php
index e3edc4a0dc48e..b310f6e68774f 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Attribute/Backend/Tierprice.php
@@ -35,6 +35,7 @@ protected function _loadPriceDataColumns($columns)
$columns = parent::_loadPriceDataColumns($columns);
$columns['price_qty'] = 'qty';
$columns['percentage_value'] = 'percentage_value';
+ $columns['product_id'] = $this->getProductIdFieldName();
return $columns;
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BatchSizeCalculator.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BatchSizeCalculator.php
index 85ee8360b7127..664ed9ead16db 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BatchSizeCalculator.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BatchSizeCalculator.php
@@ -43,10 +43,10 @@ class BatchSizeCalculator
private const DEPLOYMENT_CONFIG_INDEXER_BATCHES = 'indexer/batch_size/';
/**
- * BatchSizeCalculator constructor.
* @param array $batchRowsCount
* @param array $estimators
* @param array $batchSizeAdjusters
+ * @param DeploymentConfig|null $deploymentConfig
*/
public function __construct(
array $batchRowsCount,
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRelationsCalculator.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRelationsCalculator.php
index d03c0c51703a9..730fba32c0998 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRelationsCalculator.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRelationsCalculator.php
@@ -11,6 +11,11 @@
*/
class CompositeProductRelationsCalculator
{
+ /**
+ * @var DefaultPrice
+ */
+ private $indexerResource;
+
/**
* @param DefaultPrice $indexerResource
*/
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php
index 77407ed699fbd..be1d1637b8532 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php
@@ -128,6 +128,11 @@ public function getQuery(array $dimensions, string $productType, array $entityId
['cwd' => $this->getTable('catalog_product_index_website')],
'pw.website_id = cwd.website_id',
[]
+ )->joinLeft(
+ // customer group website limitations
+ ['cgw' => $this->getTable('customer_group_excluded_website')],
+ 'cg.customer_group_id = cgw.customer_group_id AND pw.website_id = cgw.website_id',
+ []
)->joinLeft(
// we need this only for BCC in case someone expects table `tp` to be present in query
['tp' => $this->getTable('catalog_product_index_tier_price')],
@@ -227,6 +232,9 @@ public function getQuery(array $dimensions, string $productType, array $entityId
$select->where('e.entity_id IN(?)', $entityIds);
}
+ // exclude websites that are limited for customer group
+ $select->where('cgw.website_id IS NULL');
+
/**
* throw event for backward compatibility
*/
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Price/SpecialPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Price/SpecialPrice.php
index 7eb19193f8bdd..448b3769c7157 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Price/SpecialPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Price/SpecialPrice.php
@@ -6,10 +6,18 @@
namespace Magento\Catalog\Model\ResourceModel\Product\Price;
+use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
+use Magento\Catalog\Api\SpecialPriceInterface;
+use Magento\Catalog\Model\ProductIdLocatorInterface;
+use Magento\Catalog\Model\ResourceModel\Attribute;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Exception\CouldNotDeleteException;
+use Magento\Framework\App\ObjectManager;
+
/**
* Special price resource.
*/
-class SpecialPrice implements \Magento\Catalog\Api\SpecialPriceInterface
+class SpecialPrice implements SpecialPriceInterface
{
/**
* Price storage table.
@@ -26,24 +34,24 @@ class SpecialPrice implements \Magento\Catalog\Api\SpecialPriceInterface
private $datetimeTable = 'catalog_product_entity_datetime';
/**
- * @var \Magento\Catalog\Model\ResourceModel\Attribute
+ * @var Attribute
*/
private $attributeResource;
/**
- * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface
+ * @var ProductAttributeRepositoryInterface
*/
private $attributeRepository;
/**
- * @var \Magento\Catalog\Model\ProductIdLocatorInterface
+ * @var ProductIdLocatorInterface
*/
private $productIdLocator;
/**
* Metadata pool.
*
- * @var \Magento\Framework\EntityManager\MetadataPool
+ * @var MetadataPool
*/
private $metadataPool;
@@ -76,16 +84,16 @@ class SpecialPrice implements \Magento\Catalog\Api\SpecialPriceInterface
private $itemsPerOperation = 500;
/**
- * @param \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource
- * @param \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository
- * @param \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator
- * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ * @param Attribute $attributeResource
+ * @param ProductAttributeRepositoryInterface $attributeRepository
+ * @param ProductIdLocatorInterface $productIdLocator
+ * @param MetadataPool $metadataPool
*/
public function __construct(
- \Magento\Catalog\Model\ResourceModel\Attribute $attributeResource,
- \Magento\Catalog\Api\ProductAttributeRepositoryInterface $attributeRepository,
- \Magento\Catalog\Model\ProductIdLocatorInterface $productIdLocator,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool
+ Attribute $attributeResource,
+ ProductAttributeRepositoryInterface $attributeRepository,
+ ProductIdLocatorInterface $productIdLocator,
+ MetadataPool $metadataPool
) {
$this->attributeResource = $attributeResource;
$this->attributeRepository = $attributeRepository;
@@ -94,7 +102,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get(array $skus)
{
@@ -132,7 +140,7 @@ public function get(array $skus)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function update(array $prices)
{
@@ -187,49 +195,63 @@ public function update(array $prices)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(array $prices)
{
- $skus = array_unique(
- array_map(function ($price) {
- return $price->getSku();
- }, $prices)
- );
- $ids = $this->retrieveAffectedIds($skus);
$connection = $this->attributeResource->getConnection();
- $connection->beginTransaction();
- try {
- foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
- $this->attributeResource->getConnection()->delete(
- $this->attributeResource->getTable($this->priceTable),
- [
- 'attribute_id = ?' => $this->getPriceAttributeId(),
- $this->getEntityLinkField() . ' IN (?)' => $idsBunch
- ]
- );
- }
- foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
- $this->attributeResource->getConnection()->delete(
- $this->attributeResource->getTable($this->datetimeTable),
- [
- 'attribute_id IN (?)' => [$this->getPriceFromAttributeId(), $this->getPriceToAttributeId()],
- $this->getEntityLinkField() . ' IN (?)' => $idsBunch
- ]
- );
+
+ foreach ($this->getStoreSkus($prices) as $storeId => $skus) {
+
+ $ids = $this->retrieveAffectedIds(array_unique($skus));
+ $connection->beginTransaction();
+ try {
+ foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
+ $connection->delete(
+ $this->attributeResource->getTable($this->priceTable),
+ [
+ 'attribute_id = ?' => $this->getPriceAttributeId(),
+ 'store_id = ?' => $storeId,
+ $this->getEntityLinkField() . ' IN (?)' => $idsBunch
+ ]
+ );
+ }
+ foreach (array_chunk($ids, $this->itemsPerOperation) as $idsBunch) {
+ $connection->delete(
+ $this->attributeResource->getTable($this->datetimeTable),
+ [
+ 'attribute_id IN (?)' => [$this->getPriceFromAttributeId(), $this->getPriceToAttributeId()],
+ 'store_id = ?' => $storeId,
+ $this->getEntityLinkField() . ' IN (?)' => $idsBunch
+ ]
+ );
+ }
+ $connection->commit();
+ } catch (\Exception $e) {
+ $connection->rollBack();
+ throw new CouldNotDeleteException(__('Could not delete Prices'), $e);
}
- $connection->commit();
- } catch (\Exception $e) {
- $connection->rollBack();
- throw new \Magento\Framework\Exception\CouldNotDeleteException(
- __('Could not delete Prices'),
- $e
- );
}
return true;
}
+ /**
+ * Returns associative arrays of store_id as key and array of skus as value.
+ *
+ * @param \Magento\Catalog\Api\Data\SpecialPriceInterface[] $priceItems
+ * @return array
+ */
+ private function getStoreSkus(array $priceItems): array
+ {
+ $storeSkus = [];
+ foreach ($priceItems as $priceItem) {
+ $storeSkus[$priceItem->getStoreId()][] = $priceItem->getSku();
+ }
+
+ return $storeSkus;
+ }
+
/**
* Get link field.
*
@@ -312,9 +334,9 @@ private function retrieveAffectedIds(array $skus)
$affectedIds = [];
foreach ($this->productIdLocator->retrieveProductIdsBySkus($skus) as $productIds) {
- $affectedIds = array_merge($affectedIds, array_keys($productIds));
+ $affectedIds[] = array_keys($productIds);
}
- return array_unique($affectedIds);
+ return array_unique(array_merge([], ...$affectedIds));
}
}
diff --git a/app/code/Magento/Catalog/Model/Webapi/Product/Option/Type/File/Processor.php b/app/code/Magento/Catalog/Model/Webapi/Product/Option/Type/File/Processor.php
index 92302dd9ccf47..371d9e6c091f0 100644
--- a/app/code/Magento/Catalog/Model/Webapi/Product/Option/Type/File/Processor.php
+++ b/app/code/Magento/Catalog/Model/Webapi/Product/Option/Type/File/Processor.php
@@ -59,7 +59,7 @@ public function processFileContent(ImageContentInterface $imageContent)
$filePath = $this->saveFile($imageContent);
$fileAbsolutePath = $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->getAbsolutePath($filePath);
- $fileHash = md5($this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->readFile($filePath));
+ $fileHash = hash('sha256', $this->filesystem->getDirectoryRead(DirectoryList::MEDIA)->readFile($filePath));
$imageSize = getimagesize($fileAbsolutePath);
$result = [
'type' => $imageContent->getType(),
diff --git a/app/code/Magento/Catalog/Observer/Compare/BindCustomerLoginObserver.php b/app/code/Magento/Catalog/Observer/Compare/BindCustomerLoginObserver.php
index 27e1b44704156..0f165d96494af 100644
--- a/app/code/Magento/Catalog/Observer/Compare/BindCustomerLoginObserver.php
+++ b/app/code/Magento/Catalog/Observer/Compare/BindCustomerLoginObserver.php
@@ -14,6 +14,11 @@
*/
class BindCustomerLoginObserver implements ObserverInterface
{
+ /**
+ * @var Item
+ */
+ private $item;
+
/**
* @param Item $item
*/
diff --git a/app/code/Magento/Catalog/Observer/Compare/BindCustomerLogoutObserver.php b/app/code/Magento/Catalog/Observer/Compare/BindCustomerLogoutObserver.php
index 11b8d3f854f65..ece2a48da62ad 100644
--- a/app/code/Magento/Catalog/Observer/Compare/BindCustomerLogoutObserver.php
+++ b/app/code/Magento/Catalog/Observer/Compare/BindCustomerLogoutObserver.php
@@ -14,6 +14,11 @@
*/
class BindCustomerLogoutObserver implements ObserverInterface
{
+ /**
+ * @var Item
+ */
+ private $item;
+
/**
* @param Item $item
*/
diff --git a/app/code/Magento/Catalog/Plugin/Api/ProductLinkRepositoryInterface/ReindexAfterDeleteByIdProductLinksPlugin.php b/app/code/Magento/Catalog/Plugin/Api/ProductLinkRepositoryInterface/ReindexAfterDeleteByIdProductLinksPlugin.php
new file mode 100644
index 0000000000000..91e8629ec212a
--- /dev/null
+++ b/app/code/Magento/Catalog/Plugin/Api/ProductLinkRepositoryInterface/ReindexAfterDeleteByIdProductLinksPlugin.php
@@ -0,0 +1,57 @@
+fullProductIndexer = $fullProductIndexer;
+ $this->productRepository = $productRepository;
+ }
+
+ /**
+ * Complex reindex after product links has been deleted.
+ *
+ * @param ProductLinkRepositoryInterface $subject
+ * @param bool $result
+ * @param string $sku
+ * @param string $type
+ * @param string $linkedProductSku
+ * @return bool
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterDeleteById(ProductLinkRepositoryInterface $subject, bool $result, $sku): bool
+ {
+ $product = $this->productRepository->get($sku);
+ $this->fullProductIndexer->executeRow($product->getId());
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Catalog/Plugin/Api/ProductLinkRepositoryInterface/ReindexAfterSaveProductLinksPlugin.php b/app/code/Magento/Catalog/Plugin/Api/ProductLinkRepositoryInterface/ReindexAfterSaveProductLinksPlugin.php
new file mode 100644
index 0000000000000..480399035a7a3
--- /dev/null
+++ b/app/code/Magento/Catalog/Plugin/Api/ProductLinkRepositoryInterface/ReindexAfterSaveProductLinksPlugin.php
@@ -0,0 +1,56 @@
+fullProductIndexer = $fullProductIndexer;
+ $this->productRepository = $productRepository;
+ }
+
+ /**
+ * Complex reindex after product links has been saved.
+ *
+ * @param ProductLinkRepositoryInterface $subject
+ * @param bool $result
+ * @param ProductLinkInterface $entity
+ * @return bool
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterSave(ProductLinkRepositoryInterface $subject, bool $result, ProductLinkInterface $entity): bool
+ {
+ $product = $this->productRepository->get($entity->getSku());
+ $this->fullProductIndexer->executeRow($product->getId());
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Catalog/Plugin/Model/Indexer/Category/Product/MaxHeapTableSizeProcessorOnFullReindex.php b/app/code/Magento/Catalog/Plugin/Model/Indexer/Category/Product/MaxHeapTableSizeProcessorOnFullReindex.php
index e655a66e9b91b..cb4df8f162b40 100644
--- a/app/code/Magento/Catalog/Plugin/Model/Indexer/Category/Product/MaxHeapTableSizeProcessorOnFullReindex.php
+++ b/app/code/Magento/Catalog/Plugin/Model/Indexer/Category/Product/MaxHeapTableSizeProcessorOnFullReindex.php
@@ -15,6 +15,11 @@
*/
class MaxHeapTableSizeProcessorOnFullReindex
{
+ /**
+ * @var LoggerInterface
+ */
+ private $logger;
+
/**
* @var MaxHeapTableSizeProcessor
*/
diff --git a/app/code/Magento/Catalog/Plugin/RemoveImagesFromGalleryAfterRemovingProduct.php b/app/code/Magento/Catalog/Plugin/RemoveImagesFromGalleryAfterRemovingProduct.php
new file mode 100644
index 0000000000000..ef3abddf91e69
--- /dev/null
+++ b/app/code/Magento/Catalog/Plugin/RemoveImagesFromGalleryAfterRemovingProduct.php
@@ -0,0 +1,66 @@
+galleryResource = $galleryResource;
+ $this->mediaGalleryReadHandler = $mediaGalleryReadHandler;
+ }
+
+ /**
+ * Delete media gallery after deleting product
+ *
+ * @param ProductRepositoryInterface $subject
+ * @param callable $proceed
+ * @param ProductInterface $product
+ * @return bool
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function aroundDelete(
+ ProductRepositoryInterface $subject,
+ callable $proceed,
+ ProductInterface $product
+ ): bool {
+ $mediaGalleryAttributeId = $this->mediaGalleryReadHandler->getAttribute()->getAttributeId();
+ $mediaGallery = $this->galleryResource->loadProductGalleryByAttributeId($product, $mediaGalleryAttributeId);
+
+ $result = $proceed($product);
+
+ if ($mediaGallery) {
+ $this->galleryResource->deleteGallery(array_column($mediaGallery, 'value_id'));
+ }
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductImageOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductImageOnProductPageActionGroup.xml
new file mode 100644
index 0000000000000..3dca650c2dcf8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductImageOnProductPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Validates that the provided product image is present and correct.
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductInfoOnEditPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductInfoOnEditPageActionGroup.xml
new file mode 100644
index 0000000000000..9e0d13c1bc910
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminAssertProductInfoOnEditPageActionGroup.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ Verifies the general data on the Edit product details page in admin for a product.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyToDefaultValueWithoutRedirectActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyToDefaultValueWithoutRedirectActionGroup.xml
new file mode 100644
index 0000000000000..a8539859c6d7d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminChangeSeoUrlKeyToDefaultValueWithoutRedirectActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Requires navigation to category creation/edit page. Selects 'Use Default Value' checkbox for the 'URL Key' with 'Create Permanent Redirect for old URL' unchecked.
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml
index 60438e23e084c..2fb18ddfc9eb6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminClickOnAdvancedInventoryLinkActionGroup.xml
@@ -17,5 +17,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteAllProductAttributesFilteredByCodeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteAllProductAttributesFilteredByCodeActionGroup.xml
index fe5b0ae1a64ce..703b37079aee3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteAllProductAttributesFilteredByCodeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminDeleteAllProductAttributesFilteredByCodeActionGroup.xml
@@ -21,7 +21,7 @@
-
+
{{AdminDataGridTableSection.firstNotEmptyRow2}}
{{AdminConfirmationModalSection.ok}}
{{AdminMainActionsSection.delete}}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductDesignSectionActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductDesignSectionActionGroup.xml
new file mode 100644
index 0000000000000..f3d742f28cab0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminExpandProductDesignSectionActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ Expand the Design section on the Product Details page in Admin.
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductNameOnProductFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductNameOnProductFormActionGroup.xml
new file mode 100644
index 0000000000000..e2adc09376086
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductNameOnProductFormActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Fills in Name field on the Admin Products creation/edit page.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductQtyOnProductFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductQtyOnProductFormActionGroup.xml
new file mode 100644
index 0000000000000..225638f066a2c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductQtyOnProductFormActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Fills in Quantity field on the Admin Products creation/edit page.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductSkuOnProductFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductSkuOnProductFormActionGroup.xml
new file mode 100644
index 0000000000000..f7b4baaeef8e8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminFillProductSkuOnProductFormActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Fills in Sku field on the Admin Products creation/edit page.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetVisibleInAdvancedSearchActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetVisibleInAdvancedSearchActionGroup.xml
new file mode 100644
index 0000000000000..bd7c59ffc385e
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetVisibleInAdvancedSearchActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ Set 'Visible in Advanced Search' value for product attribute
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSaveCategoryFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSaveCategoryFormActionGroup.xml
index 5521cc2a7d0b2..30a9dd4276eb0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSaveCategoryFormActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminSaveCategoryFormActionGroup.xml
@@ -17,6 +17,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductFormAdvancedPricingAddTierPriceActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductFormAdvancedPricingAddTierPriceActionGroup.xml
new file mode 100644
index 0000000000000..64acee0b077c5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAdminProductFormAdvancedPricingAddTierPriceActionGroup.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+ Check tier price on Advanced Pricing dialog on the Admin Product creation/edit page.
+
+
+
+
+
+
+
+
+
+
+ $priceMinWidth
+ 60px
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductImageAdminProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductImageAdminProductPageActionGroup.xml
index 6ea154d2b01d3..c2a2dd5c13de8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductImageAdminProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductImageAdminProductPageActionGroup.xml
@@ -8,7 +8,7 @@
-
+
Validates that the provided Product Image is present and correct.
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontAttributeOptionQuantityInLayeredNavigationActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontAttributeOptionQuantityInLayeredNavigationActionGroup.xml
new file mode 100644
index 0000000000000..f1ffc1475e878
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontAttributeOptionQuantityInLayeredNavigationActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Asserts visible attribute option quantity
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductDropDownOptionValueActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductDropDownOptionValueActionGroup.xml
new file mode 100644
index 0000000000000..a633c3bc06c56
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertStorefrontProductDropDownOptionValueActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ Validates that the provided Product Option is selected under the provided Drop-down Attribute.
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductGridByCustomDateRangeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductGridByCustomDateRangeActionGroup.xml
new file mode 100644
index 0000000000000..30b15abb234d5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/FilterProductGridByCustomDateRangeActionGroup.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ Filters the Admin Products grid by the provided Date Filter.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SaveProductFormActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SaveProductFormActionGroup.xml
index f2524d6e68bfc..670eb04b55bd8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SaveProductFormActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SaveProductFormActionGroup.xml
@@ -17,6 +17,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertRelatedProductOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertRelatedProductOnProductPageActionGroup.xml
new file mode 100644
index 0000000000000..2d31eae0aedd9
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertRelatedProductOnProductPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+ Validates that the provided Product Name is present on Product details page.
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertSubCategoryNameIsNotShownInMenuActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertSubCategoryNameIsNotShownInMenuActionGroup.xml
new file mode 100644
index 0000000000000..157138f5e6674
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertSubCategoryNameIsNotShownInMenuActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Validate that the subcategory is not present in menu on Frontend.
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontRemoveFirstProductFromCompareActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontRemoveFirstProductFromCompareActionGroup.xml
new file mode 100644
index 0000000000000..fefcdb6930c61
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontRemoveFirstProductFromCompareActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ Open Compare Products list and remove a product
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
index 9639bc39b45f4..0fb4f3ef32050 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
@@ -11,6 +11,7 @@
simpleCategory
simplecategory
+ simplecategory
true
@@ -20,12 +21,14 @@
SimpleSubCategory
simplesubcategory
+ simplesubcategory
true
true
NewRootCategory
newrootcategory
+ newrootcategory
true
true
1
@@ -45,22 +48,27 @@
FirstLevelSubCategory
firstlevelsubcategory
+ firstlevelsubcategory
SecondLevelSubCategory
secondlevelsubcategory
+ secondlevelsubcategory
ThirdLevelSubCategory
- subcategory
+ thirdlevelsubcategory
+ thirdlevelsubcategory
FourthLevelSubCategory
- subcategory
+ fourthlevelsubcategory
+ fourthlevelsubcategory
FifthLevelCategory
- category
+ fifthlevelcategory
+ fifthlevelcategory
SimpleRootSubCategory
@@ -73,6 +81,7 @@
SubCategory
subcategory
+ subcategory
true
true
@@ -95,6 +104,7 @@
NotInclMenu
notinclemenu
+ notinclemenu
true
false
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
index 6e40499d0efeb..6f270feb20b60 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
@@ -8,11 +8,16 @@
-
+
/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0aHBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDIBCQkJDAsMGA0NGDIhHCEyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMv/AABEIAGAAYAMBIgACEQEDEQH/xACXAAEBAAMBAQEBAAAAAAAAAAAABgMEBQgCAQcQAAEDAQUFBgQDCQAAAAAAAAABAgMEBQYRFpESMTZV0QchcnOzwhMUIkEygaE1QlFSYXGCsbIBAAEFAQAAAAAAAAAAAAAAAAACAwQGBwERAAECAwMLBAMBAAAAAAAAAAEAAgMEERMhkRQxMzRBUVJTcXKxBRJhoSKBwUL/2gAMAwEAAhEDEQA/AP7+AYKysp7Po5aurlbFBEmL3u3NQ6ASaBdArcFnBN5/urzqn0d0Gf7q86p9HdCRkUzy3YFOWEXhOCpATef7q86p9HdBn+6vOqfR3QMimeW7AosIvCcFSAm8/wB1edU+jugz/dXnVPo7oGRTPLdgUWEXhOCpATef7q86p9HdBn+6vOqfR3QMimeW7AosIvCcFSA1bPtGktWiZWUM7Z6d6qjZG7lwXBf1Q2iO5paaOFCmyCDQoTd/uBLX8n3IUhN3+4EtfyfchIk9Zh9w8pyBpW9QvN4Bwbcsujis+pq2Q4Tq5HbW0u9XJj3Y4fc0ibjPgQjEY0GgJNTS4brj/FaIz3Q2FwFafNP4V3gc1aWz7FY+rjhVrsNjBrlcrsV3Iir/ABPxtqzRyM+boJKeJ7kakm2jkRV3Yom4TlbYf4xrnfFSBuqaCn7ouWwbc+4/FT90XTBz57RlbVvpqWjdUSRoiyfWjUbju71MUlqSyWdVPjpnsqIUVJI3ORFZ3fix+4OnoLSRU3V2HZnANKEjcEGOwVG74OxdUGjZM1RNQROqIlYuw3Zcr9pXpgn1f0xN4kQYgiww8bU4xwe0OG1eg+y7gCg8cvqOLEjuy7gCg8cvqOLEzT1HXIvcfKq0zpn9ShN3+4EtfyfchSE3f7gS1/J9yCJPWYfcPKTA0reoXm85l4P2HUf4/wDSHTPmSOOZiskY17F3tcmKKaXMwjGgvhj/AECMQrTFZ72ObvC5lvxq+gjeivRsUzXvVn4kb34qmpozxWc+NjVtWtqPiOREjbMj1Vf7YFHvMMdLTxP244ImP/maxEUhzMhaxC8UvABrXZuoR9pmLL+9xddfvXNrfkVtJyPqJaOpRiL8VHbKPT8+5THFVS1FnWnE+VKhsUbmsmamG3i1e78jsSwQzoiTRRyIm5HtRf8AZ9MjZGxGMY1rU/damCHTJPMQuDgAa5q31G0VpdnrnuRYO9xNaA1+/r9rUsmeGazqdscrHuZExHo1cVauH30U3THFBDBtfBijj2t+w1Ex0MhMgMcyG1r843J+GC1oDs69B9l3AFB45fUcWJHdl3AFB45fUcWJm3qOuRe4+VV5nTP6lCbv9wJa/k+5CkJu/wBwJa/k+5BEnrMPuHlJgaVvULzeADUlbUAAIQAAhAACF6D7LuAKDxy+o4sSO7LuAKDxy+o4sTMPUdci9x8qqTOmf1KE3f7gS1/J9yFITd/uBLX8n3IIk9Zh9w8pMDSt6hebwAakragABCAAEIAAQvQfZdwBQeOX1HFiR3ZdwBQeOX1HFiZh6jrkXuPlVSZ0z+pQwVlHT2hRy0lXE2WCVMHsduchnBEBINQmQaXhTeQLq8lp9XdRkC6vJafV3UpASMtmeY7Epy3i8RxU3kC6vJafV3UZAuryWn1d1KQBlszzHYlFvF4jipvIF1eS0+ruoyBdXktPq7qUgDLZnmOxKLeLxHFTeQLq8lp9XdRkC6vJafV3UpAGWzPMdiUW8XiOK1bPs6ksqiZR0MDYKdiqrY27kxXFf1U2gCO5xcauNSmySTUr/9k=
image/jpeg
test_image.jpg
+
+ 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
+ image/jpeg
+ adobe-base.jpg
+
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
image/png
@@ -21,4 +26,21 @@
magento-logo.png
+
+ iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAFpOLgnAAAAAXNSR0IArs4c6QAAAIRlWElmTU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAADKgAwAEAAAAAQAAADIAAAAAaawubwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KTMInWQAAAN1JREFUaAXtllEKwlAMBFvvfwsP5XEUhf2MzLOwFp3+BF42TTL7KN236+2+Dc9lOH8dny65T6uMk341MY77MfSpcFxzKnien7xomd7yPha8ux7J/SWl5csXWitxmezKy6O1SUigKC6EKSJxhQSKFVx+u5AXEVU8sUlwoyguhCkicYUEiuJCmCISV0igKC6EKSJxhQSKlZ87NMlBUcX3gzOichdBmIoiHSnCRq10BGEqinSkCBu10hGEqSjSkSJs1EpHEKaiSEeKsFErHUGYiiIdKcJGrXQEYSqKfsaRB19MEgdQHhXpAAAAAElFTkSuQmCC
+ image/png
+ blue.png
+ 5da9660dc7c0536210547e0000f9a9cf
+ b1e89172f4d192f00c1d947741759180
+
+
+ iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAFpOLgnAAAAAXNSR0IArs4c6QAAAIRlWElmTU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAIdpAAQAAAABAAAAWgAAAAAAAABgAAAAAQAAAGAAAAABAAOgAQADAAAAAQABAACgAgAEAAAAAQAAADKgAwAEAAAAAQAAADIAAAAAaawubwAAAAlwSFlzAAAOxAAADsQBlSsOGwAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KTMInWQAAAN1JREFUaAXtllEKwlAMBFuP4f2v4PkUhf2MzLOwFp3+BF42TTL7KN1v1+2+Dc9lOH8dny65T6uMk341MY77MfSpcFxzKnien7xomd7yPha8ux7J/SWl5csXWitxmezKy6O1SUigKC6EKSJxhQSKFVx+u5AXEVU8sUlwoyguhCkicYUEiuJCmCISV0igKC6EKSJxhQSKlZ87NMlBUcX3gzOichdBmIoiHSnCRq10BGEqinSkCBu10hGEqSjSkSJs1EpHEKaiSEeKsFErHUGYiiIdKcJGrXQEYSqKfsaRB7YwDVopmL/PAAAAAElFTkSuQmCC
+ image/png
+ red.png
+ 55942e709d0a1c85d5174839c2e55cea
+ 263ab50a24625c8d4f855cee8b947daa
+
+
+ c0459a796c5b8ee74254472c235a7460
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
index 7016a1c1d0358..5cc6b4ea34299 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
@@ -20,6 +20,18 @@
false
TestImageContent
+
+ image
+ Adobe Base
+ 1
+
+ - image
+ - small_image
+ - thumbnail
+
+ false
+ AdobeBaseContent
+
image
Magento Logo
@@ -30,6 +42,23 @@
false
MagentoLogoImageContent
+
+ image
+ Blue PNG
+ 1
+
+ - image
+ - small_image
+ - thumbnail
+ - swatch
+
+ false
+ BluePngImageContent
+
+
+ Red PNG
+ RedPngImageContent
+
Magento Logo
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 5375459122e69..567b3940eb6fa 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -11,7 +11,7 @@
testProductName
testSku
- testurlkey
+ testproductname
simple
4
4
@@ -39,6 +39,10 @@
TestFooBar
foobar
+
+ Simple Product Double Space
+ simple-product double-space
+
Pursuit Lumaflex™ Tone Band
x™
@@ -59,9 +63,9 @@
api-simple-product
- SimpleProduct
+ Simple Product
SimpleProduct
- simpleproduct
+ simple-product-
simple
4
123.00
@@ -72,6 +76,11 @@
EavStockItem
CustomAttributeCategoryIds
+
+ SimpleProduct
+ SimpleProduct
+ simpleproduct
+
partialTestSku
@@ -135,8 +144,9 @@
CustomAttributeCategoryIds
- SimpleProduct
- SimpleProduct
+ Simple Product 2
+ SimpleProduct2
+ simple-product-2-
simple
4
123.00
@@ -147,15 +157,15 @@
EavStockItem
- simple
- simple
+ Simple Product 3
+ SimpleProduct3
+ simple-product-3-
simple
4
123.00
4
1
1000
- simple
EavStockItem
CustomAttributeCategoryIds
@@ -1392,9 +1402,34 @@
16.00
+
+ 90.00
+
+
+ 95.00
+
+
+ 80.00
+
ProductOptionField
ProductOptionField2
+
+ ProductWithSku24MB01-
+ 24 MB01
+
+
+ ProductWithSku24MB02-
+ 24 MB02
+
+
+ ProductWithSku24MB04-
+ 24 MB04
+
+
+ ProductWithSku24MB06-
+ 24 MB06
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Helper/LocalFileAssertions.php b/app/code/Magento/Catalog/Test/Mftf/Helper/LocalFileAssertions.php
new file mode 100644
index 0000000000000..14867cd130cd5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Helper/LocalFileAssertions.php
@@ -0,0 +1,337 @@
+driver = new File();
+ }
+
+ /**
+ * Create a text file
+ *
+ * @param string $filePath
+ * @param string $text
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function createTextFile($filePath, $text): void
+ {
+ $realPath = $this->expandPath($filePath);
+ $this->driver->filePutContents($realPath, $text);
+ }
+
+ /**
+ * Delete a file if it exists
+ *
+ * @param string $filePath
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function deleteFileIfExists($filePath): void
+ {
+ $realPath = $this->expandPath($filePath);
+ if ($this->driver->isExists($realPath)) {
+ $this->driver->deleteFile($realPath);
+ }
+ }
+
+ /**
+ * Recursive delete directory
+ *
+ * @param string $path
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function deleteDirectory($path): void
+ {
+ $realPath = $this->expandPath($path);
+ if ($this->driver->isExists($realPath)) {
+ $this->driver->deleteDirectory($realPath);
+ }
+ }
+
+ /**
+ * Copy source into destination
+ *
+ * @param string $source
+ * @param string $destination
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function copy($source, $destination): void
+ {
+ $sourceRealPath = $this->expandPath($source);
+ $destinationRealPath = $this->expandPath($destination);
+ $this->driver->copy($sourceRealPath, $destinationRealPath);
+ }
+
+ /**
+ * Create directory
+ *
+ * @param string $path
+ * @param int $permissions
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function createDirectory($path, $permissions = 0777): void
+ {
+ $permissions = $this->convertOctalStringToDecimalInt($permissions);
+ $sourceRealPath = $this->expandPath($path);
+ $oldUmask = umask(0);
+ $this->driver->createDirectory($sourceRealPath, $permissions);
+ umask($oldUmask);
+ }
+
+ /**
+ * Assert a file exists
+ *
+ * @param string $filePath
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertFileExists($filePath, $message = ''): void
+ {
+ $realPath = $this->expandPath($filePath);
+ $this->assertTrue($this->driver->isExists($realPath), "Failed asserting $filePath exists. " . $message);
+ }
+
+ /**
+ * Asserts that a file with the given glob pattern exists in the given path
+ *
+ * @param string $path
+ * @param string $pattern
+ * @param string $message
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertGlobbedFileExists($path, $pattern, $message = ''): void
+ {
+ $realPath = $this->expandPath($path);
+ $files = $this->driver->search($pattern, $realPath);
+ $this->assertNotEmpty($files, "Failed asserting file matching glob pattern \"$pattern\" at location \"$path\" is not empty. " . $message);
+ }
+
+ /**
+ * Asserts that a file or directory exists
+ *
+ * @param string $path
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertPathExists($path, $message = ''): void
+ {
+ $realPath = $this->expandPath($path);
+ $this->assertTrue($this->driver->isExists($realPath), "Failed asserting $path exists. " . $message);
+ }
+
+ /**
+ * Asserts that a file or directory does not exist
+ *
+ * @param string $path
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertPathDoesNotExist($path, $message = ''): void
+ {
+ $realPath = $this->expandPath($path);
+ $this->assertFalse($this->driver->isExists($realPath), "Failed asserting $path does not exist. " . $message);
+ }
+
+ /**
+ * Assert a file does not exist
+ *
+ * @param string $filePath
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertFileDoesNotExist($filePath, $message = ''): void
+ {
+ $realPath = $this->expandPath($filePath);
+ $this->assertFalse($this->driver->isExists($realPath), $message);
+ }
+
+ /**
+ * Assert a file has no contents
+ *
+ * @param string $filePath
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertFileEmpty($filePath, $message = ''): void
+ {
+ $realPath = $this->expandPath($filePath);
+ $this->assertEmpty($this->driver->fileGetContents($realPath), "Failed asserting $filePath is empty. " . $message);
+ }
+
+ /**
+ * Assert a file is not empty
+ *
+ * @param string $filePath
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertFileNotEmpty($filePath, $message = ''): void
+ {
+ $realPath = $this->expandPath($filePath);
+ $this->assertNotEmpty($this->driver->fileGetContents($realPath), "Failed asserting $filePath is not empty. " . $message);
+ }
+
+ /**
+ * Assert a file contains a given string
+ *
+ * @param string $filePath
+ * @param string $text
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertFileContainsString($filePath, $text, $message = ''): void
+ {
+ $realPath = $this->expandPath($filePath);
+ $this->assertStringContainsString($text, $this->driver->fileGetContents($realPath), "Failed asserting $filePath contains $text. " . $message);
+ }
+
+ /**
+ * Asserts that a file with the given glob pattern at the given path contains a given string
+ *
+ * @param string $path
+ * @param string $pattern
+ * @param string $text
+ * @param int $fileIndex
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertGlobbedFileContainsString($path, $pattern, $text, $fileIndex = 0, $message = ''): void
+ {
+ $realPath = $this->expandPath($path);
+ $files = $this->driver->search($pattern, $realPath);
+ $this->assertStringContainsString($text, $this->driver->fileGetContents($files[$fileIndex] ?? ''), "Failed asserting file of index \"$fileIndex\" matching glob pattern \"$pattern\" at location \"$path\" contains $text. " . $message);
+ }
+
+ /**
+ * Assert a file does not contain a given string
+ *
+ * @param string $filePath
+ * @param string $text
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertFileDoesNotContainString($filePath, $text, $message = ''): void
+ {
+ $realPath = $this->expandPath($filePath);
+ $this->assertStringNotContainsString($text, $this->driver->fileGetContents($realPath), "Failed asserting $filePath does not contain $text. " . $message);
+ }
+
+ /**
+ * Asserts that a directory is empty
+ *
+ * @param string $path
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertDirectoryEmpty($path, $message = ''): void
+ {
+ $realPath = $this->expandPath($path);
+ $this->assertEmpty($this->driver->readDirectory($realPath), "Failed asserting $path is empty. " . $message);
+ }
+
+ /**
+ * Asserts that a directory is not empty
+ *
+ * @param string $path
+ * @param string $message
+ * @return void
+ *
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function assertDirectoryNotEmpty($path, $message = ''): void
+ {
+ $realPath = $this->expandPath($path);
+ $this->assertNotEmpty($this->driver->readDirectory($realPath), "Failed asserting $path is not empty. " . $message);
+ }
+
+ /**
+ * Helper function to convert an octal string to its decimal equivalent
+ *
+ * @param string $string
+ * @return int
+ *
+ */
+ private function convertOctalStringToDecimalInt($string): int
+ {
+ if (is_string($string)) {
+ $string = octdec($string);
+ }
+ return $string;
+ }
+
+ /**
+ * Helper function to construct the real path to the file
+ *
+ * If the given path isn't an absolute path then assume it's in context of the Magento root
+ *
+ * @param string $filePath
+ * @return string
+ */
+ private function expandPath($filePath): string
+ {
+ return (substr($filePath, 0, 1) === '/') ? $filePath : MAGENTO_BP . '/' . $filePath;
+
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
index ea4f4bf53eb71..14bf132c6a0cb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml
index d70c48f2b00e3..590b9a185e5e4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection/AdminProductFormSection.xml
@@ -75,6 +75,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
index 0d26c43d2c34b..0de3e6de1dee1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductImagesSection.xml
@@ -13,6 +13,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMessagesSection.xml
index c51d481d7dc5e..ff7edf14a50f9 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMessagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductMessagesSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection/StorefrontCategorySidebarSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection/StorefrontCategorySidebarSection.xml
index 848035b911aab..3131e9a89e8cd 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection/StorefrontCategorySidebarSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategorySidebarSection/StorefrontCategorySidebarSection.xml
@@ -16,8 +16,9 @@
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml
index ad31be6b277ee..f8000a25efdcf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductCompareMainSection.xml
@@ -14,5 +14,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
index 5efa094e2c35e..63b50e2d82f07 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml
index e1499a2484353..fad0b26262d0a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddOutOfStockProductToCompareListTest.xml
@@ -22,9 +22,7 @@
-
-
-
+
@@ -33,9 +31,7 @@
-
-
-
+
@@ -58,9 +54,7 @@
-
-
-
+
@@ -84,8 +78,7 @@
-
-
+
@@ -98,11 +91,13 @@
-
+
-
+
+
+
@@ -114,11 +109,11 @@
-
-
+
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml
index 2dc840b60f3b8..bed5297041dd1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageSimpleProductTest.xml
@@ -42,9 +42,7 @@
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml
index d1e292ff56444..d713660d7ee63 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddDefaultImageVirtualProductTest.xml
@@ -42,7 +42,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml
index 029c304873ce2..32444e3fee20f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGCatalogTest.xml
@@ -48,7 +48,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
index 5ea7253619ed9..e5c8cc5ee342c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddImageToWYSIWYGProductTest.xml
@@ -103,7 +103,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
index 73019bb5ec0e0..a574b0c7eabf3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
@@ -59,17 +59,15 @@
-
-
-
+
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductTest.xml
index 1ed079b12d1fd..8add42ec7493f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductTest.xml
@@ -55,14 +55,14 @@
-
+
-
+
@@ -79,14 +79,14 @@
-
+
-
+
@@ -109,19 +109,19 @@
-
+
-
+
-
+
@@ -213,7 +213,7 @@
$1,500.00
grabTextFromSubtotalField6
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductWithPercentageDiscountTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductWithPercentageDiscountTest.xml
index 0b29d2edb6615..6ffd157468099 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductWithPercentageDiscountTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminApplyTierPriceToProductTest/AdminApplyTierPriceToProductWithPercentageDiscountTest.xml
@@ -32,7 +32,7 @@
-
+
@@ -41,6 +41,10 @@
+
+
+
+
@@ -60,7 +64,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
index b52d18f3c0203..7a99afa3dcba7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
@@ -42,7 +42,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml
index a4a42a9999a5c..f361345478b53 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml
@@ -41,7 +41,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
index 99fd1cf3caf3f..88d24540b11f8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
@@ -42,7 +42,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml
index 3fff2c118ae6d..f956c73319425 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckPaginationInStorefrontTest.xml
@@ -124,7 +124,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml
index 32c1599355f81..8e45c223fbf49 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminConfigureProductImagePlaceholderTest.xml
@@ -89,7 +89,7 @@
-
+
@@ -124,7 +124,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml
index 0525e7543accb..1ba9c3a6ddc7b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditSimpleProductSettingsTest.xml
@@ -64,7 +64,7 @@
-
+
@@ -114,7 +114,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml
index ce3dd8fa0873c..61b7b8f6eaca8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndEditVirtualProductSettingsTest.xml
@@ -97,7 +97,7 @@
-
+
@@ -166,7 +166,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml
index 8d0534891a29b..6886775bff57c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithAnchorFieldTest.xml
@@ -71,15 +71,13 @@
-
-
-
+
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml
index f61a97219903f..fcffd272a2fe4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithFiveNestingTest.xml
@@ -67,7 +67,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithNoAnchorFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithNoAnchorFieldTest.xml
index 4173254c66fc3..d6fb7d0aa2c80 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithNoAnchorFieldTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithNoAnchorFieldTest.xml
@@ -80,12 +80,12 @@
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml
index af72dba3f8051..31ad92afb9d4f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithRequiredFieldsTest.xml
@@ -36,7 +36,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
index 10cba3ab209ef..4758bf63859b8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
@@ -114,7 +114,7 @@
-
+
@@ -130,7 +130,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
index 9db8e74b6ae7a..5931193dbe7ca 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
@@ -82,10 +82,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml
index 7447c75a778af..47c7f86067cf6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryAndUpdateAsInactiveTest.xml
@@ -31,9 +31,7 @@
-
-
-
+
@@ -64,14 +62,12 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml
index df2124759686d..fc09a1ac07d57 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveFlatCategoryTest.xml
@@ -31,9 +31,7 @@
-
-
-
+
@@ -67,13 +65,11 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml
index 2f86209da1eba..a2bd771c58fec 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateInactiveInMenuFlatCategoryTest.xml
@@ -31,9 +31,7 @@
-
-
-
+
@@ -68,13 +66,11 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
index 7b555aa84be05..45b776a6c8713 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
@@ -86,10 +86,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml
index 100d4bdef5f48..8f56a062c9113 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml
@@ -110,7 +110,7 @@
-
+
@@ -126,7 +126,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml
index d1110f593545d..432e64bded122 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductCustomAttributeSetTest.xml
@@ -79,7 +79,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest/AdminCreateProductDuplicateUrlkeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest/AdminCreateProductDuplicateUrlkeyTest.xml
index 6b3768cc88b3c..e61684b91c082 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest/AdminCreateProductDuplicateUrlkeyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductDuplicateUrlkeyTest/AdminCreateProductDuplicateUrlkeyTest.xml
@@ -32,13 +32,24 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductNotVisibleIndividuallyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductNotVisibleIndividuallyTest.xml
index 12cd5454ea8e2..deaa736ea6b4f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductNotVisibleIndividuallyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductNotVisibleIndividuallyTest.xml
@@ -23,7 +23,11 @@
-
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminConfigDefaultProductLayoutFromConfigurationSettingTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminConfigDefaultProductLayoutFromConfigurationSettingTest.xml
index ac2e86a572455..819835dead304 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminConfigDefaultProductLayoutFromConfigurationSettingTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminConfigDefaultProductLayoutFromConfigurationSettingTest.xml
@@ -5,8 +5,8 @@
* See COPYING.txt for license details.
*/
-->
-
+
@@ -21,18 +21,29 @@
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml
index 9f51d6227aa1d..32cce3633b10e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductNegativePriceTest.xml
@@ -18,11 +18,20 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductTest.xml
index 3e5ccfd8bf3b9..1c478eb4b6546 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductTest.xml
@@ -22,6 +22,11 @@
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductZeroPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductZeroPriceTest.xml
index 24f87cca958ab..b1f18a770ea0b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductZeroPriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductTest/AdminCreateSimpleProductZeroPriceTest.xml
@@ -18,13 +18,22 @@
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithDatetimeAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithDatetimeAttributeTest.xml
index 13efee209a556..47c7868b24002 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithDatetimeAttributeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithDatetimeAttributeTest.xml
@@ -48,10 +48,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithUnicodeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithUnicodeTest.xml
index a00714e412b0a..0c1785b8f725a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithUnicodeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateSimpleProductWithUnicodeTest.xml
@@ -22,6 +22,10 @@
+
+
+
+
@@ -32,9 +36,7 @@
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml
index cffefc4cd74c3..4c02c57dae535 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductFillingRequiredFieldsOnlyTest.xml
@@ -25,31 +25,54 @@
-
-
-
-
+
+
+
+
-
-
-
+
+
+
+
+
+
+
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
index e12394cbcb512..c9670ba5a8a7d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
@@ -118,9 +118,7 @@
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml
index e4cacba0224a7..0c4709b01da4f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithTierPriceTest.xml
@@ -91,14 +91,12 @@
-
+
-
-
-
+
@@ -111,7 +109,7 @@
-
+
As low as ${{tierPriceOnVirtualProduct.price}}
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml
index bf0d5d99a23bb..fd2000a1c5491 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml
@@ -87,7 +87,7 @@
-
+
@@ -107,13 +107,13 @@
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml
index 4599d0c275214..2c05b1515bc9c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductWithCustomOptionTest.xml
@@ -35,7 +35,7 @@
-
+
@@ -45,7 +45,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductsImageInCaseOfMultipleStoresTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductsImageInCaseOfMultipleStoresTest.xml
index 3514f53e8b937..0cb59ee54ae93 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductsImageInCaseOfMultipleStoresTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteProductsImageInCaseOfMultipleStoresTest.xml
@@ -66,9 +66,7 @@
-
-
-
+
@@ -95,9 +93,7 @@
-
-
-
+
@@ -179,7 +175,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml
index bdd1a4b4c70fe..2c6cc08d7689f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSimpleProductTest.xml
@@ -34,7 +34,7 @@
-
+
@@ -44,7 +44,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml
index dcfcbd699fc6b..de72de2769299 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteVirtualProductTest.xml
@@ -35,7 +35,7 @@
-
+
@@ -45,7 +45,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml
index 283ed72e62faa..044e5fa9d0d9f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminEditTextEditorProductAttributeTest.xml
@@ -46,7 +46,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml
index 4dd76e55f9330..e8ed35ecb4a9f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml
@@ -32,9 +32,7 @@
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
index 30ab17f65f3c8..44df83a4a7299 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
@@ -14,8 +14,8 @@
-
-
+
+
@@ -51,35 +51,43 @@
-
+
+
+
+
+
+
-
-
+
+
-
+
-
-
+
+
-
+
+
+
+
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml
index 3da19eb598012..ff9d99f76d6bf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveAnchoredCategoryToDefaultCategoryTest.xml
@@ -66,12 +66,10 @@
-
-
-
+
-
+
@@ -98,7 +96,7 @@
-
+
@@ -109,7 +107,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml
index 1c55b09151cf3..96f905c3d916e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryAndCheckUrlRewritesTest.xml
@@ -22,7 +22,7 @@
true
-
+
@@ -110,7 +110,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml
index fe3ffbb4fc1d7..2a03effb51ae8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryFromParentAnchoredCategoryTest.xml
@@ -59,7 +59,7 @@
-
+
@@ -88,7 +88,7 @@
-
+
@@ -99,7 +99,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml
index 8eb3d9bbb8063..c81e47a841891 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveCategoryToAnotherPositionInCategoryTreeTest.xml
@@ -56,7 +56,7 @@
-
+
@@ -76,10 +76,10 @@
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml
index 0af3911474813..002f11b5adfca 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMoveProductBetweenCategoriesTest.xml
@@ -140,7 +140,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml
index 94d7ea14b096c..edde693c51203 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminNavigateMultipleUpSellProductsTest.xml
@@ -156,9 +156,9 @@
-
+
-
+
@@ -167,7 +167,7 @@
-
+
@@ -176,7 +176,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml
index e06a7f3c5679c..a422d781b8169 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductCustomURLKeyPreservedWhenAssignedToCategoryWithoutCustomURLKeyTest.xml
@@ -35,9 +35,7 @@
-
-
-
+
@@ -80,8 +78,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateWithCustomLocaleTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateWithCustomLocaleTest.xml
new file mode 100644
index 0000000000000..eae9cebd7e638
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductGridFilteringByDateWithCustomLocaleTest.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminDownloadableProductTypeSwitchingToSimpleProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminDownloadableProductTypeSwitchingToSimpleProductTest.xml
index 6cbf03a02f3b0..b9f199c3954ea 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminDownloadableProductTypeSwitchingToSimpleProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminDownloadableProductTypeSwitchingToSimpleProductTest.xml
@@ -46,7 +46,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminVirtualProductTypeSwitchingToDownloadableProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminVirtualProductTypeSwitchingToDownloadableProductTest.xml
index 2311369db48f6..809096626270c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminVirtualProductTypeSwitchingToDownloadableProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductTypeSwitchingOnEditingTest/AdminVirtualProductTypeSwitchingToDownloadableProductTest.xml
@@ -66,7 +66,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml
index e989aa3758cf3..2cdbc26f90f7b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml
@@ -66,9 +66,7 @@
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRequiredFieldsHaveRequiredFieldIndicatorTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRequiredFieldsHaveRequiredFieldIndicatorTest.xml
index b2ed7b9628f38..49add85f76806 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRequiredFieldsHaveRequiredFieldIndicatorTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRequiredFieldsHaveRequiredFieldIndicatorTest.xml
@@ -58,8 +58,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminShowDoubleSpacesInProductGrid.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminShowDoubleSpacesInProductGrid.xml
new file mode 100644
index 0000000000000..c3e939b4155c8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminShowDoubleSpacesInProductGrid.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest/AdminSimpleProductImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest/AdminSimpleProductImagesTest.xml
index 8a33f6132aeb9..17dac7600ef9e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest/AdminSimpleProductImagesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductImagesTest/AdminSimpleProductImagesTest.xml
@@ -106,7 +106,7 @@
-
+
@@ -118,7 +118,7 @@
-
+
@@ -142,9 +142,7 @@
-
-
-
+
@@ -155,11 +153,11 @@
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductSetEditContentTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductSetEditContentTest.xml
index fc18531eca350..92966e0a00956 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductSetEditContentTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleProductSetEditContentTest.xml
@@ -70,7 +70,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml
index 3c90de572988c..a3ab78a11e09b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSimpleSetEditRelatedProductsTest.xml
@@ -78,7 +78,7 @@
-
+
@@ -98,13 +98,14 @@
-
+
-
+
+
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
index a9f8eab9a582f..b31ef59e30bbe 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
@@ -31,9 +31,7 @@
-
-
-
+
@@ -46,9 +44,7 @@
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml
index e562faf523929..e800b72f33850 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryAndCheckDefaultUrlKeyOnStoreViewTest.xml
@@ -62,7 +62,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml
index f82294ece6478..20fb1f6dc4f49 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateCategoryWithProductsTest.xml
@@ -65,16 +65,11 @@
-
-
-
-
-
-
-
+
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml
index f7f87da77b401..a6523c018bdbf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryAndAddProductsTest.xml
@@ -31,10 +31,7 @@
-
-
-
-
+
@@ -76,13 +73,11 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml
index b316e3194c986..2c7e26d4084b3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryIncludeInNavigationTest.xml
@@ -33,11 +33,9 @@
-
+
-
-
-
+
@@ -53,7 +51,7 @@
-
+
@@ -67,13 +65,11 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml
index 2124efed31293..13891c58c6018 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateFlatCategoryNameAndDescriptionTest.xml
@@ -35,9 +35,7 @@
-
-
-
+
@@ -68,13 +66,11 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
index 321f83161e945..34aea06bb69cc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
@@ -27,9 +27,7 @@
-
-
-
+
@@ -117,7 +115,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml
index ab0fcea919af0..f35eee9d1a63d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml
@@ -34,60 +34,106 @@
-
-
-
-
+
+
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
+
+
-
-
-
-
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
index d2e145adb6a86..39ff6d99b5b75 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
@@ -19,7 +19,7 @@
Use AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatCatalogTest instead
-
+
@@ -108,7 +108,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
index 52f550248819e..3ee9d0a9262c8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
@@ -36,67 +36,89 @@
-
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
index bdb56af340e7b..6f8356f86986b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
@@ -97,7 +97,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
index 08c4245147a2d..4de9552b863bb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
@@ -39,67 +39,105 @@
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
+
+
+
@@ -127,8 +165,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
index 6f5f3488b1da4..312baff6b8bcb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
@@ -95,7 +95,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
index 0d1d83efefa5d..5fae92639b489 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
@@ -94,7 +94,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml
index 39ecb8c3bfd01..2a03187af26ba 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockVisibleInCategoryOnlyTest.xml
@@ -127,7 +127,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
index 0cf0d22094cb6..4e0b5a635594c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
@@ -207,7 +207,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml
index 1c23d2cd59f39..3cef25a62775b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInCategoryOnlyTest.xml
@@ -96,7 +96,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml
index c738a589946c0..1e87a10fe753d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceOutOfStockVisibleInSearchOnlyTest.xml
@@ -111,7 +111,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml
index da43d12ab7983..41c187975a92b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithSpecialPriceInStockVisibleInCategoryAndSearchTest.xml
@@ -105,7 +105,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml
index 6d21178bbfa01..409ed2cd272a2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceInStockVisibleInCategoryOnlyTest.xml
@@ -114,7 +114,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml
index ded8f7d03029e..0123c226d3fa8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithTierPriceOutOfStockVisibleInCategoryAndSearchTest.xml
@@ -114,7 +114,7 @@
-
+
@@ -155,6 +155,6 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualProductSetEditContentTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualProductSetEditContentTest.xml
index 1e7b7cc14f9cc..ea562cbbd52bc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualProductSetEditContentTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualProductSetEditContentTest.xml
@@ -35,6 +35,6 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualSetEditRelatedProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualSetEditRelatedProductsTest.xml
index f70f5757ec5c2..f12512e998123 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualSetEditRelatedProductsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVirtualSetEditRelatedProductsTest.xml
@@ -15,13 +15,9 @@
-
-
-
-
-
+
+
-
@@ -38,6 +34,6 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByShortDescriptionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByShortDescriptionTest.xml
index 5431a19461f76..3faa9e8cf795d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByShortDescriptionTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByShortDescriptionTest.xml
@@ -18,6 +18,8 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml
index 2095d56ce6c59..2f4e20c7c8b55 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/CheckTierPricingOfProductsTest.xml
@@ -109,11 +109,7 @@
-
-
-
-
-
+
@@ -159,14 +155,14 @@
-
-
+
+
-
-
+
+
-
-
+
+
@@ -224,14 +220,14 @@
-
-
+
+
-
-
+
+
-
-
+
+
@@ -257,8 +253,8 @@
-
-
+
+
@@ -296,8 +292,8 @@
-
-
+
+
@@ -333,9 +329,7 @@
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHintTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHintTest.xml
index e50c124f7cfd9..9bebe97a8b344 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHintTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/ConfigurableOptionTextInputLengthValidationHintTest.xml
@@ -22,8 +22,12 @@
-
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml
index e679402740398..1094af357b187 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/DisplayRefreshCacheAfterChangingCategoryPageLayoutTest.xml
@@ -24,18 +24,14 @@
-
-
-
+
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml
index 916bcd7405ecf..9c18ba6cd654b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/ProductAttributeWithoutValueInCompareListTest.xml
@@ -49,7 +49,7 @@
-
+
@@ -60,7 +60,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml
index f6292a3a96c40..662ff5d0195c7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/SimpleProductTwoCustomOptionsTest.xml
@@ -63,7 +63,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml
index 64ad348257853..fe7bbd49dd408 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreLevelTest.xml
@@ -75,9 +75,7 @@
-
-
-
+
@@ -96,13 +94,10 @@
-
-
-
-
+
-
-
+
+
@@ -116,8 +111,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml
index 4d04a25c8d12f..8034e7911cb08 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StoreFrontRecentlyViewedAtStoreViewLevelTest.xml
@@ -67,9 +67,7 @@
-
-
-
+
@@ -90,13 +88,11 @@
-
-
-
+
-
-
+
+
@@ -114,8 +110,8 @@
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchSimpleProductBySkuWithHyphenTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchSimpleProductBySkuWithHyphenTest.xml
index 69f78c63e2675..b1caeac384d85 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchSimpleProductBySkuWithHyphenTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchSimpleProductBySkuWithHyphenTest.xml
@@ -15,12 +15,9 @@
-
+
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchVirtualProductBySkuWithHyphenTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchVirtualProductBySkuWithHyphenTest.xml
index b87cce398498f..5ef2fd65fec1b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchVirtualProductBySkuWithHyphenTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontAdvanceCatalogSearchVirtualProductBySkuWithHyphenTest.xml
@@ -15,12 +15,9 @@
-
-
+
+
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml
index 3ff477070cc30..d8c798dbc1409 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithCategoryFilterTest.xml
@@ -43,10 +43,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml
index a2316efb3a743..befb64d01bfde 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCategoryPageWithoutCategoryFilterTest.xml
@@ -43,10 +43,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml
index ca561e4af70de..c9be526e095aa 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontCheckDefaultNumberProductsToDisplayTest.xml
@@ -188,13 +188,11 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml
index 5d584bed989b3..d0c6c4fe86aee 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontConfigurableOptionsThumbImagesTest.xml
@@ -170,7 +170,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml
index 9731b66209df0..34d712f49e338 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontForthLevelCategoryTest.xml
@@ -34,9 +34,7 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest/StorefrontProductNameWithDoubleQuoteTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest/StorefrontProductNameWithDoubleQuoteTest.xml
index 1032c322053da..02b21394770c1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest/StorefrontProductNameWithDoubleQuoteTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest/StorefrontProductNameWithDoubleQuoteTest.xml
@@ -42,7 +42,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest/StorefrontProductNameWithHTMLEntitiesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest/StorefrontProductNameWithHTMLEntitiesTest.xml
index 9819357704d44..9c248a289ebd4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest/StorefrontProductNameWithHTMLEntitiesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductNameWithDoubleQuoteTest/StorefrontProductNameWithHTMLEntitiesTest.xml
@@ -36,7 +36,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml
index a311b63418a69..203fa753d10fb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductWithEmptyAttributeTest.xml
@@ -26,6 +26,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml
index c7817ed181ae0..d56faf9d5dec4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontProductsCompareWithEmptyAttributeTest.xml
@@ -46,7 +46,7 @@
-
+
@@ -54,7 +54,7 @@
-
+
@@ -62,7 +62,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml
index 71041ee7e1349..dad3e05d81a53 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml
@@ -120,9 +120,8 @@
-
-
-
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml
index ce419167e9514..e8bb48cfdd62b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitleTest.xml
@@ -12,14 +12,11 @@
-
-
-
-
-
+
+
@@ -45,38 +42,39 @@
-
+
-
+
+
-
-
+
-
+
-
+
+
-
+
-
+
-
+
-
+
@@ -84,14 +82,13 @@
-
+
-
-
-
+
+
@@ -117,6 +114,6 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRememberCategoryPaginationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRememberCategoryPaginationTest.xml
index 78fbed1aef3a9..107000a337991 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRememberCategoryPaginationTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontRememberCategoryPaginationTest.xml
@@ -44,7 +44,7 @@
-
+
@@ -61,10 +61,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml
index 164701fa5bc6d..5ff0a002e11ed 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontSpecialPriceForDifferentTimezonesForWebsitesTest.xml
@@ -69,7 +69,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml
index ee6ff0c224545..1790b2be68e29 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnCatalogTest.xml
@@ -39,7 +39,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
index f75053f495c4d..e52b334e66de9 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/VerifyTinyMCEv4IsNativeWYSIWYGOnProductTest.xml
@@ -52,7 +52,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php
index 157f641335497..55551a4ed603a 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/ListProductTest.php
@@ -195,7 +195,7 @@ public function testGetIdentities()
$this->catCollectionMock->expects($this->once())
->method('getIterator')
- ->willReturn([$currentCategory]);
+ ->willReturn(new \ArrayIterator([$currentCategory]));
$this->prodCollectionMock->expects($this->any())
->method('getIterator')
diff --git a/app/code/Magento/Catalog/Test/Unit/Block/Ui/ProductViewCounterTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Ui/ProductViewCounterTest.php
index 6026d1462e461..87f5be4b21333 100644
--- a/app/code/Magento/Catalog/Test/Unit/Block/Ui/ProductViewCounterTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Ui/ProductViewCounterTest.php
@@ -166,6 +166,7 @@ public function testGetCurrentProductDataWithNonEmptyProduct()
{
$productMock = $this->getMockBuilder(ProductInterface::class)
->disableOriginalConstructor()
+ ->addMethods(['isAvailable'])
->getMockForAbstractClass();
$productRendererMock = $this->getMockBuilder(ProductRenderInterface::class)
->disableOriginalConstructor()
@@ -173,7 +174,6 @@ public function testGetCurrentProductDataWithNonEmptyProduct()
$storeMock = $this->getMockBuilder(Store::class)
->disableOriginalConstructor()
->getMock();
-
$this->registryMock->expects($this->once())
->method('registry')
->with('product')
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php
index 2560b56a04e84..f38ffcd822cd9 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Initialization/Helper/AttributeFilterTest.php
@@ -216,6 +216,29 @@ public function setupInputDataProvider()
['description', null, 'descr text'],
],
],
+ 'update_product_with_empty_string_attribute' => [
+ 'requestProductData' => [
+ 'name' => 'testName3',
+ 'sku' => 'testSku3',
+ 'price' => '103',
+ 'special_price' => '100',
+ 'custom_attribute' => '',
+ ],
+ 'useDefaults' => [],
+ 'expectedProductData' => [
+ 'name' => 'testName3',
+ 'sku' => 'testSku3',
+ 'price' => '103',
+ 'special_price' => '100',
+ 'custom_attribute' => '',
+ ],
+ 'initialProductData' => [
+ ['name', null, 'testName2'],
+ ['sku', null, 'testSku2'],
+ ['price', null, '101'],
+ ['custom_attribute', null, '0'],
+ ],
+ ],
];
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/SaveHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/SaveHandlerTest.php
index c807ea881da6e..8721e86614469 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/SaveHandlerTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Attribute/Backend/TierPrice/SaveHandlerTest.php
@@ -180,4 +180,143 @@ public function testExecuteWithException(): void
$this->saveHandler->execute($product);
}
+
+ /**
+ * @param $tierPrices
+ * @param $tierPricesStored
+ * @param $tierPricesExpected
+ * @dataProvider executeWithWebsitePriceDataProvider
+ */
+ public function testExecuteWithWebsitePrice($tierPrices, $tierPricesStored, $tierPricesExpected): void
+ {
+ $productId = 10;
+ $linkField = 'entity_id';
+
+ /** @var MockObject $product */
+ $product = $this->getMockBuilder(ProductInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getData','setData', 'getStoreId'])
+ ->getMockForAbstractClass();
+ $product->expects($this->atLeastOnce())->method('getData')->willReturnMap(
+ [
+ ['tier_price', $tierPrices],
+ ['entity_id', $productId]
+ ]
+ );
+ $product->expects($this->atLeastOnce())->method('getStoreId')->willReturn(0);
+ $product->expects($this->atLeastOnce())->method('setData')->with('tier_price_changed', 1);
+ $store = $this->getMockBuilder(StoreInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getWebsiteId'])
+ ->getMockForAbstractClass();
+ $store->expects($this->atLeastOnce())->method('getWebsiteId')->willReturn(1);
+ $this->storeManager->expects($this->atLeastOnce())->method('getStore')->willReturn($store);
+ /** @var MockObject $attribute */
+ $attribute = $this->getMockBuilder(ProductAttributeInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getName', 'isScopeGlobal'])
+ ->getMockForAbstractClass();
+ $attribute->expects($this->atLeastOnce())->method('getName')->willReturn('tier_price');
+ $attribute->expects($this->atLeastOnce())->method('isScopeGlobal')->willReturn(false);
+ $this->attributeRepository->expects($this->atLeastOnce())->method('get')->with('tier_price')
+ ->willReturn($attribute);
+ $productMetadata = $this->getMockBuilder(EntityMetadataInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getLinkField'])
+ ->getMockForAbstractClass();
+ $productMetadata->expects($this->atLeastOnce())->method('getLinkField')->willReturn($linkField);
+ $this->metadataPoll->expects($this->atLeastOnce())->method('getMetadata')
+ ->with(ProductInterface::class)
+ ->willReturn($productMetadata);
+ $customerGroup = $this->getMockBuilder(GroupInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getId'])
+ ->getMockForAbstractClass();
+ $customerGroup->expects($this->atLeastOnce())->method('getId')->willReturn(3200);
+ $this->groupManagement->expects($this->atLeastOnce())->method('getAllCustomersGroup')
+ ->willReturn($customerGroup);
+ $this->tierPriceResource
+ ->expects($this->at(1))
+ ->method('savePriceData')
+ ->with(new \Magento\Framework\DataObject($tierPricesExpected[0]))
+ ->willReturnSelf();
+ $this->tierPriceResource
+ ->expects($this->at(2))
+ ->method('savePriceData')
+ ->with(new \Magento\Framework\DataObject($tierPricesExpected[1]))
+ ->willReturnSelf();
+ $this->tierPriceResource
+ ->expects($this->at(3))
+ ->method('savePriceData')
+ ->with(new \Magento\Framework\DataObject($tierPricesExpected[2]))
+ ->willReturnSelf();
+ $this->tierPriceResource
+ ->expects($this->atLeastOnce())
+ ->method('loadPriceData')
+ ->willReturn($tierPricesStored);
+
+ $this->assertEquals($product, $this->saveHandler->execute($product));
+ }
+
+ public function executeWithWebsitePriceDataProvider(): array
+ {
+ $productId = 10;
+ return [[
+ 'tierPrices' => [
+ [
+ 'price_id' => 1,
+ 'website_id' => 0,
+ 'price_qty' => 1,
+ 'cust_group' => 0,
+ 'price' => 10,
+ 'product_id' => $productId
+ ],[
+ 'price_id' => 2,
+ 'website_id' => 1,
+ 'price_qty' => 2,
+ 'cust_group' => 3200,
+ 'price' => null,
+ 'percentage_value' => 20,
+ 'product_id' => $productId
+ ]
+ ],
+ 'tierPricesStored' => [
+ [
+ 'price_id' => 3,
+ 'website_id' => 1,
+ 'price_qty' => 3,
+ 'cust_group' => 0,
+ 'price' => 30,
+ 'product_id' => $productId
+ ]
+ ],
+ 'tierPricesExpected' => [
+ [
+ 'website_id' => 0,
+ 'qty' => 1,
+ 'customer_group_id' => 0,
+ 'all_groups' => 0,
+ 'value' => 10,
+ 'percentage_value' => null,
+ 'entity_id' => $productId
+ ],[
+ 'website_id' => 1,
+ 'qty' => 2,
+ 'customer_group_id' => 0,
+ 'all_groups' => 1,
+ 'value' => null,
+ 'percentage_value' => 20,
+ 'entity_id' => $productId
+ ],[
+ 'website_id' => 1,
+ 'qty' => 3,
+ 'customer_group_id' => 0,
+ 'all_groups' => 0,
+ 'value' => 30,
+ 'percentage_value' => null,
+ 'entity_id' => $productId
+ ]
+ ]
+ ]];
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php
index 6928f9161b815..96948ed8d1aa8 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/FileInfoTest.php
@@ -129,21 +129,13 @@ public function testGetMimeType()
$absoluteFilePath = '/a/b/c/pub/media/catalog/category/filename.ext1';
$expected = 'ext1';
-
- $this->mediaDirectory->expects($this->at(0))
- ->method('getAbsolutePath')
- ->with(null)
- ->willReturn('/a/b/c/pub/media/');
-
- $this->mediaDirectory->expects($this->at(1))
- ->method('getAbsolutePath')
- ->with(null)
- ->willReturn('/a/b/c/pub/media/');
-
- $this->mediaDirectory->expects($this->at(2))
- ->method('getAbsolutePath')
- ->with('/catalog/category/filename.ext1')
- ->willReturn($absoluteFilePath);
+ $this->mediaDirectory->method('getAbsolutePath')
+ ->willReturnMap(
+ [
+ [null, '/a/b/c/pub/media'],
+ ['/catalog/category/filename.ext1', $absoluteFilePath]
+ ]
+ );
$this->mime->expects($this->once())
->method('getMimeType')
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php
index 673e12a5b42b5..2924bf66949c1 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Attribute/RepositoryTest.php
@@ -267,13 +267,16 @@ public function testSaveDoesNotSaveAttributeOptionsIfOptionsAreAbsentInPayload()
{
$attributeId = 1;
$attributeCode = 'existing_attribute_code';
+ $backendModel = 'backend_model';
$attributeMock = $this->createMock(Attribute::class);
$attributeMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode);
$attributeMock->expects($this->any())->method('getAttributeId')->willReturn($attributeId);
+ $attributeMock->expects($this->once())->method('setBackendModel')->with($backendModel)->willReturnSelf();
$existingModelMock = $this->createMock(Attribute::class);
$existingModelMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode);
$existingModelMock->expects($this->any())->method('getAttributeId')->willReturn($attributeId);
+ $existingModelMock->expects($this->once())->method('getBackendModel')->willReturn($backendModel);
$this->eavAttributeRepositoryMock->expects($this->any())
->method('get')
@@ -292,6 +295,7 @@ public function testSaveDoesNotSaveAttributeOptionsIfOptionsAreAbsentInPayload()
*/
public function testSaveSavesDefaultFrontendLabelIfItIsPresentInPayload()
{
+ $backendModel = 'backend_model';
$labelMock = $this->getMockForAbstractClass(AttributeFrontendLabelInterface::class);
$labelMock->expects($this->any())->method('getStoreId')->willReturn(1);
$labelMock->expects($this->any())->method('getLabel')->willReturn('Store Scope Label');
@@ -304,11 +308,13 @@ public function testSaveSavesDefaultFrontendLabelIfItIsPresentInPayload()
$attributeMock->expects($this->any())->method('getDefaultFrontendLabel')->willReturn(null);
$attributeMock->expects($this->any())->method('getFrontendLabels')->willReturn([$labelMock]);
$attributeMock->expects($this->any())->method('getOptions')->willReturn([]);
+ $attributeMock->expects($this->once())->method('setBackendModel')->with($backendModel)->willReturnSelf();
$existingModelMock = $this->createMock(Attribute::class);
$existingModelMock->expects($this->any())->method('getDefaultFrontendLabel')->willReturn('Default Label');
$existingModelMock->expects($this->any())->method('getAttributeId')->willReturn($attributeId);
$existingModelMock->expects($this->any())->method('getAttributeCode')->willReturn($attributeCode);
+ $existingModelMock->expects($this->once())->method('getBackendModel')->willReturn($backendModel);
$this->eavAttributeRepositoryMock->expects($this->any())
->method('get')
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php
index ac67ba52d7728..016e755f27b4d 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/RepositoryTest.php
@@ -262,7 +262,7 @@ public function testSave()
->getMock();
$optionCollection->expects($this->once())->method('getProductOptions')->willReturn([$this->optionMock]);
$this->optionCollectionFactory->expects($this->once())->method('create')->willReturn($optionCollection);
- $this->optionMock->expects($this->once())->method('getValues')->willReturn([
+ $this->optionMock->expects($this->exactly(2))->method('getValues')->willReturn([
$originalValue1,
$originalValue2,
$originalValue3
@@ -291,7 +291,7 @@ public function testSaveWhenOptionTypeWasChanged()
->getMock();
$optionCollection->expects($this->once())->method('getProductOptions')->willReturn([$this->optionMock]);
$this->optionCollectionFactory->expects($this->once())->method('create')->willReturn($optionCollection);
- $this->optionMock->expects($this->once())->method('getValues')->willReturn(null);
+ $this->optionMock->expects($this->exactly(2))->method('getValues')->willReturn(null);
$this->assertEquals($this->optionMock, $this->optionRepository->save($this->optionMock));
}
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php
index fa8a3fc1e6059..82916cf42ebe3 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Option/SaveHandlerTest.php
@@ -11,9 +11,13 @@
use Magento\Catalog\Model\Product\Option;
use Magento\Catalog\Model\Product\Option\Repository;
use Magento\Catalog\Model\Product\Option\SaveHandler;
+use Magento\Catalog\Model\ResourceModel\Product\Relation;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+/**
+ * Test for \Magento\Catalog\Model\Product\Option\SaveHandler.
+ */
class SaveHandlerTest extends TestCase
{
/**
@@ -36,6 +40,14 @@ class SaveHandlerTest extends TestCase
*/
protected $optionRepository;
+ /**
+ * @var Relation|MockObject
+ */
+ private $relationMock;
+
+ /**
+ * @inheridoc
+ */
protected function setUp(): void
{
$this->entity = $this->getMockBuilder(Product::class)
@@ -47,11 +59,19 @@ protected function setUp(): void
$this->optionRepository = $this->getMockBuilder(Repository::class)
->disableOriginalConstructor()
->getMock();
+ $this->relationMock = $this->getMockBuilder(Relation::class)
+ ->disableOriginalConstructor()
+ ->getMock();
- $this->model = new SaveHandler($this->optionRepository);
+ $this->model = new SaveHandler($this->optionRepository, $this->relationMock);
}
- public function testExecute()
+ /**
+ * Test for execute
+ *
+ * @return void
+ */
+ public function testExecute(): void
{
$this->optionMock->expects($this->any())->method('getOptionId')->willReturn(5);
$this->entity->expects($this->once())->method('getOptions')->willReturn([$this->optionMock]);
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php
index c14bb7f524d03..61bbaf94e9fff 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/Type/PriceTest.php
@@ -21,6 +21,7 @@
use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Model\Website;
use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\MockObject\RuntimeException;
use PHPUnit\Framework\TestCase;
/**
@@ -110,7 +111,7 @@ protected function setUp(): void
$this->groupManagementMock->expects($this->any())->method('getAllCustomersGroup')
->willReturn($group);
$this->tierPriceExtensionFactoryMock = $this->getMockBuilder(ProductTierPriceExtensionFactory::class)
- ->setMethods(['create'])
+ ->onlyMethods(['create'])
->disableOriginalConstructor()
->getMock();
$this->model = $this->objectManagerHelper->getObject(
@@ -182,9 +183,7 @@ function () {
);
// create sample TierPrice objects that would be coming from a REST call
- $tierPriceExtensionMock = $this->getMockBuilder(ProductTierPriceExtensionInterface::class)
- ->setMethods(['getWebsiteId', 'setWebsiteId', 'getPercentageValue', 'setPercentageValue'])
- ->getMockForAbstractClass();
+ $tierPriceExtensionMock = $this->getProductTierPriceExtensionInterfaceMock();
$tierPriceExtensionMock->expects($this->any())->method('getWebsiteId')->willReturn($expectedWebsiteId);
$tierPriceExtensionMock->expects($this->any())->method('getPercentageValue')->willReturn(null);
$tp1 = $this->objectManagerHelper->getObject(TierPrice::class);
@@ -226,9 +225,7 @@ function () {
$this->assertEquals($tps[$i]->getQty(), $tpData['price_qty'], 'Qty does not match');
}
- $tierPriceExtensionMock = $this->getMockBuilder(ProductTierPriceExtensionInterface::class)
- ->setMethods(['getWebsiteId', 'setWebsiteId', 'getPercentageValue', 'setPercentageValue'])
- ->getMockForAbstractClass();
+ $tierPriceExtensionMock = $this->getProductTierPriceExtensionInterfaceMock();
$tierPriceExtensionMock->expects($this->any())->method('getPercentageValue')->willReturn(50);
$tierPriceExtensionMock->expects($this->any())->method('setWebsiteId');
$this->tierPriceExtensionFactoryMock->expects($this->any())
@@ -289,9 +286,7 @@ function () {
return $this->objectManagerHelper->getObject(TierPrice::class);
}
);
- $tierPriceExtensionMock = $this->getMockBuilder(ProductTierPriceExtensionInterface::class)
- ->onlyMethods(['getPercentageValue', 'setPercentageValue'])
- ->getMockForAbstractClass();
+ $tierPriceExtensionMock = $this->getProductTierPriceExtensionInterfaceMock();
$tierPriceExtensionMock->method('getPercentageValue')
->willReturn(50);
$this->tierPriceExtensionFactoryMock->method('create')
@@ -299,4 +294,22 @@ function () {
$this->assertInstanceOf(TierPrice::class, $this->model->getTierPrices($this->product)[0]);
}
+
+ /**
+ * Build ProductTierPriceExtensionInterface mock.
+ *
+ * @return MockObject
+ */
+ private function getProductTierPriceExtensionInterfaceMock(): MockObject
+ {
+ $mockBuilder = $this->getMockBuilder(ProductTierPriceExtensionInterface::class)
+ ->disableOriginalConstructor();
+ try {
+ $mockBuilder->addMethods(['getPercentageValue', 'setPercentageValue', 'setWebsiteId', 'getWebsiteId']);
+ } catch (RuntimeException $e) {
+ // ProductTierPriceExtensionInterface already generated and has all necessary methods.
+ }
+
+ return $mockBuilder->getMock();
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
index a68eb3e652da9..a0c682aa3e416 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductIdLocatorTest.php
@@ -13,7 +13,6 @@
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\EntityManager\EntityMetadataInterface;
use Magento\Framework\EntityManager\MetadataPool;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
@@ -23,14 +22,19 @@
class ProductIdLocatorTest extends TestCase
{
/**
- * @var MetadataPool|MockObject
+ * @var int
*/
- private $metadataPool;
+ private $idsLimit;
/**
- * @var CollectionFactory|MockObject
+ * @var string
*/
- private $collectionFactory;
+ private $linkField;
+
+ /**
+ * @var Collection|MockObject
+ */
+ private $collection;
/**
* @var ProductIdLocator
@@ -38,79 +42,125 @@ class ProductIdLocatorTest extends TestCase
private $model;
/**
- * Set up.
- *
- * @return void
+ * @inheritDoc
*/
protected function setUp(): void
{
- $this->metadataPool = $this->getMockBuilder(MetadataPool::class)
- ->setMethods(['getMetadata'])
- ->disableOriginalConstructor()
- ->getMock();
- $this->collectionFactory = $this
- ->getMockBuilder(CollectionFactory::class)
+ $metadataPool = $this->createMock(MetadataPool::class);
+ $collectionFactory = $this->getMockBuilder(CollectionFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
+ $this->idsLimit = 4;
- $objectManager = new ObjectManager($this);
- $this->model = $objectManager->getObject(
- ProductIdLocator::class,
- [
- 'metadataPool' => $this->metadataPool,
- 'collectionFactory' => $this->collectionFactory,
- ]
- );
+ $this->linkField = 'entity_id';
+ $metaDataInterface = $this->createMock(EntityMetadataInterface::class);
+ $metaDataInterface->method('getLinkField')
+ ->willReturn($this->linkField);
+ $metadataPool->method('getMetadata')
+ ->with(ProductInterface::class)
+ ->willReturn($metaDataInterface);
+
+ $this->collection = $this->createMock(Collection::class);
+ $collectionFactory->method('create')
+ ->willReturn($this->collection);
+
+ $this->model = new ProductIdLocator($metadataPool, $collectionFactory, $this->idsLimit);
}
- /**
- * Test retrieve
- */
public function testRetrieveProductIdsBySkus()
{
$skus = ['sku_1', 'sku_2'];
- $collection = $this->getMockBuilder(Collection::class)
- ->setMethods(
- [
- 'getItems',
- 'addFieldToFilter',
- 'setPageSize',
- 'getLastPageNumber',
- 'setCurPage',
- 'clear'
- ]
- )
- ->disableOriginalConstructor()
- ->getMock();
+
$product = $this->getMockBuilder(ProductInterface::class)
->setMethods(['getSku', 'getData', 'getTypeId'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $metaDataInterface = $this->getMockBuilder(EntityMetadataInterface::class)
- ->setMethods(['getLinkField'])
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
- $this->collectionFactory->expects($this->once())->method('create')->willReturn($collection);
- $collection->expects($this->once())->method('addFieldToFilter')
- ->with(ProductInterface::SKU, ['in' => $skus])->willReturnSelf();
- $collection->expects($this->atLeastOnce())->method('getItems')->willReturn([$product]);
- $collection->expects($this->atLeastOnce())->method('setPageSize')->willReturnSelf();
- $collection->expects($this->atLeastOnce())->method('getLastPageNumber')->willReturn(1);
- $collection->expects($this->atLeastOnce())->method('setCurPage')->with(1)->willReturnSelf();
- $collection->expects($this->atLeastOnce())->method('clear')->willReturnSelf();
- $this->metadataPool
- ->expects($this->once())
- ->method('getMetadata')
- ->with(ProductInterface::class)
- ->willReturn($metaDataInterface);
- $metaDataInterface->expects($this->once())->method('getLinkField')->willReturn('entity_id');
- $product->expects($this->once())->method('getSku')->willReturn('sku_1');
- $product->expects($this->once())->method('getData')->with('entity_id')->willReturn(1);
- $product->expects($this->once())->method('getTypeId')->willReturn('simple');
+ $product->method('getSku')
+ ->willReturn('sku_1');
+ $product->method('getData')
+ ->with($this->linkField)
+ ->willReturn(1);
+ $product->method('getTypeId')
+ ->willReturn('simple');
+
+ $this->collection->expects($this->once())
+ ->method('addFieldToFilter')
+ ->with(ProductInterface::SKU, ['in' => $skus])
+ ->willReturnSelf();
+ $this->collection->expects($this->atLeastOnce())
+ ->method('getItems')
+ ->willReturn([$product]);
+ $this->collection->expects($this->atLeastOnce())
+ ->method('setPageSize')
+ ->willReturnSelf();
+ $this->collection->expects($this->atLeastOnce())
+ ->method('getLastPageNumber')
+ ->willReturn(1);
+ $this->collection->expects($this->atLeastOnce())
+ ->method('setCurPage')
+ ->with(1)
+ ->willReturnSelf();
+ $this->collection->expects($this->atLeastOnce())
+ ->method('clear')
+ ->willReturnSelf();
+
$this->assertEquals(
['sku_1' => [1 => 'simple']],
$this->model->retrieveProductIdsBySkus($skus)
);
}
+
+ public function testRetrieveProductIdsWithNumericSkus()
+ {
+ $skus = ['111', '222', '333', '444', '555'];
+ $products = [];
+ foreach ($skus as $sku) {
+ $product = $this->getMockBuilder(ProductInterface::class)
+ ->setMethods(['getSku', 'getData', 'getTypeId'])
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $product->method('getSku')
+ ->willReturn($sku);
+ $product->method('getData')
+ ->with($this->linkField)
+ ->willReturn((int) $sku);
+ $product->method('getTypeId')
+ ->willReturn('simple');
+ $products[] = $product;
+ }
+
+ $this->collection->expects($this->atLeastOnce())
+ ->method('addFieldToFilter')
+ ->withConsecutive([ProductInterface::SKU, ['in' => $skus]], [ProductInterface::SKU, ['in' => ['1']]])
+ ->willReturnSelf();
+ $this->collection->expects($this->atLeastOnce())
+ ->method('getItems')
+ ->willReturnOnConsecutiveCalls($products, []);
+ $this->collection->expects($this->atLeastOnce())
+ ->method('setPageSize')
+ ->willReturnSelf();
+ $this->collection->expects($this->atLeastOnce())
+ ->method('getLastPageNumber')
+ ->willReturn(1);
+ $this->collection->expects($this->atLeastOnce())
+ ->method('setCurPage')
+ ->with(1)
+ ->willReturnSelf();
+ $this->collection->expects($this->atLeastOnce())
+ ->method('clear')
+ ->willReturnSelf();
+
+ $this->assertEquals(
+ [
+ '111' => [111 => 'simple'],
+ '222' => [222 => 'simple'],
+ '333' => [333 => 'simple'],
+ '444' => [444 => 'simple'],
+ '555' => [555 => 'simple'],
+ ],
+ $this->model->retrieveProductIdsBySkus($skus)
+ );
+ $this->assertEmpty($this->model->retrieveProductIdsBySkus(['1']));
+ }
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
index 42d0778daa4af..91313d4cd1995 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTest.php
@@ -508,28 +508,17 @@ public function testGetStoreIds()
/**
* @dataProvider getSingleStoreIds
* @param bool $isObjectNew
+ * @return void
*/
- public function testGetStoreSingleSiteModelIds(
- bool $isObjectNew
- ) {
+ public function testGetStoreSingleSiteModelIds(bool $isObjectNew): void
+ {
$websiteIDs = [0 => 2];
- $this->model->setWebsiteIds(
- !$isObjectNew ? $websiteIDs : array_flip($websiteIDs)
- );
+ $this->model->setWebsiteIds(!$isObjectNew ? $websiteIDs : array_flip($websiteIDs));
$this->model->isObjectNew($isObjectNew);
- $this->storeManager->expects(
- $this->exactly(
- (int)!$isObjectNew
- )
- )
- ->method('isSingleStoreMode')
- ->willReturn(true);
-
- $this->website->expects(
- $this->once()
- )->method('getStoreIds')
+ $this->website->expects($this->once())
+ ->method('getStoreIds')
->willReturn($websiteIDs);
$this->assertEquals($websiteIDs, $this->model->getStoreIds());
@@ -1095,6 +1084,35 @@ public function testSaveAndDuplicate()
$this->model->afterSave();
}
+ /**
+ * Test for save method behavior with type options
+ */
+ public function testSaveWithoutTypeOptions()
+ {
+ $this->model->setCanSaveCustomOptions(false);
+ $this->model->setTypeHasOptions(true);
+ $this->model->setTypeHasRequiredOptions(true);
+ $this->configureSaveTest();
+ $this->model->beforeSave();
+ $this->model->afterSave();
+ $this->assertTrue($this->model->getTypeHasOptions());
+ $this->assertTrue($this->model->getTypeHasRequiredOptions());
+ }
+
+ /**
+ * Test for save method with provided options data
+ */
+ public function testSaveWithProvidedRequiredOptions()
+ {
+ $this->model->setData("has_options", "1");
+ $this->model->setData("required_options", "1");
+ $this->configureSaveTest();
+ $this->model->beforeSave();
+ $this->model->afterSave();
+ $this->assertTrue($this->model->getHasOptions());
+ $this->assertTrue($this->model->getRequiredOptions());
+ }
+
public function testGetIsSalableSimple()
{
$typeInstanceMock =
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/MediaImageDeleteProcessorTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/MediaImageDeleteProcessorTest.php
new file mode 100644
index 0000000000000..bfc113ce41609
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/MediaImageDeleteProcessorTest.php
@@ -0,0 +1,200 @@
+objectManager = new ObjectManager($this);
+
+ $this->productMock = $this->getMockBuilder(Product::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getId', 'getMediaGalleryImages'])
+ ->getMock();
+
+ $this->imageConfig = $this->getMockBuilder(MediaConfig::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getBaseMediaUrl', 'getMediaUrl', 'getBaseMediaPath', 'getMediaPath'])
+ ->getMock();
+
+ $this->mediaDirectory = $this->getMockBuilder(Filesystem::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getRelativePath', 'isFile', 'delete'])
+ ->getMock();
+
+ $this->imageProcessor = $this->getMockBuilder(Processor::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['removeImage'])
+ ->getMock();
+
+ $this->productGallery = $this->getMockBuilder(Gallery::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['deleteGallery', 'countImageUses'])
+ ->getMock();
+
+ $this->mediaImageDeleteProcessor = $this->objectManager->getObject(
+ MediaImageDeleteProcessor::class,
+ [
+ 'imageConfig' => $this->imageConfig,
+ 'mediaDirectory' => $this->mediaDirectory,
+ 'imageProcessor' => $this->imageProcessor,
+ 'productGallery' => $this->productGallery
+ ]
+ );
+ }
+
+ /**
+ * Test mediaImageDeleteProcessor execute method
+ *
+ * @dataProvider executeCategoryProductMediaDeleteDataProvider
+ * @param int $productId
+ * @param array $productImages
+ * @param bool $isValidFile
+ * @param bool $imageUsedBefore
+ */
+ public function testExecuteCategoryProductMediaDelete(
+ int $productId,
+ array $productImages,
+ bool $isValidFile,
+ bool $imageUsedBefore
+ ): void {
+ $this->productMock->expects($this->any())
+ ->method('getId')
+ ->willReturn($productId);
+
+ $this->productMock->expects($this->any())
+ ->method('getMediaGalleryImages')
+ ->willReturn($productImages);
+
+ $this->mediaDirectory->expects($this->any())
+ ->method('isFile')
+ ->willReturn($isValidFile);
+
+ $this->mediaDirectory->expects($this->any())
+ ->method('getRelativePath')
+ ->withConsecutive([$productImages[0]->getFile()], [$productImages[1]->getFile()])
+ ->willReturnOnConsecutiveCalls($productImages[0]->getPath(), $productImages[1]->getPath());
+
+ $this->productGallery->expects($this->any())
+ ->method('countImageUses')
+ ->willReturn($imageUsedBefore);
+
+ $this->productGallery->expects($this->any())
+ ->method('deleteGallery')
+ ->willReturnSelf();
+
+ $this->imageProcessor->expects($this->any())
+ ->method('removeImage')
+ ->willReturnSelf();
+
+ $this->mediaImageDeleteProcessor->execute($this->productMock);
+ }
+
+ /**
+ * @return array
+ */
+ public function executeCategoryProductMediaDeleteDataProvider(): array
+ {
+ $imageDirectoryPath = '/media/dir1/dir2/catalog/product/';
+ $image1FilePath = '/test/test1.jpg';
+ $image2FilePath = '/test/test2.jpg';
+ $productImages = [
+ new DataObject([
+ 'value_id' => 1,
+ 'file' => $image1FilePath,
+ 'media_type' => 'image',
+ 'path' => $imageDirectoryPath.$image1FilePath
+ ]),
+ new DataObject([
+ 'value_id' => 2,
+ 'file' => $image2FilePath,
+ 'media_type' => 'image',
+ 'path' => $imageDirectoryPath.$image2FilePath
+ ])
+ ];
+ return [
+ 'test image can be deleted with existing product and product images' =>
+ [
+ 12,
+ $productImages,
+ true,
+ false
+ ],
+ 'test image can not be deleted without valid product id' =>
+ [
+ 0,
+ $productImages,
+ true,
+ false
+ ],
+ 'test image can not be deleted without valid product images' =>
+ [
+ 12,
+ [new DataObject(['file' => null]), new DataObject(['file' => null])],
+ true,
+ false
+ ],
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/AbstractRepository.php b/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/AbstractRepository.php
index c7b339d5fac96..b1218c88264c4 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/AbstractRepository.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Attribute/AbstractRepository.php
@@ -11,6 +11,11 @@
*/
abstract class AbstractRepository implements RepositoryInterface
{
+ /**
+ * @var \Magento\Catalog\Api\ProductAttributeRepositoryInterface
+ */
+ private $productAttributeRepository;
+
/**
* @var null|\Magento\Catalog\Api\Data\ProductAttributeInterface[]
*/
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php
index 1e056d9f8d517..88edfc7b36cb6 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns.php
@@ -23,6 +23,11 @@ class Columns extends \Magento\Ui\Component\Listing\Columns
*/
protected $attributeRepository;
+ /**
+ * @var \Magento\Catalog\Ui\Component\ColumnFactory
+ */
+ private $columnFactory;
+
/**
* @var array
*/
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Price.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Price.php
index 53347db23f5a1..c35dad5e37bdf 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Price.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Price.php
@@ -24,6 +24,11 @@ class Price extends \Magento\Ui\Component\Listing\Columns\Column
*/
protected $localeCurrency;
+ /**
+ * @var \Magento\Store\Model\StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @param ContextInterface $context
* @param UiComponentFactory $uiComponentFactory
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
index 09c9782fc0e32..0ef5adf7a1888 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Thumbnail.php
@@ -20,6 +20,16 @@ class Thumbnail extends \Magento\Ui\Component\Listing\Columns\Column
const ALT_FIELD = 'name';
+ /**
+ * @var \Magento\Catalog\Helper\Image
+ */
+ private $imageHelper;
+
+ /**
+ * @var \Magento\Framework\UrlInterface
+ */
+ private $urlBuilder;
+
/**
* @param ContextInterface $context
* @param UiComponentFactory $uiComponentFactory
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
index 430b6c004e772..6915265b48818 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Websites.php
@@ -165,7 +165,7 @@ protected function getFieldsForFieldset()
$websitesList = $this->getWebsitesList();
$isNewProduct = !$this->locator->getProduct()->getId();
$tooltip = [
- 'link' => 'https://docs.magento.com/m2/ce/user_guide/configuration/scope.html',
+ 'link' => 'https://docs.magento.com/user-guide/configuration/scope.html',
'description' => __(
'If your Magento installation has multiple websites, ' .
'you can edit the scope to use the product on specific sites.'
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
index 298595b3d0f62..f4334bc25efd8 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
@@ -25,58 +25,4 @@ protected function _productLimitationJoinPrice()
$this->_productLimitationFilters->setUsePriceIndex(false);
return $this->_productLimitationPrice(true);
}
-
- /**
- * Return approximately amount if too much entities.
- *
- * @return int|mixed
- */
- public function getSize()
- {
- $sql = $this->getSelectCountSql();
- $possibleCount = $this->analyzeCount($sql);
-
- if ($possibleCount > 20000) {
- return $possibleCount;
- }
-
- return parent::getSize();
- }
-
- /**
- * Analyze amount of entities in DB.
- *
- * @param $sql
- * @return int|mixed
- * @throws \Zend_Db_Statement_Exception
- */
- private function analyzeCount($sql)
- {
- $results = $this->getConnection()->query('EXPLAIN ' . $sql)->fetchAll();
- $alias = $this->getMainTableAlias();
-
- foreach ($results as $result) {
- if ($result['table'] == $alias) {
- return $result['rows'];
- }
- }
-
- return 0;
- }
-
- /**
- * Identify main table alias or its name if alias is not defined.
- *
- * @return string
- * @throws \LogicException
- */
- private function getMainTableAlias()
- {
- foreach ($this->getSelect()->getPart(\Magento\Framework\DB\Select::FROM) as $tableAlias => $tableMetadata) {
- if ($tableMetadata['joinType'] == 'from') {
- return $tableAlias;
- }
- }
- throw new \LogicException("Main table cannot be identified.");
- }
}
diff --git a/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php b/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php
index 00bac7e61b5b4..f486eed91da5f 100644
--- a/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php
+++ b/app/code/Magento/Catalog/ViewModel/Product/Checker/AddToCompareAvailability.php
@@ -7,10 +7,10 @@
namespace Magento\Catalog\ViewModel\Product\Checker;
-use Magento\Framework\View\Element\Block\ArgumentInterface;
use Magento\Catalog\Api\Data\ProductInterface;
-use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
+use Magento\CatalogInventory\Api\StockConfigurationInterface;
+use Magento\Framework\View\Element\Block\ArgumentInterface;
/**
* Check is available add to compare.
@@ -39,25 +39,9 @@ public function __construct(StockConfigurationInterface $stockConfiguration)
public function isAvailableForCompare(ProductInterface $product): bool
{
if ((int)$product->getStatus() !== Status::STATUS_DISABLED) {
- return $this->isInStock($product) || $this->stockConfiguration->isShowOutOfStock();
+ return $product->isSalable() || $this->stockConfiguration->isShowOutOfStock();
}
return false;
}
-
- /**
- * Get is in stock status.
- *
- * @param ProductInterface $product
- * @return bool
- */
- private function isInStock(ProductInterface $product): bool
- {
- $quantityAndStockStatus = $product->getQuantityAndStockStatus();
- if (!$quantityAndStockStatus) {
- return $product->isSalable();
- }
-
- return $quantityAndStockStatus['is_in_stock'] ?? false;
- }
}
diff --git a/app/code/Magento/Catalog/ViewModel/Product/OptionsData.php b/app/code/Magento/Catalog/ViewModel/Product/OptionsData.php
new file mode 100644
index 0000000000000..a4b77ca89ee7a
--- /dev/null
+++ b/app/code/Magento/Catalog/ViewModel/Product/OptionsData.php
@@ -0,0 +1,29 @@
+
+
+
+
+ - sku
+ - media_gallery
+
+
+
diff --git a/app/code/Magento/Catalog/etc/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml
index 4e10453f542bb..a1b2202309d62 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/system.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml
@@ -218,7 +218,7 @@
Catalog media URL format
Magento\Catalog\Model\Config\Source\Web\CatalogMediaUrlFormat
- Learn more about catalog URL formats.Warning! If you switch back to legacy mode, you must use the CLI to regenerate images .]]>
+ Learn more about catalog URL formats.Warning! If you switch back to legacy mode, you must use the CLI to regenerate images .]]>
diff --git a/app/code/Magento/Catalog/etc/db_schema.xml b/app/code/Magento/Catalog/etc/db_schema.xml
index ce34914a2f5d4..f6a1dbe5e41a1 100644
--- a/app/code/Magento/Catalog/etc/db_schema.xml
+++ b/app/code/Magento/Catalog/etc/db_schema.xml
@@ -13,7 +13,7 @@
-
+
-
+
diff --git a/app/code/Magento/Catalog/etc/db_schema_whitelist.json b/app/code/Magento/Catalog/etc/db_schema_whitelist.json
index efc45112920e2..fd332606bb220 100644
--- a/app/code/Magento/Catalog/etc/db_schema_whitelist.json
+++ b/app/code/Magento/Catalog/etc/db_schema_whitelist.json
@@ -1020,6 +1020,7 @@
"entity_id": true
},
"constraint": {
+ "PRIMARY": true,
"FK_A6C6C8FAA386736921D3A7C4B50B1185": true,
"CAT_PRD_ENTT_MDA_GLR_VAL_TO_ENTT_ENTT_ID_CAT_PRD_ENTT_ENTT_ID": true,
"CAT_PRD_ENTT_MDA_GLR_VAL_TO_ENTT_VAL_ID_ENTT_ID": true
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index 8a116282e2578..b509debe7bae1 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -1319,4 +1319,13 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml
index 1fd47fde304ec..a33c3a19e1e4f 100644
--- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml
+++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml
@@ -40,4 +40,8 @@
Magento\Catalog\Model\Product\Webapi\Rest\RequestTypeBasedDeserializer
+
+
+
+
diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml
index a709f23d8c12b..03671ba0bb2e0 100644
--- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml
+++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml
@@ -40,4 +40,8 @@
Magento\Framework\Webapi\Rest\Request\Deserializer\Xml
+
+
+
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml
index e5f8a360c334c..7ae3a2ade6557 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/attribute/options.phtml
@@ -4,6 +4,7 @@
* See COPYING.txt for license details.
*/
+// phpcs:disable PHPCompatibility.Miscellaneous.RemovedAlternativePHPTags.MaybeASPOpenTagFound
/** @var $block \Magento\Eav\Block\Adminhtml\Attribute\Edit\Options\Options */
$stores = $block->getStoresSortedBySortOrder();
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml
index 261de795f7199..e4f3dba6f984d 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/attribute_set.phtml
@@ -5,6 +5,7 @@
*/
// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis
+// phpcs:disable PHPCompatibility.Miscellaneous.RemovedAlternativePHPTags.MaybeASPOpenTagFound
/* @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\AttributeSet */
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml
index ce7dac70010b1..6848e6f269bc0 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/option.phtml
@@ -5,6 +5,7 @@
*/
// phpcs:disable Magento2.Templates.ThisInTemplate.FoundThis
+// phpcs:disable PHPCompatibility.Miscellaneous.RemovedAlternativePHPTags.MaybeASPOpenTagFound
/** @var $block \Magento\Catalog\Block\Adminhtml\Product\Edit\Tab\Options\Option */
/** @var \Magento\Framework\View\Helper\SecureHtmlRenderer $secureRenderer */
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml
index 2063609bf0568..748f7d229dbbb 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/product/edit/options/type/date.phtml
@@ -4,6 +4,7 @@
* See COPYING.txt for license details.
*/
+// phpcs:disable PHPCompatibility.Miscellaneous.RemovedAlternativePHPTags.MaybeASPOpenTagFound
?>
-
+ }
+
= $block->escapeHtml(__('You have no items to compare.')) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml
index 6a47978f1e5c6..afd804591fec1 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/list.phtml
@@ -87,6 +87,12 @@ $_helper = $block->getData('outputHelper');
data-product-sku="= $escaper->escapeHtml($_product->getSku()) ?>"
action="= $escaper->escapeUrl($postParams['action']) ?>"
method="post">
+ getData('viewModel')->getOptionsData($_product); ?>
+
+
+
@@ -153,16 +159,14 @@ $_helper = $block->getData('outputHelper');
- = $block->getToolbarHtml() ?>
- isRedirectToCartEnabled()): ?>
-
-
+ }
+
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml
index e426b940deab7..6fd619de7fd6c 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/items.phtml
@@ -287,7 +287,7 @@ $_item = null;
- getIsSalable()):?>
+ isAvailable()):?>
= $block->escapeHtml(__('In stock')) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml
index 76ef6baf4993e..3c8687d090baf 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/list/toolbar.phtml
@@ -10,27 +10,23 @@
*
* @var $block \Magento\Catalog\Block\Product\ProductList\Toolbar
*/
-
-// phpcs:disable Magento2.Security.IncludeFile.FoundIncludeFile
-// phpcs:disable PSR2.Methods.FunctionCallSignature.SpaceBeforeOpenBracket
?>
getCollection()->getSize()) :?>
helper(\Magento\Framework\Json\Helper\Data::class)->jsonDecode($block->getWidgetOptionsJson());
$widgetOptions = $this->helper(\Magento\Framework\Json\Helper\Data::class)->jsonEncode($widget['productListToolbarForm']);
?>
- isExpanded()) :?>
- getTemplateFile('Magento_Catalog::product/list/toolbar/viewmode.phtml')) ?>
-
-
- getTemplateFile('Magento_Catalog::product/list/toolbar/amount.phtml')) ?>
-
- = $block->getPagerHtml() ?>
-
- getTemplateFile('Magento_Catalog::product/list/toolbar/limiter.phtml')) ?>
-
- isExpanded()) :?>
- getTemplateFile('Magento_Catalog::product/list/toolbar/sorter.phtml')) ?>
-
+ getIsBottom()): ?>
+ = $block->getPagerHtml() ?>
+ = $block->fetchView($block->getTemplateFile('Magento_Catalog::product/list/toolbar/limiter.phtml')) ?>
+
+ isExpanded()): ?>
+ = $block->fetchView($block->getTemplateFile('Magento_Catalog::product/list/toolbar/viewmode.phtml')) ?>
+
+ = $block->fetchView($block->getTemplateFile('Magento_Catalog::product/list/toolbar/amount.phtml')) ?>
+ isExpanded()): ?>
+ = $block->fetchView($block->getTemplateFile('Magento_Catalog::product/list/toolbar/sorter.phtml')) ?>
+
+
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/listing.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/listing.phtml
index 6cebd51284f48..49dd702a6e39c 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/listing.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/listing.phtml
@@ -63,7 +63,7 @@ $_helper = $this->helper(Magento\Catalog\Helper\Output::class);
. ' data-mage-init=\'{ "redirectUrl": { "event": "click", url: "' . $block->escapeUrl($block->getAddToCartUrl($_product)) . '"} }\'>'
. '
' . $block->escapeHtml(__('Add to Cart')) . ' ';
} else {
- $info['button'] = $_product->getIsSalable() ? '
' . $block->escapeHtml(__('In stock')) . '
' :
+ $info['button'] = $_product->isAvailable() ? '
' . $block->escapeHtml(__('In stock')) . '
' :
'
' . $block->escapeHtml(__('Out of stock')) . '
';
}
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/file.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/file.phtml
index f5fd1c5aa64e1..4385829a5bdc1 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/file.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/options/type/file.phtml
@@ -51,7 +51,7 @@
id="= /* @noEscape */ $_fileName ?>"
class="product-custom-option= $_option->getIsRequire() ? ' required' : '' ?>"
= $_fileExists ? 'disabled="disabled"' : '' ?> />
-
getFileExtension()):?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_default_list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_default_list.phtml
index 53a0682311b1f..fce91564c96a2 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_default_list.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/column/new_default_list.phtml
@@ -52,7 +52,7 @@
- getIsSalable()) :?>
+ isAvailable()) :?>
= $block->escapeHtml(__('In stock')) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml
index 5108c488aec19..66683ef328e08 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_grid.phtml
@@ -89,7 +89,7 @@ if ($exist = ($block->getProductCollection() && $block->getProductCollection()->
- getIsSalable()) :?>
+ isAvailable()) :?>
= $block->escapeHtml(__('In stock')) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml
index 378cd49493a6e..ceb32e78c7e44 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/widget/new/content/new_list.phtml
@@ -88,7 +88,7 @@ if ($exist = ($block->getProductCollection() && $block->getProductCollection()->
- getIsSalable()) :?>
+ isAvailable()) :?>
= $block->escapeHtml(__('In stock')) ?>
= $block->escapeHtml(__('Out of stock')) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/web/template/product/addtocart-button.html b/app/code/Magento/Catalog/view/frontend/web/template/product/addtocart-button.html
index 05dbf02703285..867b5f40d98db 100644
--- a/app/code/Magento/Catalog/view/frontend/web/template/product/addtocart-button.html
+++ b/app/code/Magento/Catalog/view/frontend/web/template/product/addtocart-button.html
@@ -15,10 +15,10 @@
-
+
-
+
diff --git a/app/code/Magento/CatalogAnalytics/README.md b/app/code/Magento/CatalogAnalytics/README.md
index 0c4ee155c4f27..bfea74e7ddd88 100644
--- a/app/code/Magento/CatalogAnalytics/README.md
+++ b/app/code/Magento/CatalogAnalytics/README.md
@@ -1,3 +1,3 @@
# Magento_CatalogAnalytics module
-The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.3/advanced-reporting/modules.html).
+The Magento_CatalogAnalytics module configures data definitions for a data collection related to the Catalog module entities to be used in [Advanced Reporting](https://devdocs.magento.com/guides/v2.4/advanced-reporting/modules.html).
diff --git a/app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/PriceTiers.php b/app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/PriceTiers.php
index efba88ff154bb..3c6cc849081ee 100644
--- a/app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/PriceTiers.php
+++ b/app/code/Magento/CatalogCustomerGraphQl/Model/Resolver/PriceTiers.php
@@ -19,7 +19,6 @@
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\Pricing\PriceCurrencyInterface;
-use Magento\Store\Api\Data\StoreInterface;
/**
* Resolver for price_tiers
@@ -125,6 +124,10 @@ public function resolve(
return [];
}
+ if (!$product->getTierPrices()) {
+ return [];
+ }
+
$productId = (int)$product->getId();
$this->tiers->addProductFilter($productId);
@@ -152,7 +155,8 @@ private function formatAndFilterTierPrices(
array $tierPrices,
string $currencyCode
): array {
-
+ $this->formatAndFilterTierPrices = [];
+ $this->tierPricesQty = [];
foreach ($tierPrices as $key => $tierPrice) {
$tierPrice->setValue($this->priceCurrency->convertAndRound($tierPrice->getValue()));
$this->formatTierPrices($productPrice, $currencyCode, $tierPrice);
diff --git a/app/code/Magento/CatalogCustomerGraphQl/composer.json b/app/code/Magento/CatalogCustomerGraphQl/composer.json
index a7c887af0379b..ce80ee602327e 100644
--- a/app/code/Magento/CatalogCustomerGraphQl/composer.json
+++ b/app/code/Magento/CatalogCustomerGraphQl/composer.json
@@ -7,8 +7,7 @@
"magento/framework": "*",
"magento/module-catalog": "*",
"magento/module-customer": "*",
- "magento/module-catalog-graph-ql": "*",
- "magento/module-store": "*"
+ "magento/module-catalog-graph-ql": "*"
},
"license": [
"OSL-3.0",
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php
index d46776bfe498e..4501915682178 100644
--- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/AttributeOptionProvider.php
@@ -8,6 +8,7 @@
namespace Magento\CatalogGraphQl\DataProvider\Product\LayeredNavigation;
use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Select;
use Magento\Store\Model\Store;
/**
@@ -95,6 +96,8 @@ public function getOptions(array $optionIds, ?int $storeId, array $attributeCode
)->where(
'a.attribute_id = options.attribute_id AND option_value.store_id = ?',
Store::DEFAULT_STORE_ID
+ )->order(
+ 'options.sort_order ' . Select::SQL_ASC
);
$select->where('option_value.option_id IN (?)', $optionIds);
@@ -112,11 +115,11 @@ public function getOptions(array $optionIds, ?int $storeId, array $attributeCode
/**
* Format result
*
- * @param \Magento\Framework\DB\Select $select
+ * @param Select $select
* @return array
* @throws \Zend_Db_Statement_Exception
*/
- private function formatResult(\Magento\Framework\DB\Select $select): array
+ private function formatResult(Select $select): array
{
$statement = $this->resourceConnection->getConnection()->query($select);
@@ -131,7 +134,9 @@ private function formatResult(\Magento\Framework\DB\Select $select): array
'options' => [],
];
}
- $result[$option['attribute_code']]['options'][$option['option_id']] = $option['option_label'];
+ if (!empty($option['option_id'])) {
+ $result[$option['attribute_code']]['options'][$option['option_id']] = $option['option_label'];
+ }
}
return $result;
diff --git a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php
index 5fce0fcdf3ca2..1d27a3afd7dbf 100644
--- a/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php
+++ b/app/code/Magento/CatalogGraphQl/DataProvider/Product/LayeredNavigation/Builder/Attribute.php
@@ -86,12 +86,12 @@ public function build(AggregationInterface $aggregation, ?int $storeId): array
$attribute['attribute_code'] ?? $bucketName
);
- foreach ($bucket->getValues() as $value) {
- $metrics = $value->getMetrics();
+ $options = $this->getSortedOptions($bucket,$attribute['options'] ?: []);
+ foreach ($options as $option) {
$result[$bucketName]['options'][] = $this->layerFormatter->buildItem(
- $attribute['options'][$metrics['value']] ?? $metrics['value'],
- $metrics['value'],
- $metrics['count']
+ $option['label'],
+ $option['value'],
+ $option['count']
);
}
}
@@ -161,4 +161,36 @@ function (AggregationValueInterface $value) {
$attributes
);
}
+
+ /**
+ * Get sorted options
+ *
+ * @param BucketInterface $bucket
+ * @param array $optionLabels
+ * @return array
+ */
+ private function getSortedOptions(BucketInterface $bucket, array $optionLabels): array
+ {
+ /**
+ * Option labels array has been sorted
+ */
+ $options = $optionLabels;
+ foreach ($bucket->getValues() as $value) {
+ $metrics = $value->getMetrics();
+ $optionValue = $metrics['value'];
+ $optionLabel = $optionLabels[$optionValue] ?? $optionValue;
+ $options[$optionValue] = $metrics + ['label' => $optionLabel];
+ }
+
+ /**
+ * Delete options without bucket values
+ */
+ foreach ($options as $optionId => $option) {
+ if (!is_array($options[$optionId])) {
+ unset($options[$optionId]);
+ }
+ }
+
+ return array_values($options);
+ }
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php b/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php
index ab100c7272ba0..356ff17183a57 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Category/DepthCalculator.php
@@ -7,6 +7,7 @@
namespace Magento\CatalogGraphQl\Model\Category;
+use GraphQL\Language\AST\Node;
use GraphQL\Language\AST\FieldNode;
use GraphQL\Language\AST\InlineFragmentNode;
use GraphQL\Language\AST\NodeKind;
@@ -26,22 +27,35 @@ class DepthCalculator
*/
public function calculate(ResolveInfo $resolveInfo, FieldNode $fieldNode) : int
{
- $selections = $fieldNode->selectionSet->selections ?? [];
+ return $this->calculateRecursive($resolveInfo, $fieldNode);
+ }
+
+ /**
+ * Calculate recursive the total depth of a category tree inside a GraphQL request
+ *
+ * @param ResolveInfo $resolveInfo
+ * @param Node $node
+ * @return int
+ */
+ private function calculateRecursive(ResolveInfo $resolveInfo, Node $node) : int
+ {
+ if ($node->kind === NodeKind::FRAGMENT_SPREAD) {
+ $selections = isset($resolveInfo->fragments[$node->name->value]) ?
+ $resolveInfo->fragments[$node->name->value]->selectionSet->selections : [];
+ } else {
+ $selections = $node->selectionSet->selections ?? [];
+ }
$depth = count($selections) ? 1 : 0;
$childrenDepth = [0];
- foreach ($selections as $node) {
- if (isset($node->alias) && null !== $node->alias) {
+ foreach ($selections as $subNode) {
+ if (isset($subNode->alias) && null !== $subNode->alias) {
continue;
}
- if ($node->kind === NodeKind::INLINE_FRAGMENT) {
- $childrenDepth[] = $this->addInlineFragmentDepth($resolveInfo, $node);
- } elseif ($node->kind === NodeKind::FRAGMENT_SPREAD && isset($resolveInfo->fragments[$node->name->value])) {
- foreach ($resolveInfo->fragments[$node->name->value]->selectionSet->selections as $spreadNode) {
- $childrenDepth[] = $this->calculate($resolveInfo, $spreadNode);
- }
+ if ($subNode->kind === NodeKind::INLINE_FRAGMENT) {
+ $childrenDepth[] = $this->addInlineFragmentDepth($resolveInfo, $subNode);
} else {
- $childrenDepth[] = $this->calculate($resolveInfo, $node);
+ $childrenDepth[] = $this->calculateRecursive($resolveInfo, $subNode);
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Category/ParentCategoryUidsArgsProcessor.php b/app/code/Magento/CatalogGraphQl/Model/Category/ParentCategoryUidsArgsProcessor.php
new file mode 100644
index 0000000000000..700b95d79990a
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Category/ParentCategoryUidsArgsProcessor.php
@@ -0,0 +1,69 @@
+uidEncoder = $uidEncoder;
+ }
+
+ /**
+ * Composite processor that loops through available processors for arguments that come from graphql input
+ *
+ * @param string $fieldName,
+ * @param array $args
+ * @return array
+ * @throws GraphQlInputException
+ */
+ public function process(
+ string $fieldName,
+ array $args
+ ): array {
+ $filterKey = 'filters';
+ $parentUidFilter = $args[$filterKey][self::UID] ?? [];
+ $parentIdFilter = $args[$filterKey][self::ID] ?? [];
+ if (!empty($parentIdFilter)
+ && !empty($parentUidFilter)
+ && ($fieldName === 'categories' || $fieldName === 'categoryList')) {
+ throw new GraphQlInputException(
+ __('`%1` and `%2` can\'t be used at the same time.', [self::ID, self::UID])
+ );
+ } elseif (!empty($parentUidFilter)) {
+ if (isset($parentUidFilter['eq'])) {
+ $args[$filterKey][self::ID]['eq'] = $this->uidEncoder->decode(
+ $parentUidFilter['eq']
+ );
+ } elseif (!empty($parentUidFilter['in'])) {
+ foreach ($parentUidFilter['in'] as $parentUids) {
+ $args[$filterKey][self::ID]['in'][] = $this->uidEncoder->decode($parentUids);
+ }
+ }
+ unset($args[$filterKey][self::UID]);
+ }
+ return $args;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/CategoryTypeResolver.php b/app/code/Magento/CatalogGraphQl/Model/CategoryTypeResolver.php
new file mode 100755
index 0000000000000..d4b7c644fe828
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/CategoryTypeResolver.php
@@ -0,0 +1,30 @@
+getMessage()));
}
- $rootCategoryIds = $filterResult['category_ids'];
- $filterResult['items'] = $this->fetchCategories($rootCategoryIds, $info);
+ $rootCategoryIds = $filterResult['category_ids'] ?? [];
+
+ $filterResult['items'] = $this->fetchCategories($rootCategoryIds, $info, (int) $store->getId());
return $filterResult;
}
@@ -95,13 +96,14 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
*
* @param array $categoryIds
* @param ResolveInfo $info
+ * @param int $storeId
* @return array
*/
- private function fetchCategories(array $categoryIds, ResolveInfo $info)
+ private function fetchCategories(array $categoryIds, ResolveInfo $info, int $storeId)
{
$fetchedCategories = [];
foreach ($categoryIds as $categoryId) {
- $categoryTree = $this->categoryTree->getTree($info, $categoryId);
+ $categoryTree = $this->categoryTree->getTree($info, $categoryId, $storeId);
if (empty($categoryTree)) {
continue;
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php
index 747e05806a821..ee0ec69aaea74 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryList.php
@@ -81,8 +81,7 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
} catch (InputException $e) {
throw new GraphQlInputException(__($e->getMessage()));
}
-
- return $this->fetchCategories($rootCategoryIds, $info);
+ return $this->fetchCategories($rootCategoryIds, $info, (int) $store->getId());
}
/**
@@ -90,13 +89,14 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
*
* @param array $categoryIds
* @param ResolveInfo $info
+ * @param int $storeId
* @return array
*/
- private function fetchCategories(array $categoryIds, ResolveInfo $info)
+ private function fetchCategories(array $categoryIds, ResolveInfo $info, int $storeId)
{
$fetchedCategories = [];
foreach ($categoryIds as $categoryId) {
- $categoryTree = $this->categoryTree->getTree($info, $categoryId);
+ $categoryTree = $this->categoryTree->getTree($info, $categoryId, $storeId);
if (empty($categoryTree)) {
continue;
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
index 4284aed610848..cddba2e91f701 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
@@ -71,7 +71,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
if ($rootCategoryId !== Category::TREE_ROOT_ID) {
$this->checkCategoryIsActive->execute($rootCategoryId);
}
- $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId);
+ $store = $context->getExtensionAttributes()->getStore();
+ $categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId, (int)$store->getId());
if (empty($categoriesTree) || ($categoriesTree->count() == 0)) {
throw new GraphQlNoSuchEntityException(__('Category doesn\'t exist'));
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/CustomizableDateTypeOptionValue.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/CustomizableDateTypeOptionValue.php
new file mode 100644
index 0000000000000..1b9583cc7239e
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/CustomizableDateTypeOptionValue.php
@@ -0,0 +1,42 @@
+fieldNodes[0];
$collection = $this->collectionFactory->create();
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php
index b38a2c9bb04d9..e212f33a35d29 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/ExtractDataFromCategoryTree.php
@@ -67,7 +67,8 @@ public function execute(\Iterator $iterator): array
$tree = $this->mergeCategoriesTrees($currentLevelTree, $tree);
}
}
- return $tree;
+
+ return $this->sortTree($tree);
}
/**
@@ -141,4 +142,24 @@ private function explodePathToArray(array $pathElements, int $index): array
}
return $tree;
}
+
+ /**
+ * Recursive method to sort tree
+ *
+ * @param array $tree
+ * @return array
+ */
+ private function sortTree(array $tree): array
+ {
+ foreach ($tree as &$node) {
+ if ($node['children']) {
+ uasort($node['children'], function ($element1, $element2) {
+ return $element1['position'] > $element2['position'];
+ });
+ $node['children'] = $this->sortTree($node['children']);
+ }
+ }
+
+ return $tree;
+ }
}
diff --git a/app/code/Magento/CatalogGraphQl/etc/di.xml b/app/code/Magento/CatalogGraphQl/etc/di.xml
index 8c6fac0fe621c..bb2a069d738ee 100644
--- a/app/code/Magento/CatalogGraphQl/etc/di.xml
+++ b/app/code/Magento/CatalogGraphQl/etc/di.xml
@@ -72,6 +72,7 @@
- Magento\CatalogGraphQl\Model\Resolver\Products\Query\CategoryUidArgsProcessor
- Magento\CatalogGraphQl\Model\Category\CategoryUidsArgsProcessor
+ - Magento\CatalogGraphQl\Model\Category\ParentCategoryUidsArgsProcessor
diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
index 79281ff42cf26..9bed85a3258e3 100644
--- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
@@ -49,6 +49,12 @@ enum PriceTypeEnum @doc(description: "This enumeration the price type.") {
DYNAMIC
}
+enum CustomizableDateTypeEnum @doc(description: "This enumeration customizable date type.") {
+ DATE
+ DATE_TIME
+ TIME
+}
+
type ProductPrices @doc(description: "ProductPrices is deprecated, replaced by PriceRange. The ProductPrices object contains the regular price of an item, as well as its minimum and maximum prices. Only composite products, which include bundle, configurable, and grouped products, can contain a minimum and maximum price.") {
minimalPrice: Price @deprecated(reason: "Use PriceRange.minimum_price.") @doc(description: "The lowest possible final price for all the options defined within a composite product. If you are specifying a price range, this would be the from value.")
maximalPrice: Price @deprecated(reason: "Use PriceRange.maximum_price.") @doc(description: "The highest possible final price for all the options defined within a composite product. If you are specifying a price range, this would be the to value.")
@@ -136,7 +142,7 @@ type CustomizableAreaValue @doc(description: "CustomizableAreaValue defines the
uid: ID! @doc(description: "The unique ID for a `CustomizableAreaValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid")
}
-type CategoryTree implements CategoryInterface @doc(description: "Category Tree implementation.") {
+type CategoryTree implements CategoryInterface, RoutableInterface @doc(description: "Category tree implementation") {
children: [CategoryTree] @doc(description: "Child categories tree.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\CategoryTree")
}
@@ -154,6 +160,7 @@ type CustomizableDateOption implements CustomizableOptionInterface @doc(descript
type CustomizableDateValue @doc(description: "CustomizableDateValue defines the price and sku of a product whose page contains a customized date picker.") {
price: Float @doc(description: "The price assigned to this option.")
price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC.")
+ type: CustomizableDateTypeEnum @doc(description: "DATE, DATE_TIME or TIME") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableDateTypeOptionValue")
sku: String @doc(description: "The Stock Keeping Unit for this option.")
uid: ID! @doc(description: "The unique ID for a `CustomizableDateValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableEnteredOptionValueUid")
}
@@ -301,10 +308,10 @@ type CustomizableCheckboxValue @doc(description: "CustomizableCheckboxValue defi
uid: ID! @doc(description: "The unique ID for a `CustomizableCheckboxValue` object.") @resolver(class: "Magento\\CatalogGraphQl\\Model\\Resolver\\Product\\CustomizableSelectedOptionValueUid")
}
-type VirtualProduct implements ProductInterface, CustomizableProductInterface @doc(description: "A virtual product is non-tangible product that does not require shipping and is not kept in inventory.") {
+type VirtualProduct implements ProductInterface, RoutableInterface, CustomizableProductInterface @doc(description: "A virtual product is a non-tangible product that does not require shipping and is not kept in inventory") {
}
-type SimpleProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "A simple product is tangible and are usually sold as single units or in fixed quantities.")
+type SimpleProduct implements ProductInterface, RoutableInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "A simple product is tangible and is usually sold in single units or in fixed quantities")
{
}
@@ -332,7 +339,8 @@ input CategoryFilterInput @doc(description: "CategoryFilterInput defines the fi
{
ids: FilterEqualTypeInput @deprecated(reason: "Use the `category_uid` argument instead.") @doc(description: "Deprecated: use 'category_uid' to filter uniquely identifiers of categories.")
category_uid: FilterEqualTypeInput @doc(description: "Filter by the unique category ID for a `CategoryInterface` object.")
- parent_id: FilterEqualTypeInput @doc(description: "Filter by the unique parent category ID for a `CategoryInterface` object.")
+ parent_id: FilterEqualTypeInput @deprecated @doc(description: "Filter by the unique parent category ID for a `CategoryInterface` object.")
+ parent_category_uid: FilterEqualTypeInput @doc(description: "Filter by the unique parent category ID for a `CategoryInterface` object.")
url_key: FilterEqualTypeInput @doc(description: "Filter by the part of the URL that identifies the category.")
name: FilterMatchTypeInput @doc(description: "Filter by the display name of the category.")
url_path: FilterEqualTypeInput @doc(description: "Filter by the URL path for the category.")
diff --git a/app/code/Magento/CatalogImportExport/Model/Export/Product/WebsiteFilter.php b/app/code/Magento/CatalogImportExport/Model/Export/Product/WebsiteFilter.php
new file mode 100644
index 0000000000000..11ef78a8ca815
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Model/Export/Product/WebsiteFilter.php
@@ -0,0 +1,33 @@
+addWebsiteFilter($filters[self::NAME]);
+
+ return $collection;
+ }
+}
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 673dbcb3b3c99..d0c93658fc282 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -1855,7 +1855,7 @@ protected function _saveProducts()
}
$productTypeModel = $this->_productTypeModels[$productType];
- if (!empty($rowData['tax_class_name'])) {
+ if (isset($rowData['tax_class_name']) && strlen($rowData['tax_class_name'])) {
$rowData['tax_class_id'] =
$this->taxClassProcessor->upsertTaxClass($rowData['tax_class_name'], $productTypeModel);
}
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product/TaxClassProcessor.php b/app/code/Magento/CatalogImportExport/Model/Import/Product/TaxClassProcessor.php
index af102cc50b8b9..e068a07404822 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product/TaxClassProcessor.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product/TaxClassProcessor.php
@@ -7,9 +7,25 @@
use Magento\CatalogImportExport\Model\Import\Product\Type\AbstractType;
use Magento\Tax\Model\ClassModel;
+use Magento\Tax\Model\ClassModelFactory;
+use Magento\Tax\Model\ResourceModel\TaxClass\Collection;
+use Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory;
+/**
+ * Imported products tax class processor
+ */
class TaxClassProcessor
{
+ /**
+ * Empty tax class name
+ */
+ private const CLASS_NONE_NAME = 'none';
+
+ /**
+ * Empty tax class ID
+ */
+ private const CLASS_NONE_ID = 0;
+
/**
* Tax attribute code.
*/
@@ -25,24 +41,24 @@ class TaxClassProcessor
/**
* Instance of tax class collection factory.
*
- * @var \Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory
+ * @var CollectionFactory
*/
protected $collectionFactory;
/**
* Instance of tax model factory.
*
- * @var \Magento\Tax\Model\ClassModelFactory
+ * @var ClassModelFactory
*/
protected $classModelFactory;
/**
- * @param \Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory $collectionFactory
- * @param \Magento\Tax\Model\ClassModelFactory $classModelFactory
+ * @param CollectionFactory $collectionFactory
+ * @param ClassModelFactory $classModelFactory
*/
public function __construct(
- \Magento\Tax\Model\ResourceModel\TaxClass\CollectionFactory $collectionFactory,
- \Magento\Tax\Model\ClassModelFactory $classModelFactory
+ CollectionFactory $collectionFactory,
+ ClassModelFactory $classModelFactory
) {
$this->collectionFactory = $collectionFactory;
$this->classModelFactory = $classModelFactory;
@@ -59,9 +75,9 @@ protected function initTaxClasses()
if (empty($this->taxClasses)) {
$collection = $this->collectionFactory->create();
$collection->addFieldToFilter('class_type', ClassModel::TAX_CLASS_TYPE_PRODUCT);
- /* @var $collection \Magento\Tax\Model\ResourceModel\TaxClass\Collection */
+ /* @var $collection Collection */
foreach ($collection as $taxClass) {
- $this->taxClasses[$taxClass->getClassName()] = $taxClass->getId();
+ $this->taxClasses[mb_strtolower($taxClass->getClassName())] = $taxClass->getId();
}
}
return $this;
@@ -76,7 +92,7 @@ protected function initTaxClasses()
*/
protected function createTaxClass($taxClassName, AbstractType $productTypeModel)
{
- /** @var \Magento\Tax\Model\ClassModelFactory $taxClass */
+ /** @var ClassModelFactory $taxClass */
$taxClass = $this->classModelFactory->create();
$taxClass->setClassType(ClassModel::TAX_CLASS_TYPE_PRODUCT);
$taxClass->setClassName($taxClassName);
@@ -98,10 +114,22 @@ protected function createTaxClass($taxClassName, AbstractType $productTypeModel)
*/
public function upsertTaxClass($taxClassName, AbstractType $productTypeModel)
{
- if (!isset($this->taxClasses[$taxClassName])) {
- $this->taxClasses[$taxClassName] = $this->createTaxClass($taxClassName, $productTypeModel);
+ $normalizedTaxClassName = mb_strtolower($taxClassName);
+
+ if ($normalizedTaxClassName === (string) self::CLASS_NONE_ID) {
+ $normalizedTaxClassName = self::CLASS_NONE_NAME;
+ }
+
+ if (!isset($this->taxClasses[$normalizedTaxClassName])) {
+ $this->taxClasses[$normalizedTaxClassName] = $normalizedTaxClassName === self::CLASS_NONE_NAME
+ ? self::CLASS_NONE_ID
+ : $this->createTaxClass($taxClassName, $productTypeModel);
+ }
+ if ($normalizedTaxClassName === self::CLASS_NONE_NAME) {
+ // Add None option to tax_class_id options.
+ $productTypeModel->addAttributeOption(self::ATRR_CODE, self::CLASS_NONE_ID, self::CLASS_NONE_ID);
}
- return $this->taxClasses[$taxClassName];
+ return $this->taxClasses[$normalizedTaxClassName];
}
}
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/ExportAllProductsActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/ExportAllProductsActionGroup.xml
index 3edbb1b821843..bceac07f2a64e 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/ExportAllProductsActionGroup.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/ExportAllProductsActionGroup.xml
@@ -12,13 +12,18 @@
Exports the unfiltered Products list. Validates that the Success Message is present.
-
+
+
+
+
-
+
+
+
-
+
-
-
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/ExportProductsFilterByAttributeActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/ExportProductsFilterByAttributeActionGroup.xml
index f3ca894202893..f34236b49843c 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/ExportProductsFilterByAttributeActionGroup.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/ExportProductsFilterByAttributeActionGroup.xml
@@ -17,8 +17,9 @@
-
+
+
@@ -26,6 +27,7 @@
-
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Data/ImportData.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Data/ImportData.xml
new file mode 100644
index 0000000000000..3ca2da4f1b894
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Data/ImportData.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ 0
+ import_attribute1
+
+
+ import_attribute1
+ ProductAttributeFrontendLabelImport1
+
+
+ option3
+
+
+
+
+ test_image.jpg
+
+
+ adobe-base.jpg
+
+
+ Test Image
+ TestImageImageContentExportImport
+
+
+ Adobe Base
+ AdobeBaseContentExportImport
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
index 785d19c000af0..1d837cd64bc08 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
@@ -11,14 +11,21 @@
-
-
-
+
+
+
+
@@ -81,7 +88,9 @@
+
+
@@ -92,33 +101,198 @@
+
+ var/export
+
-
-
-
-
+
+
-
+
+
-
+
+
+ var/export/{$grabNameFile}
+
+
+ var/export/{$grabNameFile}
+ $$firstSimpleProductForDynamic.name$$
+
+
+ var/export/{$grabNameFile}
+ $$secondSimpleProductForDynamic.name$$
+
+
+ var/export/{$grabNameFile}
+ $$createDynamicBundleProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ name=$createFirstBundleOption.option[title]$,type=$createFirstBundleOption.option[type]$,required=$createFirstBundleOption.option[required]$,sku=$$firstSimpleProductForDynamic.sku$$
+
+
+ var/export/{$grabNameFile}
+ name=$createFirstBundleOption.option[title]$,type=$createFirstBundleOption.option[type]$,required=$createFirstBundleOption.option[required]$,sku=$$secondSimpleProductForDynamic.sku$$
+
+
+ var/export/{$grabNameFile}
+ 0.000000,,,,$$createDynamicBundleProduct.sku$$
+
+
+
+
+ var/export/{$grabNameFile}
+ $$firstSimpleProductForFixed.name$$
+
+
+ var/export/{$grabNameFile}
+ $$secondSimpleProductForFixed.name$$
+
+
+ var/export/{$grabNameFile}
+ $$createFixedBundleProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ name=$createSecondBundleOption.option[title]$,type=$createSecondBundleOption.option[type]$,required=$createSecondBundleOption.option[required]$,sku=$$firstSimpleProductForFixed.sku$$
+
+
+ var/export/{$grabNameFile}
+ name=$createSecondBundleOption.option[title]$,type=$createSecondBundleOption.option[type]$,required=$createSecondBundleOption.option[required]$,sku=$$secondSimpleProductForFixed.sku$$
+
+
+ var/export/{$grabNameFile}
+ $$createFixedBundleProduct.price$$0000,,,,$$createFixedBundleProduct.sku$$
+
+
+
+
+ var/export/{$grabNameFile}
+ $$firstSimpleProductForFixedWithAttribute.name$$
+
+
+ var/export/{$grabNameFile}
+ $$secondSimpleProductForFixedWithAttribute.name$$
+
+
+ var/export/{$grabNameFile}
+ $$createFixedBundleProductWithAttribute.name$$
+
+
+ var/export/{$grabNameFile}
+ name=$createBundleOptionWithAttribute.option[title]$,type=$createBundleOptionWithAttribute.option[type]$,required=$createBundleOptionWithAttribute.option[required]$,sku=$$firstSimpleProductForFixedWithAttribute.sku$$
+
+
+ var/export/{$grabNameFile}
+ name=$createBundleOptionWithAttribute.option[title]$,type=$createBundleOptionWithAttribute.option[type]$,required=$createBundleOptionWithAttribute.option[required]$,sku=$$secondSimpleProductForFixedWithAttribute.sku$$
+
+
+ var/export/{$grabNameFile}
+ $$createFixedBundleProductWithAttribute.price$$0000,,,,$$createFixedBundleProductWithAttribute.sku$$
+
+
+
-
+
+
+
+ {$grabExportUrl}
+ $$firstSimpleProductForDynamic.name$$
+
+
+ {$grabExportUrl}
+ $$secondSimpleProductForDynamic.name$$
+
+
+ {$grabExportUrl}
+ $$createDynamicBundleProduct.name$$
+
+
+ {$grabExportUrl}
+ name=$createFirstBundleOption.option[title]$,type=$createFirstBundleOption.option[type]$,required=$createFirstBundleOption.option[required]$,sku=$$firstSimpleProductForDynamic.sku$$
+
+
+ {$grabExportUrl}
+ name=$createFirstBundleOption.option[title]$,type=$createFirstBundleOption.option[type]$,required=$createFirstBundleOption.option[required]$,sku=$$secondSimpleProductForDynamic.sku$$
+
+
+ {$grabExportUrl}
+ 0.000000,,,,$$createDynamicBundleProduct.sku$$
+
+
+
+
+ {$grabExportUrl}
+ $$firstSimpleProductForFixed.name$$
+
+
+ {$grabExportUrl}
+ $$secondSimpleProductForFixed.name$$
+
+
+ {$grabExportUrl}
+ $$createFixedBundleProduct.name$$
+
+
+ {$grabExportUrl}
+ name=$createSecondBundleOption.option[title]$,type=$createSecondBundleOption.option[type]$,required=$createSecondBundleOption.option[required]$,sku=$$firstSimpleProductForFixed.sku$$
+
+
+ {$grabExportUrl}
+ name=$createSecondBundleOption.option[title]$,type=$createSecondBundleOption.option[type]$,required=$createSecondBundleOption.option[required]$,sku=$$secondSimpleProductForFixed.sku$$
+
+
+ {$grabExportUrl}
+ $$createFixedBundleProduct.price$$0000,,,,$$createFixedBundleProduct.sku$$
+
+
+
+
+ {$grabExportUrl}
+ $$firstSimpleProductForFixedWithAttribute.name$$
+
+
+ {$grabExportUrl}
+ $$secondSimpleProductForFixedWithAttribute.name$$
+
+
+ {$grabExportUrl}
+ $$createFixedBundleProductWithAttribute.name$$
+
+
+ {$grabExportUrl}
+ name=$createBundleOptionWithAttribute.option[title]$,type=$createBundleOptionWithAttribute.option[type]$,required=$createBundleOptionWithAttribute.option[required]$,sku=$$firstSimpleProductForFixedWithAttribute.sku$$
+
+
+ {$grabExportUrl}
+ name=$createBundleOptionWithAttribute.option[title]$,type=$createBundleOptionWithAttribute.option[type]$,required=$createBundleOptionWithAttribute.option[required]$,sku=$$secondSimpleProductForFixedWithAttribute.sku$$
+
+
+ {$grabExportUrl}
+ $$createFixedBundleProductWithAttribute.price$$0000,,,,$$createFixedBundleProductWithAttribute.sku$$
+
+
+
+
+ var/export/{$grabNameFile}
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
index 9f8d65968d741..5943760853e3e 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
@@ -11,14 +11,20 @@
-
-
-
+
+
+
+
@@ -50,40 +56,94 @@
+
+
+
+ var/export
+
-
-
-
-
-
-
+
+
+
-
+
+
-
+
+
+ var/export/{$grabNameFile}
+
+
+ var/export/{$grabNameFile}
+ $$createFirstSimpleProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ $$createSecondSimpleProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ $$createGroupedProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ $$createFirstSimpleProduct.sku$$=$$createFirstSimpleProduct.quantity$$
+
+
+ var/export/{$grabNameFile}
+ $$createSecondSimpleProduct.sku$$=$$createSecondSimpleProduct.quantity$$
+
+
+
-
+
+
+
+ {$grabExportUrl}
+ $$createFirstSimpleProduct.name$$
+
+
+ {$grabExportUrl}
+ $$createSecondSimpleProduct.name$$
+
+
+ {$grabExportUrl}
+ $$createGroupedProduct.name$$
+
+
+ {$grabExportUrl}
+ $$createFirstSimpleProduct.sku$$=$$createFirstSimpleProduct.quantity$$
+
+
+ {$grabExportUrl}
+ $$createSecondSimpleProduct.sku$$=$$createSecondSimpleProduct.quantity$$
+
+
+
+
+ var/export/{$grabNameFile}
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml
index f0e6e12204a9e..5aacd9dae44f6 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportImportConfigurableProductWithImagesTest.xml
@@ -209,9 +209,7 @@
-
-
-
+
@@ -219,9 +217,7 @@
-
-
-
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
index 94478e63aa92a..6eadb8d11456b 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
@@ -11,14 +11,21 @@
-
-
-
+
+
+
+
@@ -76,6 +83,7 @@
+
@@ -83,37 +91,90 @@
+
+ var/export
+
-
-
-
-
-
+
+
+
-
+
+
-
+
+
+ var/export/{$grabNameFile}
+
+
+ var/export/{$grabNameFile}
+ $$createConfigProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ sku=$$createConfigFirstChildProduct.sku$$,$$createConfigProductAttribute.attribute_code$$=option1
+
+
+ var/export/{$grabNameFile}
+ sku=$$createConfigSecondChildProduct.sku$$,$$createConfigProductAttribute.attribute_code$$=option2
+
+
+ var/export/{$grabNameFile}
+ $$createConfigFirstChildProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ $$createConfigSecondChildProduct.name$$
+
+
+
-
+
+
+
+ {$grabExportUrl}
+ $$createConfigProduct.name$$
+
+
+ {$grabExportUrl}
+ sku=$$createConfigFirstChildProduct.sku$$,$$createConfigProductAttribute.attribute_code$$=option1
+
+
+ {$grabExportUrl}
+ sku=$$createConfigSecondChildProduct.sku$$,$$createConfigProductAttribute.attribute_code$$=option2
+
+
+ {$grabExportUrl}
+ $$createConfigFirstChildProduct.name$$
+
+
+ {$grabExportUrl}
+ $$createConfigSecondChildProduct.name$$
+
+
+
+
+ var/export/{$grabNameFile}
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
index 95cfe2c87bffb..981d63b4f23e3 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
@@ -11,14 +11,22 @@
-
+
-
+
+
@@ -92,43 +100,105 @@
+
-
+
+
+ var/export
+
-
-
-
-
+
+
-
+
+
-
+
+
+ var/export/{$grabNameFile}
+
+
+ var/export/{$grabNameFile}
+ $$createConfigProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ sku=$$createConfigFirstChildProduct.sku$$,$$createConfigProductAttribute.attribute_code$$=option1
+
+
+ var/export/{$grabNameFile}
+ sku=$$createConfigSecondChildProduct.sku$$,$$createConfigProductAttribute.attribute_code$$=option2
+
+
+ var/export/{$grabNameFile}
+ $createConfigProductImage.entry[content][name]$,"$createConfigProductImage.entry[label]$"
+
+
+ var/export/{$grabNameFile}
+ $$createConfigFirstChildProduct.name$$
+
+
+ var/export/{$grabNameFile}
+ $$createConfigSecondChildProduct.name$$
+
+
+
-
+
+
+
+ {$grabExportUrl}
+ $$createConfigProduct.name$$
+
+
+ {$grabExportUrl}
+ sku=$$createConfigFirstChildProduct.sku$$,$$createConfigProductAttribute.attribute_code$$=option1
+
+
+ {$grabExportUrl}
+ sku=$$createConfigSecondChildProduct.sku$$,$$createConfigProductAttribute.attribute_code$$=option2
+
+
+ {$grabExportUrl}
+ $createConfigProductImage.entry[content][name]$,"$createConfigProductImage.entry[label]$"
+
+
+ {$grabExportUrl}
+ $$createConfigFirstChildProduct.name$$
+
+
+ {$grabExportUrl}
+ $$createConfigSecondChildProduct.name$$
+
+
+
+
+ var/export/{$grabNameFile}
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
index 2f57d94113d38..35e9603c3055e 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
index dac97a61a967b..7de972285215e 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/TaxClassProcessorTest.php b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/TaxClassProcessorTest.php
index ba65ffa37c8c7..80ee5e92f2e79 100644
--- a/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/TaxClassProcessorTest.php
+++ b/app/code/Magento/CatalogImportExport/Test/Unit/Model/Import/Product/TaxClassProcessorTest.php
@@ -101,4 +101,22 @@ public function testUpsertTaxClassNotExist()
$taxClassId = $this->taxClassProcessor->upsertTaxClass('noExistClassName', $this->product);
$this->assertEquals(self::TEST_JUST_CREATED_TAX_CLASS_ID, $taxClassId);
}
+
+ public function testUpsertTaxClassExistCaseInsensitive()
+ {
+ $taxClassId = $this->taxClassProcessor->upsertTaxClass(strtoupper(self::TEST_TAX_CLASS_NAME), $this->product);
+ $this->assertEquals(self::TEST_TAX_CLASS_ID, $taxClassId);
+ }
+
+ public function testUpsertTaxClassNone()
+ {
+ $taxClassId = $this->taxClassProcessor->upsertTaxClass('none', $this->product);
+ $this->assertEquals(0, $taxClassId);
+ }
+
+ public function testUpsertTaxClassZero()
+ {
+ $taxClassId = $this->taxClassProcessor->upsertTaxClass(0, $this->product);
+ $this->assertEquals(0, $taxClassId);
+ }
}
diff --git a/app/code/Magento/CatalogImportExport/etc/di.xml b/app/code/Magento/CatalogImportExport/etc/di.xml
index 040e9dcda132a..c35bcbd849511 100644
--- a/app/code/Magento/CatalogImportExport/etc/di.xml
+++ b/app/code/Magento/CatalogImportExport/etc/di.xml
@@ -48,6 +48,7 @@
- Magento\CatalogImportExport\Model\Export\Product\CategoryFilter
- Magento\CatalogImportExport\Model\Export\Product\StockStatusFilter
+ - Magento\CatalogImportExport\Model\Export\Product\WebsiteFilter
diff --git a/app/code/Magento/CatalogInventory/Api/Data/StockCollectionInterface.php b/app/code/Magento/CatalogInventory/Api/Data/StockCollectionInterface.php
index 07b8429ddf188..4b9383b9eb106 100644
--- a/app/code/Magento/CatalogInventory/Api/Data/StockCollectionInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/Data/StockCollectionInterface.php
@@ -17,8 +17,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockCollectionInterface extends SearchResultsInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/Data/StockInterface.php b/app/code/Magento/CatalogInventory/Api/Data/StockInterface.php
index 581081f2924ea..e0375471acf19 100644
--- a/app/code/Magento/CatalogInventory/Api/Data/StockInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/Data/StockInterface.php
@@ -13,8 +13,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockInterface extends ExtensibleDataInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/Data/StockItemCollectionInterface.php b/app/code/Magento/CatalogInventory/Api/Data/StockItemCollectionInterface.php
index 2d5f980a57039..d280df7e9fe1e 100644
--- a/app/code/Magento/CatalogInventory/Api/Data/StockItemCollectionInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/Data/StockItemCollectionInterface.php
@@ -17,8 +17,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockItemCollectionInterface extends SearchResultsInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/Data/StockItemInterface.php b/app/code/Magento/CatalogInventory/Api/Data/StockItemInterface.php
index 759cc9883be9f..4b42c6498c942 100644
--- a/app/code/Magento/CatalogInventory/Api/Data/StockItemInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/Data/StockItemInterface.php
@@ -13,8 +13,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockItemInterface extends ExtensibleDataInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/Data/StockStatusCollectionInterface.php b/app/code/Magento/CatalogInventory/Api/Data/StockStatusCollectionInterface.php
index a7d70a943d405..c3649496f2be8 100644
--- a/app/code/Magento/CatalogInventory/Api/Data/StockStatusCollectionInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/Data/StockStatusCollectionInterface.php
@@ -13,8 +13,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockStatusCollectionInterface extends SearchResultsInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php b/app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php
index 35e56b0e3e7bb..10123c9c5a103 100644
--- a/app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/Data/StockStatusInterface.php
@@ -13,8 +13,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockStatusInterface extends ExtensibleDataInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/RegisterProductSaleInterface.php b/app/code/Magento/CatalogInventory/Api/RegisterProductSaleInterface.php
index ddb3fce22a853..e530b0d83c9c4 100644
--- a/app/code/Magento/CatalogInventory/Api/RegisterProductSaleInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/RegisterProductSaleInterface.php
@@ -14,8 +14,8 @@
* @api
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
* @since 100.3.0
*/
interface RegisterProductSaleInterface
diff --git a/app/code/Magento/CatalogInventory/Api/RevertProductSaleInterface.php b/app/code/Magento/CatalogInventory/Api/RevertProductSaleInterface.php
index 83f7d73deaed9..5d5f22580b1e4 100644
--- a/app/code/Magento/CatalogInventory/Api/RevertProductSaleInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/RevertProductSaleInterface.php
@@ -11,8 +11,8 @@
* @api
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
* @since 100.3.0
*/
interface RevertProductSaleInterface
diff --git a/app/code/Magento/CatalogInventory/Api/StockConfigurationInterface.php b/app/code/Magento/CatalogInventory/Api/StockConfigurationInterface.php
index ab52580988c5e..4436f3b220c2c 100644
--- a/app/code/Magento/CatalogInventory/Api/StockConfigurationInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockConfigurationInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockConfigurationInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockCriteriaInterface.php b/app/code/Magento/CatalogInventory/Api/StockCriteriaInterface.php
index 92f2290ec08ad..5c3c82701339a 100644
--- a/app/code/Magento/CatalogInventory/Api/StockCriteriaInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockCriteriaInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockCriteriaInterface extends \Magento\Framework\Api\CriteriaInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockIndexInterface.php b/app/code/Magento/CatalogInventory/Api/StockIndexInterface.php
index 24dbaf5bb6d5f..e3288d355f742 100644
--- a/app/code/Magento/CatalogInventory/Api/StockIndexInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockIndexInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockIndexInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockItemCriteriaInterface.php b/app/code/Magento/CatalogInventory/Api/StockItemCriteriaInterface.php
index b72289ee09278..19c5f597d4b36 100644
--- a/app/code/Magento/CatalogInventory/Api/StockItemCriteriaInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockItemCriteriaInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockItemCriteriaInterface extends \Magento\Framework\Api\CriteriaInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockItemRepositoryInterface.php b/app/code/Magento/CatalogInventory/Api/StockItemRepositoryInterface.php
index 4269569f9da1a..41b96b0d5ccd0 100644
--- a/app/code/Magento/CatalogInventory/Api/StockItemRepositoryInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockItemRepositoryInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockItemRepositoryInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockManagementInterface.php b/app/code/Magento/CatalogInventory/Api/StockManagementInterface.php
index 3c1c7ea137c89..a3fca303236b4 100644
--- a/app/code/Magento/CatalogInventory/Api/StockManagementInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockManagementInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockManagementInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php b/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php
index bab5f9b457c45..07bf2746338d9 100644
--- a/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockRegistryInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockRegistryInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockRepositoryInterface.php b/app/code/Magento/CatalogInventory/Api/StockRepositoryInterface.php
index a7d64ec9eedb3..f38d4a2ca91b3 100644
--- a/app/code/Magento/CatalogInventory/Api/StockRepositoryInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockRepositoryInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockRepositoryInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockStateInterface.php b/app/code/Magento/CatalogInventory/Api/StockStateInterface.php
index d404e885d78df..ad7291281ed3e 100644
--- a/app/code/Magento/CatalogInventory/Api/StockStateInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockStateInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockStateInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockStatusCriteriaInterface.php b/app/code/Magento/CatalogInventory/Api/StockStatusCriteriaInterface.php
index be1c9642826a7..cd26a575b676e 100644
--- a/app/code/Magento/CatalogInventory/Api/StockStatusCriteriaInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockStatusCriteriaInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockStatusCriteriaInterface extends \Magento\Framework\Api\CriteriaInterface
{
diff --git a/app/code/Magento/CatalogInventory/Api/StockStatusRepositoryInterface.php b/app/code/Magento/CatalogInventory/Api/StockStatusRepositoryInterface.php
index 91efd55761335..b120b93c9193e 100644
--- a/app/code/Magento/CatalogInventory/Api/StockStatusRepositoryInterface.php
+++ b/app/code/Magento/CatalogInventory/Api/StockStatusRepositoryInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockStatusRepositoryInterface
{
diff --git a/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Minsaleqty.php b/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Minsaleqty.php
index bc63114d99801..e7918e32f78a2 100644
--- a/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Minsaleqty.php
+++ b/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Minsaleqty.php
@@ -14,8 +14,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class Minsaleqty extends \Magento\Config\Block\System\Config\Form\Field\FieldArray\AbstractFieldArray
{
diff --git a/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Stock.php b/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Stock.php
index 3c1a6e7982708..c430d3c399b52 100644
--- a/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Stock.php
+++ b/app/code/Magento/CatalogInventory/Block/Adminhtml/Form/Field/Stock.php
@@ -16,8 +16,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class Stock extends \Magento\Framework\Data\Form\Element\Select
{
diff --git a/app/code/Magento/CatalogInventory/Block/Qtyincrements.php b/app/code/Magento/CatalogInventory/Block/Qtyincrements.php
index dd8c987fe5da4..909ec9346ebf0 100644
--- a/app/code/Magento/CatalogInventory/Block/Qtyincrements.php
+++ b/app/code/Magento/CatalogInventory/Block/Qtyincrements.php
@@ -16,8 +16,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class Qtyincrements extends Template implements IdentityInterface
{
diff --git a/app/code/Magento/CatalogInventory/Block/Stockqty/DefaultStockqty.php b/app/code/Magento/CatalogInventory/Block/Stockqty/DefaultStockqty.php
index c19dc5fb34bf6..cb7d68c92ef6f 100644
--- a/app/code/Magento/CatalogInventory/Block/Stockqty/DefaultStockqty.php
+++ b/app/code/Magento/CatalogInventory/Block/Stockqty/DefaultStockqty.php
@@ -13,8 +13,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class DefaultStockqty extends AbstractStockqty implements \Magento\Framework\DataObject\IdentityInterface
{
diff --git a/app/code/Magento/CatalogInventory/Helper/Stock.php b/app/code/Magento/CatalogInventory/Helper/Stock.php
index 87a0e3c32ad09..e79d2098be68a 100644
--- a/app/code/Magento/CatalogInventory/Helper/Stock.php
+++ b/app/code/Magento/CatalogInventory/Helper/Stock.php
@@ -20,8 +20,8 @@
* @api
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
* @since 100.0.2
*/
class Stock
diff --git a/app/code/Magento/CatalogInventory/Model/Adminhtml/Stock/Item.php b/app/code/Magento/CatalogInventory/Model/Adminhtml/Stock/Item.php
index 04e54acad5c0e..c2715241fbe1d 100644
--- a/app/code/Magento/CatalogInventory/Model/Adminhtml/Stock/Item.php
+++ b/app/code/Magento/CatalogInventory/Model/Adminhtml/Stock/Item.php
@@ -22,8 +22,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class Item extends \Magento\CatalogInventory\Model\Stock\Item implements IdentityInterface
{
diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php
index 005ffd11ac7a1..4cc44488a3a71 100644
--- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php
+++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/CacheCleaner.php
@@ -6,6 +6,7 @@
namespace Magento\CatalogInventory\Model\Indexer\Stock;
+use Magento\Catalog\Model\Category;
use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\App\ObjectManager;
@@ -88,6 +89,11 @@ public function clean(array $productIds, callable $reindex)
if ($productIds) {
$this->cacheContext->registerEntities(Product::CACHE_TAG, array_unique($productIds));
$this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]);
+ $categoryIds = $this->getCategoryIdsByProductIds($productIds);
+ if ($categoryIds){
+ $this->cacheContext->registerEntities(Category::CACHE_TAG, array_unique($categoryIds));
+ $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]);
+ }
}
}
@@ -159,6 +165,22 @@ private function getProductIdsForCacheClean(array $productStatusesBefore, array
return $productIds;
}
+ /**
+ * Get category ids for products
+ *
+ * @param array $productIds
+ * @return array
+ */
+ private function getCategoryIdsByProductIds(array $productIds): array
+ {
+ $categoryProductTable = $this->getConnection()->getTableName('catalog_category_product');
+ $select = $this->getConnection()->select()
+ ->from(['catalog_category_product' => $categoryProductTable], ['category_id'])
+ ->where('product_id IN (?)', $productIds);
+
+ return $this->getConnection()->fetchCol($select);
+ }
+
/**
* Get database connection.
*
diff --git a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php
index 12a48caf62414..3304e39f2cb29 100644
--- a/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php
+++ b/app/code/Magento/CatalogInventory/Model/Quote/Item/QuantityValidator.php
@@ -27,8 +27,8 @@
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class QuantityValidator
{
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
index dec18044b699e..05b645652093d 100644
--- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/DefaultStock.php
@@ -20,8 +20,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class DefaultStock extends AbstractIndexer implements StockInterface
{
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorInterface.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorInterface.php
index 665ebf2db2f30..4a78babd03201 100644
--- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorInterface.php
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/QueryProcessorInterface.php
@@ -13,8 +13,8 @@
* @since 100.1.0
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface QueryProcessorInterface
{
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/StockInterface.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/StockInterface.php
index 9a1945d5aefac..e111a5267da77 100644
--- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/StockInterface.php
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/Stock/StockInterface.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockInterface
{
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/StockFactory.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/StockFactory.php
index 49e4889c8edee..f109643bc09c5 100644
--- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/StockFactory.php
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Indexer/StockFactory.php
@@ -14,8 +14,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class StockFactory
{
diff --git a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php
index afb7d51335df8..adf62b75b2adb 100644
--- a/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php
+++ b/app/code/Magento/CatalogInventory/Model/ResourceModel/Stock/Status.php
@@ -24,8 +24,8 @@
* @api
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
* @since 100.0.2
*/
class Status extends AbstractDb
diff --git a/app/code/Magento/CatalogInventory/Model/Source/Backorders.php b/app/code/Magento/CatalogInventory/Model/Source/Backorders.php
index d28da4e5b3497..59d359433c268 100644
--- a/app/code/Magento/CatalogInventory/Model/Source/Backorders.php
+++ b/app/code/Magento/CatalogInventory/Model/Source/Backorders.php
@@ -11,8 +11,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class Backorders implements \Magento\Framework\Option\ArrayInterface
{
diff --git a/app/code/Magento/CatalogInventory/Model/Source/Stock.php b/app/code/Magento/CatalogInventory/Model/Source/Stock.php
index 69e80658ecd74..c0ffb619e36ce 100644
--- a/app/code/Magento/CatalogInventory/Model/Source/Stock.php
+++ b/app/code/Magento/CatalogInventory/Model/Source/Stock.php
@@ -13,8 +13,8 @@
* @since 100.0.2
*
* @deprecated 100.3.0 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
class Stock extends AbstractSource
{
diff --git a/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php b/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
index b2dfe532ffbe0..bbba3498ab03f 100644
--- a/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
+++ b/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
@@ -9,8 +9,8 @@
* Interface StockRegistryProviderInterface
*
* @deprecated 100.3.2 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockRegistryProviderInterface
{
diff --git a/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php b/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
index 5bb78e1489b39..2cc69513f31b7 100644
--- a/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
+++ b/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
@@ -11,8 +11,8 @@
* Interface StockStateProviderInterface
*
* @deprecated 100.3.2 Replaced with Multi Source Inventory
- * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
- * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.4/inventory/inventory-api-reference.html
*/
interface StockStateProviderInterface
{
diff --git a/app/code/Magento/CatalogInventory/Model/Stock/Status.php b/app/code/Magento/CatalogInventory/Model/Stock/Status.php
index 4941d5d333bdb..7066d06c7f1a7 100644
--- a/app/code/Magento/CatalogInventory/Model/Stock/Status.php
+++ b/app/code/Magento/CatalogInventory/Model/Stock/Status.php
@@ -27,6 +27,11 @@ class Status extends AbstractExtensibleModel implements StockStatusInterface
const KEY_STOCK_STATUS = 'stock_status';
/**#@-*/
+ /**
+ * @var StockRegistryInterface
+ */
+ private $stockRegistry;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
diff --git a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php
index 515080d56541c..936cafb60f332 100644
--- a/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php
+++ b/app/code/Magento/CatalogInventory/Model/Stock/StockItemRepository.php
@@ -25,6 +25,7 @@
use Magento\Framework\Stdlib\DateTime\DateTime;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Psr\Log\LoggerInterface as PsrLogger;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -98,8 +99,11 @@ class StockItemRepository implements StockItemRepositoryInterface
protected $productCollectionFactory;
/**
- * Constructor
- *
+ * @var PsrLogger
+ */
+ private $psrLogger;
+
+ /**
* @param StockConfigurationInterface $stockConfiguration
* @param StockStateProviderInterface $stockStateProvider
* @param StockItemResource $resource
@@ -111,7 +115,8 @@ class StockItemRepository implements StockItemRepositoryInterface
* @param TimezoneInterface $localeDate
* @param Processor $indexProcessor
* @param DateTime $dateTime
- * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory|null $collectionFactory
+ * @param CollectionFactory|null $productCollectionFactory
+ * @param PsrLogger|null $psrLogger
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -126,7 +131,8 @@ public function __construct(
TimezoneInterface $localeDate,
Processor $indexProcessor,
DateTime $dateTime,
- \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory = null
+ \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory = null,
+ PsrLogger $psrLogger = null
) {
$this->stockConfiguration = $stockConfiguration;
$this->stockStateProvider = $stockStateProvider;
@@ -141,6 +147,8 @@ public function __construct(
$this->dateTime = $dateTime;
$this->productCollectionFactory = $productCollectionFactory ?: ObjectManager::getInstance()
->get(CollectionFactory::class);
+ $this->psrLogger = $psrLogger ?: ObjectManager::getInstance()
+ ->get(PsrLogger::class);
}
/**
@@ -184,6 +192,7 @@ public function save(\Magento\CatalogInventory\Api\Data\StockItemInterface $stoc
$this->resource->save($stockItem);
} catch (\Exception $exception) {
+ $this->psrLogger->error($exception->getMessage());
throw new CouldNotSaveException(__('The stock item was unable to be saved. Please try again.'), $exception);
}
return $stockItem;
diff --git a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php
index b57518b681aa2..bfa854edeaaf4 100644
--- a/app/code/Magento/CatalogInventory/Model/StockStateProvider.php
+++ b/app/code/Magento/CatalogInventory/Model/StockStateProvider.php
@@ -105,47 +105,46 @@ public function verifyNotification(StockItemInterface $stockItem)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function checkQuoteItemQty(StockItemInterface $stockItem, $qty, $summaryQty, $origQty = 0)
{
$result = $this->objectFactory->create();
$result->setHasError(false);
-
$qty = $this->getNumber($qty);
-
- /**
- * Check quantity type
- */
- $result->setItemIsQtyDecimal($stockItem->getIsQtyDecimal());
- if (!$stockItem->getIsQtyDecimal()) {
- $result->setHasQtyOptionUpdate(true);
- $qty = (int) $qty ?: 1;
- /**
- * Adding stock data to quote item
- */
- $result->setItemQty($qty);
- $result->setOrigQty((int)$this->getNumber($origQty) ?: 1);
- }
+ $quoteMessage = __('Please correct the quantity for some products.');
if ($stockItem->getMinSaleQty() && $qty < $stockItem->getMinSaleQty()) {
$result->setHasError(true)
->setMessage(__('The fewest you may purchase is %1.', $stockItem->getMinSaleQty() * 1))
->setErrorCode('qty_min')
- ->setQuoteMessage(__('Please correct the quantity for some products.'))
+ ->setQuoteMessage($quoteMessage)
->setQuoteMessageIndex('qty');
return $result;
}
if ($stockItem->getMaxSaleQty() && $qty > $stockItem->getMaxSaleQty()) {
$result->setHasError(true)
- ->setMessage(__('The most you may purchase is %1.', $stockItem->getMaxSaleQty() * 1))
+ ->setMessage(__('The requested qty exceeds the maximum qty allowed in shopping cart'))
->setErrorCode('qty_max')
- ->setQuoteMessage(__('Please correct the quantity for some products.'))
+ ->setQuoteMessage($quoteMessage)
->setQuoteMessageIndex('qty');
return $result;
}
$result->addData($this->checkQtyIncrements($stockItem, $qty)->getData());
+
+ $result->setItemIsQtyDecimal($stockItem->getIsQtyDecimal());
+ if (!$stockItem->getIsQtyDecimal() && (floor($qty) !== $qty)) {
+ $result->setHasError(true)
+ ->setMessage(__('You cannot use decimal quantity for this product.'))
+ ->setErrorCode('qty_decimal')
+ ->setQuoteMessage($quoteMessage)
+ ->setQuoteMessageIndex('qty');
+
+ return $result;
+ }
+
if ($result->getHasError()) {
return $result;
}
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryChangeManageStockActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryChangeManageStockActionGroup.xml
index 2c38f14f53379..b93c2af43e64d 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryChangeManageStockActionGroup.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminCatalogInventoryChangeManageStockActionGroup.xml
@@ -16,6 +16,8 @@
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductSetMaxQtyAllowedInShoppingCartActionGroup.xml b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductSetMaxQtyAllowedInShoppingCartActionGroup.xml
index a5e4d3e9c2af7..e8871365dabea 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductSetMaxQtyAllowedInShoppingCartActionGroup.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/ActionGroup/AdminProductSetMaxQtyAllowedInShoppingCartActionGroup.xml
@@ -13,11 +13,11 @@
+
+
-
-
diff --git a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml
index e7387ddd5d674..eb076d23919ba 100644
--- a/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml
+++ b/app/code/Magento/CatalogInventory/Test/Mftf/Test/AssociatedProductToConfigurableOutOfStockTest.xml
@@ -109,8 +109,8 @@
-
-
+
+
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/CacheCleanerTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/CacheCleanerTest.php
index f65ccaf806c11..794f5d92da1e8 100644
--- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/CacheCleanerTest.php
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Indexer/Stock/CacheCleanerTest.php
@@ -7,6 +7,7 @@
namespace Magento\CatalogInventory\Test\Unit\Model\Indexer\Stock;
+use Magento\Catalog\Model\Category;
use Magento\Catalog\Model\Product;
use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\CatalogInventory\Model\Indexer\Stock\CacheCleaner;
@@ -20,6 +21,9 @@
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
+/**
+ * Test for CacheCleaner
+ */
class CacheCleanerTest extends TestCase
{
/**
@@ -70,14 +74,16 @@ protected function setUp(): void
$this->connectionMock = $this->getMockBuilder(AdapterInterface::class)
->getMock();
$this->stockConfigurationMock = $this->getMockBuilder(StockConfigurationInterface::class)
- ->setMethods(['getStockThresholdQty'])->getMockForAbstractClass();
+ ->setMethods(['getStockThresholdQty'])
+ ->getMockForAbstractClass();
$this->cacheContextMock = $this->getMockBuilder(CacheContext::class)
->disableOriginalConstructor()
->getMock();
$this->eventManagerMock = $this->getMockBuilder(ManagerInterface::class)
->getMock();
$this->metadataPoolMock = $this->getMockBuilder(MetadataPool::class)
- ->setMethods(['getMetadata', 'getLinkField'])->disableOriginalConstructor()
+ ->setMethods(['getMetadata', 'getLinkField'])
+ ->disableOriginalConstructor()
->getMock();
$this->selectMock = $this->getMockBuilder(Select::class)
->disableOriginalConstructor()
@@ -100,37 +106,63 @@ protected function setUp(): void
}
/**
+ * Test clean cache by product ids and category ids
+ *
* @param bool $stockStatusBefore
* @param bool $stockStatusAfter
* @param int $qtyAfter
* @param bool|int $stockThresholdQty
* @dataProvider cleanDataProvider
+ * @return void
*/
- public function testClean($stockStatusBefore, $stockStatusAfter, $qtyAfter, $stockThresholdQty)
+ public function testClean($stockStatusBefore, $stockStatusAfter, $qtyAfter, $stockThresholdQty): void
{
$productId = 123;
- $this->selectMock->expects($this->any())->method('from')->willReturnSelf();
- $this->selectMock->expects($this->any())->method('where')->willReturnSelf();
- $this->selectMock->expects($this->any())->method('joinLeft')->willReturnSelf();
- $this->connectionMock->expects($this->exactly(2))->method('select')->willReturn($this->selectMock);
- $this->connectionMock->expects($this->exactly(2))->method('fetchAll')->willReturnOnConsecutiveCalls(
- [
- ['product_id' => $productId, 'stock_status' => $stockStatusBefore],
- ],
- [
- ['product_id' => $productId, 'stock_status' => $stockStatusAfter, 'qty' => $qtyAfter],
- ]
- );
- $this->stockConfigurationMock->expects($this->once())->method('getStockThresholdQty')
+ $categoryId = 3;
+ $this->selectMock->expects($this->any())
+ ->method('from')
+ ->willReturnSelf();
+ $this->selectMock->expects($this->any())
+ ->method('where')
+ ->willReturnSelf();
+ $this->selectMock->expects($this->any())
+ ->method('joinLeft')
+ ->willReturnSelf();
+ $this->connectionMock->expects($this->exactly(3))
+ ->method('select')
+ ->willReturn($this->selectMock);
+ $this->connectionMock->expects($this->exactly(2))
+ ->method('fetchAll')
+ ->willReturnOnConsecutiveCalls(
+ [
+ ['product_id' => $productId, 'stock_status' => $stockStatusBefore],
+ ],
+ [
+ ['product_id' => $productId, 'stock_status' => $stockStatusAfter, 'qty' => $qtyAfter],
+ ]
+ );
+ $this->connectionMock->expects($this->exactly(1))
+ ->method('fetchCol')
+ ->willReturn([$categoryId]);
+ $this->stockConfigurationMock->expects($this->once())
+ ->method('getStockThresholdQty')
->willReturn($stockThresholdQty);
- $this->cacheContextMock->expects($this->once())->method('registerEntities')
- ->with(Product::CACHE_TAG, [$productId]);
- $this->eventManagerMock->expects($this->once())->method('dispatch')
+ $this->cacheContextMock->expects($this->exactly(2))
+ ->method('registerEntities')
+ ->withConsecutive(
+ [Product::CACHE_TAG, [$productId]],
+ [Category::CACHE_TAG, [$categoryId]],
+ );
+ $this->eventManagerMock->expects($this->exactly(2))
+ ->method('dispatch')
->with('clean_cache_by_tags', ['object' => $this->cacheContextMock]);
- $this->metadataPoolMock->expects($this->exactly(2))->method('getMetadata')
+ $this->metadataPoolMock->expects($this->exactly(2))
+ ->method('getMetadata')
->willReturnSelf();
- $this->metadataPoolMock->expects($this->exactly(2))->method('getLinkField')
+ $this->metadataPoolMock->expects($this->exactly(2))
+ ->method('getLinkField')
->willReturn('row_id');
+
$callback = function () {
};
$this->unit->clean([], $callback);
@@ -139,7 +171,7 @@ public function testClean($stockStatusBefore, $stockStatusAfter, $qtyAfter, $sto
/**
* @return array
*/
- public function cleanDataProvider()
+ public function cleanDataProvider(): array
{
return [
[true, false, 1, false],
@@ -155,29 +187,42 @@ public function cleanDataProvider()
* @param int $qtyAfter
* @param bool|int $stockThresholdQty
* @dataProvider notCleanCacheDataProvider
+ * @return void
*/
- public function testNotCleanCache($stockStatusBefore, $stockStatusAfter, $qtyAfter, $stockThresholdQty)
+ public function testNotCleanCache($stockStatusBefore, $stockStatusAfter, $qtyAfter, $stockThresholdQty): void
{
$productId = 123;
- $this->selectMock->expects($this->any())->method('from')->willReturnSelf();
- $this->selectMock->expects($this->any())->method('where')->willReturnSelf();
- $this->selectMock->expects($this->any())->method('joinLeft')->willReturnSelf();
- $this->connectionMock->expects($this->exactly(2))->method('select')->willReturn($this->selectMock);
- $this->connectionMock->expects($this->exactly(2))->method('fetchAll')->willReturnOnConsecutiveCalls(
- [
- ['product_id' => $productId, 'stock_status' => $stockStatusBefore],
- ],
- [
- ['product_id' => $productId, 'stock_status' => $stockStatusAfter, 'qty' => $qtyAfter],
- ]
- );
- $this->stockConfigurationMock->expects($this->once())->method('getStockThresholdQty')
+ $this->selectMock->expects($this->any())->method('from')
+ ->willReturnSelf();
+ $this->selectMock->expects($this->any())->method('where')
+ ->willReturnSelf();
+ $this->selectMock->expects($this->any())->method('joinLeft')
+ ->willReturnSelf();
+ $this->connectionMock->expects($this->exactly(2))
+ ->method('select')
+ ->willReturn($this->selectMock);
+ $this->connectionMock->expects($this->exactly(2))
+ ->method('fetchAll')
+ ->willReturnOnConsecutiveCalls(
+ [
+ ['product_id' => $productId, 'stock_status' => $stockStatusBefore],
+ ],
+ [
+ ['product_id' => $productId, 'stock_status' => $stockStatusAfter, 'qty' => $qtyAfter],
+ ]
+ );
+ $this->stockConfigurationMock->expects($this->once())
+ ->method('getStockThresholdQty')
->willReturn($stockThresholdQty);
- $this->cacheContextMock->expects($this->never())->method('registerEntities');
- $this->eventManagerMock->expects($this->never())->method('dispatch');
- $this->metadataPoolMock->expects($this->exactly(2))->method('getMetadata')
+ $this->cacheContextMock->expects($this->never())
+ ->method('registerEntities');
+ $this->eventManagerMock->expects($this->never())
+ ->method('dispatch');
+ $this->metadataPoolMock->expects($this->exactly(2))
+ ->method('getMetadata')
->willReturnSelf();
- $this->metadataPoolMock->expects($this->exactly(2))->method('getLinkField')
+ $this->metadataPoolMock->expects($this->exactly(2))
+ ->method('getLinkField')
->willReturn('row_id');
$callback = function () {
@@ -188,7 +233,7 @@ public function testNotCleanCache($stockStatusBefore, $stockStatusAfter, $qtyAft
/**
* @return array
*/
- public function notCleanCacheDataProvider()
+ public function notCleanCacheDataProvider(): array
{
return [
[true, true, 1, false],
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Spi/StockStateProviderTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Spi/StockStateProviderTest.php
index 9d2fb66dc716b..0d900bde60157 100644
--- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Spi/StockStateProviderTest.php
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Spi/StockStateProviderTest.php
@@ -21,6 +21,8 @@
use PHPUnit\Framework\TestCase;
/**
+ * Unit tests for \Magento\CatalogInventory\Model\StockStateProvider class.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class StockStateProviderTest extends TestCase
@@ -115,6 +117,9 @@ class StockStateProviderTest extends TestCase
'getProductName',
];
+ /**
+ * @inheritDoc
+ */
protected function setUp(): void
{
$this->objectManagerHelper = new ObjectManagerHelper($this);
@@ -383,7 +388,7 @@ protected function getVariations()
'suggestQty' => 51,
'getStockQty' => $stockQty,
'checkQtyIncrements' => false,
- 'checkQuoteItemQty' => false,
+ 'checkQuoteItemQty' => true,
],
],
[
@@ -411,7 +416,7 @@ protected function getVariations()
'getStockQty' => $stockQty,
'checkQtyIncrements' => false,
'checkQuoteItemQty' => true,
- ]
+ ],
],
[
'values' => [
@@ -438,8 +443,8 @@ protected function getVariations()
'getStockQty' => null,
'checkQtyIncrements' => false,
'checkQuoteItemQty' => true,
- ]
- ]
+ ],
+ ],
];
}
diff --git a/app/code/Magento/CatalogInventory/i18n/en_US.csv b/app/code/Magento/CatalogInventory/i18n/en_US.csv
index af989dc06d47e..4bac24ed998b6 100644
--- a/app/code/Magento/CatalogInventory/i18n/en_US.csv
+++ b/app/code/Magento/CatalogInventory/i18n/en_US.csv
@@ -71,3 +71,5 @@ Stock,Stock
"Qty Uses Decimals","Qty Uses Decimals"
"Allow Multiple Boxes for Shipping","Allow Multiple Boxes for Shipping"
"Done","Done"
+"The requested qty exceeds the maximum qty allowed in shopping cart","The requested qty exceeds the maximum qty allowed in shopping cart"
+"You cannot use decimal quantity for this product.","You cannot use decimal quantity for this product."
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php
index df167d171e001..38b48e05c55c2 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexBuilder.php
@@ -7,13 +7,23 @@
namespace Magento\CatalogRule\Model\Indexer;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\ProductFactory;
+use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\CatalogRule\Model\Indexer\IndexBuilder\ProductLoader;
+use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
use Magento\CatalogRule\Model\ResourceModel\Rule\Collection as RuleCollection;
use Magento\CatalogRule\Model\ResourceModel\Rule\CollectionFactory as RuleCollectionFactory;
use Magento\CatalogRule\Model\Rule;
+use Magento\Eav\Model\Config;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Pricing\PriceCurrencyInterface;
-use Magento\CatalogRule\Model\Indexer\IndexBuilder\ProductLoader;
-use Magento\CatalogRule\Model\Indexer\IndexerTableSwapperInterface as TableSwapper;
+use Magento\Framework\Stdlib\DateTime;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Model\ScopeInterface;
+use Magento\Store\Model\StoreManagerInterface;
+use Psr\Log\LoggerInterface;
/**
* Catalog rule index builder
@@ -46,12 +56,12 @@ class IndexBuilder
protected $_catalogRuleGroupWebsiteColumnsList = ['rule_id', 'customer_group_id', 'website_id'];
/**
- * @var \Magento\Framework\App\ResourceConnection
+ * @var ResourceConnection
*/
protected $resource;
/**
- * @var \Magento\Store\Model\StoreManagerInterface
+ * @var StoreManagerInterface
*/
protected $storeManager;
@@ -61,7 +71,7 @@ class IndexBuilder
protected $ruleCollectionFactory;
/**
- * @var \Psr\Log\LoggerInterface
+ * @var LoggerInterface
*/
protected $logger;
@@ -71,22 +81,22 @@ class IndexBuilder
protected $priceCurrency;
/**
- * @var \Magento\Eav\Model\Config
+ * @var Config
*/
protected $eavConfig;
/**
- * @var \Magento\Framework\Stdlib\DateTime
+ * @var DateTime
*/
protected $dateFormat;
/**
- * @var \Magento\Framework\Stdlib\DateTime\DateTime
+ * @var DateTime\DateTime
*/
protected $dateTime;
/**
- * @var \Magento\Catalog\Model\ProductFactory
+ * @var ProductFactory
*/
protected $productFactory;
@@ -136,7 +146,12 @@ class IndexBuilder
private $pricesPersistor;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher
+ * @var TimezoneInterface|mixed
+ */
+ private $localeDate;
+
+ /**
+ * @var ActiveTableSwitcher|mixed
*/
private $activeTableSwitcher;
@@ -146,20 +161,20 @@ class IndexBuilder
private $tableSwapper;
/**
- * @var ProductLoader
+ * @var ProductLoader|mixed
*/
private $productLoader;
/**
* @param RuleCollectionFactory $ruleCollectionFactory
* @param PriceCurrencyInterface $priceCurrency
- * @param \Magento\Framework\App\ResourceConnection $resource
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Psr\Log\LoggerInterface $logger
- * @param \Magento\Eav\Model\Config $eavConfig
- * @param \Magento\Framework\Stdlib\DateTime $dateFormat
- * @param \Magento\Framework\Stdlib\DateTime\DateTime $dateTime
- * @param \Magento\Catalog\Model\ProductFactory $productFactory
+ * @param ResourceConnection $resource
+ * @param StoreManagerInterface $storeManager
+ * @param LoggerInterface $logger
+ * @param Config $eavConfig
+ * @param DateTime $dateFormat
+ * @param DateTime\DateTime $dateTime
+ * @param ProductFactory $productFactory
* @param int $batchCount
* @param ProductPriceCalculator|null $productPriceCalculator
* @param ReindexRuleProduct|null $reindexRuleProduct
@@ -167,21 +182,23 @@ class IndexBuilder
* @param RuleProductsSelectBuilder|null $ruleProductsSelectBuilder
* @param ReindexRuleProductPrice|null $reindexRuleProductPrice
* @param RuleProductPricesPersistor|null $pricesPersistor
- * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher
+ * @param ActiveTableSwitcher|null $activeTableSwitcher
* @param ProductLoader|null $productLoader
* @param TableSwapper|null $tableSwapper
+ * @param TimezoneInterface|null $localeDate
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
RuleCollectionFactory $ruleCollectionFactory,
PriceCurrencyInterface $priceCurrency,
- \Magento\Framework\App\ResourceConnection $resource,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Psr\Log\LoggerInterface $logger,
- \Magento\Eav\Model\Config $eavConfig,
- \Magento\Framework\Stdlib\DateTime $dateFormat,
- \Magento\Framework\Stdlib\DateTime\DateTime $dateTime,
- \Magento\Catalog\Model\ProductFactory $productFactory,
+ ResourceConnection $resource,
+ StoreManagerInterface $storeManager,
+ LoggerInterface $logger,
+ Config $eavConfig,
+ DateTime $dateFormat,
+ DateTime\DateTime $dateTime,
+ ProductFactory $productFactory,
$batchCount = 1000,
ProductPriceCalculator $productPriceCalculator = null,
ReindexRuleProduct $reindexRuleProduct = null,
@@ -189,9 +206,10 @@ public function __construct(
RuleProductsSelectBuilder $ruleProductsSelectBuilder = null,
ReindexRuleProductPrice $reindexRuleProductPrice = null,
RuleProductPricesPersistor $pricesPersistor = null,
- \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null,
+ ActiveTableSwitcher $activeTableSwitcher = null,
ProductLoader $productLoader = null,
- TableSwapper $tableSwapper = null
+ TableSwapper $tableSwapper = null,
+ TimezoneInterface $localeDate = null
) {
$this->resource = $resource;
$this->connection = $resource->getConnection();
@@ -224,19 +242,22 @@ public function __construct(
RuleProductPricesPersistor::class
);
$this->activeTableSwitcher = $activeTableSwitcher ?? ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class
+ ActiveTableSwitcher::class
);
$this->productLoader = $productLoader ?? ObjectManager::getInstance()->get(
ProductLoader::class
);
$this->tableSwapper = $tableSwapper ??
ObjectManager::getInstance()->get(TableSwapper::class);
+ $this->localeDate = $localeDate ??
+ ObjectManager::getInstance()->get(TimezoneInterface::class);
}
/**
* Reindex by id
*
* @param int $id
+ * @throws LocalizedException
* @return void
* @api
*/
@@ -254,7 +275,7 @@ public function reindexById($id)
$this->reindexRuleGroupWebsite->execute();
} catch (\Exception $e) {
$this->critical($e);
- throw new \Magento\Framework\Exception\LocalizedException(
+ throw new LocalizedException(
__('Catalog rule indexing failed. See details in exception log.')
);
}
@@ -264,7 +285,7 @@ public function reindexById($id)
* Reindex by ids
*
* @param array $ids
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
* @return void
* @api
*/
@@ -274,7 +295,7 @@ public function reindexByIds(array $ids)
$this->doReindexByIds($ids);
} catch (\Exception $e) {
$this->critical($e);
- throw new \Magento\Framework\Exception\LocalizedException(
+ throw new LocalizedException(
__("Catalog rule indexing failed. See details in exception log.")
);
}
@@ -308,7 +329,7 @@ protected function doReindexByIds($ids)
/**
* Full reindex
*
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
* @return void
* @api
*/
@@ -318,7 +339,7 @@ public function reindexFull()
$this->doReindexFull();
} catch (\Exception $e) {
$this->critical($e);
- throw new \Magento\Framework\Exception\LocalizedException(
+ throw new LocalizedException(
__("Catalog rule indexing failed. See details in exception log.")
);
}
@@ -404,9 +425,6 @@ private function assignProductToRule(Rule $rule, int $productEntityId, array $we
);
$customerGroupIds = $rule->getCustomerGroupIds();
- $fromTime = strtotime($rule->getFromDate());
- $toTime = strtotime($rule->getToDate());
- $toTime = $toTime ? $toTime + self::SECONDS_IN_DAY - 1 : 0;
$sortOrder = (int)$rule->getSortOrder();
$actionOperator = $rule->getSimpleAction();
$actionAmount = $rule->getDiscountAmount();
@@ -414,6 +432,15 @@ private function assignProductToRule(Rule $rule, int $productEntityId, array $we
$rows = [];
foreach ($websiteIds as $websiteId) {
+ $scopeTz = new \DateTimeZone(
+ $this->localeDate->getConfigTimezone(ScopeInterface::SCOPE_WEBSITE, $websiteId)
+ );
+ $fromTime = $rule->getFromDate()
+ ? (new \DateTime($rule->getFromDate(), $scopeTz))->getTimestamp()
+ : 0;
+ $toTime = $rule->getToDate()
+ ? (new \DateTime($rule->getToDate(), $scopeTz))->getTimestamp() + IndexBuilder::SECONDS_IN_DAY - 1
+ : 0;
foreach ($customerGroupIds as $customerGroupId) {
$rows[] = [
'rule_id' => $ruleId,
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php
index f99f8c50a7f9a..0ddae74ff0a55 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/IndexerTableSwapper.php
@@ -122,4 +122,14 @@ public function swapIndexTables(array $originalTablesNames)
$this->resourceConnection->getConnection()->dropTable($tableName);
}
}
+
+ /**
+ * Cleanup leftover temporary tables
+ */
+ public function __destruct()
+ {
+ foreach ($this->temporaryTables as $tableName) {
+ $this->resourceConnection->getConnection()->dropTable($tableName);
+ }
+ }
}
diff --git a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
index 3e2301f34c4f8..ff9893ae1b906 100644
--- a/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
+++ b/app/code/Magento/CatalogRule/Model/Indexer/ReindexRuleProduct.php
@@ -107,6 +107,11 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
$actionStop = $rule->getStopRulesProcessing();
$fromTimeInAdminTz = $this->parseDateByWebsiteTz((string)$rule->getFromDate(), self::ADMIN_WEBSITE_ID);
$toTimeInAdminTz = $this->parseDateByWebsiteTz((string)$rule->getToDate(), self::ADMIN_WEBSITE_ID);
+ $excludedWebsites = [];
+ $ruleExtensionAttributes = $rule->getExtensionAttributes();
+ if ($ruleExtensionAttributes && $ruleExtensionAttributes->getExcludeWebsiteIds()) {
+ $excludedWebsites = $ruleExtensionAttributes->getExcludeWebsiteIds();
+ }
$rows = [];
foreach ($websiteIds as $websiteId) {
@@ -124,22 +129,26 @@ public function execute(Rule $rule, $batchCount, $useAdditionalTable = false)
}
foreach ($customerGroupIds as $customerGroupId) {
- $rows[] = [
- 'rule_id' => $ruleId,
- 'from_time' => $fromTime,
- 'to_time' => $toTime,
- 'website_id' => $websiteId,
- 'customer_group_id' => $customerGroupId,
- 'product_id' => $productId,
- 'action_operator' => $actionOperator,
- 'action_amount' => $actionAmount,
- 'action_stop' => $actionStop,
- 'sort_order' => $sortOrder,
- ];
-
- if (count($rows) == $batchCount) {
- $connection->insertMultiple($indexTable, $rows);
- $rows = [];
+ if (!array_key_exists($customerGroupId, $excludedWebsites)
+ || !in_array((int)$websiteId, array_values($excludedWebsites[$customerGroupId]), true)
+ ) {
+ $rows[] = [
+ 'rule_id' => $ruleId,
+ 'from_time' => $fromTime,
+ 'to_time' => $toTime,
+ 'website_id' => $websiteId,
+ 'customer_group_id' => $customerGroupId,
+ 'product_id' => $productId,
+ 'action_operator' => $actionOperator,
+ 'action_amount' => $actionAmount,
+ 'action_stop' => $actionStop,
+ 'sort_order' => $sortOrder,
+ ];
+
+ if (count($rows) === $batchCount) {
+ $connection->insertMultiple($indexTable, $rows);
+ $rows = [];
+ }
}
}
}
diff --git a/app/code/Magento/CatalogRule/Model/ResourceModel/Rule/Collection.php b/app/code/Magento/CatalogRule/Model/ResourceModel/Rule/Collection.php
index fdc039067cc3d..f7a01de2d2774 100644
--- a/app/code/Magento/CatalogRule/Model/ResourceModel/Rule/Collection.php
+++ b/app/code/Magento/CatalogRule/Model/ResourceModel/Rule/Collection.php
@@ -5,8 +5,8 @@
*/
namespace Magento\CatalogRule\Model\ResourceModel\Rule;
-use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Serialize\Serializer\Json;
class Collection extends \Magento\Rule\Model\ResourceModel\Rule\Collection\AbstractCollection
{
@@ -22,6 +22,16 @@ class Collection extends \Magento\Rule\Model\ResourceModel\Rule\Collection\Abstr
*/
protected $serializer;
+ /**
+ * @var string
+ */
+ protected $_eventPrefix = 'catalog_rule_collection';
+
+ /**
+ * @var string
+ */
+ protected $_eventObject = 'catalog_rule';
+
/**
* Collection constructor.
* @param \Magento\Framework\Data\Collection\EntityFactoryInterface $entityFactory
diff --git a/app/code/Magento/CatalogRule/Model/Rule.php b/app/code/Magento/CatalogRule/Model/Rule.php
index 2d92192368960..da32801ace477 100644
--- a/app/code/Magento/CatalogRule/Model/Rule.php
+++ b/app/code/Magento/CatalogRule/Model/Rule.php
@@ -589,7 +589,7 @@ protected function _invalidateCache()
*/
public function afterSave()
{
- if (!$this->getIsActive()) {
+ if (!$this->getIsActive() && !$this->getOrigData(self::IS_ACTIVE)) {
return parent::afterSave();
}
@@ -601,6 +601,7 @@ public function afterSave()
} else {
$this->_ruleProductProcessor->getIndexer()->invalidate();
}
+
return parent::afterSave();
}
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml
index 27edab962033e..d16255e0237b3 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/ActionGroup/AdminCatalogPriceRuleDeleteAllActionGroup.xml
@@ -16,7 +16,7 @@
-
+
{{AdminDataGridTableSection.firstNotEmptyRow}}
{{AdminConfirmationModalSection.ok}}
{{AdminMainActionsSection.delete}}
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
index 3109c56122d1b..c49b31ad47342 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleByCategoryTest.xml
@@ -78,16 +78,14 @@
-
-
-
+
-
+
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml
index 97e93c8f762c5..26e1966ece365 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminApplyCatalogRuleForConfigurableProductWithSpecialPricesTest.xml
@@ -127,9 +127,7 @@
-
-
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml
index 946a25d721cba..843bc4e722e65 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleByPercentTest.xml
@@ -25,9 +25,7 @@
-
-
-
+
@@ -49,13 +47,13 @@
-
+
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleForCustomerGroupTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleForCustomerGroupTest.xml
index e55cabd506466..a6a735bb81de8 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleForCustomerGroupTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminCreateCatalogPriceRuleTest/AdminCreateCatalogPriceRuleForCustomerGroupTest.xml
@@ -49,9 +49,7 @@
-
-
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest.xml
index 831470e0d64ca..c6a3291561fa1 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromConfigurableProductTest.xml
@@ -72,9 +72,7 @@
-
-
-
+
@@ -115,12 +113,10 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml
index 5a62b1a373f94..c6452612f82a4 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleEntityTest/AdminDeleteCatalogPriceRuleEntityFromSimpleProductTest.xml
@@ -60,18 +60,16 @@
-
-
-
+
-
+
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
index d03ca6b22d66a..333c4ab06aad1 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminDeleteCatalogPriceRuleTest.xml
@@ -38,6 +38,15 @@
+
+
+
+
+
+
+
+
+
@@ -52,7 +61,7 @@
-
+
@@ -86,12 +95,10 @@
-
-
-
+
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
index da62981c99202..1951aa6c0f6a8 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/AdminEnableAttributeIsUndefinedCatalogPriceRuleTest.xml
@@ -80,9 +80,7 @@
-
-
-
+
@@ -128,9 +126,7 @@
-
-
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml
index 6de7bba59c340..0e1059f2f5f18 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogPriceRuleByProductAttributeTest.xml
@@ -234,19 +234,17 @@
-
-
-
+
-
+
-
+
@@ -259,7 +257,7 @@
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml
index c3690e72e084f..b1d6935bec28d 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleAndConfigurableProductTest.xml
@@ -113,12 +113,10 @@
-
-
-
+
-
+
@@ -156,7 +154,7 @@
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml
index ece8dc4bacf28..6d8de2cb1d9bb 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductAndFixedMethodTest.xml
@@ -64,15 +64,11 @@
-
-
-
-
-
-
+
+
-
+
@@ -93,7 +89,7 @@
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml
index 3b7c9c181f1b1..ba446380a4f63 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductForNewCustomerGroupTest.xml
@@ -40,7 +40,7 @@
-
+
@@ -74,7 +74,7 @@
-
+
@@ -95,7 +95,7 @@
-
+
@@ -113,7 +113,7 @@
-
+
@@ -134,7 +134,7 @@
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml
index 45e97f179a11f..77b10a09d6ce0 100644
--- a/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml
+++ b/app/code/Magento/CatalogRule/Test/Mftf/Test/ApplyCatalogRuleForSimpleProductWithCustomOptionsTest.xml
@@ -71,15 +71,11 @@
-
-
-
-
-
-
+
+
-
+
@@ -100,7 +96,7 @@
-
+
@@ -115,7 +111,7 @@
-
+
@@ -130,7 +126,7 @@
-
+
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
index 50f4eb0805ed2..fff8a80c88e3d 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/Indexer/ReindexRuleProductTest.php
@@ -5,7 +5,6 @@
*/
declare(strict_types=1);
-
namespace Magento\CatalogRule\Test\Unit\Model\Indexer;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
@@ -33,11 +32,6 @@ class ReindexRuleProductTest extends TestCase
*/
private $resourceMock;
- /**
- * @var ActiveTableSwitcher|MockObject
- */
- private $activeTableSwitcherMock;
-
/**
* @var IndexerTableSwapperInterface|MockObject
*/
@@ -48,44 +42,56 @@ class ReindexRuleProductTest extends TestCase
*/
private $localeDateMock;
+ /**
+ * @var AdapterInterface|MockObject
+ */
+ private $connectionMock;
+
+ /**
+ * @var Rule|MockObject
+ */
+ private $ruleMock;
+
protected function setUp(): void
{
$this->resourceMock = $this->createMock(ResourceConnection::class);
- $this->activeTableSwitcherMock = $this->createMock(ActiveTableSwitcher::class);
+ $activeTableSwitcherMock = $this->createMock(ActiveTableSwitcher::class);
$this->tableSwapperMock = $this->getMockForAbstractClass(IndexerTableSwapperInterface::class);
$this->localeDateMock = $this->getMockForAbstractClass(TimezoneInterface::class);
+ $this->connectionMock = $this->getMockForAbstractClass(AdapterInterface::class);
+ $this->ruleMock = $this->createMock(Rule::class);
$this->model = new ReindexRuleProduct(
$this->resourceMock,
- $this->activeTableSwitcherMock,
+ $activeTableSwitcherMock,
$this->tableSwapperMock,
$this->localeDateMock,
true
);
}
- public function testExecuteIfRuleInactive()
+ public function testExecuteIfRuleInactive(): void
{
$ruleMock = $this->createMock(Rule::class);
- $ruleMock->expects($this->once())
+ $ruleMock->expects(self::once())
->method('getIsActive')
->willReturn(false);
- $this->assertFalse($this->model->execute($ruleMock, 100, true));
+ self::assertFalse($this->model->execute($ruleMock, 100, true));
}
- public function testExecuteIfRuleWithoutWebsiteIds()
+ public function testExecuteIfRuleWithoutWebsiteIds(): void
{
$ruleMock = $this->createMock(Rule::class);
- $ruleMock->expects($this->once())
+ $ruleMock->expects(self::once())
->method('getIsActive')
->willReturn(true);
- $ruleMock->expects($this->once())
+ $ruleMock->expects(self::once())
->method('getWebsiteIds')
->willReturn(null);
- $this->assertFalse($this->model->execute($ruleMock, 100, true));
+ self::assertFalse($this->model->execute($ruleMock, 100, true));
}
- public function testExecute()
+ public function testExecute(): void
{
$websiteId = 3;
$adminTimeZone = 'America/Chicago';
@@ -96,36 +102,8 @@ public function testExecute()
6 => [$websiteId => 1],
];
- $this->tableSwapperMock->expects($this->once())
- ->method('getWorkingTableName')
- ->with('catalogrule_product')
- ->willReturn('catalogrule_product_replica');
-
- $connectionMock = $this->getMockForAbstractClass(AdapterInterface::class);
- $this->resourceMock->expects($this->at(0))
- ->method('getConnection')
- ->willReturn($connectionMock);
- $this->resourceMock->expects($this->at(1))
- ->method('getTableName')
- ->with('catalogrule_product')
- ->willReturn('catalogrule_product');
- $this->resourceMock->expects($this->at(2))
- ->method('getTableName')
- ->with('catalogrule_product_replica')
- ->willReturn('catalogrule_product_replica');
-
- $ruleMock = $this->createMock(Rule::class);
- $ruleMock->expects($this->once())->method('getIsActive')->willReturn(true);
- $ruleMock->expects($this->exactly(2))->method('getWebsiteIds')->willReturn([$websiteId]);
- $ruleMock->expects($this->once())->method('getMatchingProductIds')->willReturn($productIds);
- $ruleMock->expects($this->once())->method('getId')->willReturn(100);
- $ruleMock->expects($this->once())->method('getCustomerGroupIds')->willReturn([10]);
- $ruleMock->expects($this->atLeastOnce())->method('getFromDate')->willReturn('2017-06-21');
- $ruleMock->expects($this->atLeastOnce())->method('getToDate')->willReturn('2017-06-30');
- $ruleMock->expects($this->once())->method('getSortOrder')->willReturn(1);
- $ruleMock->expects($this->once())->method('getSimpleAction')->willReturn('simple_action');
- $ruleMock->expects($this->once())->method('getDiscountAmount')->willReturn(43);
- $ruleMock->expects($this->once())->method('getStopRulesProcessing')->willReturn(true);
+ $this->prepareResourceMock();
+ $this->prepareRuleMock([3], $productIds, [10]);
$this->localeDateMock->method('getConfigTimezone')
->willReturnMap([
@@ -175,13 +153,141 @@ public function testExecute()
]
];
- $connectionMock->expects($this->at(0))
+ $this->connectionMock->expects(self::at(0))
->method('insertMultiple')
->with('catalogrule_product_replica', $batchRows);
- $connectionMock->expects($this->at(1))
+ $this->connectionMock->expects(self::at(1))
->method('insertMultiple')
->with('catalogrule_product_replica', $rowsNotInBatch);
- $this->assertTrue($this->model->execute($ruleMock, 2, true));
+ self::assertTrue($this->model->execute($this->ruleMock, 2, true));
+ }
+
+ public function testExecuteWithExcludedWebsites(): void
+ {
+ $websitesIds = [1, 2, 3];
+ $adminTimeZone = 'America/Chicago';
+ $websiteTz = 'America/Los_Angeles';
+ $productIds = [
+ 1 => [1 => 1],
+ 2 => [2 => 1],
+ 3 => [3 => 1],
+ ];
+
+ $this->prepareResourceMock();
+ $this->prepareRuleMock($websitesIds, $productIds, [10, 20]);
+
+ $extensionAttributes = $this->getMockBuilder(\Magento\Framework\Api\ExtensionAttributesInterface::class)
+ ->setMethods(['getExtensionAttributes', 'getExcludeWebsiteIds'])
+ ->disableOriginalConstructor()
+ ->getMockForAbstractClass();
+ $this->ruleMock->expects(self::once())->method('getExtensionAttributes')
+ ->willReturn($extensionAttributes);
+ $extensionAttributes->expects(self::exactly(2))->method('getExcludeWebsiteIds')
+ ->willReturn([10 => [1, 2]]);
+
+ $this->localeDateMock->method('getConfigTimezone')
+ ->willReturnMap([
+ [ScopeInterface::SCOPE_WEBSITE, self::ADMIN_WEBSITE_ID, $adminTimeZone],
+ [ScopeInterface::SCOPE_WEBSITE, 1, $websiteTz],
+ [ScopeInterface::SCOPE_WEBSITE, 2, $websiteTz],
+ [ScopeInterface::SCOPE_WEBSITE, 3, $websiteTz],
+ ]);
+
+ $batchRows = [
+ [
+ 'rule_id' => 100,
+ 'from_time' => 1498028400,
+ 'to_time' => 1498892399,
+ 'website_id' => 1,
+ 'customer_group_id' => 20,
+ 'product_id' => 1,
+ 'action_operator' => 'simple_action',
+ 'action_amount' => 43,
+ 'action_stop' => true,
+ 'sort_order' => 1,
+ ],
+ [
+ 'rule_id' => 100,
+ 'from_time' => 1498028400,
+ 'to_time' => 1498892399,
+ 'website_id' => 2,
+ 'customer_group_id' => 20,
+ 'product_id' => 2,
+ 'action_operator' => 'simple_action',
+ 'action_amount' => 43,
+ 'action_stop' => true,
+ 'sort_order' => 1,
+ ],
+ [
+ 'rule_id' => 100,
+ 'from_time' => 1498028400,
+ 'to_time' => 1498892399,
+ 'website_id' => 3,
+ 'customer_group_id' => 10,
+ 'product_id' => 3,
+ 'action_operator' => 'simple_action',
+ 'action_amount' => 43,
+ 'action_stop' => true,
+ 'sort_order' => 1,
+ ],
+ [
+ 'rule_id' => 100,
+ 'from_time' => 1498028400,
+ 'to_time' => 1498892399,
+ 'website_id' => 3,
+ 'customer_group_id' => 20,
+ 'product_id' => 3,
+ 'action_operator' => 'simple_action',
+ 'action_amount' => 43,
+ 'action_stop' => true,
+ 'sort_order' => 1,
+ ]
+ ];
+
+ $this->connectionMock->expects(self::at(0))
+ ->method('insertMultiple')
+ ->with('catalogrule_product_replica', $batchRows);
+
+ self::assertTrue($this->model->execute($this->ruleMock, 100, true));
+ }
+
+ private function prepareResourceMock(): void
+ {
+ $this->tableSwapperMock->expects(self::once())
+ ->method('getWorkingTableName')
+ ->with('catalogrule_product')
+ ->willReturn('catalogrule_product_replica');
+ $this->resourceMock->expects(self::at(0))
+ ->method('getConnection')
+ ->willReturn($this->connectionMock);
+ $this->resourceMock->expects(self::at(1))
+ ->method('getTableName')
+ ->with('catalogrule_product')
+ ->willReturn('catalogrule_product');
+ $this->resourceMock->expects(self::at(2))
+ ->method('getTableName')
+ ->with('catalogrule_product_replica')
+ ->willReturn('catalogrule_product_replica');
+ }
+
+ /**
+ * @param array $websiteId
+ * @param array $productIds
+ * @param array $customerGroupIds
+ */
+ private function prepareRuleMock(array $websiteId, array $productIds, array $customerGroupIds): void
+ {
+ $this->ruleMock->expects(self::once())->method('getIsActive')->willReturn(true);
+ $this->ruleMock->expects(self::exactly(2))->method('getWebsiteIds')->willReturn($websiteId);
+ $this->ruleMock->expects(self::once())->method('getMatchingProductIds')->willReturn($productIds);
+ $this->ruleMock->expects(self::once())->method('getId')->willReturn(100);
+ $this->ruleMock->expects(self::once())->method('getCustomerGroupIds')->willReturn($customerGroupIds);
+ $this->ruleMock->expects(self::atLeastOnce())->method('getFromDate')->willReturn('2017-06-21');
+ $this->ruleMock->expects(self::atLeastOnce())->method('getToDate')->willReturn('2017-06-30');
+ $this->ruleMock->expects(self::once())->method('getSortOrder')->willReturn(1);
+ $this->ruleMock->expects(self::once())->method('getSimpleAction')->willReturn('simple_action');
+ $this->ruleMock->expects(self::once())->method('getDiscountAmount')->willReturn(43);
+ $this->ruleMock->expects(self::once())->method('getStopRulesProcessing')->willReturn(true);
}
}
diff --git a/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php b/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php
index e6d2fc78b2aeb..0649cfc6112cc 100644
--- a/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php
+++ b/app/code/Magento/CatalogRule/Test/Unit/Model/RuleTest.php
@@ -9,6 +9,7 @@
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
+use Magento\CatalogRule\Api\Data\RuleInterface;
use Magento\CatalogRule\Model\Indexer\Rule\RuleProductProcessor;
use Magento\CatalogRule\Model\Rule;
use Magento\CatalogRule\Model\Rule\Condition\CombineFactory;
@@ -31,46 +32,60 @@
*/
class RuleTest extends TestCase
{
- /** @var Rule */
- protected $rule;
+ /**
+ * @var Rule
+ */
+ private $rule;
- /** @var ObjectManager */
+ /**
+ * @var ObjectManager
+ */
private $objectManager;
- /** @var StoreManagerInterface|MockObject */
- protected $storeManager;
+ /**
+ * @var StoreManagerInterface|MockObject
+ */
+ private $storeManager;
- /** @var MockObject */
- protected $combineFactory;
+ /**
+ * @var CombineFactory|MockObject
+ */
+ private $combineFactory;
- /** @var Store|MockObject */
- protected $storeModel;
+ /**
+ * @var Store|MockObject
+ */
+ private $storeModel;
- /** @var Website|MockObject */
- protected $websiteModel;
+ /**
+ * @var Website|MockObject
+ */
+ private $websiteModel;
- /** @var Combine|MockObject */
- protected $condition;
+ /**
+ * @var Combine|MockObject
+ */
+ private $condition;
/**
* @var RuleProductProcessor|MockObject
*/
- protected $_ruleProductProcessor;
+ private $_ruleProductProcessor;
/**
* @var CollectionFactory|MockObject
*/
- protected $_productCollectionFactory;
+ private $_productCollectionFactory;
/**
* @var Iterator|MockObject
*/
- protected $_resourceIterator;
+ private $_resourceIterator;
/**
* @var Product|MockObject
*/
- protected $productModel;
+ private $productModel;
/**
* Set up before test
@@ -85,7 +100,7 @@ protected function setUp(): void
$this->combineFactory = $this->createPartialMock(
CombineFactory::class,
[
- 'create'
+ 'create',
]
);
$this->productModel = $this->createPartialMock(
@@ -93,7 +108,7 @@ protected function setUp(): void
[
'__wakeup',
'getId',
- 'setData'
+ 'setData',
]
);
$this->condition = $this->getMockBuilder(Combine::class)
@@ -106,7 +121,7 @@ protected function setUp(): void
[
'__wakeup',
'getId',
- 'getDefaultStore'
+ 'getDefaultStore',
]
);
$this->_ruleProductProcessor = $this->createMock(
@@ -192,7 +207,7 @@ public function testCallbackValidateProduct($validate)
'has_options' => '0',
'required_options' => '0',
'created_at' => '2014-06-25 13:14:30',
- 'updated_at' => '2014-06-25 14:37:15'
+ 'updated_at' => '2014-06-25 14:37:15',
];
$this->storeManager->expects($this->any())->method('getWebsites')->with(false)
->willReturn([$this->websiteModel, $this->websiteModel]);
@@ -263,14 +278,14 @@ public function validateDataDataProvider()
'simple_action' => 'by_fixed',
'discount_amount' => '123',
],
- true
+ true,
],
[
[
'simple_action' => 'by_percent',
'discount_amount' => '9,99',
],
- true
+ true,
],
[
[
@@ -279,7 +294,7 @@ public function validateDataDataProvider()
],
[
'Percentage discount should be between 0 and 100.',
- ]
+ ],
],
[
[
@@ -288,7 +303,7 @@ public function validateDataDataProvider()
],
[
'Percentage discount should be between 0 and 100.',
- ]
+ ],
],
[
[
@@ -297,7 +312,7 @@ public function validateDataDataProvider()
],
[
'Discount value should be 0 or greater.',
- ]
+ ],
],
[
[
@@ -306,7 +321,7 @@ public function validateDataDataProvider()
],
[
'Unknown action.',
- ]
+ ],
],
];
}
@@ -325,33 +340,48 @@ public function testAfterDelete()
}
/**
- * Test after update action for inactive rule
+ * Test after update action for active and deactivated rule.
*
+ * @dataProvider afterUpdateDataProvider
+ * @param int $active
* @return void
*/
- public function testAfterUpdateInactive()
+ public function testAfterUpdate(int $active)
{
$this->rule->isObjectNew(false);
- $this->rule->setIsActive(0);
- $this->_ruleProductProcessor->expects($this->never())->method('getIndexer');
+ $this->rule->setIsActive($active);
+ $this->rule->setOrigData(RuleInterface::IS_ACTIVE, 1);
+ $indexer = $this->getMockForAbstractClass(IndexerInterface::class);
+ $indexer->expects($this->once())->method('invalidate');
+ $this->_ruleProductProcessor->expects($this->once())->method('getIndexer')->willReturn($indexer);
$this->rule->afterSave();
}
/**
- * Test after update action for active rule
+ * Test after update action for inactive rule.
*
* @return void
*/
- public function testAfterUpdateActive()
+ public function testAfterUpdateInactiveRule()
{
$this->rule->isObjectNew(false);
- $this->rule->setIsActive(1);
- $indexer = $this->getMockForAbstractClass(IndexerInterface::class);
- $indexer->expects($this->once())->method('invalidate');
- $this->_ruleProductProcessor->expects($this->once())->method('getIndexer')->willReturn($indexer);
+ $this->rule->setIsActive(0);
+ $this->rule->setOrigData(RuleInterface::IS_ACTIVE, 0);
+ $this->_ruleProductProcessor->expects($this->never())->method('getIndexer');
$this->rule->afterSave();
}
+ /**
+ * @return array
+ */
+ public function afterUpdateDataProvider(): array
+ {
+ return [
+ ['active' => 0],
+ ['active' => 1],
+ ];
+ }
+
/**
* Test isRuleBehaviorChanged action
*
diff --git a/app/code/Magento/CatalogRule/etc/extension_attributes.xml b/app/code/Magento/CatalogRule/etc/extension_attributes.xml
new file mode 100644
index 0000000000000..78568d8961ade
--- /dev/null
+++ b/app/code/Magento/CatalogRule/etc/extension_attributes.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
index 59e3c4668e8a4..64bc5efe8e385 100644
--- a/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
+++ b/app/code/Magento/CatalogRule/view/adminhtml/ui_component/catalog_rule_form.xml
@@ -133,7 +133,7 @@
number
- https://docs.magento.com/m2/ce/user_guide/configuration/scope.html
+ https://docs.magento.com/user-guide/configuration/scope.html
What is this?
Websites
diff --git a/app/code/Magento/CatalogSearch/Model/Attribute/SearchWeight.php b/app/code/Magento/CatalogSearch/Model/Attribute/SearchWeight.php
index fa42cadb8a2fa..840eb147a86fd 100644
--- a/app/code/Magento/CatalogSearch/Model/Attribute/SearchWeight.php
+++ b/app/code/Magento/CatalogSearch/Model/Attribute/SearchWeight.php
@@ -15,6 +15,11 @@
*/
class SearchWeight
{
+ /**
+ * @var \Magento\Framework\Search\Request\Config
+ */
+ private $config;
+
/**
* @param \Magento\Framework\Search\Request\Config $config
*/
diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
index 849137b57b52c..603a5c8a4793d 100644
--- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
+++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext.php
@@ -111,7 +111,7 @@ class Fulltext implements
* @param array $data
* @param ProcessManager|null $processManager
* @param int|null $batchSize
- * @param DeploymentConfig $deploymentConfig
+ * @param DeploymentConfig|null $deploymentConfig
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function __construct(
@@ -182,7 +182,7 @@ public function executeByDimensions(array $dimensions, \Traversable $entityIds =
$i = 0;
$this->batchSize = $this->deploymentConfig->get(
- self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . self::INDEXER_ID
+ self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . self::INDEXER_ID . '/partial_reindex'
) ?? $this->batchSize;
foreach ($entityIds as $entityId) {
diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php
index 3ce8a96fb5070..59bdd0e47327a 100644
--- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php
+++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Action/Full.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\CatalogSearch\Model\Indexer\Fulltext;
+use Magento\Framework\App\DeploymentConfig;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
@@ -199,6 +200,19 @@ class Full
private $batchSize;
/**
+ * @var DeploymentConfig|null
+ */
+ private $deploymentConfig;
+
+ /**
+ * Deployment config path
+ *
+ * @var string
+ */
+ private const DEPLOYMENT_CONFIG_INDEXER_BATCHES = 'indexer/batch_size/';
+
+ /**
+ * Full constructor.
* @param ResourceConnection $resource
* @param \Magento\Catalog\Model\Product\Type $catalogProductType
* @param \Magento\Eav\Model\Config $eavConfig
@@ -216,10 +230,12 @@ class Full
* @param \Magento\CatalogSearch\Model\ResourceModel\Fulltext $fulltextResource
* @param \Magento\Framework\Search\Request\DimensionFactory $dimensionFactory
* @param \Magento\Framework\Indexer\ConfigInterface $indexerConfig
- * @param mixed $indexIteratorFactory
+ * @param null $indexIteratorFactory
* @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool
* @param DataProvider|null $dataProvider
* @param int $batchSize
+ * @param DeploymentConfig|null $deploymentConfig
+ *
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
@@ -244,7 +260,8 @@ public function __construct(
$indexIteratorFactory = null,
\Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
DataProvider $dataProvider = null,
- $batchSize = 500
+ $batchSize = 500,
+ ?DeploymentConfig $deploymentConfig = null
) {
$this->resource = $resource;
$this->connection = $resource->getConnection();
@@ -268,6 +285,7 @@ public function __construct(
->get(\Magento\Framework\EntityManager\MetadataPool::class);
$this->dataProvider = $dataProvider ?: ObjectManager::getInstance()->get(DataProvider::class);
$this->batchSize = $batchSize;
+ $this->deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->get(DeploymentConfig::class);
}
/**
@@ -360,6 +378,9 @@ public function rebuildStoreIndex($storeId, $productIds = null)
];
$lastProductId = 0;
+ $this->batchSize = $this->deploymentConfig->get(
+ self::DEPLOYMENT_CONFIG_INDEXER_BATCHES . Fulltext::INDEXER_ID . '/mysql_get'
+ ) ?? $this->batchSize;
$products = $this->dataProvider
->getSearchableProducts($storeId, $staticFields, $productIds, $lastProductId, $this->batchSize);
while (count($products) > 0) {
diff --git a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Model/Plugin/Category.php b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Model/Plugin/Category.php
index ed841996ea07b..5c5b4591f8a55 100644
--- a/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Model/Plugin/Category.php
+++ b/app/code/Magento/CatalogSearch/Model/Indexer/Fulltext/Model/Plugin/Category.php
@@ -9,6 +9,7 @@
use Magento\Catalog\Model\ResourceModel\Category as Resource;
use Magento\CatalogSearch\Model\Indexer\Fulltext\Processor;
+use Magento\Framework\DataObject;
/**
* Perform indexer invalidation after a category delete.
@@ -33,12 +34,15 @@ public function __construct(Processor $fulltextIndexerProcessor)
*
* @param Resource $subjectCategory
* @param Resource $resultCategory
+ * @param DataObject $object
* @return Resource
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function afterDelete(Resource $subjectCategory, Resource $resultCategory) : Resource
+ public function afterDelete(Resource $subjectCategory, Resource $resultCategory, DataObject $object) : Resource
{
- $this->fulltextIndexerProcessor->markIndexerAsInvalid();
+ if ($object->getIsActive() || $object->getDeletedChildrenIds()) {
+ $this->fulltextIndexerProcessor->markIndexerAsInvalid();
+ }
return $resultCategory;
}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php
index d37f0f8a5153b..7e9be408a3850 100644
--- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Search/Collection.php
@@ -17,6 +17,11 @@
class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection implements
\Magento\Search\Model\SearchCollectionInterface
{
+ /**
+ * @var array
+ */
+ private $indexUsageEnforcements;
+
/**
* Attribute collection
*
@@ -61,6 +66,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param \Magento\Customer\Api\GroupManagementInterface $groupManagement
* @param \Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeCollectionFactory
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
+ * @param array $indexUsageEnforcements
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -84,7 +90,8 @@ public function __construct(
\Magento\Framework\Stdlib\DateTime $dateTime,
\Magento\Customer\Api\GroupManagementInterface $groupManagement,
\Magento\Catalog\Model\ResourceModel\Product\Attribute\CollectionFactory $attributeCollectionFactory,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ array $indexUsageEnforcements = []
) {
$this->_attributeCollectionFactory = $attributeCollectionFactory;
parent::__construct(
@@ -109,6 +116,7 @@ public function __construct(
$groupManagement,
$connection
);
+ $this->indexUsageEnforcements = $indexUsageEnforcements;
}
/**
@@ -197,6 +205,35 @@ protected function _hasAttributeOptionsAndSearchable($attribute)
return false;
}
+ /**
+ * Prepare table names for the index enforcements
+ *
+ * @return array
+ */
+ private function prepareIndexEnforcements() : array
+ {
+ $result = [];
+ foreach ($this->indexUsageEnforcements as $table => $index) {
+ $table = $this->getTable($table);
+ if ($this->isIndexExists($table, $index)) {
+ $result[$table] = $index;
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Check if index exists in the table
+ *
+ * @param string $table
+ * @param string $index
+ * @return bool
+ */
+ private function isIndexExists(string $table, string $index) : bool
+ {
+ return array_key_exists($index, $this->_conn->getIndexList($table));
+ }
+
/**
* Retrieve SQL for search entities
*
@@ -208,6 +245,7 @@ protected function _getSearchEntityIdsSql($query, $searchOnlyInCurrentStore = tr
{
$tables = [];
$selects = [];
+ $preparedIndexEnforcements = $this->prepareIndexEnforcements();
$likeOptions = ['position' => 'any'];
@@ -249,23 +287,56 @@ protected function _getSearchEntityIdsSql($query, $searchOnlyInCurrentStore = tr
$ifValueId = $this->getConnection()->getIfNullSql('t2.value', 't1.value');
foreach ($tables as $table => $attributeIds) {
- $selects[] = $this->getConnection()->select()->from(
- ['t1' => $table],
- $linkField
- )->joinLeft(
- ['t2' => $table],
- $joinCondition,
- []
- )->where(
- 't1.attribute_id IN (?)',
- $attributeIds,
- \Zend_Db::INT_TYPE
- )->where(
- 't1.store_id = ?',
- 0
- )->where(
- $this->_resourceHelper->getCILike($ifValueId, $this->_searchQuery, $likeOptions)
- );
+ if (!empty($preparedIndexEnforcements[$table])) {
+ $condition1 = $this->_conn->quoteInto(
+ '`t1`.`attribute_id` IN (?)',
+ $attributeIds,
+ \Zend_Db::INT_TYPE
+ );
+ $condition2 = '`t1`.`store_id` = 0';
+ $quotedField = $this->_conn->quoteIdentifier($ifValueId);
+ $condition3 = $this->_conn->quoteInto(
+ $quotedField . ' LIKE ?',
+ $this->_resourceHelper->addLikeEscape($this->_searchQuery, $likeOptions)
+ );
+
+ //force index statement not implemented in framework
+ // phpcs:ignore Magento2.SQL.RawQuery
+ $select = sprintf(
+ 'SELECT `t1`.`%s` FROM `%s` AS `t1` FORCE INDEX(%s)
+ LEFT JOIN `%s` AS `t2` FORCE INDEX(%s)
+ ON %s WHERE %s AND %s AND (%s)',
+ $linkField,
+ $table,
+ $preparedIndexEnforcements[$table],
+ $table,
+ $preparedIndexEnforcements[$table],
+ $joinCondition,
+ $condition1,
+ $condition2,
+ $condition3
+ );
+ } else {
+ $select = $this->getConnection()->select();
+ $select->from(
+ ['t1' => $table],
+ $linkField
+ )->joinLeft(
+ ['t2' => $table],
+ $joinCondition,
+ []
+ )->where(
+ 't1.attribute_id IN (?)',
+ $attributeIds,
+ \Zend_Db::INT_TYPE
+ )->where(
+ 't1.store_id = ?',
+ 0
+ )->where(
+ $this->_resourceHelper->getCILike($ifValueId, $this->_searchQuery, $likeOptions)
+ );
+ }
+ $selects[] = $select;
}
$sql = $this->_getSearchInOptionSql($query);
diff --git a/app/code/Magento/CatalogSearch/Model/Search/ReaderPlugin.php b/app/code/Magento/CatalogSearch/Model/Search/ReaderPlugin.php
index 2f6a402b20406..4adb9c2299e95 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/ReaderPlugin.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/ReaderPlugin.php
@@ -5,6 +5,9 @@
*/
namespace Magento\CatalogSearch\Model\Search;
+use Magento\CatalogSearch\Model\Search\Request\ModifierInterface;
+use Magento\Framework\Config\ReaderInterface;
+
/**
* @deprecated 101.0.0
* @see \Magento\ElasticSearch
@@ -12,34 +15,34 @@
class ReaderPlugin
{
/**
- * @var \Magento\CatalogSearch\Model\Search\RequestGenerator
+ * @var ModifierInterface
*/
- private $requestGenerator;
+ private $requestModifier;
/**
- * @param \Magento\CatalogSearch\Model\Search\RequestGenerator $requestGenerator
+ * @param ModifierInterface $requestModifier
*/
public function __construct(
- \Magento\CatalogSearch\Model\Search\RequestGenerator $requestGenerator
+ ModifierInterface $requestModifier
) {
- $this->requestGenerator = $requestGenerator;
+ $this->requestModifier = $requestModifier;
}
/**
* Merge reader's value with generated
*
- * @param \Magento\Framework\Config\ReaderInterface $subject
+ * @param ReaderInterface $subject
* @param array $result
* @param string|null $scope
* @return array
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterRead(
- \Magento\Framework\Config\ReaderInterface $subject,
+ ReaderInterface $subject,
array $result,
$scope = null
) {
- $result = array_merge_recursive($result, $this->requestGenerator->generate());
+ $result = $this->requestModifier->modify($result);
return $result;
}
}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/Request/MatchQueriesModifier.php b/app/code/Magento/CatalogSearch/Model/Search/Request/MatchQueriesModifier.php
new file mode 100644
index 0000000000000..8d1675884c3ba
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/Request/MatchQueriesModifier.php
@@ -0,0 +1,62 @@
+queries = $queries;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function modify(array $requests): array
+ {
+ foreach ($requests as &$request) {
+ foreach ($this->queries as $query => $fields) {
+ if (!empty($request[self::NODE_QUERIES][$query][self::NODE_MATCH])) {
+ foreach ($request[self::NODE_QUERIES][$query][self::NODE_MATCH] as $index => $match) {
+ $field = $match[self::NODE_MATCH_ATTRIBUTE_FIELD] ?? null;
+ if ($field !== null && isset($fields[$field])) {
+ $request[self::NODE_QUERIES][$query][self::NODE_MATCH][$index] += $fields[$field];
+ }
+ }
+ }
+ }
+ }
+ return $requests;
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/Request/ModifierComposite.php b/app/code/Magento/CatalogSearch/Model/Search/Request/ModifierComposite.php
new file mode 100644
index 0000000000000..6290b5bd7ce48
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/Request/ModifierComposite.php
@@ -0,0 +1,46 @@
+modifiers = $modifiers;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function modify(array $requests): array
+ {
+ foreach ($this->modifiers as $modifier) {
+ $requests = $modifier->modify($requests);
+ }
+ return $requests;
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/Request/ModifierInterface.php b/app/code/Magento/CatalogSearch/Model/Search/Request/ModifierInterface.php
new file mode 100644
index 0000000000000..68421d6cb1d4f
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/Request/ModifierInterface.php
@@ -0,0 +1,22 @@
+collectionFactory = $collectionFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function modify(array $requests): array
+ {
+ $attributes = $this->getSearchableAttributes();
+ foreach ($requests as $code => $request) {
+ $matches = $request['queries']['partial_search']['match'] ?? [];
+ if ($matches) {
+ foreach ($matches as $index => $match) {
+ $field = $match['field'] ?? null;
+ if ($field && $field !== '*' && !isset($attributes[$field])) {
+ unset($matches[$index]);
+ }
+ }
+ $requests[$code]['queries']['partial_search']['match'] = array_values($matches);
+ }
+ }
+ return $requests;
+ }
+
+ /**
+ * Retrieve searchable attributes
+ *
+ * @return Attribute[]
+ */
+ private function getSearchableAttributes(): array
+ {
+ $attributes = [];
+ /** @var Collection $collection */
+ $collection = $this->collectionFactory->create();
+ $collection->addFieldToFilter(
+ ['is_searchable', 'is_visible_in_advanced_search', 'is_filterable', 'is_filterable_in_search'],
+ [1, 1, [1, 2], 1]
+ );
+
+ /** @var Attribute $attribute */
+ foreach ($collection->getItems() as $attribute) {
+ $attributes[$attribute->getAttributeCode()] = $attribute;
+ }
+
+ return $attributes;
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/Request/SearchModifier.php b/app/code/Magento/CatalogSearch/Model/Search/Request/SearchModifier.php
new file mode 100644
index 0000000000000..54082dd28ec04
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/Request/SearchModifier.php
@@ -0,0 +1,39 @@
+requestGenerator = $requestGenerator;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function modify(array $requests): array
+ {
+ $requests = array_merge_recursive($requests, $this->requestGenerator->generate());
+ return $requests;
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php
index caa49d0f0c4a4..94922c9ce772d 100644
--- a/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php
+++ b/app/code/Magento/CatalogSearch/Model/Search/RequestGenerator.php
@@ -186,6 +186,7 @@ private function generateAdvancedSearchRequest()
[
'field' => $attribute->getAttributeCode(),
'boost' => $attribute->getSearchWeight() ?: 1,
+ 'matchCondition' => 'match_phrase_prefix',
],
],
];
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AssertStorefrontNoResultsMessageOnSearchPageActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AssertStorefrontNoResultsMessageOnSearchPageActionGroup.xml
new file mode 100644
index 0000000000000..6001708b41a10
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AssertStorefrontNoResultsMessageOnSearchPageActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ Check if search returned no results
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AssertStorefrontProductNotOnSearchPageActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AssertStorefrontProductNotOnSearchPageActionGroup.xml
new file mode 100644
index 0000000000000..c35be6f46e7da
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AssertStorefrontProductNotOnSearchPageActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontFillFormAdvancedSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontFillFormAdvancedSearchActionGroup.xml
index 1afdb6e5e46fa..81a283b9fcf3a 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontFillFormAdvancedSearchActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontFillFormAdvancedSearchActionGroup.xml
@@ -23,6 +23,7 @@
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByDescriptionTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByDescriptionTest.xml
index c02ef4957ad3d..395cfd1870130 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByDescriptionTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByDescriptionTest.xml
@@ -12,9 +12,8 @@
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByNameTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByNameTest.xml
index 0c8e192f9366e..99448fd730d99 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByNameTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByNameTest.xml
@@ -13,9 +13,8 @@
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByPriceTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByPriceTest.xml
index 99c09b5ba93a5..af70c3ba1c3ad 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByPriceTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByPriceTest.xml
@@ -13,9 +13,8 @@
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByShortDescriptionTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByShortDescriptionTest.xml
index 1e18c5ea4d0a9..38399f2729d3a 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByShortDescriptionTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductByShortDescriptionTest.xml
@@ -13,9 +13,8 @@
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductBySkuTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductBySkuTest.xml
index 34e0a73e91fe0..e12505299c76b 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductBySkuTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdvanceCatalogSearchSimpleProductTest/AdvanceCatalogSearchSimpleProductBySkuTest.xml
@@ -13,9 +13,8 @@
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
index 28eb53542ad99..f787b160e7689 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/EndToEndB2CLoggedInUserTest.xml
@@ -23,7 +23,7 @@
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartBundleDynamicTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartBundleDynamicTest.xml
index 09f7ee455ebb5..a6cec94803738 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartBundleDynamicTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartBundleDynamicTest.xml
@@ -43,10 +43,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartBundleFixedTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartBundleFixedTest.xml
index 98d1b3412360c..e587840f3bffc 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartBundleFixedTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartBundleFixedTest.xml
@@ -54,10 +54,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartConfigurableTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartConfigurableTest.xml
index 3298eff34759b..1a25ee6bc57ad 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartConfigurableTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartConfigurableTest.xml
@@ -25,10 +25,7 @@
-
-
-
-
+
@@ -38,6 +35,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartDownloadableTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartDownloadableTest.xml
index 85e3c46654502..2201b17c31a0b 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartDownloadableTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartDownloadableTest.xml
@@ -27,10 +27,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartGroupedTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartGroupedTest.xml
index 3488a63140809..efe1018c29e55 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartGroupedTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartGroupedTest.xml
@@ -27,10 +27,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartTest.xml
index 26f4cd77b60bc..b0137f145bff2 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartTest.xml
@@ -23,10 +23,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartVirtualTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartVirtualTest.xml
index 9277baba94aa8..b2e86ae896ab3 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartVirtualTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchAndAddToCartVirtualTest.xml
@@ -23,10 +23,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchEmptyResultsTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchEmptyResultsTest.xml
index a6654db91effb..02f6ffd5db8e7 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchEmptyResultsTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchEmptyResultsTest.xml
@@ -24,10 +24,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductBySkuTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductBySkuTest.xml
index 83436ebb44c6d..4bde337ab78dc 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductBySkuTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/SearchEntityResultsTest/QuickSearchProductBySkuTest.xml
@@ -23,10 +23,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchByPartialNameTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchByPartialNameTest.xml
index d8f8924c4db0b..d85b17348b0bc 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchByPartialNameTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchByPartialNameTest.xml
@@ -10,17 +10,15 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/testSchema.xsd">
+
-
+
-
-
-
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchEntitySimpleProductTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchEntitySimpleProductTest.xml
index 14ae988e6ce79..1e27f9f6d05af 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchEntitySimpleProductTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontAdvancedSearchEntitySimpleProductTest.xml
@@ -31,10 +31,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchNotSearchableTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchNotSearchableTest.xml
new file mode 100644
index 0000000000000..b8ca2a16e7515
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchNotSearchableTest.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchStemmingTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchStemmingTest.xml
new file mode 100644
index 0000000000000..b1528b169cbdf
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontPartialWordQuickSearchStemmingTest.xml
@@ -0,0 +1,162 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 5127SS YALE JUNIOR KNOB ENTRANCE SET 5 PINS SATIN STAI
+
+
+
+
+ 5127AC YALE JUNIOR KNOB ENTRANCE SET 5 PINS ANTIQUE CO
+
+
+
+
+ 5127AB YALE JUNIOR KNOB ENTRANCE SET 5 PINS ANTIQUE BRASS
+
+
+
+
+ 5127SS-YALE
+
+
+
+
+ 5127AC-CO
+
+
+
+
+ 5127AB-BRASS
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchConfigurableChildrenTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchConfigurableChildrenTest.xml
index 67e8bc6bf183c..fe30acc174249 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchConfigurableChildrenTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontQuickSearchConfigurableChildrenTest.xml
@@ -72,10 +72,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml
index 26280ed67d183..634ee57f17237 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/StorefrontUpdateSearchTermEntityTest.xml
@@ -25,10 +25,7 @@
-
-
-
-
+
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/ReaderPluginTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/ReaderPluginTest.php
index 5f342f1735dee..3b19c98aca3ca 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/ReaderPluginTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/ReaderPluginTest.php
@@ -8,7 +8,7 @@
namespace Magento\CatalogSearch\Test\Unit\Model\Search;
use Magento\CatalogSearch\Model\Search\ReaderPlugin;
-use Magento\CatalogSearch\Model\Search\RequestGenerator;
+use Magento\CatalogSearch\Model\Search\Request\ModifierInterface;
use Magento\Framework\Config\ReaderInterface;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use PHPUnit\Framework\MockObject\MockObject;
@@ -16,8 +16,8 @@
class ReaderPluginTest extends TestCase
{
- /** @var RequestGenerator|MockObject */
- protected $requestGenerator;
+ /** @var ModifierInterface|MockObject */
+ protected $requestModifier;
/** @var ObjectManager */
protected $objectManagerHelper;
@@ -27,22 +27,20 @@ class ReaderPluginTest extends TestCase
protected function setUp(): void
{
- $this->requestGenerator = $this->getMockBuilder(RequestGenerator::class)
+ $this->requestModifier = $this->getMockBuilder(ModifierInterface::class)
->disableOriginalConstructor()
->getMock();
$this->objectManagerHelper = new ObjectManager($this);
- $this->object = $this->objectManagerHelper->getObject(
- ReaderPlugin::class,
- ['requestGenerator' => $this->requestGenerator]
- );
+ $this->object = new ReaderPlugin($this->requestModifier);
}
public function testAfterRead()
{
$readerConfig = ['test' => 'b', 'd' => 'e'];
- $this->requestGenerator->expects($this->once())
- ->method('generate')
+ $this->requestModifier->expects($this->once())
+ ->method('modify')
+ ->with($readerConfig)
->willReturn(['test' => 'a']);
$result = $this->object->afterRead(
@@ -53,6 +51,6 @@ public function testAfterRead()
null
);
- $this->assertEquals(['test' => ['b', 'a'], 'd' => 'e'], $result);
+ $this->assertEquals(['test' => 'a'], $result);
}
}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/MatchQueriesModifierTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/MatchQueriesModifierTest.php
new file mode 100644
index 0000000000000..00576f9ad029c
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/MatchQueriesModifierTest.php
@@ -0,0 +1,130 @@
+assertEquals($expected, $model->modify($requests));
+ }
+
+ /**
+ * @return array
+ */
+ public function modifyDataProvider(): array
+ {
+ return [
+ [
+ [
+ 'partial_search' => [
+ 'name' => [
+ 'analyzer' => 'standard',
+ 'max_expansions' => 20,
+ ]
+ ],
+ ],
+ [
+ 'search_1' => [
+ 'filters' => [
+ 'category_filter' => [
+ 'name' => 'category_filter',
+ 'field' => 'category_ids',
+ 'value' => '$category_ids$',
+ ]
+ ],
+ 'queries' => [
+ 'partial_search' => [
+ 'name' => 'partial_search',
+ 'value' => '$search_term$',
+ 'match' => [
+ [
+ 'field' => '*'
+ ],
+ [
+ 'field' => 'sku',
+ 'matchCondition' => 'match_phrase_prefix',
+ ],
+ [
+ 'field' => 'name',
+ 'matchCondition' => 'match_phrase_prefix',
+ ],
+ ]
+ ]
+ ]
+ ],
+ 'search_2' => [
+ 'filters' => [
+ 'category_filter' => [
+ 'name' => 'category_filter',
+ 'field' => 'category_ids',
+ 'value' => '$category_ids$',
+ ]
+ ]
+ ]
+ ],
+ [
+ 'search_1' => [
+ 'filters' => [
+ 'category_filter' => [
+ 'name' => 'category_filter',
+ 'field' => 'category_ids',
+ 'value' => '$category_ids$',
+ ]
+ ],
+ 'queries' => [
+ 'partial_search' => [
+ 'name' => 'partial_search',
+ 'value' => '$search_term$',
+ 'match' => [
+ [
+ 'field' => '*'
+ ],
+ [
+ 'field' => 'sku',
+ 'matchCondition' => 'match_phrase_prefix',
+ ],
+ [
+ 'field' => 'name',
+ 'matchCondition' => 'match_phrase_prefix',
+ 'analyzer' => 'standard',
+ 'max_expansions' => 20,
+ ],
+ ]
+ ]
+ ]
+ ],
+ 'search_2' => [
+ 'filters' => [
+ 'category_filter' => [
+ 'name' => 'category_filter',
+ 'field' => 'category_ids',
+ 'value' => '$category_ids$',
+ ]
+ ]
+ ]
+ ]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/ModifierCompositeTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/ModifierCompositeTest.php
new file mode 100644
index 0000000000000..936ad83b8a630
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/ModifierCompositeTest.php
@@ -0,0 +1,86 @@
+modifier1 = $this->getMockForAbstractClass(ModifierInterface::class);
+ $this->modifier2 = $this->getMockForAbstractClass(ModifierInterface::class);
+ $this->model = new ModifierComposite(
+ [
+ $this->modifier1,
+ $this->modifier2
+ ]
+ );
+ }
+
+ /**
+ * Test that all modifiers are executed
+ */
+ public function testModify(): void
+ {
+ $requests = ['a', 'b', 'c'];
+ $this->modifier1->expects($this->once())
+ ->method('modify')
+ ->with($requests)
+ ->willReturn(['a', 'b', 'c', 'd']);
+
+ $this->modifier2->expects($this->once())
+ ->method('modify')
+ ->with(['a', 'b', 'c', 'd'])
+ ->willReturn(['a', 'c', 'd']);
+
+ $this->assertEquals(['a', 'c', 'd'], $this->model->modify($requests));
+ }
+
+ /**
+ * Test that exception is thrown if modifier is not instance of ModifierInterface
+ */
+ public function testInvalidModifier(): void
+ {
+ $exception = new \InvalidArgumentException(
+ 'Magento\Framework\DataObject must implement Magento\CatalogSearch\Model\Search\Request\ModifierInterface'
+ );
+ $this->expectExceptionObject($exception);
+ $this->model = new ModifierComposite(
+ [
+ new DataObject()
+ ]
+ );
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/PartialSearchModifierTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/PartialSearchModifierTest.php
new file mode 100644
index 0000000000000..2fabec670a57e
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/PartialSearchModifierTest.php
@@ -0,0 +1,157 @@
+createMock(CollectionFactory::class);
+ $this->collection = $this->getMockBuilder(Collection::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['load', 'addFieldToFilter'])
+ ->getMock();
+ $collectionFactory->method('create')
+ ->willReturn($this->collection);
+ $this->model = new PartialSearchModifier($collectionFactory);
+ }
+
+ /**
+ * Test that not searchable attributes are removed from the request
+ *
+ * @param array $attributes
+ * @param array $requests
+ * @param array $expected
+ * @dataProvider modifyDataProvider
+ */
+ public function testModify(array $attributes, array $requests, array $expected): void
+ {
+ $items = [];
+ foreach ($attributes as $attribute) {
+ $item = $this->getMockForAbstractClass(\Magento\Eav\Api\Data\AttributeInterface::class);
+ $item->method('getAttributeCode')
+ ->willReturn($attribute);
+ $items[] = $item;
+ }
+ $reflectionProperty = new \ReflectionProperty($this->collection, '_items');
+ $reflectionProperty->setAccessible(true);
+ $reflectionProperty->setValue($this->collection, $items);
+ $this->assertEquals($expected, $this->model->modify($requests));
+ }
+
+ /**
+ * @return array
+ */
+ public function modifyDataProvider(): array
+ {
+ return [
+ [
+ [
+ 'name',
+ ],
+ [
+ 'search_1' => [
+ 'filters' => [
+ 'category_filter' => [
+ 'name' => 'category_filter',
+ 'field' => 'category_ids',
+ 'value' => '$category_ids$',
+ ]
+ ],
+ 'queries' => [
+ 'partial_search' => [
+ 'name' => 'partial_search',
+ 'value' => '$search_term$',
+ 'match' => [
+ [
+ 'field' => '*'
+ ],
+ [
+ 'field' => 'sku',
+ 'matchCondition' => 'match_phrase_prefix',
+ ],
+ [
+ 'field' => 'name',
+ 'matchCondition' => 'match_phrase_prefix',
+ ],
+ ]
+ ]
+ ]
+ ],
+ 'search_2' => [
+ 'filters' => [
+ 'category_filter' => [
+ 'name' => 'category_filter',
+ 'field' => 'category_ids',
+ 'value' => '$category_ids$',
+ ]
+ ]
+ ]
+ ],
+ [
+ 'search_1' => [
+ 'filters' => [
+ 'category_filter' => [
+ 'name' => 'category_filter',
+ 'field' => 'category_ids',
+ 'value' => '$category_ids$',
+ ]
+ ],
+ 'queries' => [
+ 'partial_search' => [
+ 'name' => 'partial_search',
+ 'value' => '$search_term$',
+ 'match' => [
+ [
+ 'field' => '*'
+ ],
+ [
+ 'field' => 'name',
+ 'matchCondition' => 'match_phrase_prefix',
+ ],
+ ]
+ ]
+ ]
+ ],
+ 'search_2' => [
+ 'filters' => [
+ 'category_filter' => [
+ 'name' => 'category_filter',
+ 'field' => 'category_ids',
+ 'value' => '$category_ids$',
+ ]
+ ]
+ ]
+ ]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/SearchModifierTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/SearchModifierTest.php
new file mode 100644
index 0000000000000..2c7ce97751f1a
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/Search/Request/SearchModifierTest.php
@@ -0,0 +1,52 @@
+requestGenerator = $this->createMock(RequestGenerator::class);
+ $this->model = new SearchModifier($this->requestGenerator);
+ }
+
+ /**
+ * Test that the result is merged into the initial requests
+ */
+ public function testModifier(): void
+ {
+ $requests = ['a' => ['x', 'y'], 'b' => ['k']];
+ $expected = ['a' => ['x', 'y', 'z'], 'b' => ['k'], 'c' => ['n']];
+ $this->requestGenerator->expects($this->once())
+ ->method('generate')
+ ->willReturn(['a' => ['z'], 'c' => ['n']]);
+ $this->assertEquals($expected, $this->model->modify($requests));
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml
index f8e2a262d73ca..43ba82f047e41 100644
--- a/app/code/Magento/CatalogSearch/etc/di.xml
+++ b/app/code/Magento/CatalogSearch/etc/di.xml
@@ -14,6 +14,7 @@
+
@@ -248,4 +249,27 @@
+
+
+
+ - Magento\CatalogSearch\Model\Search\Request\SearchModifier
+ - Magento\CatalogSearch\Model\Search\Request\PartialSearchModifier
+ - Magento\CatalogSearch\Model\Search\Request\MatchQueriesModifier
+
+
+
+
+
+
+ -
+
-
+
- prefix_search
+
+ -
+
- sku_prefix_search
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/view/frontend/layout/catalogsearch_advanced_result.xml b/app/code/Magento/CatalogSearch/view/frontend/layout/catalogsearch_advanced_result.xml
index 9c20f614076b3..67958af8b54cb 100644
--- a/app/code/Magento/CatalogSearch/view/frontend/layout/catalogsearch_advanced_result.xml
+++ b/app/code/Magento/CatalogSearch/view/frontend/layout/catalogsearch_advanced_result.xml
@@ -36,5 +36,10 @@
+
+
+ Magento\Catalog\ViewModel\Product\OptionsData
+
+