setTemplate('catalog/category/checkboxes/tree.phtml');
+ $this->setTemplate('Magento_Catalog::catalog/category/checkboxes/tree.phtml');
}
/**
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Config/YearRange.php b/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Config/YearRange.php
index 0026e52e039ef..cd6c5021f0cc9 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Config/YearRange.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Form/Renderer/Config/YearRange.php
@@ -32,10 +32,9 @@ protected function _getElementHtml(AbstractElement $element)
$from = $element->setValue(isset($values[0]) ? $values[0] : null)->getElementHtml();
$to = $element->setValue(isset($values[1]) ? $values[1] : null)->getElementHtml();
- return __(
- '
from '
- ) . $from . __(
- '
to '
- ) . $to;
+ return '
' . __('from') . ' '
+ . $from .
+ '
' . __('to') . ' '
+ . $to;
}
}
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Tree/Attribute.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Tree/Attribute.php
index f5e3f94418687..cb0a739b56e4e 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Tree/Attribute.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Set/Main/Tree/Attribute.php
@@ -14,5 +14,5 @@ class Attribute extends \Magento\Backend\Block\Template
/**
* @var string
*/
- protected $_template = 'catalog/product/attribute/set/main/tree/attribute.phtml';
+ protected $_template = 'Magento_Catalog::catalog/product/attribute/set/main/tree/attribute.phtml';
}
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Ajax/Serializer.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Ajax/Serializer.php
index a7129f509316b..3d131a6e08810 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Ajax/Serializer.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Edit/Tab/Ajax/Serializer.php
@@ -41,7 +41,7 @@ public function __construct(
public function _construct()
{
parent::_construct();
- $this->setTemplate('catalog/product/edit/serializer.phtml');
+ $this->setTemplate('Magento_Catalog::catalog/product/edit/serializer.phtml');
return $this;
}
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php
index f30b37877b78f..6cb6f0e526e9f 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Helper/Form/Gallery.php
@@ -102,7 +102,7 @@ public function getElementHtml()
*/
public function getImages()
{
- return $this->registry->registry('current_product')->getData('media_gallery') ?: null;
+ return $this->getDataObject()->getData('media_gallery') ?: null;
}
/**
@@ -117,7 +117,7 @@ public function getContentHtml()
$content->setId($this->getHtmlId() . '_content')->setElement($this);
$content->setFormName($this->formName);
$galleryJs = $content->getJsObjectName();
- $content->getUploader()->getConfig()->setMegiaGallery($galleryJs);
+ $content->getUploader()->getConfig()->setMediaGallery($galleryJs);
return $content->toHtml();
}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Attributes.php b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
index b353e477a056c..cb59d86a74512 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Attributes.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
@@ -67,12 +67,11 @@ public function getProduct()
}
/**
- * $excludeAttr is optional array of attribute codes to
- * exclude them from additional data array
+ * $excludeAttr is optional array of attribute codes to exclude them from additional data array
*
* @param array $excludeAttr
* @return array
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getAdditionalData(array $excludeAttr = [])
{
@@ -80,7 +79,7 @@ public function getAdditionalData(array $excludeAttr = [])
$product = $this->getProduct();
$attributes = $product->getAttributes();
foreach ($attributes as $attribute) {
- if ($attribute->getIsVisibleOnFront() && !in_array($attribute->getAttributeCode(), $excludeAttr)) {
+ if ($this->isVisibleOnFrontend($attribute, $excludeAttr)) {
$value = $attribute->getFrontend()->getValue($product);
if ($value instanceof Phrase) {
@@ -100,4 +99,18 @@ public function getAdditionalData(array $excludeAttr = [])
}
return $data;
}
+
+ /**
+ * Determine if we should display the attribute on the front-end
+ *
+ * @param \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute
+ * @param array $excludeAttr
+ * @return bool
+ */
+ protected function isVisibleOnFrontend(
+ \Magento\Eav\Model\Entity\Attribute\AbstractAttribute $attribute,
+ array $excludeAttr
+ ) {
+ return ($attribute->getIsVisibleOnFront() && !in_array($attribute->getAttributeCode(), $excludeAttr));
+ }
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php
index 8f570e35989cb..0a54475b15f9c 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Delete.php
@@ -44,12 +44,12 @@ public function execute()
$this->_eventManager->dispatch('catalog_controller_category_delete', ['category' => $category]);
$this->_auth->getAuthStorage()->setDeletedPath($category->getPath());
$this->categoryRepository->delete($category);
- $this->messageManager->addSuccess(__('You deleted the category.'));
+ $this->messageManager->addSuccessMessage(__('You deleted the category.'));
} catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
return $resultRedirect->setPath('catalog/*/edit', ['_current' => true]);
} catch (\Exception $e) {
- $this->messageManager->addError(__('Something went wrong while trying to delete the category.'));
+ $this->messageManager->addErrorMessage(__('Something went wrong while trying to delete the category.'));
return $resultRedirect->setPath('catalog/*/edit', ['_current' => true]);
}
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php
index cc03ab870739b..aa9ae88754b6a 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Category/Save.php
@@ -280,7 +280,7 @@ public function imagePreprocessing($data)
continue;
}
- $data[$attributeCode] = false;
+ $data[$attributeCode] = '';
}
return $data;
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute.php
index 775097c5eba1d..ca7652ebb43b5 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute.php
@@ -54,7 +54,7 @@ protected function _validateProducts()
}
if ($error) {
- $this->messageManager->addError($error);
+ $this->messageManager->addErrorMessage($error);
}
return !$error;
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
index 82496446aef9f..0fbf9054ef1bd 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
@@ -192,7 +192,7 @@ public function execute()
$this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
}
- $this->messageManager->addSuccess(
+ $this->messageManager->addSuccessMessage(
__('A total of %1 record(s) were updated.', count($this->attributeHelper->getProductIds()))
);
@@ -205,9 +205,9 @@ public function execute()
$this->_productPriceIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
}
} catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
} catch (\Exception $e) {
- $this->messageManager->addException(
+ $this->messageManager->addExceptionMessage(
$e,
__('Something went wrong while updating the product(s) attributes.')
);
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php
index bb18436be6102..a873f08d082d7 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Validate.php
@@ -68,7 +68,7 @@ public function execute()
$response->setError(true);
$response->setMessage($e->getMessage());
} catch (\Exception $e) {
- $this->messageManager->addException(
+ $this->messageManager->addExceptionMessage(
$e,
__('Something went wrong while updating the product(s) attributes.')
);
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php
index cc5a658a9296d..bef6aee0e2afd 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Delete.php
@@ -21,23 +21,23 @@ public function execute()
// entity type check
$model->load($id);
if ($model->getEntityTypeId() != $this->_entityTypeId) {
- $this->messageManager->addError(__('We can\'t delete the attribute.'));
+ $this->messageManager->addErrorMessage(__('We can\'t delete the attribute.'));
return $resultRedirect->setPath('catalog/*/');
}
try {
$model->delete();
- $this->messageManager->addSuccess(__('You deleted the product attribute.'));
+ $this->messageManager->addSuccessMessage(__('You deleted the product attribute.'));
return $resultRedirect->setPath('catalog/*/');
} catch (\Exception $e) {
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
return $resultRedirect->setPath(
'catalog/*/edit',
['attribute_id' => $this->getRequest()->getParam('attribute_id')]
);
}
}
- $this->messageManager->addError(__('We can\'t find an attribute to delete.'));
+ $this->messageManager->addErrorMessage(__('We can\'t find an attribute to delete.'));
return $resultRedirect->setPath('catalog/*/');
}
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php
index fd97aaa50389e..a99cbdbade181 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Edit.php
@@ -25,14 +25,14 @@ public function execute()
$model->load($id);
if (!$model->getId()) {
- $this->messageManager->addError(__('This attribute no longer exists.'));
+ $this->messageManager->addErrorMessage(__('This attribute no longer exists.'));
$resultRedirect = $this->resultRedirectFactory->create();
return $resultRedirect->setPath('catalog/*/');
}
// entity type check
if ($model->getEntityTypeId() != $this->_entityTypeId) {
- $this->messageManager->addError(__('This attribute cannot be edited.'));
+ $this->messageManager->addErrorMessage(__('This attribute cannot be edited.'));
$resultRedirect = $this->resultRedirectFactory->create();
return $resultRedirect->setPath('catalog/*/');
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
index b61be2b95b960..db452113ada06 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
@@ -92,9 +92,7 @@ public function execute()
$attributeSet->setEntityTypeId($this->_entityTypeId)->load($setName, 'attribute_set_name');
if ($attributeSet->getId()) {
$setName = $this->_objectManager->get(\Magento\Framework\Escaper::class)->escapeHtml($setName);
- $this->messageManager->addError(
- __('A "%1" attribute set name already exists. Create a new name and try again.', $setName)
- );
+ $this->messageManager->addErrorMessage(__('An attribute set named \'%1\' already exists.', $setName));
$layout = $this->layoutFactory->create();
$layout->initMessages();
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Duplicate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Duplicate.php
index 7e8b03a66f603..63e52eead064c 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Duplicate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Duplicate.php
@@ -43,11 +43,11 @@ public function execute()
$product = $this->productBuilder->build($this->getRequest());
try {
$newProduct = $this->productCopier->copy($product);
- $this->messageManager->addSuccess(__('You duplicated the product.'));
+ $this->messageManager->addSuccessMessage(__('You duplicated the product.'));
$resultRedirect->setPath('catalog/*/edit', ['_current' => true, 'id' => $newProduct->getId()]);
} catch (\Exception $e) {
$this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
$resultRedirect->setPath('catalog/*/edit', ['_current' => true]);
}
return $resultRedirect;
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php
index 838bce7272250..1b9316a95ad59 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Edit.php
@@ -52,12 +52,12 @@ public function execute()
if (($productId && !$product->getEntityId())) {
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
$resultRedirect = $this->resultRedirectFactory->create();
- $this->messageManager->addError(__('This product doesn\'t exist.'));
+ $this->messageManager->addErrorMessage(__('This product doesn\'t exist.'));
return $resultRedirect->setPath('catalog/*/');
} elseif ($productId === 0) {
/** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
$resultRedirect = $this->resultRedirectFactory->create();
- $this->messageManager->addError(__('Invalid product id. Should be numeric value greater than 0'));
+ $this->messageManager->addErrorMessage(__('Invalid product id. Should be numeric value greater than 0'));
return $resultRedirect->setPath('catalog/*/');
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Group/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Group/Save.php
index 4909e22775e55..8a5f375f2b706 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Group/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Group/Save.php
@@ -29,12 +29,12 @@ public function execute()
);
if ($model->itemExists()) {
- $this->messageManager->addError(__('A group with the same name already exists.'));
+ $this->messageManager->addErrorMessage(__('A group with the same name already exists.'));
} else {
try {
$model->save();
} catch (\Exception $e) {
- $this->messageManager->addError(__('Something went wrong while saving this group.'));
+ $this->messageManager->addErrorMessage(__('Something went wrong while saving this group.'));
}
}
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
index 95339870b4d61..d82f4a04fb252 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Initialization/Helper.php
@@ -104,7 +104,7 @@ class Helper
* @param \Magento\Backend\Helper\Js $jsHelper
* @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter
* @param CustomOptionFactory|null $customOptionFactory
- * @param ProductLinkFactory |null $productLinkFactory
+ * @param ProductLinkFactory|null $productLinkFactory
* @param ProductRepositoryInterface|null $productRepository
* @param LinkTypeProvider|null $linkTypeProvider
* @param AttributeFilter|null $attributeFilter
@@ -159,6 +159,7 @@ public function initializeFromData(\Magento\Catalog\Model\Product $product, arra
}
$productData = $this->normalize($productData);
+ $productData = $this->convertSpecialFromDateStringToObject($productData);
if (!empty($productData['is_downloadable'])) {
$productData['product_has_weight'] = 0;
@@ -452,4 +453,19 @@ private function fillProductOptions(Product $product, array $productOptions)
return $product->setOptions($customOptions);
}
+
+ /**
+ * Convert string date presentation into object
+ *
+ * @param array $productData
+ * @return array
+ */
+ private function convertSpecialFromDateStringToObject($productData)
+ {
+ if (isset($productData['special_from_date']) && $productData['special_from_date'] != '') {
+ $productData['special_from_date'] = new \DateTime($productData['special_from_date']);
+ }
+
+ return $productData;
+ }
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
index 2402fb213cda0..f32c6edd57394 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/MassDelete.php
@@ -64,9 +64,12 @@ public function execute()
$this->productRepository->delete($product);
$productDeleted++;
}
- $this->messageManager->addSuccess(
- __('A total of %1 record(s) have been deleted.', $productDeleted)
- );
+
+ if ($productDeleted) {
+ $this->messageManager->addSuccessMessage(
+ __('A total of %1 record(s) have been deleted.', $productDeleted)
+ );
+ }
return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT)->setPath('catalog/*/index');
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
index 84837262a8154..ff3ce60d92787 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
@@ -204,7 +204,8 @@ private function handleImageRemoveError($postData, $productId)
if ($removedImagesAmount) {
$expectedImagesAmount = count($postData['product']['media_gallery']['images']) - $removedImagesAmount;
$product = $this->productRepository->getById($productId);
- if ($expectedImagesAmount != count($product->getMediaGallery('images'))) {
+ $images = $product->getMediaGallery('images');
+ if (is_array($images) && $expectedImagesAmount != count($images)) {
$this->messageManager->addNoticeMessage(
__('The image cannot be removed as it has been assigned to the other image role')
);
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php
index b49a4dabe223c..f2695311732f0 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Delete.php
@@ -36,10 +36,10 @@ public function execute()
$resultRedirect = $this->resultRedirectFactory->create();
try {
$this->attributeSetRepository->deleteById($setId);
- $this->messageManager->addSuccess(__('The attribute set has been removed.'));
+ $this->messageManager->addSuccessMessage(__('The attribute set has been removed.'));
$resultRedirect->setPath('catalog/*/');
} catch (\Exception $e) {
- $this->messageManager->addError(__('We can\'t delete this set right now.'));
+ $this->messageManager->addErrorMessage(__('We can\'t delete this set right now.'));
$resultRedirect->setUrl($this->_redirect->getRedirectUrl($this->getUrl('*')));
}
return $resultRedirect;
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php
index dfddcf7e92b97..c5dd9ce6d8e77 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Set/Save.php
@@ -127,15 +127,15 @@ public function execute()
$model->initFromSkeleton($this->getRequest()->getParam('skeleton_set'));
}
$model->save();
- $this->messageManager->addSuccess(__('You saved the attribute set.'));
+ $this->messageManager->addSuccessMessage(__('You saved the attribute set.'));
} catch (\Magento\Framework\Exception\AlreadyExistsException $e) {
$this->messageManager->addErrorMessage($e->getMessage());
$hasError = true;
} catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
$hasError = true;
} catch (\Exception $e) {
- $this->messageManager->addException($e, __('Something went wrong while saving the attribute set.'));
+ $this->messageManager->addExceptionMessage($e, __('Something went wrong while saving the attribute set.'));
$hasError = true;
}
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
index 63f46fd32e6f7..e131bfe38c546 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Validate.php
@@ -137,7 +137,7 @@ public function execute()
$response->setError(true);
$response->setMessages([$e->getMessage()]);
} catch (\Exception $e) {
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
$layout = $this->layoutFactory->create();
$layout->initMessages();
$response->setError(true);
diff --git a/app/code/Magento/Catalog/Controller/Category/View.php b/app/code/Magento/Catalog/Controller/Category/View.php
index a72f764a5f1f6..226e572505076 100644
--- a/app/code/Magento/Catalog/Controller/Category/View.php
+++ b/app/code/Magento/Catalog/Controller/Category/View.php
@@ -111,7 +111,7 @@ public function __construct(
/**
* Initialize requested category object
*
- * @return \Magento\Catalog\Model\Category
+ * @return \Magento\Catalog\Model\Category|bool
*/
protected function _initCategory()
{
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
index 89eb6c9be929f..eb9cc83125541 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Add.php
@@ -36,7 +36,14 @@ public function execute()
$productName = $this->_objectManager->get(
\Magento\Framework\Escaper::class
)->escapeHtml($product->getName());
- $this->messageManager->addSuccess(__('You added product %1 to the comparison list.', $productName));
+ $this->messageManager->addComplexSuccessMessage(
+ 'addCompareSuccessMessage',
+ [
+ 'product_name' => $productName,
+ 'compare_list_url' => $this->_url->getUrl('catalog/product_compare')
+ ]
+ );
+
$this->_eventManager->dispatch('catalog_product_compare_add_product', ['product' => $product]);
}
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php b/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php
index 30470d13f002d..568fbf1d05677 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Clear.php
@@ -30,12 +30,12 @@ public function execute()
try {
$items->clear();
- $this->messageManager->addSuccess(__('You cleared the comparison list.'));
+ $this->messageManager->addSuccessMessage(__('You cleared the comparison list.'));
$this->_objectManager->get(\Magento\Catalog\Helper\Product\Compare::class)->calculate();
} catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addError($e->getMessage());
+ $this->messageManager->addErrorMessage($e->getMessage());
} catch (\Exception $e) {
- $this->messageManager->addException($e, __('Something went wrong clearing the comparison list.'));
+ $this->messageManager->addExceptionMessage($e, __('Something went wrong clearing the comparison list.'));
}
/** @var \Magento\Framework\Controller\Result\Redirect $resultRedirect */
diff --git a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
index fadb94761a236..2acbe5ce4d582 100644
--- a/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
+++ b/app/code/Magento/Catalog/Controller/Product/Compare/Remove.php
@@ -44,7 +44,7 @@ public function execute()
$item->delete();
$productName = $this->_objectManager->get(\Magento\Framework\Escaper::class)
->escapeHtml($product->getName());
- $this->messageManager->addSuccess(
+ $this->messageManager->addSuccessMessage(
__('You removed product %1 from the comparison list.', $productName)
);
$this->_eventManager->dispatch(
diff --git a/app/code/Magento/Catalog/Controller/Product/View.php b/app/code/Magento/Catalog/Controller/Product/View.php
index 4c577eb897589..ed437361fddd3 100644
--- a/app/code/Magento/Catalog/Controller/Product/View.php
+++ b/app/code/Magento/Catalog/Controller/Product/View.php
@@ -78,13 +78,16 @@ public function execute()
if ($this->getRequest()->isPost() && $this->getRequest()->getParam(self::PARAM_NAME_URL_ENCODED)) {
$product = $this->_initProduct();
+
if (!$product) {
return $this->noProductRedirect();
}
+
if ($specifyOptions) {
$notice = $product->getTypeInstance()->getSpecifyOptionMessage();
- $this->messageManager->addNotice($notice);
+ $this->messageManager->addNoticeMessage($notice);
}
+
if ($this->getRequest()->isAjax()) {
$this->getResponse()->representJson(
$this->_objectManager->get(\Magento\Framework\Json\Helper\Data::class)->jsonEncode([
diff --git a/app/code/Magento/Catalog/CustomerData/CompareProducts.php b/app/code/Magento/Catalog/CustomerData/CompareProducts.php
index 0e688042c615a..afbeab8c9070e 100644
--- a/app/code/Magento/Catalog/CustomerData/CompareProducts.php
+++ b/app/code/Magento/Catalog/CustomerData/CompareProducts.php
@@ -19,6 +19,11 @@ class CompareProducts implements SectionSourceInterface
*/
protected $productUrl;
+ /**
+ * @var \Magento\Catalog\Helper\Output
+ */
+ private $outputHelper;
+
/**
* @param \Magento\Catalog\Helper\Product\Compare $helper
* @param \Magento\Catalog\Model\Product\Url $productUrl
@@ -54,6 +59,7 @@ public function getSectionData()
protected function getItems()
{
$items = [];
+ /** @var \Magento\Catalog\Model\Product $item */
foreach ($this->helper->getItemCollection() as $item) {
$items[] = [
'id' => $item->getId(),
diff --git a/app/code/Magento/Catalog/Helper/Data.php b/app/code/Magento/Catalog/Helper/Data.php
index 9eebcdb2df34f..ae20cda460796 100644
--- a/app/code/Magento/Catalog/Helper/Data.php
+++ b/app/code/Magento/Catalog/Helper/Data.php
@@ -32,6 +32,10 @@ class Data extends \Magento\Framework\App\Helper\AbstractHelper
const CONFIG_USE_STATIC_URLS = 'cms/wysiwyg/use_static_urls_in_catalog';
+ /**
+ * @deprecated
+ * @see \Magento\Catalog\Helper\Output::isDirectivesExists
+ */
const CONFIG_PARSE_URL_DIRECTIVES = 'catalog/frontend/parse_url_directives';
const XML_PATH_DISPLAY_PRODUCT_COUNT = 'catalog/layered_navigation/display_product_count';
@@ -443,6 +447,8 @@ public function isUsingStaticUrlsAllowed()
* Check if the parsing of URL directives is allowed for the catalog
*
* @return bool
+ * @deprecated
+ * @see \Magento\Catalog\Helper\Output::isDirectivesExists
*/
public function isUrlDirectivesParsingAllowed()
{
@@ -457,6 +463,7 @@ public function isUrlDirectivesParsingAllowed()
* Retrieve template processor for catalog content
*
* @return \Magento\Framework\Filter\Template
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getPageTemplateProcessor()
{
diff --git a/app/code/Magento/Catalog/Helper/Output.php b/app/code/Magento/Catalog/Helper/Output.php
index facd5351f269a..ad0f9508cb67e 100644
--- a/app/code/Magento/Catalog/Helper/Output.php
+++ b/app/code/Magento/Catalog/Helper/Output.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Helper;
use Magento\Catalog\Model\Category as ModelCategory;
@@ -45,20 +47,29 @@ class Output extends \Magento\Framework\App\Helper\AbstractHelper
protected $_escaper;
/**
+ * @var array
+ */
+ private $directivePatterns;
+
+ /**
+ * Output constructor.
* @param \Magento\Framework\App\Helper\Context $context
* @param \Magento\Eav\Model\Config $eavConfig
* @param Data $catalogData
* @param \Magento\Framework\Escaper $escaper
+ * @param array $directivePatterns
*/
public function __construct(
\Magento\Framework\App\Helper\Context $context,
\Magento\Eav\Model\Config $eavConfig,
Data $catalogData,
- \Magento\Framework\Escaper $escaper
+ \Magento\Framework\Escaper $escaper,
+ $directivePatterns = []
) {
$this->_eavConfig = $eavConfig;
$this->_catalogData = $catalogData;
$this->_escaper = $escaper;
+ $this->directivePatterns = $directivePatterns;
parent::__construct($context);
}
@@ -134,6 +145,7 @@ public function process($method, $result, $params)
* @param string $attributeName
* @return string
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function productAttribute($product, $attributeHtml, $attributeName)
{
@@ -151,10 +163,12 @@ public function productAttribute($product, $attributeHtml, $attributeName)
$attributeHtml = nl2br($attributeHtml);
}
}
- if ($attribute->getIsHtmlAllowedOnFront() && $attribute->getIsWysiwygEnabled()) {
- if ($this->_catalogData->isUrlDirectivesParsingAllowed()) {
- $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml);
- }
+ if ($attributeHtml !== null
+ && $attribute->getIsHtmlAllowedOnFront()
+ && $attribute->getIsWysiwygEnabled()
+ && $this->isDirectivesExists($attributeHtml)
+ ) {
+ $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml);
}
$attributeHtml = $this->process(
@@ -173,6 +187,7 @@ public function productAttribute($product, $attributeHtml, $attributeName)
* @param string $attributeHtml
* @param string $attributeName
* @return string
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function categoryAttribute($category, $attributeHtml, $attributeName)
{
@@ -185,10 +200,13 @@ public function categoryAttribute($category, $attributeHtml, $attributeName)
) {
$attributeHtml = $this->_escaper->escapeHtml($attributeHtml);
}
- if ($attribute->getIsHtmlAllowedOnFront() && $attribute->getIsWysiwygEnabled()) {
- if ($this->_catalogData->isUrlDirectivesParsingAllowed()) {
- $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml);
- }
+ if ($attributeHtml !== null
+ && $attribute->getIsHtmlAllowedOnFront()
+ && $attribute->getIsWysiwygEnabled()
+ && $this->isDirectivesExists($attributeHtml)
+
+ ) {
+ $attributeHtml = $this->_getTemplateProcessor()->filter($attributeHtml);
}
$attributeHtml = $this->process(
'categoryAttribute',
@@ -197,4 +215,22 @@ public function categoryAttribute($category, $attributeHtml, $attributeName)
);
return $attributeHtml;
}
+
+ /**
+ * Check if string has directives
+ *
+ * @param string $attributeHtml
+ * @return bool
+ */
+ public function isDirectivesExists($attributeHtml)
+ {
+ $matches = false;
+ foreach ($this->directivePatterns as $pattern) {
+ if (preg_match($pattern, $attributeHtml)) {
+ $matches = true;
+ break;
+ }
+ }
+ return $matches;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilter.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilter.php
index f8cf810ffb570..8de708dd467c8 100644
--- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilter.php
+++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/FilterProcessor/ProductCategoryFilter.php
@@ -21,8 +21,13 @@ class ProductCategoryFilter implements CustomFilterInterface
*/
public function apply(Filter $filter, AbstractDb $collection)
{
- $conditionType = $filter->getConditionType() ?: 'eq';
- $categoryFilter = [$conditionType => [$filter->getValue()]];
+ $value = $filter->getValue();
+ $conditionType = $filter->getConditionType() ?: 'in';
+ $filterValue = [$value];
+ if (($conditionType === 'in' || $conditionType === 'nin') && is_string($value)) {
+ $filterValue = explode(',', $value);
+ }
+ $categoryFilter = [$conditionType => $filterValue];
/** @var Collection $collection */
$collection->addCategoriesFilter($categoryFilter);
diff --git a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php
index a2dff83173b37..cd450e26cd832 100644
--- a/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php
+++ b/app/code/Magento/Catalog/Model/Category/Attribute/Backend/Image.php
@@ -106,7 +106,7 @@ public function beforeSave($object)
$object->setData($this->additionalData . $attributeName, $value);
$object->setData($attributeName, $imageName);
} elseif (!is_string($value)) {
- $object->setData($attributeName, '');
+ $object->setData($attributeName, null);
}
return parent::beforeSave($object);
diff --git a/app/code/Magento/Catalog/Model/ImageExtractor.php b/app/code/Magento/Catalog/Model/ImageExtractor.php
index d2c11a3762961..1c20608670672 100644
--- a/app/code/Magento/Catalog/Model/ImageExtractor.php
+++ b/app/code/Magento/Catalog/Model/ImageExtractor.php
@@ -32,12 +32,16 @@ public function process(\DOMElement $mediaNode, $mediaParentTag)
continue;
}
$attributeTagName = $attribute->tagName;
- if ($attributeTagName === 'background') {
- $nodeValue = $this->processImageBackground($attribute->nodeValue);
- } elseif ($attributeTagName === 'width' || $attributeTagName === 'height') {
- $nodeValue = intval($attribute->nodeValue);
+ if ((bool)$attribute->getAttribute('xsi:nil') !== true) {
+ if ($attributeTagName === 'background') {
+ $nodeValue = $this->processImageBackground($attribute->nodeValue);
+ } elseif ($attributeTagName === 'width' || $attributeTagName === 'height') {
+ $nodeValue = intval($attribute->nodeValue);
+ } else {
+ $nodeValue = $attribute->nodeValue;
+ }
} else {
- $nodeValue = !in_array($attribute->nodeValue, ['false', '0']);
+ $nodeValue = null;
}
$result[$mediaParentTag][$moduleNameImage][Image::MEDIA_TYPE_CONFIG_NODE][$imageId][$attribute->tagName]
= $nodeValue;
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
index 09dbed350c5e4..f8121b55dbf99 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
@@ -8,6 +8,7 @@
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
use Magento\Framework\DB\Query\Generator as QueryGenerator;
use Magento\Framework\App\ResourceConnection;
+use Magento\Indexer\Model\ProcessManager;
/**
* Class Full reindex action
@@ -44,6 +45,11 @@ class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
*/
private $activeTableSwitcher;
+ /**
+ * @var ProcessManager
+ */
+ private $processManager;
+
/**
* @param ResourceConnection $resource
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -52,9 +58,10 @@ class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
* @param \Magento\Framework\Indexer\BatchSizeManagementInterface|null $batchSizeManagement
* @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
* @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool
- * @param \Magento\Indexer\Model\Indexer\StateFactory|null $stateFactory
* @param int|null $batchRowsCount
* @param ActiveTableSwitcher|null $activeTableSwitcher
+ * @param ProcessManager $processManager
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\App\ResourceConnection $resource,
@@ -65,7 +72,8 @@ public function __construct(
\Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
\Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
$batchRowsCount = null,
- ActiveTableSwitcher $activeTableSwitcher = null
+ ActiveTableSwitcher $activeTableSwitcher = null,
+ ProcessManager $processManager = null
) {
parent::__construct(
$resource,
@@ -85,6 +93,7 @@ public function __construct(
);
$this->batchRowsCount = $batchRowsCount;
$this->activeTableSwitcher = $activeTableSwitcher ?: $objectManager->get(ActiveTableSwitcher::class);
+ $this->processManager = $processManager ?: $objectManager->get(ProcessManager::class);
}
/**
@@ -133,6 +142,38 @@ public function execute()
return $this;
}
+ /**
+ * Run reindexation
+ *
+ * @return void
+ */
+ protected function reindex()
+ {
+ $userFunctions = [];
+
+ foreach ($this->storeManager->getStores() as $store) {
+ if ($this->getPathFromCategoryId($store->getRootCategoryId())) {
+ $userFunctions[$store->getId()] = function () use ($store) {
+ return $this->reindexStore($store);
+ };
+ }
+ }
+
+ $this->processManager->execute($userFunctions);
+ }
+
+ /**
+ * Execute indexation by store
+ *
+ * @param \Magento\Store\Model\Store $store
+ */
+ private function reindexStore($store)
+ {
+ $this->reindexRootCategory($store);
+ $this->reindexAnchorCategories($store);
+ $this->reindexNonAnchorCategories($store);
+ }
+
/**
* Publish data from tmp to replica table
*
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php
index 9f4e19bf95a8d..005936a75e6d6 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreGroup.php
@@ -97,7 +97,7 @@ protected function validate(AbstractModel $group)
public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $storeGroup)
{
foreach ($storeGroup->getStores() as $store) {
- $this->tableMaintainer->dropTablesForStore($store->getId());
+ $this->tableMaintainer->dropTablesForStore((int)$store->getId());
}
return $objectResource;
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php
index 114d2a94f5b35..b6f9e6adf4a1c 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/StoreView.php
@@ -51,7 +51,7 @@ public function afterSave(AbstractDb $subject, AbstractDb $objectResource, Abstr
*/
public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $store)
{
- $this->tableMaintainer->dropTablesForStore($store->getId());
+ $this->tableMaintainer->dropTablesForStore((int)$store->getId());
return $objectResource;
}
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php
index 387a8085310e4..50700e672237e 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Plugin/Website.php
@@ -39,7 +39,7 @@ public function __construct(
public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $website)
{
foreach ($website->getStoreIds() as $storeId) {
- $this->tableMaintainer->dropTablesForStore($storeId);
+ $this->tableMaintainer->dropTablesForStore((int)$storeId);
}
return $objectResource;
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php
index 1278434fcad43..3c2629bc570f2 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/TableMaintainer.php
@@ -91,6 +91,8 @@ private function getTable($table)
* @param string $newTableName
*
* @return void
+ *
+ * @throws \Zend_Db_Exception
*/
private function createTable($mainTableName, $newTableName)
{
@@ -135,11 +137,13 @@ public function getMainTable(int $storeId)
* @param $storeId
*
* @return void
+ *
+ * @throws \Zend_Db_Exception
*/
public function createTablesForStore(int $storeId)
{
$mainTableName = $this->getMainTable($storeId);
- //Create index table for store based on on main replica table
+ //Create index table for store based on main replica table
//Using main replica table is necessary for backward capability and TableResolver plugin work
$this->createTable(
$this->getTable(AbstractAction::MAIN_INDEX_TABLE . $this->additionalTableSuffix),
@@ -206,12 +210,12 @@ public function createMainTmpTable(int $storeId)
*
* @return string
*
- * @throws \Exception
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getMainTmpTable(int $storeId)
{
if (!isset($this->mainTmpTable[$storeId])) {
- throw new \Exception('Temporary table does not exist');
+ throw new \Magento\Framework\Exception\NoSuchEntityException('Temporary table does not exist');
}
return $this->mainTmpTable[$storeId];
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
index 182f04de4ab0e..a218266c25034 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Category/Action/Rows.php
@@ -161,7 +161,7 @@ protected function removeEntries()
$this->getIndexTable($store->getId()),
['product_id IN (?)' => $this->limitationByProducts]
);
- };
+ }
}
/**
@@ -228,7 +228,7 @@ private function getCategoryIdsFromIndex(array $productIds)
->distinct()
)
);
- };
+ }
$parentCategories = $categoryIds;
foreach ($categoryIds as $categoryId) {
$parentIds = explode('/', $this->getPathFromCategoryId($categoryId));
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/AbstractAction.php
index b6206f96b91e0..6101e5cd362e4 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/AbstractAction.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/AbstractAction.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Indexer\Product\Eav;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\AbstractEav;
@@ -12,6 +14,11 @@
*/
abstract class AbstractAction
{
+ /**
+ * Config path for enable EAV indexer
+ */
+ const ENABLE_EAV_INDEXER = 'catalog/search/enable_eav_indexer';
+
/**
* EAV Indexers by type
*
@@ -29,17 +36,27 @@ abstract class AbstractAction
*/
protected $_eavDecimalFactory;
+ /**
+ * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ */
+ private $scopeConfig;
+
/**
* AbstractAction constructor.
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory
+ * @param \Magento\Framework\App\Config\ScopeConfigInterface|null $scopeConfig
*/
public function __construct(
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory
+ \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory,
+ \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null
) {
$this->_eavDecimalFactory = $eavDecimalFactory;
$this->_eavSourceFactory = $eavSourceFactory;
+ $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
+ \Magento\Framework\App\Config\ScopeConfigInterface::class
+ );
}
/**
@@ -92,6 +109,9 @@ public function getIndexer($type)
*/
public function reindex($ids = null)
{
+ if (!$this->isEavIndexerEnabled()) {
+ return;
+ }
foreach ($this->getIndexers() as $indexer) {
if ($ids === null) {
$indexer->reindexAll();
@@ -149,4 +169,19 @@ protected function processRelations(AbstractEav $indexer, array $ids, bool $only
return array_unique(array_merge($ids, $childIds, $parentIds));
}
+
+ /**
+ * Get EAV indexer status
+ *
+ * @return bool
+ */
+ private function isEavIndexerEnabled(): bool
+ {
+ $eavIndexerStatus = $this->scopeConfig->getValue(
+ self::ENABLE_EAV_INDEXER,
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ );
+
+ return (bool)$eavIndexerStatus;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
index bc747e62f641e..802176092d147 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
@@ -3,12 +3,15 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Indexer\Product\Eav\Action;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
/**
* Class Full reindex action
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction
{
@@ -32,6 +35,11 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction
*/
private $activeTableSwitcher;
+ /**
+ * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ */
+ private $scopeConfig;
+
/**
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory
@@ -39,6 +47,7 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction
* @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator
* @param ActiveTableSwitcher|null $activeTableSwitcher
+ * @param \Magento\Framework\App\Config\ScopeConfigInterface|null $scopeConfig
*/
public function __construct(
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory,
@@ -46,9 +55,13 @@ public function __construct(
\Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
\Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator = null,
- ActiveTableSwitcher $activeTableSwitcher = null
+ ActiveTableSwitcher $activeTableSwitcher = null,
+ \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null
) {
- parent::__construct($eavDecimalFactory, $eavSourceFactory);
+ $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
+ \Magento\Framework\App\Config\ScopeConfigInterface::class
+ );
+ parent::__construct($eavDecimalFactory, $eavSourceFactory, $scopeConfig);
$this->metadataPool = $metadataPool ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
\Magento\Framework\EntityManager\MetadataPool::class
);
@@ -73,6 +86,9 @@ public function __construct(
*/
public function execute($ids = null)
{
+ if (!$this->isEavIndexerEnabled()) {
+ return;
+ }
try {
foreach ($this->getIndexers() as $indexerName => $indexer) {
$connection = $indexer->getConnection();
@@ -129,4 +145,19 @@ protected function syncData($indexer, $destinationTable, $ids = null)
throw $e;
}
}
+
+ /**
+ * Get EAV indexer status
+ *
+ * @return bool
+ */
+ private function isEavIndexerEnabled(): bool
+ {
+ $eavIndexerStatus = $this->scopeConfig->getValue(
+ self::ENABLE_EAV_INDEXER,
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ );
+
+ return (bool)$eavIndexerStatus;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php
index 9dd312e9da801..3a1611299288c 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Flat/Action/Indexer.php
@@ -175,6 +175,7 @@ public function write($storeId, $productId, $valueFieldSuffix = '')
if (!empty($updateData)) {
$updateData += ['entity_id' => $productId];
+ $updateData += ['row_id' => $productId];
$updateFields = [];
foreach ($updateData as $key => $value) {
$updateFields[$key] = $key;
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
index 7aed842713f5d..e9a907f0b5097 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/AbstractAction.php
@@ -5,7 +5,11 @@
*/
namespace Magento\Catalog\Model\Indexer\Product\Price;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice;
+use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Indexer\DimensionalIndexerInterface;
+use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
/**
* Abstract action reindex class
@@ -17,7 +21,7 @@ abstract class AbstractAction
/**
* Default Product Type Price indexer resource model
*
- * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice
+ * @var DefaultPrice
*/
protected $_defaultIndexerResource;
@@ -77,6 +81,16 @@ abstract class AbstractAction
*/
private $tierPriceIndexResource;
+ /**
+ * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory
+ */
+ private $dimensionCollectionFactory;
+
+ /**
+ * @var TableMaintainer
+ */
+ private $tableMaintainer;
+
/**
* @param \Magento\Framework\App\Config\ScopeConfigInterface $config
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -85,8 +99,13 @@ abstract class AbstractAction
* @param \Magento\Framework\Stdlib\DateTime $dateTime
* @param \Magento\Catalog\Model\Product\Type $catalogProductType
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource
+ * @param DefaultPrice $defaultIndexerResource
+ * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice|null $tierPriceIndexResource
+ * @param DimensionCollectionFactory|null $dimensionCollectionFactory
+ * @param TableMaintainer|null $tableMaintainer
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\App\Config\ScopeConfigInterface $config,
@@ -96,8 +115,10 @@ public function __construct(
\Magento\Framework\Stdlib\DateTime $dateTime,
\Magento\Catalog\Model\Product\Type $catalogProductType,
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource = null
+ DefaultPrice $defaultIndexerResource,
+ \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice $tierPriceIndexResource = null,
+ \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory = null,
+ \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $tableMaintainer = null
) {
$this->_config = $config;
$this->_storeManager = $storeManager;
@@ -108,9 +129,15 @@ public function __construct(
$this->_indexerPriceFactory = $indexerPriceFactory;
$this->_defaultIndexerResource = $defaultIndexerResource;
$this->_connection = $this->_defaultIndexerResource->getConnection();
- $this->tierPriceIndexResource = $tierPriceIndexResource ?: ObjectManager::getInstance()->get(
+ $this->tierPriceIndexResource = $tierPriceIndexResource ?? ObjectManager::getInstance()->get(
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\TierPrice::class
);
+ $this->dimensionCollectionFactory = $dimensionCollectionFactory ?? ObjectManager::getInstance()->get(
+ \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class
+ );
+ $this->tableMaintainer = $tableMaintainer ?? ObjectManager::getInstance()->get(
+ \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class
+ );
}
/**
@@ -126,30 +153,29 @@ abstract public function execute($ids);
*
* @param array $processIds
* @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ * @deprecated Used only for backward compatibility for indexer, which not support indexation by dimensions
*/
protected function _syncData(array $processIds = [])
{
- // delete invalid rows
- $select = $this->_connection->select()->from(
- ['index_price' => $this->getIndexTargetTable()],
- null
- )->joinLeft(
- ['ip_tmp' => $this->_defaultIndexerResource->getIdxTable()],
- 'index_price.entity_id = ip_tmp.entity_id AND index_price.website_id = ip_tmp.website_id',
- []
- )->where(
- 'ip_tmp.entity_id IS NULL'
- );
- if (!empty($processIds)) {
- $select->where('index_price.entity_id IN(?)', $processIds);
- }
- $sql = $select->deleteFromSelect('index_price');
- $this->_connection->query($sql);
+ // for backward compatibility split data from old idx table on dimension tables
+ foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
+ $insertSelect = $this->getConnection()->select()->from(
+ ['ip_tmp' => $this->_defaultIndexerResource->getIdxTable()]
+ );
- $this->_insertFromTable(
- $this->_defaultIndexerResource->getIdxTable(),
- $this->getIndexTargetTable()
- );
+ foreach ($dimensions as $dimension) {
+ if ($dimension->getName() === WebsiteDimensionProvider::DIMENSION_NAME) {
+ $insertSelect->where('ip_tmp.website_id = ?', $dimension->getValue());
+ }
+ if ($dimension->getName() === CustomerGroupDimensionProvider::DIMENSION_NAME) {
+ $insertSelect->where('ip_tmp.customer_group_id = ?', $dimension->getValue());
+ }
+ }
+
+ $query = $insertSelect->insertFromSelect($this->tableMaintainer->getMainTable($dimensions));
+ $this->getConnection()->query($query);
+ }
return $this;
}
@@ -157,12 +183,15 @@ protected function _syncData(array $processIds = [])
* Prepare website current dates table
*
* @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
+ *
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function _prepareWebsiteDateTable()
{
$baseCurrency = $this->_config->getValue(\Magento\Directory\Model\Currency::XML_PATH_CURRENCY_BASE);
- $select = $this->_connection->select()->from(
+ $select = $this->getConnection()->select()->from(
['cw' => $this->_defaultIndexerResource->getTable('store_website')],
['website_id']
)->join(
@@ -174,7 +203,7 @@ protected function _prepareWebsiteDateTable()
);
$data = [];
- foreach ($this->_connection->fetchAll($select) as $item) {
+ foreach ($this->getConnection()->fetchAll($select) as $item) {
/** @var $website \Magento\Store\Model\Website */
$website = $this->_storeManager->getWebsite($item['website_id']);
@@ -199,6 +228,7 @@ protected function _prepareWebsiteDateTable()
'website_id' => $website->getId(),
'website_date' => $this->_dateTime->formatDate($timestamp, false),
'rate' => $rate,
+ 'default_store_id' => $store->getId()
];
}
}
@@ -207,7 +237,7 @@ protected function _prepareWebsiteDateTable()
$this->_emptyTable($table);
if ($data) {
foreach ($data as $row) {
- $this->_connection->insertOnDuplicate($table, $row, array_keys($row));
+ $this->getConnection()->insertOnDuplicate($table, $row, array_keys($row));
}
}
@@ -230,9 +260,13 @@ protected function _prepareTierPriceIndex($entityIds = null)
/**
* Retrieve price indexers per product type
*
+ * @param bool $fullReindexAction
+ *
* @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface[]
+ *
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
- public function getTypeIndexers()
+ public function getTypeIndexers($fullReindexAction = false)
{
if ($this->_indexers === null) {
$this->_indexers = [];
@@ -242,14 +276,20 @@ public function getTypeIndexers()
$typeInfo['price_indexer']
) ? $typeInfo['price_indexer'] : get_class($this->_defaultIndexerResource);
- $isComposite = !empty($typeInfo['composite']);
$indexer = $this->_indexerPriceFactory->create(
- $modelName
- )->setTypeId(
- $typeId
- )->setIsComposite(
- $isComposite
+ $modelName,
+ [
+ 'fullReindexAction' => $fullReindexAction
+ ]
);
+ // left setters for backward compatibility
+ if ($indexer instanceof DefaultPrice) {
+ $indexer->setTypeId(
+ $typeId
+ )->setIsComposite(
+ !empty($typeInfo['composite'])
+ );
+ }
$this->_indexers[$typeId] = $indexer;
}
}
@@ -262,7 +302,9 @@ public function getTypeIndexers()
*
* @param string $productTypeId
* @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface
+ *
* @throws \Magento\Framework\Exception\InputException
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function _getIndexer($productTypeId)
{
@@ -283,19 +325,19 @@ protected function _getIndexer($productTypeId)
*/
protected function _insertFromTable($sourceTable, $destTable, $where = null)
{
- $sourceColumns = array_keys($this->_connection->describeTable($sourceTable));
- $targetColumns = array_keys($this->_connection->describeTable($destTable));
- $select = $this->_connection->select()->from($sourceTable, $sourceColumns);
+ $sourceColumns = array_keys($this->getConnection()->describeTable($sourceTable));
+ $targetColumns = array_keys($this->getConnection()->describeTable($destTable));
+ $select = $this->getConnection()->select()->from($sourceTable, $sourceColumns);
if ($where) {
$select->where($where);
}
- $query = $this->_connection->insertFromSelect(
+ $query = $this->getConnection()->insertFromSelect(
$select,
$destTable,
$targetColumns,
\Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
);
- $this->_connection->query($query);
+ $this->getConnection()->query($query);
}
/**
@@ -306,7 +348,7 @@ protected function _insertFromTable($sourceTable, $destTable, $where = null)
*/
protected function _emptyTable($table)
{
- $this->_connection->delete($table);
+ $this->getConnection()->delete($table);
}
/**
@@ -314,46 +356,64 @@ protected function _emptyTable($table)
*
* @param array $changedIds
* @return array Affected ids
+ *
+ * @throws \Magento\Framework\Exception\InputException
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function _reindexRows($changedIds = [])
{
- $this->_emptyTable($this->_defaultIndexerResource->getIdxTable());
$this->_prepareWebsiteDateTable();
$productsTypes = $this->getProductsTypes($changedIds);
- $compositeIds = [];
- $notCompositeIds = [];
+ $parentProductsTypes = $this->getParentProductsTypes($changedIds);
+ $changedIds = array_merge($changedIds, ...array_values($parentProductsTypes));
+ $productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes);
+
+ if ($changedIds) {
+ $this->deleteIndexData($changedIds);
+ }
foreach ($productsTypes as $productType => $entityIds) {
$indexer = $this->_getIndexer($productType);
- if ($indexer->getIsComposite()) {
- $compositeIds += $entityIds;
+ if ($indexer instanceof DimensionalIndexerInterface) {
+ foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
+ $this->tableMaintainer->createMainTmpTable($dimensions);
+ $temporaryTable = $this->tableMaintainer->getMainTmpTable($dimensions);
+ $this->_emptyTable($temporaryTable);
+ $indexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false));
+ // copy to index
+ $this->_insertFromTable(
+ $temporaryTable,
+ $this->tableMaintainer->getMainTable($dimensions)
+ );
+ }
} else {
- $notCompositeIds += $entityIds;
+ // handle 3d-party indexers for backward compatibility
+ $this->_emptyTable($this->_defaultIndexerResource->getIdxTable());
+ $this->_copyRelationIndexData($entityIds);
+ $indexer->reindexEntity($entityIds);
+ $this->_syncData($entityIds);
}
}
- if (!empty($notCompositeIds)) {
- $parentProductsTypes = $this->getParentProductsTypes($notCompositeIds);
- $productsTypes = array_merge_recursive($productsTypes, $parentProductsTypes);
- foreach ($parentProductsTypes as $parentProductsIds) {
- $compositeIds = $compositeIds + $parentProductsIds;
- $changedIds = array_merge($changedIds, $parentProductsIds);
- }
- }
-
- if (!empty($compositeIds)) {
- $this->_copyRelationIndexData($compositeIds, $notCompositeIds);
- }
- $this->_prepareTierPriceIndex($compositeIds + $notCompositeIds);
+ return $changedIds;
+ }
- foreach ($productsTypes as $productType => $entityIds) {
- $indexer = $this->_getIndexer($productType);
- $indexer->reindexEntity($entityIds);
+ /**
+ * @param array $entityIds
+ * @return void
+ */
+ private function deleteIndexData(array $entityIds)
+ {
+ foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
+ $select = $this->getConnection()->select()->from(
+ ['index_price' => $this->tableMaintainer->getMainTable($dimensions)],
+ null
+ )->where('index_price.entity_id IN (?)', $entityIds);
+ $query = $select->deleteFromSelect('index_price');
+ $this->getConnection()->query($query);
}
- $this->_syncData($changedIds);
-
- return $compositeIds + $notCompositeIds;
}
/**
@@ -362,11 +422,15 @@ protected function _reindexRows($changedIds = [])
* @param null|array $parentIds
* @param array $excludeIds
* @return \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
+ * @deprecated Used only for backward compatibility for do not broke custom indexer implementation
+ * which do not work by dimensions.
+ * For indexers, which support dimensions all composite products read data directly from main price indexer table
+ * or replica table for partial or full reindex correspondingly.
*/
protected function _copyRelationIndexData($parentIds, $excludeIds = null)
{
$linkField = $this->getProductIdFieldName();
- $select = $this->_connection->select()->from(
+ $select = $this->getConnection()->select()->from(
$this->_defaultIndexerResource->getTable('catalog_product_relation'),
['child_id']
)->join(
@@ -381,22 +445,45 @@ protected function _copyRelationIndexData($parentIds, $excludeIds = null)
$select->where('child_id NOT IN(?)', $excludeIds);
}
- $children = $this->_connection->fetchCol($select);
+ $children = $this->getConnection()->fetchCol($select);
if ($children) {
- $select = $this->_connection->select()->from(
- $this->getIndexTargetTable()
- )->where(
- 'entity_id IN(?)',
- $children
- );
- $query = $select->insertFromSelect($this->_defaultIndexerResource->getIdxTable(), [], false);
- $this->_connection->query($query);
+ foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
+ $select = $this->getConnection()->select()->from(
+ $this->getIndexTargetTableByDimension($dimensions)
+ )->where(
+ 'entity_id IN(?)',
+ $children
+ );
+ $query = $select->insertFromSelect($this->_defaultIndexerResource->getIdxTable(), [], false);
+ $this->getConnection()->query($query);
+ }
}
return $this;
}
+ /**
+ * Retrieve index table by dimension that will be used for write operations.
+ *
+ * This method is used during both partial and full reindex to identify the table.
+ *
+ * @param \Magento\Framework\Search\Request\Dimension[] $dimensions
+ *
+ * @return string
+ */
+ private function getIndexTargetTableByDimension(array $dimensions)
+ {
+ $indexTargetTable = $this->getIndexTargetTable();
+ if ($indexTargetTable === self::getIndexTargetTable()) {
+ $indexTargetTable = $this->tableMaintainer->getMainTable($dimensions);
+ }
+ if ($indexTargetTable === self::getIndexTargetTable() . '_replica') {
+ $indexTargetTable = $this->tableMaintainer->getMainReplicaTable($dimensions);
+ }
+ return $indexTargetTable;
+ }
+
/**
* Retrieve index table that will be used for write operations.
*
@@ -415,8 +502,8 @@ protected function getIndexTargetTable()
protected function getProductIdFieldName()
{
$table = $this->_defaultIndexerResource->getTable('catalog_product_entity');
- $indexList = $this->_connection->getIndexList($table);
- return $indexList[$this->_connection->getPrimaryKeyName($table)]['COLUMNS_LIST'][0];
+ $indexList = $this->getConnection()->getIndexList($table);
+ return $indexList[$this->getConnection()->getPrimaryKeyName($table)]['COLUMNS_LIST'][0];
}
/**
@@ -427,14 +514,14 @@ protected function getProductIdFieldName()
*/
private function getProductsTypes(array $changedIds = [])
{
- $select = $this->_connection->select()->from(
+ $select = $this->getConnection()->select()->from(
$this->_defaultIndexerResource->getTable('catalog_product_entity'),
['entity_id', 'type_id']
);
if ($changedIds) {
$select->where('entity_id IN (?)', $changedIds);
}
- $pairs = $this->_connection->fetchPairs($select);
+ $pairs = $this->getConnection()->fetchPairs($select);
$byType = [];
foreach ($pairs as $productId => $productType) {
@@ -445,14 +532,15 @@ private function getProductsTypes(array $changedIds = [])
}
/**
- * Get parent products types.
+ * Get parent products types
+ * Used for add composite products to reindex if we have only simple products in changed ids set
*
* @param array $productsIds
* @return array
*/
private function getParentProductsTypes(array $productsIds)
{
- $select = $this->_connection->select()->from(
+ $select = $this->getConnection()->select()->from(
['l' => $this->_defaultIndexerResource->getTable('catalog_product_relation')],
''
)->join(
@@ -463,7 +551,7 @@ private function getParentProductsTypes(array $productsIds)
'l.child_id IN(?)',
$productsIds
);
- $pairs = $this->_connection->fetchPairs($select);
+ $pairs = $this->getConnection()->fetchPairs($select);
$byType = [];
foreach ($pairs as $productId => $productType) {
@@ -472,4 +560,14 @@ private function getParentProductsTypes(array $productsIds)
return $byType;
}
+
+ /**
+ * Get connection
+ *
+ * @return \Magento\Framework\DB\Adapter\AdapterInterface
+ */
+ private function getConnection()
+ {
+ return $this->_defaultIndexerResource->getConnection();
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
index ba04af8ec1f41..1a75751570658 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
@@ -6,9 +6,17 @@
namespace Magento\Catalog\Model\Indexer\Product\Price\Action;
use Magento\Framework\App\ObjectManager;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface;
+use Magento\Framework\EntityManager\EntityMetadataInterface;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Indexer\DimensionalIndexerInterface;
+use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
+use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
/**
* Class Full reindex action
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
@@ -33,6 +41,26 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
*/
private $activeTableSwitcher;
+ /**
+ * @var EntityMetadataInterface
+ */
+ private $productMetaDataCached;
+
+ /**
+ * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory
+ */
+ private $dimensionCollectionFactory;
+
+ /**
+ * @var \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer
+ */
+ private $dimensionTableMaintainer;
+
+ /**
+ * @var \Magento\Indexer\Model\ProcessManager
+ */
+ private $processManager;
+
/**
* @param \Magento\Framework\App\Config\ScopeConfigInterface $config
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -46,7 +74,9 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
* @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator|null $batchSizeCalculator
* @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
* @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher
- *
+ * @param \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory|null $dimensionCollectionFactory
+ * @param \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer|null $dimensionTableMaintainer
+ * @param \Magento\Indexer\Model\ProcessManager $processManager
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -61,7 +91,10 @@ public function __construct(
\Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
\Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator $batchSizeCalculator = null,
\Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
- \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null
+ \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null,
+ \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory = null,
+ \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $dimensionTableMaintainer = null,
+ \Magento\Indexer\Model\ProcessManager $processManager = null
) {
parent::__construct(
$config,
@@ -85,6 +118,15 @@ public function __construct(
$this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get(
\Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class
);
+ $this->dimensionCollectionFactory = $dimensionCollectionFactory ?: ObjectManager::getInstance()->get(
+ \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class
+ );
+ $this->dimensionTableMaintainer = $dimensionTableMaintainer ?: ObjectManager::getInstance()->get(
+ \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class
+ );
+ $this->processManager = $processManager ?: ObjectManager::getInstance()->get(
+ \Magento\Indexer\Model\ProcessManager::class
+ );
}
/**
@@ -92,75 +134,335 @@ public function __construct(
*
* @param array|int|null $ids
* @return void
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Exception
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function execute($ids = null)
{
try {
- $this->_defaultIndexerResource->getTableStrategy()->setUseIdxTable(false);
- $this->_prepareWebsiteDateTable();
+ //Prepare indexer tables before full reindex
+ $this->prepareTables();
+
+ /** @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $indexer */
+ foreach ($this->getTypeIndexers(true) as $typeId => $priceIndexer) {
+ if ($priceIndexer instanceof DimensionalIndexerInterface) {
+ //New price reindex mechanism
+ $this->reindexProductTypeWithDimensions($priceIndexer, $typeId);
+ continue;
+ }
+
+ $priceIndexer->getTableStrategy()->setUseIdxTable(false);
+
+ //Old price reindex mechanism
+ $this->reindexProductType($priceIndexer, $typeId);
+ }
+
+ //Final replacement of tables from replica to main
+ $this->switchTables();
+ } catch (\Exception $e) {
+ throw new LocalizedException(__($e->getMessage()), $e);
+ }
+ }
+
+ /**
+ * Prepare indexer tables before full reindex
+ *
+ * @return void
+ * @throws \Exception
+ */
+ private function prepareTables()
+ {
+ $this->_defaultIndexerResource->getTableStrategy()->setUseIdxTable(false);
+
+ $this->_prepareWebsiteDateTable();
+
+ $this->truncateReplicaTables();
+ }
+
+ /**
+ * Truncate replica tables by dimensions
+ *
+ * @return void
+ * @throws \Exception
+ */
+ private function truncateReplicaTables()
+ {
+ foreach ($this->dimensionCollectionFactory->create() as $dimension) {
+ $dimensionTable = $this->dimensionTableMaintainer->getMainReplicaTable($dimension);
+ $this->_defaultIndexerResource->getConnection()->truncateTable($dimensionTable);
+ }
+ }
+
+ /**
+ * Reindex new 'Dimensional' price indexer by product type
+ *
+ * @param DimensionalIndexerInterface $priceIndexer
+ * @param string $typeId
+ *
+ * @return void
+ * @throws \Exception
+ */
+ private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId)
+ {
+ $userFunctions = [];
+ foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
+ $userFunctions[] = function () use ($priceIndexer, $dimensions, $typeId) {
+ return $this->reindexByBatches($priceIndexer, $dimensions, $typeId);
+ };
+ }
+ $this->processManager->execute($userFunctions);
+ }
+
+ /**
+ * Reindex new 'Dimensional' price indexer by batches
+ *
+ * @param DimensionalIndexerInterface $priceIndexer
+ * @param array $dimensions
+ * @param string $typeId
+ *
+ * @return void
+ * @throws \Exception
+ */
+ private function reindexByBatches(DimensionalIndexerInterface $priceIndexer, array $dimensions, string $typeId)
+ {
+ foreach ($this->getBatchesForIndexer($typeId) as $batch) {
+ $this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions, $typeId);
+ }
+ }
+
+ /**
+ * Get batches for new 'Dimensional' price indexer
+ *
+ * @param string $typeId
+ *
+ * @return \Generator
+ * @throws \Exception
+ */
+ private function getBatchesForIndexer(string $typeId)
+ {
+ $connection = $this->_defaultIndexerResource->getConnection();
+ return $this->batchProvider->getBatches(
+ $connection,
+ $this->getProductMetaData()->getEntityTable(),
+ $this->getProductMetaData()->getIdentifierField(),
+ $this->batchSizeCalculator->estimateBatchSize(
+ $connection,
+ $typeId
+ )
+ );
+ }
+
+ /**
+ * Reindex by batch for new 'Dimensional' price indexer
+ *
+ * @param DimensionalIndexerInterface $priceIndexer
+ * @param array $batch
+ * @param array $dimensions
+ * @param string $typeId
+ *
+ * @return void
+ * @throws \Exception
+ */
+ private function reindexByBatchWithDimensions(
+ DimensionalIndexerInterface $priceIndexer,
+ array $batch,
+ array $dimensions,
+ string $typeId
+ ) {
+ $entityIds = $this->getEntityIdsFromBatch($typeId, $batch);
+
+ if (!empty($entityIds)) {
+ $this->dimensionTableMaintainer->createMainTmpTable($dimensions);
+ $temporaryTable = $this->dimensionTableMaintainer->getMainTmpTable($dimensions);
+ $this->_emptyTable($temporaryTable);
+
+ $priceIndexer->executeByDimensions($dimensions, \SplFixedArray::fromArray($entityIds, false));
- $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
- $replicaTable = $this->activeTableSwitcher->getAdditionalTableName(
- $this->_defaultIndexerResource->getMainTable()
+ // Sync data from temp table to index table
+ $this->_insertFromTable(
+ $temporaryTable,
+ $this->dimensionTableMaintainer->getMainReplicaTable($dimensions)
);
+ }
+ }
- // Prepare replica table for indexation.
- $this->_defaultIndexerResource->getConnection()->truncateTable($replicaTable);
+ /**
+ * Reindex old price indexer by product type
+ *
+ * @param PriceInterface $priceIndexer
+ * @param string $typeId
+ *
+ * @return void
+ * @throws \Exception
+ */
+ private function reindexProductType(PriceInterface $priceIndexer, string $typeId)
+ {
+ foreach ($this->getBatchesForIndexer($typeId) as $batch) {
+ $this->reindexBatch($priceIndexer, $batch, $typeId);
+ }
+ }
- /** @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $indexer */
- foreach ($this->getTypeIndexers() as $indexer) {
- $indexer->getTableStrategy()->setUseIdxTable(false);
- $connection = $indexer->getConnection();
-
- $batches = $this->batchProvider->getBatches(
- $connection,
- $entityMetadata->getEntityTable(),
- $entityMetadata->getIdentifierField(),
- $this->batchSizeCalculator->estimateBatchSize($connection, $indexer->getTypeId())
- );
-
- foreach ($batches as $batch) {
- // Get entity ids from batch
- $select = $connection->select();
- $select->distinct(true);
- $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
- $select->where('type_id = ?', $indexer->getTypeId());
-
- $entityIds = $this->batchProvider->getBatchIds($connection, $select, $batch);
-
- if (!empty($entityIds)) {
- // Temporary table will created if not exists
- $idxTableName = $this->_defaultIndexerResource->getIdxTable();
- $this->_emptyTable($idxTableName);
-
- if ($indexer->getIsComposite()) {
- $this->_copyRelationIndexData($entityIds);
- }
- $this->_prepareTierPriceIndex($entityIds);
-
- // Reindex entities by id
- $indexer->reindexEntity($entityIds);
-
- // Sync data from temp table to index table
- $this->_insertFromTable($idxTableName, $replicaTable);
-
- // Drop temporary index table
- $connection->dropTable($idxTableName);
- }
- }
+ /**
+ * Reindex by batch for old price indexer
+ *
+ * @param PriceInterface $priceIndexer
+ * @param array $batch
+ * @param string $typeId
+ *
+ * @return void
+ * @throws \Exception
+ */
+ private function reindexBatch(PriceInterface $priceIndexer, array $batch, string $typeId)
+ {
+ $entityIds = $this->getEntityIdsFromBatch($typeId, $batch);
+
+ if (!empty($entityIds)) {
+ // Temporary table will created if not exists
+ $idxTableName = $this->_defaultIndexerResource->getIdxTable();
+ $this->_emptyTable($idxTableName);
+
+ if ($priceIndexer->getIsComposite()) {
+ $this->_copyRelationIndexData($entityIds);
}
+
+ // Reindex entities by id
+ $priceIndexer->reindexEntity($entityIds);
+
+ // Sync data from temp table to index table
+ $this->_insertFromTable($idxTableName, $this->getReplicaTable());
+
+ // Drop temporary index table
+ $this->_defaultIndexerResource->getConnection()->dropTable($idxTableName);
+ }
+ }
+
+ /**
+ * Get Entity Ids from batch
+ *
+ * @param string $typeId
+ * @param array $batch
+ *
+ * @return array
+ * @throws \Exception
+ */
+ private function getEntityIdsFromBatch(string $typeId, array $batch)
+ {
+ $connection = $this->_defaultIndexerResource->getConnection();
+
+ // Get entity ids from batch
+ $select = $connection
+ ->select()
+ ->distinct(true)
+ ->from(
+ ['e' => $this->getProductMetaData()->getEntityTable()],
+ $this->getProductMetaData()->getIdentifierField()
+ )
+ ->where('type_id = ?', $typeId);
+
+ return $this->batchProvider->getBatchIds($connection, $select, $batch);
+ }
+
+ /**
+ * Get product meta data
+ *
+ * @return EntityMetadataInterface
+ * @throws \Exception
+ */
+ private function getProductMetaData()
+ {
+ if ($this->productMetaDataCached === null) {
+ $this->productMetaDataCached = $this->metadataPool->getMetadata(ProductInterface::class);
+ }
+
+ return $this->productMetaDataCached;
+ }
+
+ /**
+ * Get replica table
+ *
+ * @return string
+ * @throws \Exception
+ */
+ private function getReplicaTable()
+ {
+ return $this->activeTableSwitcher->getAdditionalTableName(
+ $this->_defaultIndexerResource->getMainTable()
+ );
+ }
+
+ /**
+ * Replacement of tables from replica to main
+ *
+ * @return void
+ */
+ private function switchTables()
+ {
+ // Switch dimension tables
+ $mainTablesByDimension = [];
+
+ foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
+ $mainTablesByDimension[] = $this->dimensionTableMaintainer->getMainTable($dimensions);
+
+ //Move data from indexers with old realisation
+ $this->moveDataFromReplicaTableToReplicaTables($dimensions);
+ }
+
+ if (count($mainTablesByDimension) > 0) {
$this->activeTableSwitcher->switchTable(
$this->_defaultIndexerResource->getConnection(),
- [$this->_defaultIndexerResource->getMainTable()]
+ $mainTablesByDimension
);
- } catch (\Exception $e) {
- throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage()), $e);
}
}
/**
+ * Move data from old price indexer mechanism to new indexer mechanism by dimensions.
+ * Used only for backward compatibility
+ *
+ * @param array $dimensions
+ *
+ * @return void
+ */
+ private function moveDataFromReplicaTableToReplicaTables(array $dimensions)
+ {
+ if (!$dimensions) {
+ return;
+ }
+ $select = $this->dimensionTableMaintainer->getConnection()->select()->from(
+ $this->dimensionTableMaintainer->getMainReplicaTable([])
+ );
+
+ $check = clone $select;
+ $check->reset('columns')->columns('count(*)');
+
+ if (!$this->dimensionTableMaintainer->getConnection()->query($check)->fetchColumn()) {
+ return;
+ }
+
+ $replicaTablesByDimension = $this->dimensionTableMaintainer->getMainReplicaTable($dimensions);
+
+ foreach ($dimensions as $dimension) {
+ if ($dimension->getName() === WebsiteDimensionProvider::DIMENSION_NAME) {
+ $select->where('website_id = ?', $dimension->getValue());
+ }
+ if ($dimension->getName() === CustomerGroupDimensionProvider::DIMENSION_NAME) {
+ $select->where('customer_group_id = ?', $dimension->getValue());
+ }
+ }
+
+ $this->dimensionTableMaintainer->getConnection()->query(
+ $this->dimensionTableMaintainer->getConnection()->insertFromSelect(
+ $select,
+ $replicaTablesByDimension,
+ [],
+ \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ )
+ );
+ }
+
+ /**
+ * @deprecated
+ *
* @inheritdoc
*/
protected function getIndexTargetTable()
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionCollectionFactory.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionCollectionFactory.php
new file mode 100644
index 0000000000000..31459af81ccc7
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionCollectionFactory.php
@@ -0,0 +1,72 @@
+multiDimensionProviderFactory = $multiDimensionProviderFactory;
+ $this->dimensionProviders = $dimensionProviders;
+ $this->dimensionModeConfiguration = $dimensionModeConfiguration;
+ }
+
+ /**
+ * Create MultiDimensionProvider for specified "dimension mode".
+ * By default return multiplication of dimensions by current set mode
+ *
+ * @param string|null $dimensionsMode
+ * @return MultiDimensionProvider
+ */
+ public function create(string $dimensionsMode = null): MultiDimensionProvider
+ {
+ $dimensionConfiguration = $this->dimensionModeConfiguration->getDimensionConfiguration($dimensionsMode);
+
+ $providers = [];
+ foreach ($dimensionConfiguration as $dimensionName) {
+ if (!isset($this->dimensionProviders[$dimensionName])) {
+ throw new \LogicException(
+ 'Dimension Provider is missing. Cannot handle unknown dimension: ' . $dimensionName
+ );
+ }
+ $providers[] = clone $this->dimensionProviders[$dimensionName];
+ }
+
+ return $this->multiDimensionProviderFactory->create(
+ [
+ 'dimensionProviders' => $providers
+ ]
+ );
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php
new file mode 100644
index 0000000000000..7a4d8e313462d
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/DimensionModeConfiguration.php
@@ -0,0 +1,102 @@
+ [
+ ],
+ self::DIMENSION_WEBSITE => [
+ WebsiteDimensionProvider::DIMENSION_NAME
+ ],
+ self::DIMENSION_CUSTOMER_GROUP => [
+ CustomerGroupDimensionProvider::DIMENSION_NAME
+ ],
+ self::DIMENSION_WEBSITE_AND_CUSTOMER_GROUP => [
+ WebsiteDimensionProvider::DIMENSION_NAME,
+ CustomerGroupDimensionProvider::DIMENSION_NAME
+ ],
+ ];
+
+ /**
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
+ /**
+ * @var string
+ */
+ private $currentMode;
+
+ /**
+ * @param ScopeConfigInterface $scopeConfig
+ */
+ public function __construct(ScopeConfigInterface $scopeConfig)
+ {
+ $this->scopeConfig = $scopeConfig;
+ }
+
+ /**
+ * Return dimension modes configuration.
+ *
+ * @return array
+ */
+ public function getDimensionModes(): array
+ {
+ return $this->modesMapping;
+ }
+
+ /**
+ * Get names of dimensions which used for provided mode.
+ * By default return dimensions for current enabled mode
+ *
+ * @param string|null $mode
+ * @return string[]
+ * @throws \InvalidArgumentException
+ */
+ public function getDimensionConfiguration(string $mode = null): array
+ {
+ if ($mode && !isset($this->modesMapping[$mode])) {
+ throw new \InvalidArgumentException(
+ sprintf('Undefined dimension mode "%s".', $mode)
+ );
+ }
+ return $this->modesMapping[$mode ?? $this->getCurrentMode()];
+ }
+
+ /**
+ * @return string
+ */
+ private function getCurrentMode(): string
+ {
+ if (null === $this->currentMode) {
+ $this->currentMode = $this->scopeConfig->getValue(ModeSwitcherConfiguration::XML_PATH_PRICE_DIMENSIONS_MODE)
+ ?: self::DIMENSION_NONE;
+ }
+
+ return $this->currentMode;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php
new file mode 100644
index 0000000000000..c418f2e1f253b
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcher.php
@@ -0,0 +1,211 @@
+tableMaintainer = $tableMaintainer;
+ $this->dimensionCollectionFactory = $dimensionCollectionFactory;
+ $this->dimensionModeConfiguration = $dimensionModeConfiguration;
+ $this->modeSwitcherConfiguration = $modeSwitcherConfiguration;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getDimensionModes(): DimensionModes
+ {
+ $dimensionsList = [];
+ foreach ($this->dimensionModeConfiguration->getDimensionModes() as $dimension => $modes) {
+ $dimensionsList[] = new DimensionMode($dimension, $modes);
+ }
+
+ return new DimensionModes($dimensionsList);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function switchMode(string $currentMode, string $previousMode)
+ {
+ //Create new tables and move data
+ $this->createTables($currentMode);
+ $this->moveData($currentMode, $previousMode);
+
+ //Change config options
+ $this->modeSwitcherConfiguration->saveMode($currentMode);
+
+ //Delete old tables
+ $this->dropTables($previousMode);
+ }
+
+ /**
+ * Create new tables
+ *
+ * @param string $currentMode
+ *
+ * @return void
+ * @throws \Zend_Db_Exception
+ */
+ public function createTables(string $currentMode)
+ {
+ foreach ($this->getDimensionsArray($currentMode) as $dimensions) {
+ if (!empty($dimensions)) {
+ $this->tableMaintainer->createTablesForDimensions($dimensions);
+ }
+ }
+ }
+
+ /**
+ * Move data from old tables to new
+ *
+ * @param string $currentMode
+ * @param string $previousMode
+ *
+ * @return void
+ */
+ public function moveData(string $currentMode, string $previousMode)
+ {
+ $dimensionsArrayForCurrentMode = $this->getDimensionsArray($currentMode);
+ $dimensionsArrayForPreviousMode = $this->getDimensionsArray($previousMode);
+
+ foreach ($dimensionsArrayForCurrentMode as $dimensionsForCurrentMode) {
+ $newTable = $this->tableMaintainer->getMainTable($dimensionsForCurrentMode);
+ if (empty($dimensionsForCurrentMode)) {
+ // new mode is 'none'
+ foreach ($dimensionsArrayForPreviousMode as $dimensionsForPreviousMode) {
+ $oldTable = $this->tableMaintainer->getMainTable($dimensionsForPreviousMode);
+ $this->insertFromOldTablesToNew($newTable, $oldTable);
+ }
+ } else {
+ // new mode is not 'none'
+ foreach ($dimensionsArrayForPreviousMode as $dimensionsForPreviousMode) {
+ $oldTable = $this->tableMaintainer->getMainTable($dimensionsForPreviousMode);
+ $this->insertFromOldTablesToNew($newTable, $oldTable, $dimensionsForCurrentMode);
+ }
+ }
+ }
+ }
+
+ /**
+ * Drop old tables
+ *
+ * @param string $previousMode
+ *
+ * @return void
+ */
+ public function dropTables(string $previousMode)
+ {
+ foreach ($this->getDimensionsArray($previousMode) as $dimensions) {
+ if (empty($dimensions)) {
+ $this->tableMaintainer->truncateTablesForDimensions($dimensions);
+ } else {
+ $this->tableMaintainer->dropTablesForDimensions($dimensions);
+ }
+ }
+ }
+
+ /**
+ * Get dimensions array
+ *
+ * @param string $mode
+ *
+ * @return \Magento\Framework\Indexer\MultiDimensionProvider
+ */
+ private function getDimensionsArray(string $mode): \Magento\Framework\Indexer\MultiDimensionProvider
+ {
+ if (isset($this->dimensionsArray[$mode])) {
+ return $this->dimensionsArray[$mode];
+ }
+
+ $this->dimensionsArray[$mode] = $this->dimensionCollectionFactory->create($mode);
+
+ return $this->dimensionsArray[$mode];
+ }
+
+ /**
+ * Insert from old tables data to new
+ *
+ * @param string $newTable
+ * @param string $oldTable
+ * @param Dimension[] $dimensions
+ *
+ * @return void
+ */
+ private function insertFromOldTablesToNew(string $newTable, string $oldTable, array $dimensions = [])
+ {
+ $select = $this->tableMaintainer->getConnection()->select()->from($oldTable);
+
+ foreach ($dimensions as $dimension) {
+ if ($dimension->getName() === WebsiteDimensionProvider::DIMENSION_NAME) {
+ $select->where('website_id = ?', $dimension->getValue());
+ }
+ if ($dimension->getName() === CustomerGroupDimensionProvider::DIMENSION_NAME) {
+ $select->where('customer_group_id = ?', $dimension->getValue());
+ }
+ }
+ $this->tableMaintainer->getConnection()->query(
+ $this->tableMaintainer->getConnection()->insertFromSelect(
+ $select,
+ $newTable,
+ [],
+ \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ )
+ );
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php
new file mode 100644
index 0000000000000..ae00ec51f2960
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/ModeSwitcherConfiguration.php
@@ -0,0 +1,70 @@
+configWriter = $configWriter;
+ $this->cacheTypeList = $cacheTypeList;
+ $this->indexer = $indexer;
+ }
+
+ /**
+ * Save switcher mode and invalidate reindex.
+ *
+ * @param string $mode
+ * @return void
+ * @throws \InvalidArgumentException
+ */
+ public function saveMode(string $mode)
+ {
+ //Change config options
+ $this->configWriter->saveConfig(self::XML_PATH_PRICE_DIMENSIONS_MODE, $mode);
+ $this->cacheTypeList->cleanType('config');
+ $this->indexer->load(\Magento\Catalog\Model\Indexer\Product\Price\Processor::INDEXER_ID);
+ $this->indexer->invalidate();
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php
index 32b2db8a7008c..9b99ee8c8dc8c 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/CustomerGroup.php
@@ -5,9 +5,15 @@
*/
namespace Magento\Catalog\Model\Indexer\Product\Price\Plugin;
+use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration;
use Magento\Customer\Api\GroupRepositoryInterface;
use Magento\Customer\Api\Data\GroupInterface;
-use \Magento\Catalog\Model\Indexer\Product\Price\UpdateIndexInterface;
+use Magento\Catalog\Model\Indexer\Product\Price\UpdateIndexInterface;
+use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer;
+use Magento\Framework\Indexer\Dimension;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
+use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
class CustomerGroup
{
@@ -17,14 +23,46 @@ class CustomerGroup
private $updateIndex;
/**
- * Constructor
+ * @var TableMaintainer
+ */
+ private $tableMaintainer;
+
+ /**
+ * DimensionFactory
*
+ * @var DimensionFactory
+ */
+ private $dimensionFactory;
+
+ /**
+ * @var DimensionModeConfiguration
+ */
+ private $dimensionModeConfiguration;
+
+ /**
+ * @var WebsiteDimensionProvider
+ */
+ private $websiteDimensionProvider;
+
+ /**
* @param UpdateIndexInterface $updateIndex
+ * @param TableMaintainer $tableMaintainer
+ * @param DimensionFactory $dimensionFactory
+ * @param DimensionModeConfiguration $dimensionModeConfiguration
+ * @param WebsiteDimensionProvider $websiteDimensionProvider
*/
public function __construct(
- UpdateIndexInterface $updateIndex
+ UpdateIndexInterface $updateIndex,
+ TableMaintainer $tableMaintainer,
+ DimensionFactory $dimensionFactory,
+ DimensionModeConfiguration $dimensionModeConfiguration,
+ WebsiteDimensionProvider $websiteDimensionProvider
) {
$this->updateIndex = $updateIndex;
+ $this->tableMaintainer = $tableMaintainer;
+ $this->dimensionFactory = $dimensionFactory;
+ $this->dimensionModeConfiguration = $dimensionModeConfiguration;
+ $this->websiteDimensionProvider = $websiteDimensionProvider;
}
/**
@@ -32,7 +70,8 @@ public function __construct(
*
* @param GroupRepositoryInterface $subject
* @param \Closure $proceed
- * @param GroupInterface $result
+ * @param GroupInterface $group
+ *
* @return GroupInterface
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
@@ -43,7 +82,64 @@ public function aroundSave(
) {
$isGroupNew = !$group->getId();
$group = $proceed($group);
+ if ($isGroupNew) {
+ foreach ($this->getAffectedDimensions((string)$group->getId()) as $dimensions) {
+ $this->tableMaintainer->createTablesForDimensions($dimensions);
+ }
+ }
$this->updateIndex->update($group, $isGroupNew);
return $group;
}
+
+ /**
+ * Update price index after customer group deleted
+ *
+ * @param GroupRepositoryInterface $subject
+ * @param bool $result
+ * @param string $groupId
+ *
+ * @return bool
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterDeleteById(GroupRepositoryInterface $subject, bool $result, string $groupId)
+ {
+ foreach ($this->getAffectedDimensions($groupId) as $dimensions) {
+ $this->tableMaintainer->dropTablesForDimensions($dimensions);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get affected dimensions
+ *
+ * @param string $groupId
+ * @return Dimension[][]
+ */
+ private function getAffectedDimensions(string $groupId): array
+ {
+ $currentDimensions = $this->dimensionModeConfiguration->getDimensionConfiguration();
+ // do not return dimensions if Customer Group dimension is not present in configuration
+ if (!in_array(CustomerGroupDimensionProvider::DIMENSION_NAME, $currentDimensions, true)) {
+ return [];
+ }
+ $customerGroupDimension = $this->dimensionFactory->create(
+ CustomerGroupDimensionProvider::DIMENSION_NAME,
+ $groupId
+ );
+
+ $dimensions = [];
+ if (in_array(WebsiteDimensionProvider::DIMENSION_NAME, $currentDimensions, true)) {
+ foreach ($this->websiteDimensionProvider as $websiteDimension) {
+ $dimensions[] = [
+ $customerGroupDimension,
+ $websiteDimension
+ ];
+ }
+ } else {
+ $dimensions[] = [$customerGroupDimension];
+ }
+
+ return $dimensions;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/TableResolver.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/TableResolver.php
new file mode 100644
index 0000000000000..fbeec22783090
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/TableResolver.php
@@ -0,0 +1,140 @@
+priceTableResolver = $priceTableResolver;
+ $this->storeManager = $storeManager;
+ $this->httpContext = $context;
+ $this->dimensionFactory = $dimensionFactory;
+ $this->dimensionModeConfiguration = $dimensionModeConfiguration;
+ }
+
+ /**
+ * Replacing catalog_product_index_price table name on the table name segmented per dimension.
+ *
+ * @param ResourceConnection $subject
+ * @param string $result
+ * @param string|string[] $tableName
+ * @return string
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterGetTableName(
+ ResourceConnection $subject,
+ string $result,
+ $tableName
+ ) {
+ if (!is_array($tableName)
+ && $tableName === 'catalog_product_index_price'
+ && $this->dimensionModeConfiguration->getDimensionConfiguration()
+ ) {
+ return $this->priceTableResolver->resolve('catalog_product_index_price', $this->getDimensions());
+ }
+
+ return $result;
+ }
+
+ /**
+ * @return Dimension[]
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ private function getDimensions(): array
+ {
+ $dimensions = [];
+ foreach ($this->dimensionModeConfiguration->getDimensionConfiguration() as $dimensionName) {
+ if ($dimensionName === WebsiteDimensionProvider::DIMENSION_NAME) {
+ $dimensions[] = $this->createDimensionFromWebsite();
+ }
+ if ($dimensionName === CustomerGroupDimensionProvider::DIMENSION_NAME) {
+ $dimensions[] = $this->createDimensionFromCustomerGroup();
+ }
+ }
+
+ return $dimensions;
+ }
+
+ /**
+ * @return Dimension
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ private function createDimensionFromWebsite(): Dimension
+ {
+ $storeKey = $this->httpContext->getValue(StoreManagerInterface::CONTEXT_STORE);
+ return $this->dimensionFactory->create(
+ WebsiteDimensionProvider::DIMENSION_NAME,
+ (string)$this->storeManager->getStore($storeKey)->getWebsiteId()
+ );
+ }
+
+ /**
+ * @return Dimension
+ */
+ private function createDimensionFromCustomerGroup(): Dimension
+ {
+ return $this->dimensionFactory->create(
+ CustomerGroupDimensionProvider::DIMENSION_NAME,
+ (string)$this->httpContext->getValue(CustomerContext::CONTEXT_GROUP)
+ );
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/Website.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/Website.php
index 269515e292e17..4831680f07c33 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/Website.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Plugin/Website.php
@@ -5,33 +5,128 @@
*/
namespace Magento\Catalog\Model\Indexer\Product\Price\Plugin;
+use Magento\Catalog\Model\Indexer\Product\Price\DimensionModeConfiguration;
+use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer;
+use Magento\Framework\Indexer\Dimension;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
+use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
+use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
+use Magento\Framework\Model\AbstractModel;
+
class Website
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
+ * @var TableMaintainer
+ */
+ private $tableMaintainer;
+
+ /**
+ * DimensionFactory
+ *
+ * @var DimensionFactory
+ */
+ private $dimensionFactory;
+
+ /**
+ * @var DimensionModeConfiguration
*/
- protected $_processor;
+ private $dimensionModeConfiguration;
/**
- * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $processor
+ * @var CustomerGroupDimensionProvider
*/
- public function __construct(\Magento\Catalog\Model\Indexer\Product\Price\Processor $processor)
+ private $customerGroupDimensionProvider;
+
+ /**
+ * @param TableMaintainer $tableMaintainer
+ * @param DimensionFactory $dimensionFactory
+ * @param DimensionModeConfiguration $dimensionModeConfiguration
+ * @param CustomerGroupDimensionProvider $customerGroupDimensionProvider
+ */
+ public function __construct(
+ TableMaintainer $tableMaintainer,
+ DimensionFactory $dimensionFactory,
+ DimensionModeConfiguration $dimensionModeConfiguration,
+ CustomerGroupDimensionProvider $customerGroupDimensionProvider
+ ) {
+ $this->tableMaintainer = $tableMaintainer;
+ $this->dimensionFactory = $dimensionFactory;
+ $this->dimensionModeConfiguration = $dimensionModeConfiguration;
+ $this->customerGroupDimensionProvider = $customerGroupDimensionProvider;
+ }
+
+ /**
+ * Update price index after website deleted
+ *
+ * @param AbstractDb $subject
+ * @param AbstractDb $objectResource
+ * @param AbstractModel $website
+ *
+ * @return AbstractDb
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterDelete(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $website)
{
- $this->_processor = $processor;
+ foreach ($this->getAffectedDimensions($website->getId()) as $dimensions) {
+ $this->tableMaintainer->dropTablesForDimensions($dimensions);
+ }
+
+ return $objectResource;
}
/**
- * Invalidate price indexer
+ * Update price index after website created
*
- * @param \Magento\Store\Model\ResourceModel\Website $subject
- * @param \Magento\Store\Model\ResourceModel\Website $result
- * @return \Magento\Store\Model\ResourceModel\Website
+ * @param AbstractDb $subject
+ * @param AbstractDb $objectResource
+ * @param AbstractModel $website
*
+ * @return AbstractDb
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function afterDelete(\Magento\Store\Model\ResourceModel\Website $subject, $result)
+ public function afterSave(AbstractDb $subject, AbstractDb $objectResource, AbstractModel $website)
+ {
+ if ($website->isObjectNew()) {
+ foreach ($this->getAffectedDimensions($website->getId()) as $dimensions) {
+ $this->tableMaintainer->createTablesForDimensions($dimensions);
+ }
+ }
+
+ return $objectResource;
+ }
+
+ /**
+ * Get affected dimensions
+ *
+ * @param string $websiteId
+ *
+ * @return Dimension[][]
+ */
+ private function getAffectedDimensions(string $websiteId): array
{
- $this->_processor->markIndexerAsInvalid();
- return $result;
+ $currentDimensions = $this->dimensionModeConfiguration->getDimensionConfiguration();
+ // do not return dimensions if Website dimension is not present in configuration
+ if (!in_array(WebsiteDimensionProvider::DIMENSION_NAME, $currentDimensions, true)) {
+ return [];
+ }
+ $websiteDimension = $this->dimensionFactory->create(
+ WebsiteDimensionProvider::DIMENSION_NAME,
+ $websiteId
+ );
+
+ $dimensions = [];
+ if (in_array(CustomerGroupDimensionProvider::DIMENSION_NAME, $currentDimensions, true)) {
+ foreach ($this->customerGroupDimensionProvider as $customerGroupDimension) {
+ $dimensions[] = [
+ $customerGroupDimension,
+ $websiteDimension
+ ];
+ }
+ } else {
+ $dimensions[] = [$websiteDimension];
+ }
+
+ return $dimensions;
}
}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/PriceTableResolver.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/PriceTableResolver.php
new file mode 100644
index 0000000000000..eeaa731aac686
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/PriceTableResolver.php
@@ -0,0 +1,78 @@
+indexScopeResolver = $indexScopeResolver;
+ $this->dimensionModeConfiguration = $dimensionModeConfiguration;
+ }
+
+ /**
+ * Return price table name based on dimension
+ * @param string $index
+ * @param array $dimensions
+ * @return string
+ */
+ public function resolve($index, array $dimensions)
+ {
+ if ($index === 'catalog_product_index_price') {
+ $dimensions = $this->filterDimensions($dimensions);
+ }
+ return $this->indexScopeResolver->resolve($index, $dimensions);
+ }
+
+ /**
+ * @param Dimension[] $dimensions
+ * @return array
+ * @throws \Exception
+ */
+ private function filterDimensions($dimensions): array
+ {
+ $existDimensions = [];
+ $currentDimensions = $this->dimensionModeConfiguration->getDimensionConfiguration();
+ foreach ($dimensions as $dimension) {
+ if ((string)$dimension->getValue() === '') {
+ throw new \InvalidArgumentException(
+ sprintf('Dimension value of "%s" can not be empty', $dimension->getName())
+ );
+ }
+ if (in_array($dimension->getName(), $currentDimensions, true)) {
+ $existDimensions[] = $dimension;
+ }
+ }
+
+ return $existDimensions;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php
new file mode 100644
index 0000000000000..e3077baaeb7a6
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/TableMaintainer.php
@@ -0,0 +1,280 @@
+resource = $resource;
+ $this->tableResolver = $tableResolver;
+ $this->connectionName = $connectionName;
+ }
+
+ /**
+ * Get connection for work with price indexer
+ *
+ * @return AdapterInterface
+ */
+ public function getConnection(): AdapterInterface
+ {
+ if (null === $this->connection) {
+ $this->connection = $this->resource->getConnection($this->connectionName);
+ }
+ return $this->connection;
+ }
+
+ /**
+ * Return validated table name
+ *
+ * @param string $table
+ * @return string
+ */
+ private function getTable(string $table): string
+ {
+ return $this->resource->getTableName($table);
+ }
+
+ /**
+ * Create table based on main table
+ *
+ * @param string $mainTableName
+ * @param string $newTableName
+ *
+ * @return void
+ *
+ * @throws \Zend_Db_Exception
+ */
+ private function createTable(string $mainTableName, string $newTableName)
+ {
+ if (!$this->getConnection()->isTableExists($newTableName)) {
+ $this->getConnection()->createTable(
+ $this->getConnection()->createTableByDdl($mainTableName, $newTableName)
+ );
+ }
+ }
+
+ /**
+ * Drop table
+ *
+ * @param string $tableName
+ *
+ * @return void
+ */
+ private function dropTable(string $tableName)
+ {
+ if ($this->getConnection()->isTableExists($tableName)) {
+ $this->getConnection()->dropTable($tableName);
+ }
+ }
+
+ /**
+ * Truncate table
+ *
+ * @param string $tableName
+ *
+ * @return void
+ */
+ private function truncateTable(string $tableName)
+ {
+ if ($this->getConnection()->isTableExists($tableName)) {
+ $this->getConnection()->truncateTable($tableName);
+ }
+ }
+
+ /**
+ * Get array key for tmp table
+ *
+ * @param Dimension[] $dimensions
+ *
+ * @return string
+ */
+ private function getArrayKeyForTmpTable(array $dimensions): string
+ {
+ $key = 'temp';
+ foreach ($dimensions as $dimension) {
+ $key .= $dimension->getName() . '_' . $dimension->getValue();
+ }
+ return $key;
+ }
+
+ /**
+ * Return main index table name
+ *
+ * @param Dimension[] $dimensions
+ *
+ * @return string
+ */
+ public function getMainTable(array $dimensions): string
+ {
+ return $this->tableResolver->resolve(self::MAIN_INDEX_TABLE, $dimensions);
+ }
+
+ /**
+ * Create main and replica index tables for dimensions
+ *
+ * @param Dimension[] $dimensions
+ *
+ * @return void
+ *
+ * @throws \Zend_Db_Exception
+ */
+ public function createTablesForDimensions(array $dimensions)
+ {
+ $mainTableName = $this->getMainTable($dimensions);
+ //Create index table for dimensions based on main replica table
+ //Using main replica table is necessary for backward capability and TableResolver plugin work
+ $this->createTable(
+ $this->getTable(self::MAIN_INDEX_TABLE . $this->additionalTableSuffix),
+ $mainTableName
+ );
+
+ $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix;
+ //Create replica table for dimensions based on main replica table
+ $this->createTable(
+ $this->getTable(self::MAIN_INDEX_TABLE . $this->additionalTableSuffix),
+ $mainReplicaTableName
+ );
+ }
+
+ /**
+ * Drop main and replica index tables for dimensions
+ *
+ * @param Dimension[] $dimensions
+ *
+ * @return void
+ */
+ public function dropTablesForDimensions(array $dimensions)
+ {
+ $mainTableName = $this->getMainTable($dimensions);
+ $this->dropTable($mainTableName);
+
+ $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix;
+ $this->dropTable($mainReplicaTableName);
+ }
+
+ /**
+ * Truncate main and replica index tables for dimensions
+ *
+ * @param Dimension[] $dimensions
+ *
+ * @return void
+ */
+ public function truncateTablesForDimensions(array $dimensions)
+ {
+ $mainTableName = $this->getMainTable($dimensions);
+ $this->truncateTable($mainTableName);
+
+ $mainReplicaTableName = $this->getMainTable($dimensions) . $this->additionalTableSuffix;
+ $this->truncateTable($mainReplicaTableName);
+ }
+
+ /**
+ * Return replica index table name
+ *
+ * @param Dimension[] $dimensions
+ *
+ * @return string
+ */
+ public function getMainReplicaTable(array $dimensions): string
+ {
+ return $this->getMainTable($dimensions) . $this->additionalTableSuffix;
+ }
+
+ /**
+ * Create temporary index table for dimensions
+ *
+ * @param Dimension[] $dimensions
+ *
+ * @return void
+ */
+ public function createMainTmpTable(array $dimensions)
+ {
+ // Create temporary table based on template table catalog_product_index_price_tmp without indexes
+ $templateTableName = $this->resource->getTableName(self::MAIN_INDEX_TABLE . '_tmp');
+ $temporaryTableName = $this->getMainTable($dimensions) . $this->tmpTableSuffix;
+ $this->getConnection()->createTemporaryTableLike($temporaryTableName, $templateTableName, true);
+ $this->mainTmpTable[$this->getArrayKeyForTmpTable($dimensions)] = $temporaryTableName;
+ }
+
+ /**
+ * Return temporary index table name
+ *
+ * @param Dimension[] $dimensions
+ *
+ * @return string
+ *
+ * @throws \LogicException
+ */
+ public function getMainTmpTable(array $dimensions): string
+ {
+ $cacheKey = $this->getArrayKeyForTmpTable($dimensions);
+ if (!isset($this->mainTmpTable[$cacheKey])) {
+ throw new \LogicException(
+ sprintf('Temporary table for provided dimensions "%s" does not exist', $cacheKey)
+ );
+ }
+ return $this->mainTmpTable[$cacheKey];
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php
index a4db630f0234b..d21a8666ec0ac 100644
--- a/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php
+++ b/app/code/Magento/Catalog/Model/Layer/Filter/AbstractFilter.php
@@ -241,7 +241,7 @@ protected function _createItem($label, $value, $count = 0)
}
/**
- * Get all product ids from from collection with applied filters
+ * Get all product ids from collection with applied filters
*
* @return array
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
index 270a2f229678b..f6d3ca36c1e1e 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Repository.php
@@ -119,16 +119,7 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib
$attribute->setIsUserDefined($existingModel->getIsUserDefined());
$attribute->setFrontendInput($existingModel->getFrontendInput());
- if (is_array($attribute->getFrontendLabels())) {
- $defaultFrontendLabel = $attribute->getDefaultFrontendLabel();
- $frontendLabel[0] = !empty($defaultFrontendLabel)
- ? $defaultFrontendLabel
- : $existingModel->getDefaultFrontendLabel();
- foreach ($attribute->getFrontendLabels() as $item) {
- $frontendLabel[$item->getStoreId()] = $item->getLabel();
- }
- $attribute->setDefaultFrontendLabel($frontendLabel);
- }
+ $this->updateDefaultFrontendLabel($attribute, $existingModel);
} else {
$attribute->setAttributeId(null);
@@ -136,22 +127,10 @@ public function save(\Magento\Catalog\Api\Data\ProductAttributeInterface $attrib
throw InputException::requiredField('frontend_label');
}
- $frontendLabels = [];
- if ($attribute->getDefaultFrontendLabel()) {
- $frontendLabels[0] = $attribute->getDefaultFrontendLabel();
- }
- if ($attribute->getFrontendLabels() && is_array($attribute->getFrontendLabels())) {
- foreach ($attribute->getFrontendLabels() as $label) {
- $frontendLabels[$label->getStoreId()] = $label->getLabel();
- }
- if (!isset($frontendLabels[0]) || !$frontendLabels[0]) {
- throw InputException::invalidFieldValue('frontend_label', null);
- }
+ $frontendLabel = $this->updateDefaultFrontendLabel($attribute, null);
- $attribute->setDefaultFrontendLabel($frontendLabels);
- }
$attribute->setAttributeCode(
- $attribute->getAttributeCode() ?: $this->generateCode($frontendLabels[0])
+ $attribute->getAttributeCode() ?: $this->generateCode($frontendLabel)
);
$this->validateCode($attribute->getAttributeCode());
$this->validateFrontendInput($attribute->getFrontendInput());
@@ -275,4 +254,52 @@ protected function validateFrontendInput($frontendInput)
throw InputException::invalidFieldValue('frontend_input', $frontendInput);
}
}
+
+ /**
+ * This method sets default frontend value using given default frontend value or frontend value from admin store
+ * if default frontend value is not presented.
+ * If both default frontend label and admin store frontend label are not given it throws exception
+ * for attribute creation process or sets existing attribute value for attribute update action.
+ *
+ * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
+ * @param \Magento\Catalog\Api\Data\ProductAttributeInterface|null $existingModel
+ * @return string|null
+ * @throws InputException
+ */
+ private function updateDefaultFrontendLabel($attribute, $existingModel)
+ {
+ $frontendLabel = $attribute->getDefaultFrontendLabel();
+ if (empty($frontendLabel)) {
+ $frontendLabel = $this->extractAdminStoreFrontendLabel($attribute);
+ if (empty($frontendLabel)) {
+ if ($existingModel) {
+ $frontendLabel = $existingModel->getDefaultFrontendLabel();
+ } else {
+ throw InputException::invalidFieldValue('frontend_label', null);
+ }
+ }
+ $attribute->setDefaultFrontendLabel($frontendLabel);
+ }
+ return $frontendLabel;
+ }
+
+ /**
+ * This method extracts frontend label from FrontendLabel object for admin store.
+ *
+ * @param \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute
+ * @return string|null
+ */
+ private function extractAdminStoreFrontendLabel($attribute)
+ {
+ $frontendLabel = [];
+ $frontendLabels = $attribute->getFrontendLabels();
+ if (isset($frontendLabels[0])
+ && $frontendLabels[0] instanceof \Magento\Eav\Api\Data\AttributeFrontendLabelInterface
+ ) {
+ foreach ($attribute->getFrontendLabels() as $label) {
+ $frontendLabel[$label->getStoreId()] = $label->getLabel();
+ }
+ }
+ return $frontendLabel[0] ?? null;
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php
new file mode 100644
index 0000000000000..68d0877c6cd66
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverComposite.php
@@ -0,0 +1,61 @@
+itemResolvers = $itemResolvers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFinalProduct(ItemInterface $item) : ProductInterface
+ {
+ $finalProduct = $item->getProduct();
+ foreach ($this->itemResolvers as $resolver) {
+ $resolvedProduct = $this->getItemResolverInstance($resolver)->getFinalProduct($item);
+ if ($resolvedProduct !== $finalProduct) {
+ $finalProduct = $resolvedProduct;
+ break;
+ }
+ }
+ return $finalProduct;
+ }
+
+ /**
+ * Get the instance of the item resolver by class name.
+ *
+ * @param string $className
+ * @return ItemResolverInterface
+ */
+ private function getItemResolverInstance(string $className) : ItemResolverInterface
+ {
+ if (!isset($this->itemResolversInstances[$className])) {
+ $this->itemResolversInstances[$className] = ObjectManager::getInstance()->get($className);
+ }
+ return $this->itemResolversInstances[$className];
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverInterface.php b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverInterface.php
new file mode 100644
index 0000000000000..35c0a7835cb6c
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Product/Configuration/Item/ItemResolverInterface.php
@@ -0,0 +1,26 @@
+productOptionValue = $productOptionValue;
$this->optionTypeFactory = $optionFactory;
$this->validatorPool = $validatorPool;
$this->string = $string;
+ $this->customOptionValuesFactory = $customOptionValuesFactory ?:
+ \Magento\Framework\App\ObjectManager::getInstance()->get(ProductCustomOptionValuesInterfaceFactory::class);
+
parent::__construct(
$context,
$registry,
@@ -390,20 +401,21 @@ public function beforeSave()
*/
public function afterSave()
{
- $this->getValueInstance()->unsetValues();
$values = $this->getValues() ?: $this->getData('values');
if (is_array($values)) {
foreach ($values as $value) {
- if ($value instanceof \Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface) {
+ if ($value instanceof ProductCustomOptionValuesInterface) {
$data = $value->getData();
} else {
$data = $value;
}
- $this->getValueInstance()->addValue($data);
- }
- $this->getValueInstance()->setOption($this)->saveValues();
- } elseif ($this->getGroupByType($this->getType()) == self::OPTION_GROUP_SELECT) {
+ $this->customOptionValuesFactory->create()
+ ->addValue($data)
+ ->setOption($this)
+ ->saveValues();
+ }
+ } elseif ($this->getGroupByType($this->getType()) === self::OPTION_GROUP_SELECT) {
throw new LocalizedException(__('Select type options required values rows.'));
}
@@ -804,7 +816,7 @@ public function setImageSizeY($imageSizeY)
}
/**
- * @param \Magento\Catalog\Api\Data\ProductCustomOptionValuesInterface[] $values
+ * @param ProductCustomOptionValuesInterface[] $values
* @return $this
*/
public function setValues(array $values = null)
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Type/Text.php b/app/code/Magento/Catalog/Model/Product/Option/Type/Text.php
index fd0eae188fea9..9ffe75e513bce 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Type/Text.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Type/Text.php
@@ -84,7 +84,7 @@ public function validateUserValue($values)
*/
public function prepareForCart()
{
- if ($this->getIsValid() && strlen($this->getUserValue()) > 0) {
+ if ($this->getIsValid() && ($this->getUserValue() !== '')) {
return $this->getUserValue();
} else {
return null;
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
index 1e5c7f76d829b..ee508e30cc93e 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/DefaultValidator.php
@@ -106,7 +106,9 @@ protected function isValidOptionTitle($title, $storeId)
if ($storeId > \Magento\Store\Model\Store::DEFAULT_STORE_ID && $title === null) {
return true;
}
- if ($this->isEmpty($title)) {
+
+ // checking whether title is null and is empty string
+ if ($title === null || $title === '') {
return false;
}
@@ -132,7 +134,7 @@ protected function validateOptionType(Option $option)
*/
protected function validateOptionValue(Option $option)
{
- return $this->isInRange($option->getPriceType(), $this->priceTypes) && !$this->isNegative($option->getPrice());
+ return $this->isInRange($option->getPriceType(), $this->priceTypes);
}
/**
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php
index f04ab497e1d4f..44756890b6ed7 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Validator/Select.php
@@ -83,7 +83,7 @@ protected function isValidOptionPrice($priceType, $price, $storeId)
if (!$priceType && !$price) {
return true;
}
- if (!$this->isInRange($priceType, $this->priceTypes) || $this->isNegative($price)) {
+ if (!$this->isInRange($priceType, $this->priceTypes)) {
return false;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Value.php b/app/code/Magento/Catalog/Model/Product/Option/Value.php
index fb7759b210bd9..ebbc060c99edf 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Value.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Value.php
@@ -76,6 +76,7 @@ class Value extends AbstractModel implements \Magento\Catalog\Api\Data\ProductCu
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
+ * @param CustomOptionPriceCalculator|null $customOptionPriceCalculator
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -89,6 +90,7 @@ public function __construct(
$this->_valueCollectionFactory = $valueCollectionFactory;
$this->customOptionPriceCalculator = $customOptionPriceCalculator
?? \Magento\Framework\App\ObjectManager::getInstance()->get(CustomOptionPriceCalculator::class);
+
parent::__construct(
$context,
$registry,
@@ -201,19 +203,15 @@ public function getProduct()
*/
public function saveValues()
{
+ $option = $this->getOption();
+
foreach ($this->getValues() as $value) {
$this->isDeleted(false);
- $this->setData(
- $value
- )->setData(
- 'option_id',
- $this->getOption()->getId()
- )->setData(
- 'store_id',
- $this->getOption()->getStoreId()
- );
-
- if ($this->getData('is_delete') == '1') {
+ $this->setData($value)
+ ->setData('option_id', $option->getId())
+ ->setData('store_id', $option->getStoreId());
+
+ if ((bool) $this->getData('is_delete') === true) {
if ($this->getId()) {
$this->deleteValues($this->getId());
$this->delete();
@@ -222,7 +220,7 @@ public function saveValues()
$this->save();
}
}
- //eof foreach()
+
return $this;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
index d3f0c8be6f649..1b5cf37f6cbb8 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/AbstractType.php
@@ -292,13 +292,7 @@ public function attributesCompare($attributeOne, $attributeTwo)
$sortOne = $attributeOne->getGroupSortPath() * 1000 + $attributeOne->getSortPath() * 0.0001;
$sortTwo = $attributeTwo->getGroupSortPath() * 1000 + $attributeTwo->getSortPath() * 0.0001;
- if ($sortOne > $sortTwo) {
- return 1;
- } elseif ($sortOne < $sortTwo) {
- return -1;
- }
-
- return 0;
+ return $sortOne <=> $sortTwo;
}
/**
diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php
index 4c0122694285d..0dbfda2103471 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository.php
@@ -7,9 +7,11 @@
namespace Magento\Catalog\Model;
+use Magento\Catalog\Api\Data\ProductExtension;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product\Gallery\MimeTypeExtensionMap;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
+use Magento\Eav\Model\Entity\Attribute\Exception as AttributeException;
use Magento\Framework\Api\Data\ImageContentInterface;
use Magento\Framework\Api\Data\ImageContentInterfaceFactory;
use Magento\Framework\Api\ImageContentValidatorInterface;
@@ -22,6 +24,7 @@
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Exception\TemporaryState\CouldNotSaveException as TemporaryCouldNotSaveException;
use Magento\Framework\Exception\StateException;
use Magento\Framework\Exception\ValidatorException;
@@ -306,10 +309,10 @@ protected function getCacheKey($data)
* Add product to internal cache and truncate cache if it has more than cacheLimit elements.
*
* @param string $cacheKey
- * @param \Magento\Catalog\Api\Data\ProductInterface $product
+ * @param ProductInterface $product
* @return void
*/
- private function cacheProduct($cacheKey, \Magento\Catalog\Api\Data\ProductInterface $product)
+ private function cacheProduct($cacheKey, ProductInterface $product)
{
$this->instancesById[$product->getId()][$cacheKey] = $product;
$this->saveProductInLocalCache($product, $cacheKey);
@@ -326,7 +329,7 @@ private function cacheProduct($cacheKey, \Magento\Catalog\Api\Data\ProductInterf
*
* @param array $productData
* @param bool $createNew
- * @return \Magento\Catalog\Api\Data\ProductInterface|Product
+ * @return ProductInterface|Product
* @throws NoSuchEntityException
*/
protected function initializeProductData(array $productData, $createNew)
@@ -414,12 +417,12 @@ protected function processNewMediaGalleryEntry(
/**
* Process product links, creating new links, updating and deleting existing links
*
- * @param \Magento\Catalog\Api\Data\ProductInterface $product
+ * @param ProductInterface $product
* @param \Magento\Catalog\Api\Data\ProductLinkInterface[] $newLinks
* @return $this
* @throws NoSuchEntityException
*/
- private function processLinks(\Magento\Catalog\Api\Data\ProductInterface $product, $newLinks)
+ private function processLinks(ProductInterface $product, $newLinks)
{
if ($newLinks === null) {
// If product links were not specified, don't do anything
@@ -514,13 +517,14 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE
$newEntries = $mediaGalleryEntries;
}
+ $images = (array)$product->getMediaGallery('images');
+ $images = $this->determineImageRoles($product, $images);
+
$this->getMediaGalleryProcessor()->clearMediaAttribute($product, array_keys($product->getMediaAttributes()));
- $images = $product->getMediaGallery('images');
- if ($images) {
- foreach ($images as $image) {
- if (!isset($image['removed']) && !empty($image['types'])) {
- $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']);
- }
+
+ foreach ($images as $image) {
+ if (!isset($image['removed']) && !empty($image['types'])) {
+ $this->getMediaGalleryProcessor()->setMediaAttribute($product, $image['types'], $image['file']);
}
}
@@ -547,11 +551,11 @@ protected function processMediaGallery(ProductInterface $product, $mediaGalleryE
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
- public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveOptions = false)
+ public function save(ProductInterface $product, $saveOptions = false)
{
$tierPrices = $product->getData('tier_price');
@@ -565,12 +569,18 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO
if (!$product->hasData(Product::STATUS)) {
$product->setStatus($existingProduct->getStatus());
}
+
+ /** @var ProductExtension $extensionAttributes */
+ $extensionAttributes = $product->getExtensionAttributes();
+ if (empty($extensionAttributes->__toArray())) {
+ $product->setExtensionAttributes($existingProduct->getExtensionAttributes());
+ }
} catch (NoSuchEntityException $e) {
$existingProduct = null;
}
$productDataArray = $this->extensibleDataObjectConverter
- ->toNestedArray($product, [], \Magento\Catalog\Api\Data\ProductInterface::class);
+ ->toNestedArray($product, [], ProductInterface::class);
$productDataArray = array_replace($productDataArray, $product->getData());
$ignoreLinksFlag = $product->getData('ignore_links_flag');
$productLinks = null;
@@ -596,47 +606,11 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO
);
}
- try {
- if ($tierPrices !== null) {
- $product->setData('tier_price', $tierPrices);
- }
- $this->removeProductFromLocalCache($product->getSku());
- unset($this->instancesById[$product->getId()]);
- $this->resourceModel->save($product);
- } catch (ConnectionException $exception) {
- throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException(
- __('Database connection error'),
- $exception,
- $exception->getCode()
- );
- } catch (DeadlockException $exception) {
- throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException(
- __('Database deadlock found when trying to get lock'),
- $exception,
- $exception->getCode()
- );
- } catch (LockWaitException $exception) {
- throw new \Magento\Framework\Exception\TemporaryState\CouldNotSaveException(
- __('Database lock wait timeout exceeded'),
- $exception,
- $exception->getCode()
- );
- } catch (\Magento\Eav\Model\Entity\Attribute\Exception $exception) {
- throw \Magento\Framework\Exception\InputException::invalidFieldValue(
- $exception->getAttributeCode(),
- $product->getData($exception->getAttributeCode()),
- $exception
- );
- } catch (ValidatorException $e) {
- throw new CouldNotSaveException(__($e->getMessage()));
- } catch (LocalizedException $e) {
- throw $e;
- } catch (\Exception $e) {
- throw new \Magento\Framework\Exception\CouldNotSaveException(
- __('The product was unable to be saved. Please try again.'),
- $e
- );
+ if ($tierPrices !== null) {
+ $product->setData('tier_price', $tierPrices);
}
+
+ $this->saveProduct($product);
$this->removeProductFromLocalCache($product->getSku());
unset($this->instancesById[$product->getId()]);
@@ -646,7 +620,7 @@ public function save(\Magento\Catalog\Api\Data\ProductInterface $product, $saveO
/**
* {@inheritdoc}
*/
- public function delete(\Magento\Catalog\Api\Data\ProductInterface $product)
+ public function delete(ProductInterface $product)
{
$sku = $product->getSku();
$productId = $product->getId();
@@ -758,6 +732,32 @@ public function cleanCache()
$this->instancesById = null;
}
+ /**
+ * Ascertain image roles, if they are not set against the gallery entries
+ *
+ * @param ProductInterface $product
+ * @param array $images
+ * @return array
+ */
+ private function determineImageRoles(ProductInterface $product, array $images) : array
+ {
+ $imagesWithRoles = [];
+ foreach ($images as $image) {
+ if (!isset($image['types'])) {
+ $image['types'] = [];
+ if (isset($image['file'])) {
+ foreach (array_keys($product->getMediaAttributes()) as $attribute) {
+ if ($image['file'] == $product->getData($attribute)) {
+ $image['types'][] = $attribute;
+ }
+ }
+ }
+ }
+ $imagesWithRoles[] = $image;
+ }
+ return $imagesWithRoles;
+ }
+
/**
* @return Product\Gallery\Processor
*/
@@ -835,4 +835,55 @@ private function prepareSku(string $sku): string
{
return mb_strtolower(trim($sku));
}
+
+ /**
+ * Save product resource model.
+ *
+ * @param ProductInterface|Product $product
+ * @throws TemporaryCouldNotSaveException
+ * @throws InputException
+ * @throws CouldNotSaveException
+ * @throws LocalizedException
+ */
+ private function saveProduct($product): void
+ {
+ try {
+ $this->removeProductFromLocalCache($product->getSku());
+ unset($this->instancesById[$product->getId()]);
+ $this->resourceModel->save($product);
+ } catch (ConnectionException $exception) {
+ throw new TemporaryCouldNotSaveException(
+ __('Database connection error'),
+ $exception,
+ $exception->getCode()
+ );
+ } catch (DeadlockException $exception) {
+ throw new TemporaryCouldNotSaveException(
+ __('Database deadlock found when trying to get lock'),
+ $exception,
+ $exception->getCode()
+ );
+ } catch (LockWaitException $exception) {
+ throw new TemporaryCouldNotSaveException(
+ __('Database lock wait timeout exceeded'),
+ $exception,
+ $exception->getCode()
+ );
+ } catch (AttributeException $exception) {
+ throw InputException::invalidFieldValue(
+ $exception->getAttributeCode(),
+ $product->getData($exception->getAttributeCode()),
+ $exception
+ );
+ } catch (ValidatorException $e) {
+ throw new CouldNotSaveException(__($e->getMessage()));
+ } catch (LocalizedException $e) {
+ throw $e;
+ } catch (\Exception $e) {
+ throw new CouldNotSaveException(
+ __('The product was unable to be saved. Please try again.'),
+ $e
+ );
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category.php b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
index fa68ae3f865ef..9de0e8a849046 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category.php
@@ -490,7 +490,7 @@ public function getProductsPosition($category)
}
/**
- * Get chlden categories count
+ * Get children categories count
*
* @param int $categoryId
* @return int
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php b/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php
index bed129e19168f..3699e29f8201a 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Layer/Filter/Price.php
@@ -5,6 +5,15 @@
*/
namespace Magento\Catalog\Model\ResourceModel\Layer\Filter;
+use Magento\Framework\App\Http\Context;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Search\Request\IndexScopeResolverInterface;
+use Magento\Store\Model\StoreManagerInterface;
+use Magento\Customer\Model\Context as CustomerContext;
+use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
+use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
+
/**
* Catalog Layer Price Filter resource model
*
@@ -41,6 +50,21 @@ class Price extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
*/
private $storeManager;
+ /**
+ * @var IndexScopeResolverInterface|null
+ */
+ private $priceTableResolver;
+
+ /**
+ * @var Context
+ */
+ private $httpContext;
+
+ /**
+ * @var DimensionFactory|null
+ */
+ private $dimensionFactory;
+
/**
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
* @param \Magento\Framework\Event\ManagerInterface $eventManager
@@ -48,6 +72,9 @@ class Price extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
* @param \Magento\Customer\Model\Session $session
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param null $connectionName
+ * @param IndexScopeResolverInterface|null $priceTableResolver
+ * @param Context|null $httpContext
+ * @param DimensionFactory|null $dimensionFactory
*/
public function __construct(
\Magento\Framework\Model\ResourceModel\Db\Context $context,
@@ -55,12 +82,19 @@ public function __construct(
\Magento\Catalog\Model\Layer\Resolver $layerResolver,
\Magento\Customer\Model\Session $session,
\Magento\Store\Model\StoreManagerInterface $storeManager,
- $connectionName = null
+ $connectionName = null,
+ IndexScopeResolverInterface $priceTableResolver = null,
+ Context $httpContext = null,
+ DimensionFactory $dimensionFactory = null
) {
$this->layer = $layerResolver->get();
$this->session = $session;
$this->storeManager = $storeManager;
$this->_eventManager = $eventManager;
+ $this->priceTableResolver = $priceTableResolver
+ ?? ObjectManager::getInstance()->get(IndexScopeResolverInterface::class);
+ $this->httpContext = $httpContext ?? ObjectManager::getInstance()->get(Context::class);
+ $this->dimensionFactory = $dimensionFactory ?? ObjectManager::getInstance()->get(DimensionFactory::class);
parent::__construct($context, $connectionName);
}
@@ -118,11 +152,8 @@ protected function _getSelect()
// remove join with main table
$fromPart = $select->getPart(\Magento\Framework\DB\Select::FROM);
- if (!isset(
- $fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::INDEX_TABLE_ALIAS]
- ) || !isset(
- $fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::MAIN_TABLE_ALIAS]
- )
+ if (!isset($fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::INDEX_TABLE_ALIAS]) ||
+ !isset($fromPart[\Magento\Catalog\Model\ResourceModel\Product\Collection::MAIN_TABLE_ALIAS])
) {
return $select;
}
@@ -376,6 +407,30 @@ protected function _construct()
$this->_init('catalog_product_index_price', 'entity_id');
}
+ /**
+ * {@inheritdoc}
+ * @return string
+ */
+ public function getMainTable()
+ {
+ $storeKey = $this->httpContext->getValue(StoreManagerInterface::CONTEXT_STORE);
+ $priceTableName = $this->priceTableResolver->resolve(
+ 'catalog_product_index_price',
+ [
+ $this->dimensionFactory->create(
+ WebsiteDimensionProvider::DIMENSION_NAME,
+ (string)$this->storeManager->getStore($storeKey)->getWebsiteId()
+ ),
+ $this->dimensionFactory->create(
+ CustomerGroupDimensionProvider::DIMENSION_NAME,
+ (string)$this->httpContext->getValue(CustomerContext::CONTEXT_GROUP)
+ )
+ ]
+ );
+
+ return $this->getTable($priceTableName);
+ }
+
/**
* Retrieve joined price index table alias
*
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
index 9b87515450a12..4384effc4e359 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -12,11 +12,15 @@
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
use Magento\CatalogUrlRewrite\Model\ProductUrlRewriteGenerator;
use Magento\Customer\Api\GroupManagementInterface;
+use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
use Magento\Store\Model\Store;
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Framework\Indexer\DimensionFactory;
/**
* Product collection
@@ -276,9 +280,18 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
*/
private $tableMaintainer;
+ /**
+ * @var PriceTableResolver
+ */
+ private $priceTableResolver;
+
+ /**
+ * @var DimensionFactory
+ */
+ private $dimensionFactory;
+
/**
* Collection constructor
- *
* @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
* @param \Psr\Log\LoggerInterface $logger
* @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
@@ -302,7 +315,8 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
* @param ProductLimitationFactory|null $productLimitationFactory
* @param MetadataPool|null $metadataPool
* @param TableMaintainer|null $tableMaintainer
- *
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -328,7 +342,9 @@ public function __construct(
\Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
ProductLimitationFactory $productLimitationFactory = null,
MetadataPool $metadataPool = null,
- TableMaintainer $tableMaintainer = null
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null
) {
$this->moduleManager = $moduleManager;
$this->_catalogProductFlatState = $catalogProductFlatState;
@@ -359,6 +375,9 @@ public function __construct(
$connection
);
$this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class);
+ $this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get(PriceTableResolver::class);
+ $this->dimensionFactory = $dimensionFactory
+ ?: ObjectManager::getInstance()->get(DimensionFactory::class);
}
/**
@@ -913,7 +932,7 @@ private function mapConditionType($conditionType)
'eq' => 'in',
'neq' => 'nin'
];
- return isset($conditionsMap[$conditionType]) ? $conditionsMap[$conditionType] : $conditionType;
+ return $conditionsMap[$conditionType] ?? $conditionType;
}
/**
@@ -1061,14 +1080,15 @@ public function getAllAttributeValues($attribute)
$select = clone $this->getSelect();
$attribute = $this->getEntity()->getAttribute($attribute);
- $aiField = $this->getConnection()->getAutoIncrementField($this->getMainTable());
+ $fieldMainTable = $this->getConnection()->getAutoIncrementField($this->getMainTable());
+ $fieldJoinTable = $attribute->getEntity()->getLinkField();
$select->reset()
->from(
['cpe' => $this->getMainTable()],
['entity_id']
)->join(
['cpa' => $attribute->getBackend()->getTable()],
- 'cpe.' . $aiField . ' = cpa.' . $aiField,
+ 'cpe.' . $fieldMainTable . ' = cpa.' . $fieldJoinTable,
['store_id', 'value']
)->where('attribute_id = ?', (int)$attribute->getId());
@@ -1680,7 +1700,7 @@ public function addAttributeToSort($attribute, $dir = self::SORT_ORDER_ASC)
return $this;
} elseif ($attribute == 'is_saleable') {
- $this->getSelect()->order("is_saleable " . $dir);
+ $this->getSelect()->order("is_salable " . $dir);
return $this;
}
@@ -1863,7 +1883,12 @@ protected function _productLimitationJoinPrice()
protected function _productLimitationPrice($joinLeft = false)
{
$filters = $this->_productLimitationFilters;
- if (!$filters->isUsingPriceIndex()) {
+ if (!$filters->isUsingPriceIndex() ||
+ !isset($filters['website_id']) ||
+ (string)$filters['website_id'] === '' ||
+ !isset($filters['customer_group_id']) ||
+ (string)$filters['customer_group_id'] === ''
+ ) {
return $this;
}
@@ -1898,7 +1923,23 @@ protected function _productLimitationPrice($joinLeft = false)
'max_price',
'tier_price',
];
- $tableName = ['price_index' => $this->getTable('catalog_product_index_price')];
+
+ $tableName = [
+ 'price_index' => $this->priceTableResolver->resolve(
+ 'catalog_product_index_price',
+ [
+ $this->dimensionFactory->create(
+ CustomerGroupDimensionProvider::DIMENSION_NAME,
+ (string)$filters['customer_group_id']
+ ),
+ $this->dimensionFactory->create(
+ WebsiteDimensionProvider::DIMENSION_NAME,
+ (string)$filters['website_id']
+ )
+ ]
+ )
+ ];
+
if ($joinLeft) {
$select->joinLeft($tableName, $joinCond, $colls);
} else {
@@ -2228,6 +2269,7 @@ public function addPriceDataFieldFilter($comparisonFormat, $fields)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
* @since 101.0.1
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function addMediaGalleryData()
{
@@ -2239,34 +2281,36 @@ public function addMediaGalleryData()
return $this;
}
- /** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
- $attribute = $this->getAttribute('media_gallery');
- $select = $this->getMediaGalleryResource()->createBatchBaseSelect(
- $this->getStoreId(),
- $attribute->getAttributeId()
- );
-
- $mediaGalleries = [];
- $linkField = $this->getProductEntityMetadata()->getLinkField();
$items = $this->getItems();
+ $linkField = $this->getProductEntityMetadata()->getLinkField();
- $select->where(
- 'entity.' . $linkField . ' IN (?)',
- array_map(
- function ($item) use ($linkField) {
- return $item->getData($linkField);
- },
- $items
- )
- );
+ $select = $this->getMediaGalleryResource()
+ ->createBatchBaseSelect(
+ $this->getStoreId(),
+ $this->getAttribute('media_gallery')->getAttributeId()
+ )->reset(
+ Select::ORDER // we don't care what order is in current scenario
+ )->where(
+ 'entity.' . $linkField . ' IN (?)',
+ array_map(
+ function ($item) use ($linkField) {
+ return (int) $item->getOrigData($linkField);
+ },
+ $items
+ )
+ );
+
+ $mediaGalleries = [];
foreach ($this->getConnection()->fetchAll($select) as $row) {
$mediaGalleries[$row[$linkField]][] = $row;
}
foreach ($items as $item) {
- $mediaEntries = isset($mediaGalleries[$item->getData($linkField)]) ?
- $mediaGalleries[$item->getData($linkField)] : [];
- $this->getGalleryReadHandler()->addMediaDataToProduct($item, $mediaEntries);
+ $this->getGalleryReadHandler()
+ ->addMediaDataToProduct(
+ $item,
+ $mediaGalleries[$item->getOrigData($linkField)] ?? []
+ );
}
$this->setFlag('media_gallery_added', true);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php
index ee1df8f23424d..ebe04fb63b217 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/LinkedProductSelectBuilderByIndexPrice.php
@@ -7,9 +7,13 @@
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\ResourceModel\Product\BaseSelectProcessorInterface;
+use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DB\Select;
use Magento\Catalog\Model\ResourceModel\Product\LinkedProductSelectBuilderInterface;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
+use Magento\Framework\Search\Request\IndexScopeResolverInterface;
class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuilderInterface
{
@@ -38,6 +42,16 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild
*/
private $baseSelectProcessor;
+ /**
+ * @var IndexScopeResolverInterface|null
+ */
+ private $priceTableResolver;
+
+ /**
+ * @var DimensionFactory|null
+ */
+ private $dimensionFactory;
+
/**
* LinkedProductSelectBuilderByIndexPrice constructor.
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
@@ -45,13 +59,17 @@ class LinkedProductSelectBuilderByIndexPrice implements LinkedProductSelectBuild
* @param \Magento\Customer\Model\Session $customerSession
* @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
* @param BaseSelectProcessorInterface|null $baseSelectProcessor
+ * @param IndexScopeResolverInterface|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
*/
public function __construct(
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\App\ResourceConnection $resourceConnection,
\Magento\Customer\Model\Session $customerSession,
\Magento\Framework\EntityManager\MetadataPool $metadataPool,
- BaseSelectProcessorInterface $baseSelectProcessor = null
+ BaseSelectProcessorInterface $baseSelectProcessor = null,
+ IndexScopeResolverInterface $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null
) {
$this->storeManager = $storeManager;
$this->resource = $resourceConnection;
@@ -59,6 +77,9 @@ public function __construct(
$this->metadataPool = $metadataPool;
$this->baseSelectProcessor = (null !== $baseSelectProcessor)
? $baseSelectProcessor : ObjectManager::getInstance()->get(BaseSelectProcessorInterface::class);
+ $this->priceTableResolver = $priceTableResolver
+ ?? ObjectManager::getInstance()->get(IndexScopeResolverInterface::class);
+ $this->dimensionFactory = $dimensionFactory ?? ObjectManager::getInstance()->get(DimensionFactory::class);
}
/**
@@ -68,6 +89,8 @@ public function build($productId)
{
$linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
$productTable = $this->resource->getTableName('catalog_product_entity');
+ $websiteId = $this->storeManager->getStore()->getWebsiteId();
+ $customerGroupId = $this->customerSession->getCustomerGroupId();
$priceSelect = $this->resource->getConnection()->select()
->from(['parent' => $productTable], '')
@@ -80,12 +103,20 @@ public function build($productId)
sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
['entity_id']
)->joinInner(
- ['t' => $this->resource->getTableName('catalog_product_index_price')],
+ [
+ 't' => $this->priceTableResolver->resolve('catalog_product_index_price', [
+ $this->dimensionFactory->create(WebsiteDimensionProvider::DIMENSION_NAME, (string)$websiteId),
+ $this->dimensionFactory->create(
+ CustomerGroupDimensionProvider::DIMENSION_NAME,
+ (string)$customerGroupId
+ ),
+ ])
+ ],
sprintf('t.entity_id = %s.entity_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
[]
)->where('parent.entity_id = ?', $productId)
- ->where('t.website_id = ?', $this->storeManager->getStore()->getWebsiteId())
- ->where('t.customer_group_id = ?', $this->customerSession->getCustomerGroupId())
+ ->where('t.website_id = ?', $websiteId)
+ ->where('t.customer_group_id = ?', $customerGroupId)
->order('t.min_price ' . Select::SQL_ASC)
->order(BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS . '.' . $linkField . ' ' . Select::SQL_ASC)
->limit(1);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BasePriceModifier.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BasePriceModifier.php
new file mode 100644
index 0000000000000..cf5ba451c380e
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/BasePriceModifier.php
@@ -0,0 +1,38 @@
+priceModifiers = $priceModifiers;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = []) : void
+ {
+ foreach ($this->priceModifiers as $priceModifier) {
+ $priceModifier->modifyPrice($priceTable, $entityIds);
+ }
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimator.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimator.php
index 24cb4fedd57e5..a499777df871a 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimator.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CompositeProductRowSizeEstimator.php
@@ -18,7 +18,7 @@ class CompositeProductRowSizeEstimator implements IndexTableRowSizeEstimatorInte
/**
* Calculated memory size for one record in catalog_product_index_price table
*/
- const MEMORY_SIZE_FOR_ONE_ROW = 250;
+ const MEMORY_SIZE_FOR_ONE_ROW = 200;
/**
* @var WebsiteManagementInterface
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php
new file mode 100644
index 0000000000000..47fc6802d7eaf
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php
@@ -0,0 +1,464 @@
+resource = $resource;
+ $this->metadataPool = $metadataPool;
+ $this->connectionName = $connectionName;
+ $this->columnValueExpressionFactory = $columnValueExpressionFactory;
+ $this->dataHelper = $dataHelper;
+ $this->tableStrategy = $tableStrategy;
+ }
+
+ /**
+ * Apply custom option price to temporary index price table
+ *
+ * @param IndexTableStructure $priceTable
+ * @param array $entityIds
+ * @return void
+ * @throws \Exception
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds = []) : void
+ {
+ // no need to run all queries if current products have no custom options
+ if (!$this->checkIfCustomOptionsExist($priceTable)) {
+ return;
+ }
+
+ $connection = $this->getConnection();
+ $finalPriceTable = $priceTable->getTableName();
+
+ $coaTable = $this->getCustomOptionAggregateTable();
+ $this->prepareCustomOptionAggregateTable();
+
+ $copTable = $this->getCustomOptionPriceTable();
+ $this->prepareCustomOptionPriceTable();
+
+ $select = $this->getSelectForOptionsWithMultipleValues($finalPriceTable);
+ $query = $select->insertFromSelect($coaTable);
+ $connection->query($query);
+
+ $select = $this->getSelectForOptionsWithOneValue($finalPriceTable);
+ $query = $select->insertFromSelect($coaTable);
+ $connection->query($query);
+
+ $select = $this->getSelectAggregated($coaTable);
+ $query = $select->insertFromSelect($copTable);
+ $connection->query($query);
+
+ // update tmp price index with prices from custom options (from previous aggregated table)
+ $select = $this->getSelectForUpdate($copTable);
+ $query = $select->crossUpdateFromSelect(['i' => $finalPriceTable]);
+ $connection->query($query);
+
+ $connection->delete($coaTable);
+ $connection->delete($copTable);
+ }
+
+ /**
+ * @param IndexTableStructure $priceTable
+ * @return bool
+ * @throws \Exception
+ */
+ private function checkIfCustomOptionsExist(IndexTableStructure $priceTable): bool
+ {
+ $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
+
+ $select = $this->getConnection()
+ ->select()
+ ->from(
+ ['i' => $priceTable->getTableName()],
+ ['entity_id']
+ )->join(
+ ['e' => $this->getTable('catalog_product_entity')],
+ 'e.entity_id = i.entity_id',
+ []
+ )->join(
+ ['o' => $this->getTable('catalog_product_option')],
+ 'o.product_id = e.' . $metadata->getLinkField(),
+ ['option_id']
+ );
+
+ return !empty($this->getConnection()->fetchRow($select));
+ }
+
+ /**
+ * @return \Magento\Framework\DB\Adapter\AdapterInterface
+ */
+ private function getConnection()
+ {
+ if (null === $this->connection) {
+ $this->connection = $this->resource->getConnection($this->connectionName);
+ }
+
+ return $this->connection;
+ }
+
+ /**
+ * Prepare prices for products with custom options that has multiple values
+ *
+ * @param string $sourceTable
+ * @return \Magento\Framework\DB\Select
+ * @throws \Exception
+ */
+ private function getSelectForOptionsWithMultipleValues(string $sourceTable): Select
+ {
+ $connection = $this->resource->getConnection($this->connectionName);
+ $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
+
+ $select = $connection->select()
+ ->from(
+ ['i' => $sourceTable],
+ ['entity_id', 'customer_group_id', 'website_id']
+ )->join(
+ ['e' => $this->getTable('catalog_product_entity')],
+ 'e.entity_id = i.entity_id',
+ []
+ )->join(
+ ['cwd' => $this->getTable('catalog_product_index_website')],
+ 'i.website_id = cwd.website_id',
+ []
+ )->join(
+ ['o' => $this->getTable('catalog_product_option')],
+ 'o.product_id = e.' . $metadata->getLinkField(),
+ ['option_id']
+ )->join(
+ ['ot' => $this->getTable('catalog_product_option_type_value')],
+ 'ot.option_id = o.option_id',
+ []
+ )->join(
+ ['otpd' => $this->getTable('catalog_product_option_type_price')],
+ 'otpd.option_type_id = ot.option_type_id AND otpd.store_id = 0',
+ []
+ )->group(
+ ['i.entity_id', 'i.customer_group_id', 'i.website_id', 'o.option_id']
+ );
+
+ if ($this->isPriceGlobal()) {
+ $optPriceType = 'otpd.price_type';
+ $optPriceValue = 'otpd.price';
+ } else {
+ $select->joinLeft(
+ ['otps' => $this->getTable('catalog_product_option_type_price')],
+ 'otps.option_type_id = otpd.option_type_id AND otpd.store_id = cwd.default_store_id',
+ []
+ );
+
+ $optPriceType = $connection->getCheckSql(
+ 'otps.option_type_price_id > 0',
+ 'otps.price_type',
+ 'otpd.price_type'
+ );
+ $optPriceValue = $connection->getCheckSql('otps.option_type_price_id > 0', 'otps.price', 'otpd.price');
+ }
+
+ $minPriceRound = $this->columnValueExpressionFactory
+ ->create([
+ 'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)"
+ ]);
+ $minPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $minPriceRound);
+ $minPriceMin = $this->columnValueExpressionFactory
+ ->create([
+ 'expression' => "MIN({$minPriceExpr})"
+ ]);
+ $minPrice = $connection->getCheckSql("MIN(o.is_require) = 1", $minPriceMin, '0');
+
+ $tierPriceRound = $this->columnValueExpressionFactory
+ ->create([
+ 'expression' => "ROUND(i.tier_price * ({$optPriceValue} / 100), 4)"
+ ]);
+ $tierPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $tierPriceRound);
+ $tierPriceMin = $this->columnValueExpressionFactory
+ ->create([
+ 'expression' => "MIN({$tierPriceExpr})"
+ ]);
+ $tierPriceValue = $connection->getCheckSql("MIN(o.is_require) > 0", $tierPriceMin, 0);
+ $tierPrice = $connection->getCheckSql("MIN(i.tier_price) IS NOT NULL", $tierPriceValue, "NULL");
+
+ $maxPriceRound = $this->columnValueExpressionFactory
+ ->create([
+ 'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)"
+ ]);
+ $maxPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $maxPriceRound);
+ $maxPrice = $connection->getCheckSql(
+ "(MIN(o.type)='radio' OR MIN(o.type)='drop_down')",
+ "MAX({$maxPriceExpr})",
+ "SUM({$maxPriceExpr})"
+ );
+
+ $select->columns(
+ [
+ 'min_price' => $minPrice,
+ 'max_price' => $maxPrice,
+ 'tier_price' => $tierPrice,
+ ]
+ );
+
+ return $select;
+ }
+
+ /**
+ * Prepare prices for products with custom options that has single value
+ *
+ * @param string $sourceTable
+ * @return \Magento\Framework\DB\Select
+ * @throws \Exception
+ */
+ private function getSelectForOptionsWithOneValue(string $sourceTable): Select
+ {
+ $connection = $this->resource->getConnection($this->connectionName);
+ $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
+
+ $select = $connection->select()
+ ->from(
+ ['i' => $sourceTable],
+ ['entity_id', 'customer_group_id', 'website_id']
+ )->join(
+ ['e' => $this->getTable('catalog_product_entity')],
+ 'e.entity_id = i.entity_id',
+ []
+ )->join(
+ ['cwd' => $this->getTable('catalog_product_index_website')],
+ 'i.website_id = cwd.website_id',
+ []
+ )->join(
+ ['o' => $this->getTable('catalog_product_option')],
+ 'o.product_id = e.' . $metadata->getLinkField(),
+ ['option_id']
+ )->join(
+ ['opd' => $this->getTable('catalog_product_option_price')],
+ 'opd.option_id = o.option_id AND opd.store_id = 0',
+ []
+ );
+
+ if ($this->isPriceGlobal()) {
+ $optPriceType = 'opd.price_type';
+ $optPriceValue = 'opd.price';
+ } else {
+ $select->joinLeft(
+ ['ops' => $this->getTable('catalog_product_option_price')],
+ 'ops.option_id = opd.option_id AND ops.store_id = cwd.default_store_id',
+ []
+ );
+
+ $optPriceType = $connection->getCheckSql('ops.option_price_id > 0', 'ops.price_type', 'opd.price_type');
+ $optPriceValue = $connection->getCheckSql('ops.option_price_id > 0', 'ops.price', 'opd.price');
+ }
+
+ $minPriceRound = $this->columnValueExpressionFactory
+ ->create([
+ 'expression' => "ROUND(i.final_price * ({$optPriceValue} / 100), 4)"
+ ]);
+ $priceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $minPriceRound);
+ $minPrice = $connection->getCheckSql("{$priceExpr} > 0 AND o.is_require = 1", $priceExpr, 0);
+
+ $maxPrice = $priceExpr;
+
+ $tierPriceRound = $this->columnValueExpressionFactory
+ ->create([
+ 'expression' => "ROUND(i.tier_price * ({$optPriceValue} / 100), 4)"
+ ]);
+ $tierPriceExpr = $connection->getCheckSql("{$optPriceType} = 'fixed'", $optPriceValue, $tierPriceRound);
+ $tierPriceValue = $connection->getCheckSql("{$tierPriceExpr} > 0 AND o.is_require = 1", $tierPriceExpr, 0);
+ $tierPrice = $connection->getCheckSql("i.tier_price IS NOT NULL", $tierPriceValue, "NULL");
+
+ $select->columns(
+ [
+ 'min_price' => $minPrice,
+ 'max_price' => $maxPrice,
+ 'tier_price' => $tierPrice,
+ ]
+ );
+
+ return $select;
+ }
+
+ /**
+ * Aggregate prices with one and multiply options into one table
+ *
+ * @param string $sourceTable
+ * @return \Magento\Framework\DB\Select
+ */
+ private function getSelectAggregated(string $sourceTable): Select
+ {
+ $connection = $this->resource->getConnection($this->connectionName);
+
+ $select = $connection->select()
+ ->from(
+ [$sourceTable],
+ [
+ 'entity_id',
+ 'customer_group_id',
+ 'website_id',
+ 'min_price' => 'SUM(min_price)',
+ 'max_price' => 'SUM(max_price)',
+ 'tier_price' => 'SUM(tier_price)',
+ ]
+ )->group(
+ ['entity_id', 'customer_group_id', 'website_id']
+ );
+
+ return $select;
+ }
+
+ /**
+ * @param string $sourceTable
+ * @return \Magento\Framework\DB\Select
+ */
+ private function getSelectForUpdate(string $sourceTable): Select
+ {
+ $connection = $this->resource->getConnection($this->connectionName);
+
+ $select = $connection->select()->join(
+ ['io' => $sourceTable],
+ 'i.entity_id = io.entity_id AND i.customer_group_id = io.customer_group_id' .
+ ' AND i.website_id = io.website_id',
+ []
+ );
+ $select->columns(
+ [
+ 'min_price' => new ColumnValueExpression('i.min_price + io.min_price'),
+ 'max_price' => new ColumnValueExpression('i.max_price + io.max_price'),
+ 'tier_price' => $connection->getCheckSql(
+ 'i.tier_price IS NOT NULL',
+ 'i.tier_price + io.tier_price',
+ 'NULL'
+ ),
+ ]
+ );
+
+ return $select;
+ }
+
+ /**
+ * @param string $tableName
+ * @return string
+ */
+ private function getTable(string $tableName): string
+ {
+ return $this->resource->getTableName($tableName, $this->connectionName);
+ }
+
+ /**
+ * @return bool
+ */
+ private function isPriceGlobal(): bool
+ {
+ if ($this->isPriceGlobalFlag === null) {
+ $this->isPriceGlobalFlag = $this->dataHelper->isPriceGlobal();
+ }
+
+ return $this->isPriceGlobalFlag;
+ }
+
+ /**
+ * Retrieve table name for custom option temporary aggregation data
+ *
+ * @return string
+ */
+ private function getCustomOptionAggregateTable(): string
+ {
+ return $this->tableStrategy->getTableName('catalog_product_index_price_opt_agr');
+ }
+
+ /**
+ * Retrieve table name for custom option prices data
+ *
+ * @return string
+ */
+ private function getCustomOptionPriceTable(): string
+ {
+ return $this->tableStrategy->getTableName('catalog_product_index_price_opt');
+ }
+
+ /**
+ * Prepare table structure for custom option temporary aggregation data
+ *
+ * @return void
+ */
+ private function prepareCustomOptionAggregateTable()
+ {
+ $this->getConnection()->delete($this->getCustomOptionAggregateTable());
+ }
+
+ /**
+ * Prepare table structure for custom option prices data
+ *
+ * @return void
+ */
+ private function prepareCustomOptionPriceTable()
+ {
+ $this->getConnection()->delete($this->getCustomOptionPriceTable());
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
index 4ca407a53f8ae..168fa8f50acc2 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\AbstractIndexer;
+use Magento\Framework\Indexer\DimensionalIndexerInterface;
/**
* Default Product Type Price Indexer Resource model
@@ -16,6 +17,8 @@
* @author Magento Core Team
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
+ * @deprecated Not used anymore for price indexation. Class left for backward compatibility
+ * @see DimensionalIndexerInterface
*/
class DefaultPrice extends AbstractIndexer implements PriceInterface
{
@@ -71,7 +74,7 @@ class DefaultPrice extends AbstractIndexer implements PriceInterface
* @param \Magento\Framework\Event\ManagerInterface $eventManager
* @param \Magento\Framework\Module\Manager $moduleManager
* @param string|null $connectionName
- * @param null|IndexTableStructureFactory $indexTableStructureFactory
+ * @param IndexTableStructureFactory $indexTableStructureFactory
* @param PriceModifierInterface[] $priceModifiers
*/
public function __construct(
@@ -307,7 +310,7 @@ protected function prepareFinalPriceDataForType($entityIds, $type)
$query = $select->insertFromSelect($finalPriceTable->getTableName(), [], false);
$this->getConnection()->query($query);
- $this->applyDiscountPrices($finalPriceTable);
+ $this->modifyPriceIndex($finalPriceTable);
return $this;
}
@@ -327,6 +330,7 @@ protected function prepareFinalPriceDataForType($entityIds, $type)
protected function getSelect($entityIds = null, $type = null)
{
$metadata = $this->getMetadataPool()->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
+ $linkField = $metadata->getLinkField();
$connection = $this->getConnection();
$select = $connection->select()->from(
['e' => $this->getTable('catalog_product_entity')],
@@ -356,9 +360,38 @@ protected function getSelect($entityIds = null, $type = null)
'pw.product_id = e.entity_id AND pw.website_id = cw.website_id',
[]
)->joinLeft(
- ['tp' => $this->_getTierPriceIndexTable()],
- 'tp.entity_id = e.entity_id AND tp.website_id = cw.website_id' .
- ' AND tp.customer_group_id = cg.customer_group_id',
+ // 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')],
+ 'tp.entity_id = e.entity_id AND tp.customer_group_id = cg.customer_group_id' .
+ ' AND tp.website_id = pw.website_id',
+ []
+ )->joinLeft(
+ // calculate tier price specified as Website = `All Websites` and Customer Group = `Specific Customer Group`
+ ['tier_price_1' => $this->getTable('catalog_product_entity_tier_price')],
+ 'tier_price_1.' . $linkField . ' = e.' . $linkField . ' AND tier_price_1.all_groups = 0' .
+ ' AND tier_price_1.customer_group_id = cg.customer_group_id AND tier_price_1.qty = 1' .
+ ' AND tier_price_1.website_id = 0',
+ []
+ )->joinLeft(
+ // calculate tier price specified as Website = `Specific Website`
+ //and Customer Group = `Specific Customer Group`
+ ['tier_price_2' => $this->getTable('catalog_product_entity_tier_price')],
+ 'tier_price_2.' . $linkField . ' = e.' . $linkField . ' AND tier_price_2.all_groups = 0' .
+ ' AND tier_price_2.customer_group_id = cg.customer_group_id AND tier_price_2.qty = 1' .
+ ' AND tier_price_2.website_id = cw.website_id',
+ []
+ )->joinLeft(
+ // calculate tier price specified as Website = `All Websites` and Customer Group = `ALL GROUPS`
+ ['tier_price_3' => $this->getTable('catalog_product_entity_tier_price')],
+ 'tier_price_3.' . $linkField . ' = e.' . $linkField . ' AND tier_price_3.all_groups = 1' .
+ ' AND tier_price_3.customer_group_id = 0 AND tier_price_3.qty = 1 AND tier_price_3.website_id = 0',
+ []
+ )->joinLeft(
+ // calculate tier price specified as Website = `Specific Website` and Customer Group = `ALL GROUPS`
+ ['tier_price_4' => $this->getTable('catalog_product_entity_tier_price')],
+ 'tier_price_4.' . $linkField . ' = e.' . $linkField . ' AND tier_price_4.all_groups = 1' .
+ ' AND tier_price_4.customer_group_id = 0 AND tier_price_4.qty = 1' .
+ ' AND tier_price_4.website_id = cw.website_id',
[]
);
@@ -374,7 +407,7 @@ protected function getSelect($entityIds = null, $type = null)
$this->_addAttributeToSelect(
$select,
'status',
- 'e.' . $metadata->getLinkField(),
+ 'e.' . $linkField,
'cs.store_id',
$statusCond,
true
@@ -383,7 +416,7 @@ protected function getSelect($entityIds = null, $type = null)
$taxClassId = $this->_addAttributeToSelect(
$select,
'tax_class_id',
- 'e.' . $metadata->getLinkField(),
+ 'e.' . $linkField,
'cs.store_id'
);
} else {
@@ -394,25 +427,25 @@ protected function getSelect($entityIds = null, $type = null)
$price = $this->_addAttributeToSelect(
$select,
'price',
- 'e.' . $metadata->getLinkField(),
+ 'e.' . $linkField,
'cs.store_id'
);
$specialPrice = $this->_addAttributeToSelect(
$select,
'special_price',
- 'e.' . $metadata->getLinkField(),
+ 'e.' . $linkField,
'cs.store_id'
);
$specialFrom = $this->_addAttributeToSelect(
$select,
'special_from_date',
- 'e.' . $metadata->getLinkField(),
+ 'e.' . $linkField,
'cs.store_id'
);
$specialTo = $this->_addAttributeToSelect(
$select,
'special_to_date',
- 'e.' . $metadata->getLinkField(),
+ 'e.' . $linkField,
'cs.store_id'
);
$currentDate = 'cwd.website_date';
@@ -423,15 +456,12 @@ protected function getSelect($entityIds = null, $type = null)
$specialFromExpr = "{$specialFrom} IS NULL OR {$specialFromDate} <= {$currentDate}";
$specialToExpr = "{$specialTo} IS NULL OR {$specialToDate} >= {$currentDate}";
$specialPriceExpr = $connection->getCheckSql(
- "{$specialPrice} IS NOT NULL AND {$specialFromExpr} AND {$specialToExpr}",
+ "{$specialPrice} IS NOT NULL AND ({$specialFromExpr}) AND ({$specialToExpr})",
$specialPrice,
$maxUnsignedBigint
);
- $tierPrice = new \Zend_Db_Expr('tp.min_price');
- $tierPriceExpr = $connection->getIfNullSql(
- $tierPrice,
- $maxUnsignedBigint
- );
+ $tierPrice = $this->getTotalTierPriceExpression($price);
+ $tierPriceExpr = $connection->getIfNullSql($tierPrice, $maxUnsignedBigint);
$finalPrice = $connection->getLeastSql([
$price,
$specialPriceExpr,
@@ -512,12 +542,12 @@ protected function _prepareCustomOptionPriceTable()
}
/**
- * Apply discount prices to final price index table.
+ * Modify data in price index table.
*
* @param IndexTableStructure $finalPriceTable
* @return void
*/
- private function applyDiscountPrices(IndexTableStructure $finalPriceTable) : void
+ private function modifyPriceIndex(IndexTableStructure $finalPriceTable) : void
{
foreach ($this->priceModifiers as $priceModifier) {
$priceModifier->modifyPrice($finalPriceTable);
@@ -791,4 +821,62 @@ protected function hasEntity()
return $this->hasEntity;
}
+
+ /**
+ * @param \Zend_Db_Expr $priceExpression
+ * @return \Zend_Db_Expr
+ */
+ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression)
+ {
+ $maxUnsignedBigint = '~0';
+
+ return $this->getConnection()->getCheckSql(
+ implode(
+ ' AND ',
+ [
+ 'tier_price_1.value_id is NULL',
+ 'tier_price_2.value_id is NULL',
+ 'tier_price_3.value_id is NULL',
+ 'tier_price_4.value_id is NULL'
+ ]
+ ),
+ 'NULL',
+ $this->getConnection()->getLeastSql([
+ $this->getConnection()->getIfNullSql(
+ $this->getTierPriceExpressionForTable('tier_price_1', $priceExpression),
+ $maxUnsignedBigint
+ ),
+ $this->getConnection()->getIfNullSql(
+ $this->getTierPriceExpressionForTable('tier_price_2', $priceExpression),
+ $maxUnsignedBigint
+ ),
+ $this->getConnection()->getIfNullSql(
+ $this->getTierPriceExpressionForTable('tier_price_3', $priceExpression),
+ $maxUnsignedBigint
+ ),
+ $this->getConnection()->getIfNullSql(
+ $this->getTierPriceExpressionForTable('tier_price_4', $priceExpression),
+ $maxUnsignedBigint
+ ),
+ ])
+ );
+ }
+
+ /**
+ * @param string $tableAlias
+ * @param \Zend_Db_Expr $priceExpression
+ * @return \Zend_Db_Expr
+ */
+ private function getTierPriceExpressionForTable($tableAlias, \Zend_Db_Expr $priceExpression)
+ {
+ return $this->getConnection()->getCheckSql(
+ sprintf('%s.value = 0', $tableAlias),
+ sprintf(
+ 'ROUND(%s * (1 - ROUND(%s.percentage_value * cwd.rate, 4) / 100), 4)',
+ $priceExpression,
+ $tableAlias
+ ),
+ sprintf('ROUND(%s.value * cwd.rate, 4)', $tableAlias)
+ );
+ }
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php
index 21a7647214c26..9a310c7365ac9 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Factory.php
@@ -9,6 +9,8 @@
*/
namespace Magento\Catalog\Model\ResourceModel\Product\Indexer\Price;
+use Magento\Framework\Indexer\DimensionalIndexerInterface;
+
class Factory
{
/**
@@ -40,14 +42,17 @@ public function create($className, array $data = [])
{
$indexerPrice = $this->_objectManager->create($className, $data);
- if (!$indexerPrice instanceof \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice) {
- throw new \Magento\Framework\Exception\LocalizedException(
- __(
- '%1 doesn\'t extend \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice',
- $className
- )
- );
+ if ($indexerPrice instanceof PriceInterface || $indexerPrice instanceof DimensionalIndexerInterface) {
+ return $indexerPrice;
}
- return $indexerPrice;
+
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __(
+ 'Price indexer "%1" must implement %2 or %3',
+ $className,
+ PriceInterface::class,
+ DimensionalIndexerInterface::class
+ )
+ );
}
}
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
new file mode 100644
index 0000000000000..0005ac8dea58a
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/BaseFinalPrice.php
@@ -0,0 +1,330 @@
+ 'pw.website_id',
+ CustomerGroupDimensionProvider::DIMENSION_NAME => 'cg.customer_group_id',
+ ];
+
+ /**
+ * @var \Magento\Framework\DB\Adapter\AdapterInterface
+ */
+ private $connection;
+
+ /**
+ * @var \Magento\Framework\EntityManager\MetadataPool
+ */
+ private $metadataPool;
+
+ /**
+ * BaseFinalPrice constructor.
+ * @param \Magento\Framework\App\ResourceConnection $resource
+ * @param JoinAttributeProcessor $joinAttributeProcessor
+ * @param \Magento\Framework\Module\Manager $moduleManager
+ * @param string $connectionName
+ */
+ public function __construct(
+ \Magento\Framework\App\ResourceConnection $resource,
+ JoinAttributeProcessor $joinAttributeProcessor,
+ \Magento\Framework\Module\Manager $moduleManager,
+ \Magento\Framework\Event\ManagerInterface $eventManager,
+ \Magento\Framework\EntityManager\MetadataPool $metadataPool,
+ $connectionName = 'indexer'
+ ) {
+ $this->resource = $resource;
+ $this->connectionName = $connectionName;
+ $this->joinAttributeProcessor = $joinAttributeProcessor;
+ $this->moduleManager = $moduleManager;
+ $this->eventManager = $eventManager;
+ $this->metadataPool = $metadataPool;
+ }
+
+ /**
+ * @param Dimension[] $dimensions
+ * @param string $productType
+ * @param array $entityIds
+ * @return Select
+ * @throws \LogicException
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Zend_Db_Select_Exception
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
+ public function getQuery(array $dimensions, string $productType, array $entityIds = []): Select
+ {
+ $connection = $this->getConnection();
+ $metadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
+ $linkField = $metadata->getLinkField();
+
+ $select = $connection->select()->from(
+ ['e' => $this->getTable('catalog_product_entity')],
+ ['entity_id']
+ )->joinInner(
+ ['cg' => $this->getTable('customer_group')],
+ array_key_exists(CustomerGroupDimensionProvider::DIMENSION_NAME, $dimensions)
+ ? sprintf(
+ '%s = %s',
+ $this->dimensionToFieldMapper[CustomerGroupDimensionProvider::DIMENSION_NAME],
+ $dimensions[CustomerGroupDimensionProvider::DIMENSION_NAME]->getValue()
+ ) : '',
+ ['customer_group_id']
+ )->joinInner(
+ ['pw' => $this->getTable('catalog_product_website')],
+ 'pw.product_id = e.entity_id',
+ ['pw.website_id']
+ )->joinInner(
+ ['cwd' => $this->getTable('catalog_product_index_website')],
+ 'pw.website_id = cwd.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')],
+ 'tp.entity_id = e.entity_id AND' .
+ ' tp.customer_group_id = cg.customer_group_id AND tp.website_id = pw.website_id',
+ []
+ )->joinLeft(
+ // calculate tier price specified as Website = `All Websites` and Customer Group = `Specific Customer Group`
+ ['tier_price_1' => $this->getTable('catalog_product_entity_tier_price')],
+ 'tier_price_1.' . $linkField . ' = e.' . $linkField . ' AND tier_price_1.all_groups = 0' .
+ ' AND tier_price_1.customer_group_id = cg.customer_group_id AND tier_price_1.qty = 1' .
+ ' AND tier_price_1.website_id = 0',
+ []
+ )->joinLeft(
+ // calculate tier price specified as Website = `Specific Website`
+ //and Customer Group = `Specific Customer Group`
+ ['tier_price_2' => $this->getTable('catalog_product_entity_tier_price')],
+ 'tier_price_2.' . $linkField . ' = e.' . $linkField . ' AND tier_price_2.all_groups = 0 ' .
+ 'AND tier_price_2.customer_group_id = cg.customer_group_id AND tier_price_2.qty = 1' .
+ ' AND tier_price_2.website_id = pw.website_id',
+ []
+ )->joinLeft(
+ // calculate tier price specified as Website = `All Websites` and Customer Group = `ALL GROUPS`
+ ['tier_price_3' => $this->getTable('catalog_product_entity_tier_price')],
+ 'tier_price_3.' . $linkField . ' = e.' . $linkField . ' AND tier_price_3.all_groups = 1 ' .
+ 'AND tier_price_3.customer_group_id = 0 AND tier_price_3.qty = 1 AND tier_price_3.website_id = 0',
+ []
+ )->joinLeft(
+ // calculate tier price specified as Website = `Specific Website` and Customer Group = `ALL GROUPS`
+ ['tier_price_4' => $this->getTable('catalog_product_entity_tier_price')],
+ 'tier_price_4.' . $linkField . ' = e.' . $linkField . ' AND tier_price_4.all_groups = 1' .
+ ' AND tier_price_4.customer_group_id = 0 AND tier_price_4.qty = 1' .
+ ' AND tier_price_4.website_id = pw.website_id',
+ []
+ );
+
+ foreach ($dimensions as $dimension) {
+ if (!isset($this->dimensionToFieldMapper[$dimension->getName()])) {
+ throw new \LogicException(
+ 'Provided dimension is not valid for Price indexer: ' . $dimension->getName()
+ );
+ }
+ $select->where($this->dimensionToFieldMapper[$dimension->getName()] . ' = ?', $dimension->getValue());
+ }
+
+ if ($this->moduleManager->isEnabled('Magento_Tax')) {
+ $taxClassId = $this->joinAttributeProcessor->process($select, 'tax_class_id');
+ } else {
+ $taxClassId = new \Zend_Db_Expr(0);
+ }
+ $select->columns(['tax_class_id' => $taxClassId]);
+
+ $this->joinAttributeProcessor->process($select, 'status', Status::STATUS_ENABLED);
+
+ $price = $this->joinAttributeProcessor->process($select, 'price');
+ $specialPrice = $this->joinAttributeProcessor->process($select, 'special_price');
+ $specialFrom = $this->joinAttributeProcessor->process($select, 'special_from_date');
+ $specialTo = $this->joinAttributeProcessor->process($select, 'special_to_date');
+ $currentDate = 'cwd.website_date';
+
+ $maxUnsignedBigint = '~0';
+ $specialFromDate = $connection->getDatePartSql($specialFrom);
+ $specialToDate = $connection->getDatePartSql($specialTo);
+ $specialFromExpr = "{$specialFrom} IS NULL OR {$specialFromDate} <= {$currentDate}";
+ $specialToExpr = "{$specialTo} IS NULL OR {$specialToDate} >= {$currentDate}";
+ $specialPriceExpr = $connection->getCheckSql(
+ "{$specialPrice} IS NOT NULL AND ({$specialFromExpr}) AND ({$specialToExpr})",
+ $specialPrice,
+ $maxUnsignedBigint
+ );
+ $tierPrice = $this->getTotalTierPriceExpression($price);
+ $tierPriceExpr = $connection->getIfNullSql($tierPrice, $maxUnsignedBigint);
+ $finalPrice = $connection->getLeastSql([
+ $price,
+ $specialPriceExpr,
+ $tierPriceExpr,
+ ]);
+
+ $select->columns(
+ [
+ //orig_price in catalog_product_index_price_final_tmp
+ 'price' => $connection->getIfNullSql($price, 0),
+ //price in catalog_product_index_price_final_tmp
+ 'final_price' => $connection->getIfNullSql($finalPrice, 0),
+ 'min_price' => $connection->getIfNullSql($finalPrice, 0),
+ 'max_price' => $connection->getIfNullSql($finalPrice, 0),
+ 'tier_price' => $tierPrice,
+ ]
+ );
+
+ $select->where("e.type_id = ?", $productType);
+
+ if ($entityIds !== null) {
+ if (count($entityIds) > 1) {
+ $select->where(sprintf('e.entity_id BETWEEN %s AND %s', min($entityIds), max($entityIds)));
+ } else {
+ $select->where('e.entity_id = ?', $entityIds);
+ }
+ }
+
+ /**
+ * throw event for backward compatibility
+ */
+ $this->eventManager->dispatch(
+ 'prepare_catalog_product_index_select',
+ [
+ 'select' => $select,
+ 'entity_field' => new ColumnValueExpression('e.entity_id'),
+ 'website_field' => new ColumnValueExpression('pw.website_id'),
+ 'store_field' => new ColumnValueExpression('cwd.default_store_id'),
+ ]
+ );
+
+ return $select;
+ }
+
+ /**
+ * Get total tier price expression
+ *
+ * @param \Zend_Db_Expr $priceExpression
+ * @return \Zend_Db_Expr
+ */
+ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression)
+ {
+ $maxUnsignedBigint = '~0';
+
+ return $this->getConnection()->getCheckSql(
+ implode(
+ ' AND ',
+ [
+ 'tier_price_1.value_id is NULL',
+ 'tier_price_2.value_id is NULL',
+ 'tier_price_3.value_id is NULL',
+ 'tier_price_4.value_id is NULL'
+ ]
+ ),
+ 'NULL',
+ $this->getConnection()->getLeastSql([
+ $this->getConnection()->getIfNullSql(
+ $this->getTierPriceExpressionForTable('tier_price_1', $priceExpression),
+ $maxUnsignedBigint
+ ),
+ $this->getConnection()->getIfNullSql(
+ $this->getTierPriceExpressionForTable('tier_price_2', $priceExpression),
+ $maxUnsignedBigint
+ ),
+ $this->getConnection()->getIfNullSql(
+ $this->getTierPriceExpressionForTable('tier_price_3', $priceExpression),
+ $maxUnsignedBigint
+ ),
+ $this->getConnection()->getIfNullSql(
+ $this->getTierPriceExpressionForTable('tier_price_4', $priceExpression),
+ $maxUnsignedBigint
+ ),
+ ])
+ );
+ }
+
+ /**
+ * Get tier price expression for table
+ *
+ * @param $tableAlias
+ * @param \Zend_Db_Expr $priceExpression
+ * @return \Zend_Db_Expr
+ */
+ private function getTierPriceExpressionForTable($tableAlias, \Zend_Db_Expr $priceExpression): \Zend_Db_Expr
+ {
+ return $this->getConnection()->getCheckSql(
+ sprintf('%s.value = 0', $tableAlias),
+ sprintf(
+ 'ROUND(%s * (1 - ROUND(%s.percentage_value * cwd.rate, 4) / 100), 4)',
+ $priceExpression,
+ $tableAlias
+ ),
+ sprintf('ROUND(%s.value * cwd.rate, 4)', $tableAlias)
+ );
+ }
+
+ /**
+ * Get connection
+ *
+ * return \Magento\Framework\DB\Adapter\AdapterInterface
+ * @throws \DomainException
+ */
+ private function getConnection(): \Magento\Framework\DB\Adapter\AdapterInterface
+ {
+ if ($this->connection === null) {
+ $this->connection = $this->resource->getConnection($this->connectionName);
+ }
+
+ return $this->connection;
+ }
+
+ /**
+ * Get table
+ *
+ * @param string $tableName
+ * @return string
+ */
+ private function getTable($tableName)
+ {
+ return $this->resource->getTableName($tableName, $this->connectionName);
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/JoinAttributeProcessor.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/JoinAttributeProcessor.php
new file mode 100644
index 0000000000000..888e68a817081
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/Query/JoinAttributeProcessor.php
@@ -0,0 +1,112 @@
+eavConfig = $eavConfig;
+ $this->metadataPool = $metadataPool;
+ $this->resource = $resource;
+ $this->connectionName = $connectionName;
+ }
+
+ /**
+ * @param Select $select
+ * @param string $attributeCode
+ * @param string|null $attributeValue
+ * @return \Zend_Db_Expr
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Zend_Db_Select_Exception
+ */
+ public function process(Select $select, $attributeCode, $attributeValue = null): \Zend_Db_Expr
+ {
+ $attribute = $this->eavConfig->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode);
+ $attributeId = $attribute->getAttributeId();
+ $attributeTable = $attribute->getBackend()->getTable();
+ $connection = $this->resource->getConnection($this->connectionName);
+ $joinType = $attributeValue !== null ? 'join' : 'joinLeft';
+ $productIdField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
+
+ if ($attribute->isScopeGlobal()) {
+ $alias = 'ta_' . $attributeCode;
+ $select->{$joinType}(
+ [$alias => $attributeTable],
+ "{$alias}.{$productIdField} = e.{$productIdField} AND {$alias}.attribute_id = {$attributeId}" .
+ " AND {$alias}.store_id = 0",
+ []
+ );
+ $whereExpression = new Expression("{$alias}.value");
+ } else {
+ $dAlias = 'tad_' . $attributeCode;
+ $sAlias = 'tas_' . $attributeCode;
+
+ $select->{$joinType}(
+ [$dAlias => $attributeTable],
+ "{$dAlias}.{$productIdField} = e.{$productIdField} AND {$dAlias}.attribute_id = {$attributeId}" .
+ " AND {$dAlias}.store_id = 0",
+ []
+ );
+ $select->joinLeft(
+ [$sAlias => $attributeTable],
+ "{$sAlias}.{$productIdField} = e.{$productIdField} AND {$sAlias}.attribute_id = {$attributeId}" .
+ " AND {$sAlias}.store_id = cwd.default_store_id",
+ []
+ );
+ $whereExpression = $connection->getCheckSql(
+ $connection->getIfNullSql("{$sAlias}.value_id", -1) . ' > 0',
+ "{$sAlias}.value",
+ "{$dAlias}.value"
+ );
+ }
+
+ if ($attributeValue !== null) {
+ $select->where("{$whereExpression} = ?", $attributeValue);
+ }
+
+ return $whereExpression;
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php
new file mode 100644
index 0000000000000..5a055e5ed9603
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/SimpleProductPrice.php
@@ -0,0 +1,89 @@
+baseFinalPrice = $baseFinalPrice;
+ $this->indexTableStructureFactory = $indexTableStructureFactory;
+ $this->tableMaintainer = $tableMaintainer;
+ $this->productType = $productType;
+ $this->basePriceModifier = $basePriceModifier;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function executeByDimensions(array $dimensions, \Traversable $entityIds)
+ {
+ $this->tableMaintainer->createMainTmpTable($dimensions);
+
+ $temporaryPriceTable = $this->indexTableStructureFactory->create([
+ 'tableName' => $this->tableMaintainer->getMainTmpTable($dimensions),
+ 'entityField' => 'entity_id',
+ 'customerGroupField' => 'customer_group_id',
+ 'websiteField' => 'website_id',
+ 'taxClassField' => 'tax_class_id',
+ 'originalPriceField' => 'price',
+ 'finalPriceField' => 'final_price',
+ 'minPriceField' => 'min_price',
+ 'maxPriceField' => 'max_price',
+ 'tierPriceField' => 'tier_price',
+ ]);
+ $select = $this->baseFinalPrice->getQuery($dimensions, $this->productType, iterator_to_array($entityIds));
+ $query = $select->insertFromSelect($temporaryPriceTable->getTableName(), [], false);
+ $this->tableMaintainer->getConnection()->query($query);
+
+ $this->basePriceModifier->modifyPrice($temporaryPriceTable, iterator_to_array($entityIds));
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php
index 4775b96e3a448..179da06b59990 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option.php
@@ -307,7 +307,7 @@ protected function _saveValueTitles(\Magento\Framework\Model\AbstractModel $obje
}
/**
- * Get first col from from first row for option table
+ * Get first col from first row for option table
*
* @param string $tableName
* @param int $optionId
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php
index 91bb99ca971a7..ce0a9b6e461ce 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Option/Value.php
@@ -256,7 +256,8 @@ protected function _saveValueTitles(AbstractModel $object)
$object->unsetData('title');
}
- if ($object->getTitle()) {
+ /*** Checking whether title is not null ***/
+ if ($object->getTitle()!= null) {
if ($existInCurrentStore) {
if ($storeId == $object->getStoreId()) {
$where = [
@@ -300,7 +301,7 @@ protected function _saveValueTitles(AbstractModel $object)
}
/**
- * Get first col from from first row for option table
+ * Get first col from first row for option table
*
* @param string $tableName
* @param int $optionId
diff --git a/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php b/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php
index bcb6638b9cd25..83d59718400bd 100644
--- a/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php
+++ b/app/code/Magento/Catalog/Pricing/Price/ConfiguredRegularPrice.php
@@ -63,7 +63,7 @@ public function setItem(ItemInterface $item) : ConfiguredRegularPrice
return $this;
}
-
+
/**
* Price value of product with configured options.
*
@@ -73,7 +73,7 @@ public function getValue()
{
$basePrice = parent::getValue();
- return $this->item
+ return $this->item && $basePrice !== false
? $basePrice + $this->configuredOptions->getItemOptionsValue($basePrice, $this->item)
: $basePrice;
}
diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/DisallowUsingHtmlForProductName.php b/app/code/Magento/Catalog/Setup/Patch/Data/DisallowUsingHtmlForProductName.php
index ea8f6bbf39b31..226b94ceb1749 100644
--- a/app/code/Magento/Catalog/Setup/Patch/Data/DisallowUsingHtmlForProductName.php
+++ b/app/code/Magento/Catalog/Setup/Patch/Data/DisallowUsingHtmlForProductName.php
@@ -3,7 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
+declare(strict_types=1);
namespace Magento\Catalog\Setup\Patch\Data;
use Magento\Catalog\Setup\CategorySetupFactory;
diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/EnableDirectiveParsing.php b/app/code/Magento/Catalog/Setup/Patch/Data/EnableDirectiveParsing.php
new file mode 100644
index 0000000000000..f881b2f49f600
--- /dev/null
+++ b/app/code/Magento/Catalog/Setup/Patch/Data/EnableDirectiveParsing.php
@@ -0,0 +1,67 @@
+moduleDataSetup = $moduleDataSetup;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function apply()
+ {
+ $configTable = $this->moduleDataSetup->getTable('core_config_data');
+ $select = $this->moduleDataSetup->getConnection()->select()
+ ->from($configTable)
+ ->where('path = ?', 'catalog/frontend/parse_url_directives');
+ $config = $this->moduleDataSetup->getConnection()->fetchAll($select);
+ if (!empty($config)) {
+ $this->moduleDataSetup->getConnection()->update(
+ $configTable,
+ ['value' => '1'],
+ ['path = ?' => 'catalog/frontend/parse_url_directives', 'value IN (?)' => '0']
+ );
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getDependencies()
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php b/app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php
index f9d6abbc37493..a190bde2c6775 100644
--- a/app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php
+++ b/app/code/Magento/Catalog/Setup/Patch/Data/UpgradeWebsiteAttributes.php
@@ -156,6 +156,21 @@ private function processAttributeValues(array $attributeValueItems, $tableName)
*/
private function fetchAttributeValues($tableName)
{
+ //filter store groups which have more than 1 store
+ $multipleStoresInWebsite = array_values(
+ array_reduce(
+ array_filter($this->getGroupedStoreViews(), function ($storeViews) {
+ return is_array($storeViews) && count($storeViews) > 1;
+ }),
+ 'array_merge',
+ []
+ )
+ );
+
+ if (count($multipleStoresInWebsite) < 1) {
+ return [];
+ }
+
$connection = $this->moduleDataSetup->getConnection();
$batchSelectIterator = $this->batchQueryGenerator->generate(
'value_id',
@@ -184,9 +199,10 @@ private function fetchAttributeValues($tableName)
self::ATTRIBUTE_WEBSITE
)
->where(
- 'cpei.store_id <> ?',
- self::GLOBAL_STORE_VIEW_ID
- )
+ 'cpei.store_id IN (?)',
+ $multipleStoresInWebsite
+ ),
+ 1000
);
foreach ($batchSelectIterator as $select) {
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
index 44c960dc37641..692487c1d60cd 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
index 7c04e9bd83d56..76f65381f43fa 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml
index e7d9a63484bc6..a99420bcf95bb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCreateRootCategoryActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index db148b2cf3114..84231473b685d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
@@ -157,7 +157,7 @@
-
+
@@ -181,6 +181,37 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
index 3f4ee180fc65f..fd80838692065 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
index 33f4ccac2b98f..5948ca12dcf0f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
@@ -34,4 +34,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
index 9d6af144b8f22..1bd9bb4a09c86 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
@@ -53,6 +53,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml
index 4eca49dc28b57..8b657fa1b8aab 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontCategoryPageActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml
index 59c874b8481d3..391a1a7d670de 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInStorefrontProductPageActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml
index 304f38e227960..f2a7a0acffefa 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckItemInLayeredNavigationActionGroup.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
index e8097cfa4fffb..7373d5baea0c5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
@@ -6,12 +6,8 @@
*/
-->
-
-
-
+
-
@@ -39,8 +35,23 @@
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml
index ae9dc0557a9bd..7bb9aa60ca628 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/MoveCategoryActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
index 07fba7cc6be06..8f89a85e14892 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenEditProductOnBackendActionGroup.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml
index e8794ab895c6b..c460dcbfbec91 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductFromCategoryPageActionGroup.xml
@@ -5,7 +5,7 @@
* See COPYING.txt for license details.
-->
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml
index 53acfe2b4372d..2f9d38516bd05 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/RestoreLayoutSettingActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml
index 943fe803232e6..53e7ea3589d1e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAndMultiselectActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
index 5fbc9c5d7fcad..113e108577aa8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchForProductOnBackendActionGroup.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml
index 105a5c58788de..c7ae52d2b37c3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAddToCartCustomOptionsProductPageActionGroup.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
index 25f059c84f4ef..c980c43b8f3af 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
@@ -45,4 +45,9 @@
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml
index 7af1cacfb3da8..04e15da91777c 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontCompareActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
index eb672cd162e82..5f0d03597dab1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml
index d688a1dc2844e..82042975d5fb8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontProductPageActionGroup.xml
@@ -6,18 +6,15 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
index 42351741d9fa8..5c79c321c9431 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
simpleCategory
simplecategory
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
index 8ae57f9239902..8a26b6babdbbc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
1
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
index e93138fecfd47..389c41abf0bd1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CustomAttributeData.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
url_key
category
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
index 2423383bc19f7..a46d40c62c76e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/FrontendLabelData.xml
@@ -7,9 +7,13 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
0
attribute
+
+ 0
+ attributeTwo
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
index c674a8fc144ce..1f4b1470098e2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ImageContentData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
/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
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
index f67370dcff296..b367cdcab9d8b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
attribute
textarea
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
index 60b38812e4ced..98c9a70e6aad4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeMediaGalleryEntryData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
image
Test Image
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
index 15c2dc8bbebca..c575f1a5db82f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
option1
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml
index 68c0a54ff88fc..68f51559a9f31 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeSetData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
4
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 46ad529e5e89e..9ae551b69d388 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
testSku
simple
@@ -197,6 +197,15 @@
magento-logo
png
+
+ magento-again
+ 1.00
+ Upload File
+ Yes
+ magento-again.jpg
+ magento-again
+ jpg
+
霁产品
simple
@@ -210,6 +219,32 @@
EavStockItem
CustomAttributeCategoryIds
+
+ SimpleOne™Product
+ simple
+ 4
+ 4
+ SimpleOne™Product
+ 50.00
+ testurlkey
+ 1
+ 100
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ SimpleTwo霁产品<カネボウPro
+ simple
+ 4
+ 4
+ SimpleTwo霁产品<カネボウPro
+ 50.00
+ testurlkey
+ 1
+ 100
+ EavStockItem
+ CustomAttributeCategoryIds
+
virtualProduct
virtual
@@ -280,6 +315,10 @@
ProductOptionDateTime
ProductOptionTime
+
+
+ ProductOptionDropDownWithLongValuesTitle
+
api-virtual-product
virtual
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
index 88ff2bbace47a..6e532637fb6d3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductExtensionAttributeData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
Qty_1000
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
index b123800a6cc84..ea0bcafe56c48 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
10
100
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml
index 95905eb90d926..ca5024920ad40 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
OptionField
@@ -59,6 +59,15 @@
ProductOptionValueDropdown1
ProductOptionValueDropdown2
+
+
+ OptionDropDownWithLongTitles
+ drop_down
+ 4
+ true
+ ProductOptionValueDropdownLongTitle1
+ ProductOptionValueDropdownLongTitle2
+
OptionRadioButtons
@@ -112,4 +121,4 @@
0.00
percent
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml
index 815f8cf16809b..d16a201cd9ecc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductOptionValueData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
OptionValueDropDown1
1
@@ -50,4 +50,16 @@
2
fixed
-
\ No newline at end of file
+
+ Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj11111Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj11111
+ 1
+ 10
+ fixed
+
+
+ Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj22222Optisfvdklvfnkljvnfdklpvnfdjklfdvnjkvfdkjnvfdjkfvndj22222
+ 2
+ 20
+ percent
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
index 4fae51de86c45..39ecc2d440fc2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/StockItemData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
1000
true
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml
index a703e56beda01..ce964e2d71503 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/StoreLabelData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
0
option1
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/category-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/category-meta.xml
index 0880315db5d6b..ae491aefc10cf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/category-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/category-meta.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
application/json
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/custom_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/custom_attribute-meta.xml
index aed9b7a979836..a37bb36eb6597 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/custom_attribute-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/custom_attribute-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
string
string
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/empty_extension_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/empty_extension_attribute-meta.xml
index d8410593cb5b4..7faac6c3b6d3d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/empty_extension_attribute-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/empty_extension_attribute-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/frontend_label-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/frontend_label-meta.xml
index d0bcbd3e5db97..063b8c2e5ac63 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/frontend_label-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/frontend_label-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
integer
string
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml
index 212de2b39d363..9ece47c01fca3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
application/json
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute-meta.xml
index 93396352ba506..1e9aa3bc219e9 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
application/json
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_media_gallery_entry-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_media_gallery_entry-meta.xml
index 8033e8c33a349..521e864702e57 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_media_gallery_entry-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_media_gallery_entry-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
application/json
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_option-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_option-meta.xml
index 176afa8d58d7c..467ff9a48eb77 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_option-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_option-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
application/json
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_set-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_set-meta.xml
index eef82b07aaf4f..6f04c48e79254 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_set-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_attribute_set-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
application/json
integer
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_extension_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_extension_attribute-meta.xml
index 8d0d1e66c81e3..127a754c88808 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_extension_attribute-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_extension_attribute-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
stock_item
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml
index 5e631b2ea3a28..a2fcbb1417d6f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
string
string
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link_extension_attribute-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link_extension_attribute-meta.xml
index 07ea02f5b7aee..90888463ef8a2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link_extension_attribute-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_link_extension_attribute-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
application/json
integer
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml
index 56b3ee25ef735..450ea99b9d016 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_links-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
application/json
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option-meta.xml
index adc5a33507af6..6464c2988ad25 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
string
integer
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option_value-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option_value-meta.xml
index f4273f5796830..bce77bc3a2612 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option_value-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/product_option_value-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
string
integer
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/stock_item-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/stock_item-meta.xml
index e7e79d69055c6..6ec5f2c8051ea 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/stock_item-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/stock_item-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
integer
boolean
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/store_label-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/store_label-meta.xml
index abb9b003dc59e..584ba5eebb551 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/store_label-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/store_label-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
integer
string
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/validation_rule-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/validation_rule-meta.xml
index c568e52b2ab3c..aa120491ece5d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Metadata/validation_rule-meta.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/validation_rule-meta.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataOperation.xsd">
string
string
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml
index cfefa8cb2c4bc..e1c8e5c75e9ac 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryEditPage.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
index 7cabe0e18f0b6..9349e188430f4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminCategoryPage.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeFormPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeFormPage.xml
index b04aff5f161da..fab87f90f86dd 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeFormPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeFormPage.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml
index a5de7453d9c23..e6aafa53601a6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeGridPage.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetEditPage.xml
index 4034f2ab075d4..3e89cbc8262ce 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetEditPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetEditPage.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetGridPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetGridPage.xml
index 0d879768eb494..d55e71adca24b 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetGridPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributeSetGridPage.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributesEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributesEditPage.xml
index 4918041d2cd88..66475a93b75b1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributesEditPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductAttributesEditPage.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
index 35fa00efcfe8e..fc776b49ba213 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
@@ -16,5 +16,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductEditPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductEditPage.xml
index 9312d4dfcfbe9..c9debf8bf3b3d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductEditPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductEditPage.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductIndexPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductIndexPage.xml
index 66cd691176268..a6edf06f2c1b7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductIndexPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductIndexPage.xml
@@ -6,7 +6,7 @@
*/
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/ProductCatalogPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/ProductCatalogPage.xml
index 742b46fcaf7ed..012aeaaf14e70 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/ProductCatalogPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/ProductCatalogPage.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontCategoryPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontCategoryPage.xml
index c5b9fe869558e..469c153d38b88 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontCategoryPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontCategoryPage.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductComparePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductComparePage.xml
index f0599a021d4c4..5451d92022496 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductComparePage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductComparePage.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
index 8fd59585938be..5aaa78822af08 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/StorefrontProductPage.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminAddProductsToOptionPanelSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminAddProductsToOptionPanelSection.xml
index 4541ad25af231..069a8b28698d1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminAddProductsToOptionPanelSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminAddProductsToOptionPanelSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
index 3ed3763da19d6..8b69a44993f17 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryBasicFieldSection.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">