diff --git a/README.md b/README.md
index e73da84d66f46..9e3cf448f99fb 100644
--- a/README.md
+++ b/README.md
@@ -5,14 +5,14 @@
getUiId('content-header') ?>>
+
diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
index b3a6e5d795cea..ebc4ac1fb056a 100644
--- a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
+++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
@@ -19,8 +19,8 @@
+
-
diff --git a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
index e56cc6f32d804..f8d2f8bc11116 100644
--- a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
+++ b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
@@ -374,8 +374,17 @@ define([
function applyTierPrice(oneItemPrice, qty, optionConfig) {
var tiers = optionConfig.tierPrice,
magicKey = _.keys(oneItemPrice)[0],
+ tiersFirstKey = _.keys(optionConfig)[0],
lowest = false;
+ if (!tiers) {//tiers is undefined when options has only one option
+ tiers = optionConfig[tiersFirstKey].tierPrice;
+ }
+
+ tiers.sort(function (a, b) {//sorting based on "price_qty"
+ return a['price_qty'] - b['price_qty'];
+ });
+
_.each(tiers, function (tier, index) {
if (tier['price_qty'] > qty) {
return;
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 0730e7a7c5dc1..342bbc388f872 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
@@ -6,8 +6,10 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute;
-use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\AsynchronousOperations\Api\Data\OperationInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Backend\App\Action;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
* Class Save
@@ -16,75 +18,68 @@
class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute implements HttpPostActionInterface
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor
+ * @var \Magento\Framework\Bulk\BulkManagementInterface
*/
- protected $_productFlatIndexerProcessor;
+ private $bulkManagement;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
+ * @var \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory
*/
- protected $_productPriceIndexerProcessor;
+ private $operationFactory;
/**
- * Catalog product
- *
- * @var \Magento\Catalog\Helper\Product
+ * @var \Magento\Framework\DataObject\IdentityGeneratorInterface
*/
- protected $_catalogProduct;
+ private $identityService;
/**
- * @var \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory
+ * @var \Magento\Framework\Serialize\SerializerInterface
*/
- protected $stockItemFactory;
+ private $serializer;
/**
- * Stock Indexer
- *
- * @var \Magento\CatalogInventory\Model\Indexer\Stock\Processor
+ * @var \Magento\Authorization\Model\UserContextInterface
*/
- protected $_stockIndexerProcessor;
+ private $userContext;
/**
- * @var \Magento\Framework\Api\DataObjectHelper
+ * @var int
*/
- protected $dataObjectHelper;
+ private $bulkSize;
/**
* @param Action\Context $context
* @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper
- * @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor
- * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor
- * @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor
- * @param \Magento\Catalog\Helper\Product $catalogProduct
- * @param \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory
- * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+ * @param \Magento\Framework\Bulk\BulkManagementInterface $bulkManagement
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory $operartionFactory
+ * @param \Magento\Framework\DataObject\IdentityGeneratorInterface $identityService
+ * @param \Magento\Framework\Serialize\SerializerInterface $serializer
+ * @param \Magento\Authorization\Model\UserContextInterface $userContext
+ * @param int $bulkSize
*/
public function __construct(
Action\Context $context,
\Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper,
- \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
- \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
- \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor,
- \Magento\Catalog\Helper\Product $catalogProduct,
- \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory,
- \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+ \Magento\Framework\Bulk\BulkManagementInterface $bulkManagement,
+ \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory $operartionFactory,
+ \Magento\Framework\DataObject\IdentityGeneratorInterface $identityService,
+ \Magento\Framework\Serialize\SerializerInterface $serializer,
+ \Magento\Authorization\Model\UserContextInterface $userContext,
+ int $bulkSize = 100
) {
- $this->_productFlatIndexerProcessor = $productFlatIndexerProcessor;
- $this->_productPriceIndexerProcessor = $productPriceIndexerProcessor;
- $this->_stockIndexerProcessor = $stockIndexerProcessor;
- $this->_catalogProduct = $catalogProduct;
- $this->stockItemFactory = $stockItemFactory;
parent::__construct($context, $attributeHelper);
- $this->dataObjectHelper = $dataObjectHelper;
+ $this->bulkManagement = $bulkManagement;
+ $this->operationFactory = $operartionFactory;
+ $this->identityService = $identityService;
+ $this->serializer = $serializer;
+ $this->userContext = $userContext;
+ $this->bulkSize = $bulkSize;
}
/**
* Update product attributes
*
- * @return \Magento\Backend\Model\View\Result\Redirect
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @return \Magento\Framework\Controller\Result\Redirect
*/
public function execute()
{
@@ -93,128 +88,184 @@ public function execute()
}
/* Collect Data */
- $inventoryData = $this->getRequest()->getParam('inventory', []);
$attributesData = $this->getRequest()->getParam('attributes', []);
$websiteRemoveData = $this->getRequest()->getParam('remove_website_ids', []);
$websiteAddData = $this->getRequest()->getParam('add_website_ids', []);
- /* Prepare inventory data item options (use config settings) */
- $options = $this->_objectManager->get(\Magento\CatalogInventory\Api\StockConfigurationInterface::class)
- ->getConfigItemOptions();
- foreach ($options as $option) {
- if (isset($inventoryData[$option]) && !isset($inventoryData['use_config_' . $option])) {
- $inventoryData['use_config_' . $option] = 0;
- }
- }
+ $storeId = $this->attributeHelper->getSelectedStoreId();
+ $websiteId = $this->attributeHelper->getStoreWebsiteId($storeId);
+ $productIds = $this->attributeHelper->getProductIds();
+
+ $attributesData = $this->sanitizeProductAttributes($attributesData);
try {
- $storeId = $this->attributeHelper->getSelectedStoreId();
- if ($attributesData) {
- $dateFormat = $this->_objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class)
- ->getDateFormat(\IntlDateFormatter::SHORT);
-
- foreach ($attributesData as $attributeCode => $value) {
- $attribute = $this->_objectManager->get(\Magento\Eav\Model\Config::class)
- ->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode);
- if (!$attribute->getAttributeId()) {
- unset($attributesData[$attributeCode]);
- continue;
- }
- if ($attribute->getBackendType() == 'datetime') {
- if (!empty($value)) {
- $filterInput = new \Zend_Filter_LocalizedToNormalized(['date_format' => $dateFormat]);
- $filterInternal = new \Zend_Filter_NormalizedToLocalized(
- ['date_format' => \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT]
- );
- $value = $filterInternal->filter($filterInput->filter($value));
- } else {
- $value = null;
- }
- $attributesData[$attributeCode] = $value;
- } elseif ($attribute->getFrontendInput() == 'multiselect') {
- // Check if 'Change' checkbox has been checked by admin for this attribute
- $isChanged = (bool)$this->getRequest()->getPost('toggle_' . $attributeCode);
- if (!$isChanged) {
- unset($attributesData[$attributeCode]);
- continue;
- }
- if (is_array($value)) {
- $value = implode(',', $value);
- }
- $attributesData[$attributeCode] = $value;
- }
- }
+ $this->publish($attributesData, $websiteRemoveData, $websiteAddData, $storeId, $websiteId, $productIds);
+ $this->messageManager->addSuccessMessage(__('Message is added to queue'));
+ } catch (\Magento\Framework\Exception\LocalizedException $e) {
+ $this->messageManager->addErrorMessage($e->getMessage());
+ } catch (\Exception $e) {
+ $this->messageManager->addExceptionMessage(
+ $e,
+ __('Something went wrong while updating the product(s) attributes.')
+ );
+ }
- $this->_objectManager->get(\Magento\Catalog\Model\Product\Action::class)
- ->updateAttributes($this->attributeHelper->getProductIds(), $attributesData, $storeId);
- }
+ return $this->resultRedirectFactory->create()->setPath('catalog/product/', ['store' => $storeId]);
+ }
- if ($inventoryData) {
- // TODO why use ObjectManager?
- /** @var \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry */
- $stockRegistry = $this->_objectManager
- ->create(\Magento\CatalogInventory\Api\StockRegistryInterface::class);
- /** @var \Magento\CatalogInventory\Api\StockItemRepositoryInterface $stockItemRepository */
- $stockItemRepository = $this->_objectManager
- ->create(\Magento\CatalogInventory\Api\StockItemRepositoryInterface::class);
- foreach ($this->attributeHelper->getProductIds() as $productId) {
- $stockItemDo = $stockRegistry->getStockItem(
- $productId,
- $this->attributeHelper->getStoreWebsiteId($storeId)
- );
- if (!$stockItemDo->getProductId()) {
- $inventoryData['product_id'] = $productId;
- }
-
- $stockItemId = $stockItemDo->getId();
- $this->dataObjectHelper->populateWithArray(
- $stockItemDo,
- $inventoryData,
- \Magento\CatalogInventory\Api\Data\StockItemInterface::class
+ /**
+ * Sanitize product attributes
+ *
+ * @param array $attributesData
+ *
+ * @return array
+ */
+ private function sanitizeProductAttributes($attributesData)
+ {
+ $dateFormat = $this->_objectManager->get(TimezoneInterface::class)->getDateFormat(\IntlDateFormatter::SHORT);
+ $config = $this->_objectManager->get(\Magento\Eav\Model\Config::class);
+
+ foreach ($attributesData as $attributeCode => $value) {
+ $attribute = $config->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode);
+ if (!$attribute->getAttributeId()) {
+ unset($attributesData[$attributeCode]);
+ continue;
+ }
+ if ($attribute->getBackendType() === 'datetime') {
+ if (!empty($value)) {
+ $filterInput = new \Zend_Filter_LocalizedToNormalized(['date_format' => $dateFormat]);
+ $filterInternal = new \Zend_Filter_NormalizedToLocalized(
+ ['date_format' => \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT]
);
- $stockItemDo->setItemId($stockItemId);
- $stockItemRepository->save($stockItemDo);
+ $value = $filterInternal->filter($filterInput->filter($value));
+ } else {
+ $value = null;
}
- $this->_stockIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
- }
-
- if ($websiteAddData || $websiteRemoveData) {
- /* @var $actionModel \Magento\Catalog\Model\Product\Action */
- $actionModel = $this->_objectManager->get(\Magento\Catalog\Model\Product\Action::class);
- $productIds = $this->attributeHelper->getProductIds();
-
- if ($websiteRemoveData) {
- $actionModel->updateWebsites($productIds, $websiteRemoveData, 'remove');
+ $attributesData[$attributeCode] = $value;
+ } elseif ($attribute->getFrontendInput() === 'multiselect') {
+ // Check if 'Change' checkbox has been checked by admin for this attribute
+ $isChanged = (bool)$this->getRequest()->getPost('toggle_' . $attributeCode);
+ if (!$isChanged) {
+ unset($attributesData[$attributeCode]);
+ continue;
}
- if ($websiteAddData) {
- $actionModel->updateWebsites($productIds, $websiteAddData, 'add');
+ if (is_array($value)) {
+ $value = implode(',', $value);
}
-
- $this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
+ $attributesData[$attributeCode] = $value;
}
+ }
+ return $attributesData;
+ }
- $this->messageManager->addSuccessMessage(
- __('A total of %1 record(s) were updated.', count($this->attributeHelper->getProductIds()))
- );
-
- $this->_productFlatIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
+ /**
+ * Schedule new bulk
+ *
+ * @param array $attributesData
+ * @param array $websiteRemoveData
+ * @param array $websiteAddData
+ * @param int $storeId
+ * @param int $websiteId
+ * @param array $productIds
+ * @throws \Magento\Framework\Exception\LocalizedException
+ *
+ * @return void
+ */
+ private function publish(
+ $attributesData,
+ $websiteRemoveData,
+ $websiteAddData,
+ $storeId,
+ $websiteId,
+ $productIds
+ ):void {
+ $productIdsChunks = array_chunk($productIds, $this->bulkSize);
+ $bulkUuid = $this->identityService->generateId();
+ $bulkDescription = __('Update attributes for ' . count($productIds) . ' selected products');
+ $operations = [];
+ foreach ($productIdsChunks as $productIdsChunk) {
+ if ($websiteRemoveData || $websiteAddData) {
+ $dataToUpdate = [
+ 'website_assign' => $websiteAddData,
+ 'website_detach' => $websiteRemoveData
+ ];
+ $operations[] = $this->makeOperation(
+ 'Update website assign',
+ 'product_action_attribute.website.update',
+ $dataToUpdate,
+ $storeId,
+ $websiteId,
+ $productIdsChunk,
+ $bulkUuid
+ );
+ }
- if ($this->_catalogProduct->isDataForPriceIndexerWasChanged($attributesData)
- || !empty($websiteRemoveData)
- || !empty($websiteAddData)
- ) {
- $this->_productPriceIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
+ if ($attributesData) {
+ $operations[] = $this->makeOperation(
+ 'Update product attributes',
+ 'product_action_attribute.update',
+ $attributesData,
+ $storeId,
+ $websiteId,
+ $productIdsChunk,
+ $bulkUuid
+ );
}
- } catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addErrorMessage($e->getMessage());
- } catch (\Exception $e) {
- $this->messageManager->addExceptionMessage(
- $e,
- __('Something went wrong while updating the product(s) attributes.')
+ }
+
+ if (!empty($operations)) {
+ $result = $this->bulkManagement->scheduleBulk(
+ $bulkUuid,
+ $operations,
+ $bulkDescription,
+ $this->userContext->getUserId()
);
+ if (!$result) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Something went wrong while processing the request.')
+ );
+ }
}
+ }
+
+ /**
+ * Make asynchronous operation
+ *
+ * @param string $meta
+ * @param string $queue
+ * @param array $dataToUpdate
+ * @param int $storeId
+ * @param int $websiteId
+ * @param array $productIds
+ * @param int $bulkUuid
+ *
+ * @return OperationInterface
+ */
+ private function makeOperation(
+ $meta,
+ $queue,
+ $dataToUpdate,
+ $storeId,
+ $websiteId,
+ $productIds,
+ $bulkUuid
+ ): OperationInterface {
+ $dataToEncode = [
+ 'meta_information' => $meta,
+ 'product_ids' => $productIds,
+ 'store_id' => $storeId,
+ 'website_id' => $websiteId,
+ 'attributes' => $dataToUpdate
+ ];
+ $data = [
+ 'data' => [
+ 'bulk_uuid' => $bulkUuid,
+ 'topic_name' => $queue,
+ 'serialized_data' => $this->serializer->serialize($dataToEncode),
+ 'status' => \Magento\Framework\Bulk\OperationInterface::STATUS_TYPE_OPEN,
+ ]
+ ];
- return $this->resultRedirectFactory->create()
- ->setPath('catalog/product/', ['store' => $this->attributeHelper->getSelectedStoreId()]);
+ return $this->operationFactory->create($data);
}
}
diff --git a/app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php b/app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php
new file mode 100644
index 0000000000000..dc24a3090481e
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php
@@ -0,0 +1,163 @@
+catalogProduct = $catalogProduct;
+ $this->productFlatIndexerProcessor = $productFlatIndexerProcessor;
+ $this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
+ $this->productAction = $action;
+ $this->logger = $logger;
+ $this->serializer = $serializer;
+ $this->operationManagement = $operationManagement;
+ $this->entityManager = $entityManager;
+ }
+
+ /**
+ * Process
+ *
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
+ * @throws \Exception
+ *
+ * @return void
+ */
+ public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
+ {
+ try {
+ $serializedData = $operation->getSerializedData();
+ $data = $this->serializer->unserialize($serializedData);
+ $this->execute($data);
+ } catch (\Zend_Db_Adapter_Exception $e) {
+ $this->logger->critical($e->getMessage());
+ if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
+ || $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
+ || $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
+ ) {
+ $status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } else {
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __(
+ 'Sorry, something went wrong during product attributes update. Please see log for details.'
+ );
+ }
+ } catch (NoSuchEntityException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = ($e instanceof TemporaryStateExceptionInterface)
+ ? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
+ : OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (LocalizedException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (\Exception $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
+ }
+
+ $operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
+ ->setErrorCode($errorCode ?? null)
+ ->setResultMessage($message ?? null);
+
+ $this->entityManager->save($operation);
+ }
+
+ /**
+ * Execute
+ *
+ * @param array $data
+ *
+ * @return void
+ */
+ private function execute($data): void
+ {
+ $this->productAction->updateAttributes($data['product_ids'], $data['attributes'], $data['store_id']);
+ if ($this->catalogProduct->isDataForPriceIndexerWasChanged($data['attributes'])) {
+ $this->productPriceIndexerProcessor->reindexList($data['product_ids']);
+ }
+
+ $this->productFlatIndexerProcessor->reindexList($data['product_ids']);
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php b/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php
new file mode 100644
index 0000000000000..32ba39d9afd98
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php
@@ -0,0 +1,168 @@
+productFlatIndexerProcessor = $productFlatIndexerProcessor;
+ $this->productAction = $action;
+ $this->logger = $logger;
+ $this->serializer = $serializer;
+ $this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
+ $this->entityManager = $entityManager;
+ }
+
+ /**
+ * Process
+ *
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
+ * @throws \Exception
+ *
+ * @return void
+ */
+ public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
+ {
+ try {
+ $serializedData = $operation->getSerializedData();
+ $data = $this->serializer->unserialize($serializedData);
+ $this->execute($data);
+ } catch (\Zend_Db_Adapter_Exception $e) {
+ $this->logger->critical($e->getMessage());
+ if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
+ || $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
+ || $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
+ ) {
+ $status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __($e->getMessage());
+ } else {
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __(
+ 'Sorry, something went wrong during product attributes update. Please see log for details.'
+ );
+ }
+ } catch (NoSuchEntityException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = ($e instanceof TemporaryStateExceptionInterface)
+ ? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
+ : OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (LocalizedException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (\Exception $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
+ }
+
+ $operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
+ ->setErrorCode($errorCode ?? null)
+ ->setResultMessage($message ?? null);
+
+ $this->entityManager->save($operation);
+ }
+
+ /**
+ * Update website in products
+ *
+ * @param array $productIds
+ * @param array $websiteRemoveData
+ * @param array $websiteAddData
+ *
+ * @return void
+ */
+ private function updateWebsiteInProducts($productIds, $websiteRemoveData, $websiteAddData): void
+ {
+ if ($websiteRemoveData) {
+ $this->productAction->updateWebsites($productIds, $websiteRemoveData, 'remove');
+ }
+ if ($websiteAddData) {
+ $this->productAction->updateWebsites($productIds, $websiteAddData, 'add');
+ }
+ }
+
+ /**
+ * Execute
+ *
+ * @param array $data
+ *
+ * @return void
+ */
+ private function execute($data): void
+ {
+ $this->updateWebsiteInProducts(
+ $data['product_ids'],
+ $data['attributes']['website_detach'],
+ $data['attributes']['website_assign']
+ );
+ $this->productPriceIndexerProcessor->reindexList($data['product_ids']);
+ $this->productFlatIndexerProcessor->reindexList($data['product_ids']);
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Action.php b/app/code/Magento/Catalog/Model/Product/Action.php
index f78048424b42c..3863cf2457247 100644
--- a/app/code/Magento/Catalog/Model/Product/Action.php
+++ b/app/code/Magento/Catalog/Model/Product/Action.php
@@ -168,5 +168,7 @@ public function updateWebsites($productIds, $websiteIds, $type)
if (!$categoryIndexer->isScheduled()) {
$categoryIndexer->reindexList(array_unique($productIds));
}
+
+ $this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
}
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
index d786795bc3090..90d732c9654e1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
@@ -302,4 +302,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index 20ea93cd272f1..da570f9ed99b0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -20,7 +20,7 @@
-
+
@@ -34,6 +34,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -77,6 +96,11 @@
+
+
+
+
+
@@ -389,6 +413,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
new file mode 100644
index 0000000000000..7917fe68aaebc
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
new file mode 100644
index 0000000000000..963c9d9f1c911
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml
new file mode 100644
index 0000000000000..1bb7c179dfca8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductImagesOnProductPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml
new file mode 100644
index 0000000000000..6cb156723b286
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductNameOnProductPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml
new file mode 100644
index 0000000000000..3c62ef89e584b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductPriceOnProductPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml
new file mode 100644
index 0000000000000..85d3927a6d6d0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontAssertProductSkuOnProductPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml
new file mode 100644
index 0000000000000..f5fabae5fc4ce
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/StorefrontOpenProductPageActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml
index 4c6b0749a0f9e..31783526932b6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml
@@ -12,4 +12,9 @@
0
+
+ 55.55
+ 0
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index 0b3a31194ea36..383797933074e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -60,6 +60,48 @@
EavStockItem
CustomAttributeCategoryIds
+
+ SimpleProductForTest1
+ simple
+ 4
+ SimpleProductAfterImport1
+ 250.00
+ 4
+ 1
+ 100
+ simple-product-for-test-1
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ SimpleProductForTest2
+ simple
+ 4
+ SimpleProductAfterImport2
+ 300.00
+ 4
+ 1
+ 100
+ simple-product-for-test-2
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ SimpleProductForTest3
+ simple
+ 4
+ SimpleProductAfterImport3
+ 350.00
+ 4
+ 1
+ 100
+ simple-product-for-test-3
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
SimpleProduct
simple
@@ -386,6 +428,11 @@
ProductOptionDropDownWithLongValuesTitle
+
+
+ ProductOptionField
+ ProductOptionArea
+
api-virtual-product
virtual
@@ -906,4 +953,20 @@
EavStockItem
CustomAttributeCategoryIds
+
+ sku_simple_product_
+ simple
+ 4
+ 4
+ SimpleProduct
+ 560
+ simple-product-
+ 1
+ 25
+ 1
+ 1
+ 1
+ 2
+ EavStockItem
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
index fee86ca1caa29..ea4f4bf53eb71 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
@@ -10,5 +10,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
index e218f5ae74fc0..2de7bf19fd378 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
index 4dcda8dcd41ae..c58479a7b73e5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
@@ -11,6 +11,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index 8504683648bce..8393cee57996f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -13,6 +13,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
index 45e0b03e8d995..ea10e12fb73f5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductMediaSection.xml
@@ -9,6 +9,11 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml
new file mode 100644
index 0000000000000..37ec4e0d32528
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml
new file mode 100644
index 0000000000000..575bb56912b25
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
index 70edb0ce3ea7d..17769c79677f7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
@@ -139,7 +139,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
index d7607b4b269e8..4d581bae700d7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
@@ -54,7 +54,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
index c0eebd1512d6d..8a44c8093ca5e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
@@ -58,7 +58,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
index 845c47c0e4c20..bee13bec370da 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
@@ -52,7 +52,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
index c9a37ec40e8fa..318ab6555235e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
@@ -135,7 +135,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
index d67d5b36109e6..34d85e7b46850 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
@@ -229,7 +229,7 @@
productPriceAmount
-
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php
deleted file mode 100644
index de44af7f58afc..0000000000000
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php
+++ /dev/null
@@ -1,258 +0,0 @@
-attributeHelper = $this->createPartialMock(
- \Magento\Catalog\Helper\Product\Edit\Action\Attribute::class,
- ['getProductIds', 'getSelectedStoreId', 'getStoreWebsiteId']
- );
-
- $this->dataObjectHelperMock = $this->getMockBuilder(\Magento\Framework\Api\DataObjectHelper::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->stockIndexerProcessor = $this->createPartialMock(
- \Magento\CatalogInventory\Model\Indexer\Stock\Processor::class,
- ['reindexList']
- );
-
- $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->resultRedirectFactory = $this->getMockBuilder(\Magento\Backend\Model\View\Result\RedirectFactory::class)
- ->disableOriginalConstructor()
- ->setMethods(['create'])
- ->getMock();
- $this->resultRedirectFactory->expects($this->atLeastOnce())
- ->method('create')
- ->willReturn($resultRedirect);
-
- $this->prepareContext();
-
- $this->object = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject(
- \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Save::class,
- [
- 'context' => $this->context,
- 'attributeHelper' => $this->attributeHelper,
- 'stockIndexerProcessor' => $this->stockIndexerProcessor,
- 'dataObjectHelper' => $this->dataObjectHelperMock,
- ]
- );
- }
-
- /**
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- protected function prepareContext()
- {
- $this->stockItemRepository = $this->getMockBuilder(
- \Magento\CatalogInventory\Api\StockItemRepositoryInterface::class
- )->disableOriginalConstructor()->getMock();
-
- $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class)
- ->disableOriginalConstructor()->getMock();
- $this->response = $this->createMock(\Magento\Framework\App\Response\Http::class);
- $this->objectManager = $this->createMock(\Magento\Framework\ObjectManagerInterface::class);
- $this->eventManager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
- $this->url = $this->createMock(\Magento\Framework\UrlInterface::class);
- $this->redirect = $this->createMock(\Magento\Framework\App\Response\RedirectInterface::class);
- $this->actionFlag = $this->createMock(\Magento\Framework\App\ActionFlag::class);
- $this->view = $this->createMock(\Magento\Framework\App\ViewInterface::class);
- $this->messageManager = $this->createMock(\Magento\Framework\Message\ManagerInterface::class);
- $this->session = $this->createMock(\Magento\Backend\Model\Session::class);
- $this->authorization = $this->createMock(\Magento\Framework\AuthorizationInterface::class);
- $this->auth = $this->createMock(\Magento\Backend\Model\Auth::class);
- $this->helper = $this->createMock(\Magento\Backend\Helper\Data::class);
- $this->backendUrl = $this->createMock(\Magento\Backend\Model\UrlInterface::class);
- $this->formKeyValidator = $this->createMock(\Magento\Framework\Data\Form\FormKey\Validator::class);
- $this->localeResolver = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class);
-
- $this->context = $this->context = $this->createPartialMock(\Magento\Backend\App\Action\Context::class, [
- 'getRequest',
- 'getResponse',
- 'getObjectManager',
- 'getEventManager',
- 'getUrl',
- 'getRedirect',
- 'getActionFlag',
- 'getView',
- 'getMessageManager',
- 'getSession',
- 'getAuthorization',
- 'getAuth',
- 'getHelper',
- 'getBackendUrl',
- 'getFormKeyValidator',
- 'getLocaleResolver',
- 'getResultRedirectFactory'
- ]);
- $this->context->expects($this->any())->method('getRequest')->willReturn($this->request);
- $this->context->expects($this->any())->method('getResponse')->willReturn($this->response);
- $this->context->expects($this->any())->method('getObjectManager')->willReturn($this->objectManager);
- $this->context->expects($this->any())->method('getEventManager')->willReturn($this->eventManager);
- $this->context->expects($this->any())->method('getUrl')->willReturn($this->url);
- $this->context->expects($this->any())->method('getRedirect')->willReturn($this->redirect);
- $this->context->expects($this->any())->method('getActionFlag')->willReturn($this->actionFlag);
- $this->context->expects($this->any())->method('getView')->willReturn($this->view);
- $this->context->expects($this->any())->method('getMessageManager')->willReturn($this->messageManager);
- $this->context->expects($this->any())->method('getSession')->willReturn($this->session);
- $this->context->expects($this->any())->method('getAuthorization')->willReturn($this->authorization);
- $this->context->expects($this->any())->method('getAuth')->willReturn($this->auth);
- $this->context->expects($this->any())->method('getHelper')->willReturn($this->helper);
- $this->context->expects($this->any())->method('getBackendUrl')->willReturn($this->backendUrl);
- $this->context->expects($this->any())->method('getFormKeyValidator')->willReturn($this->formKeyValidator);
- $this->context->expects($this->any())->method('getLocaleResolver')->willReturn($this->localeResolver);
- $this->context->expects($this->any())
- ->method('getResultRedirectFactory')
- ->willReturn($this->resultRedirectFactory);
-
- $this->product = $this->createPartialMock(
- \Magento\Catalog\Model\Product::class,
- ['isProductsHasSku', '__wakeup']
- );
-
- $this->stockItemService = $this->getMockBuilder(\Magento\CatalogInventory\Api\StockRegistryInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['getStockItem', 'saveStockItem'])
- ->getMockForAbstractClass();
- $this->stockItem = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class)
- ->setMethods(['getId', 'getProductId'])
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
-
- $this->stockConfig = $this->getMockBuilder(\Magento\CatalogInventory\Api\StockConfigurationInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
-
- $this->objectManager->expects($this->any())->method('create')->will($this->returnValueMap([
- [\Magento\Catalog\Model\Product::class, [], $this->product],
- [\Magento\CatalogInventory\Api\StockRegistryInterface::class, [], $this->stockItemService],
- [\Magento\CatalogInventory\Api\StockItemRepositoryInterface::class, [], $this->stockItemRepository],
- ]));
-
- $this->objectManager->expects($this->any())->method('get')->will($this->returnValueMap([
- [\Magento\CatalogInventory\Api\StockConfigurationInterface::class, $this->stockConfig],
- ]));
- }
-
- public function testExecuteThatProductIdsAreObtainedFromAttributeHelper()
- {
- $this->attributeHelper->expects($this->any())->method('getProductIds')->will($this->returnValue([5]));
- $this->attributeHelper->expects($this->any())->method('getSelectedStoreId')->will($this->returnValue([1]));
- $this->attributeHelper->expects($this->any())->method('getStoreWebsiteId')->will($this->returnValue(1));
- $this->stockConfig->expects($this->any())->method('getConfigItemOptions')->will($this->returnValue([]));
- $this->dataObjectHelperMock->expects($this->any())
- ->method('populateWithArray')
- ->with($this->stockItem, $this->anything(), \Magento\CatalogInventory\Api\Data\StockItemInterface::class)
- ->willReturnSelf();
- $this->product->expects($this->any())->method('isProductsHasSku')->with([5])->will($this->returnValue(true));
- $this->stockItemService->expects($this->any())->method('getStockItem')->with(5, 1)
- ->will($this->returnValue($this->stockItem));
- $this->stockIndexerProcessor->expects($this->any())->method('reindexList')->with([5]);
-
- $this->request->expects($this->any())->method('getParam')->will($this->returnValueMap([
- ['inventory', [], [7]],
- ]));
-
- $this->messageManager->expects($this->never())->method('addErrorMessage');
- $this->messageManager->expects($this->never())->method('addExceptionMessage');
-
- $this->object->execute();
- }
-}
diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json
index 44d051933909b..5c3ee3da8ca81 100644
--- a/app/code/Magento/Catalog/composer.json
+++ b/app/code/Magento/Catalog/composer.json
@@ -7,6 +7,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
+ "magento/module-authorization": "*",
+ "magento/module-asynchronous-operations": "*",
"magento/module-backend": "*",
"magento/module-catalog-inventory": "*",
"magento/module-catalog-rule": "*",
diff --git a/app/code/Magento/Catalog/etc/communication.xml b/app/code/Magento/Catalog/etc/communication.xml
new file mode 100644
index 0000000000000..1a957f6ac9fe5
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/communication.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index 7d2c3699ee2c2..49447447622f9 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -72,6 +72,7 @@
+
diff --git a/app/code/Magento/Catalog/etc/queue.xml b/app/code/Magento/Catalog/etc/queue.xml
new file mode 100644
index 0000000000000..137f34a5c1e25
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_consumer.xml b/app/code/Magento/Catalog/etc/queue_consumer.xml
new file mode 100644
index 0000000000000..d9e66ae69c10c
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_consumer.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_publisher.xml b/app/code/Magento/Catalog/etc/queue_publisher.xml
new file mode 100644
index 0000000000000..1606ea42ec0b3
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_publisher.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_topology.xml b/app/code/Magento/Catalog/etc/queue_topology.xml
new file mode 100644
index 0000000000000..bdac891afbdb8
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_topology.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml
index c930d2195a01b..1c4a37fedebe3 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml
@@ -23,8 +23,8 @@
- = $block->escapeHtml(__($_data['label'])) ?>
- = /* @escapeNotVerified */ $_helper->productAttribute($_product, $_data['value'], $_data['code']) ?>
+ = $block->escapeHtml($_data['label']) ?>
+ = /* @escapeNotVerified */ $_helper->productAttribute($_product, $_data['value'], $_data['code']) ?>
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
new file mode 100644
index 0000000000000..b9eea2b114634
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
new file mode 100644
index 0000000000000..1f5ae6b6905bc
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
new file mode 100644
index 0000000000000..a587d71ba0e68
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
new file mode 100644
index 0000000000000..6f64da4693692
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
new file mode 100644
index 0000000000000..993f1c9cd9da2
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
new file mode 100644
index 0000000000000..491d20604a08b
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
new file mode 100644
index 0000000000000..f671b54803e35
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php b/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
index f711268bc7930..0fa4b919c40fa 100644
--- a/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
+++ b/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
@@ -7,16 +7,24 @@
/**
* Interface StockRegistryProviderInterface
+ *
+ * @deprecated 2.3.0 Replaced with Multi Source Inventory
+ * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
*/
interface StockRegistryProviderInterface
{
/**
+ * Get stock.
+ *
* @param int $scopeId
* @return \Magento\CatalogInventory\Api\Data\StockInterface
*/
public function getStock($scopeId);
/**
+ * Get stock item.
+ *
* @param int $productId
* @param int $scopeId
* @return \Magento\CatalogInventory\Api\Data\StockItemInterface
@@ -24,6 +32,8 @@ public function getStock($scopeId);
public function getStockItem($productId, $scopeId);
/**
+ * Get stock status.
+ *
* @param int $productId
* @param int $scopeId
* @return \Magento\CatalogInventory\Api\Data\StockStatusInterface
diff --git a/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php b/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
index 89fb54e7e496b..30f703b5b928f 100644
--- a/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
+++ b/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
@@ -9,22 +9,32 @@
/**
* Interface StockStateProviderInterface
+ *
+ * @deprecated 2.3.0 Replaced with Multi Source Inventory
+ * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
*/
interface StockStateProviderInterface
{
/**
+ * Verify stock.
+ *
* @param StockItemInterface $stockItem
* @return bool
*/
public function verifyStock(StockItemInterface $stockItem);
/**
+ * Verify notification.
+ *
* @param StockItemInterface $stockItem
* @return bool
*/
public function verifyNotification(StockItemInterface $stockItem);
/**
+ * Validate quote qty.
+ *
* @param StockItemInterface $stockItem
* @param int|float $itemQty
* @param int|float $qtyToCheck
@@ -44,8 +54,9 @@ public function checkQuoteItemQty(StockItemInterface $stockItem, $itemQty, $qtyT
public function checkQty(StockItemInterface $stockItem, $qty);
/**
- * Returns suggested qty that satisfies qty increments and minQty/maxQty/minSaleQty/maxSaleQty conditions
- * or original qty if such value does not exist
+ * Returns suggested qty or original qty if such value does not exist.
+ *
+ * Suggested qty satisfies qty increments and minQty/maxQty/minSaleQty/maxSaleQty conditions.
*
* @param StockItemInterface $stockItem
* @param int|float $qty
@@ -54,6 +65,8 @@ public function checkQty(StockItemInterface $stockItem, $qty);
public function suggestQty(StockItemInterface $stockItem, $qty);
/**
+ * Check qty increments.
+ *
* @param StockItemInterface $stockItem
* @param int|float $qty
* @return \Magento\Framework\DataObject
diff --git a/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php b/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
new file mode 100644
index 0000000000000..334d2b22edbfa
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
@@ -0,0 +1,159 @@
+stockIndexerProcessor = $stockIndexerProcessor;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->stockRegistry = $stockRegistry;
+ $this->stockItemRepository = $stockItemRepository;
+ $this->stockConfiguration = $stockConfiguration;
+ $this->attributeHelper = $attributeHelper;
+ $this->messageManager = $messageManager;
+ }
+
+ /**
+ * Around execute plugin
+ *
+ * @param Save $subject
+ * @param callable $proceed
+ *
+ * @return \Magento\Framework\Controller\ResultInterface
+ */
+ public function aroundExecute(Save $subject, callable $proceed)
+ {
+ try {
+ /** @var \Magento\Framework\App\RequestInterface $request */
+ $request = $subject->getRequest();
+ $inventoryData = $request->getParam('inventory', []);
+ $inventoryData = $this->addConfigSettings($inventoryData);
+
+ $storeId = $this->attributeHelper->getSelectedStoreId();
+ $websiteId = $this->attributeHelper->getStoreWebsiteId($storeId);
+ $productIds = $this->attributeHelper->getProductIds();
+
+ if (!empty($inventoryData)) {
+ $this->updateInventoryInProducts($productIds, $websiteId, $inventoryData);
+ }
+
+ return $proceed();
+ } catch (\Magento\Framework\Exception\LocalizedException $e) {
+ $this->messageManager->addErrorMessage($e->getMessage());
+ return $proceed();
+ } catch (\Exception $e) {
+ $this->messageManager->addExceptionMessage(
+ $e,
+ __('Something went wrong while updating the product(s) attributes.')
+ );
+ return $proceed();
+ }
+ }
+
+ /**
+ * Add config settings
+ *
+ * @param array $inventoryData
+ *
+ * @return array
+ */
+ private function addConfigSettings($inventoryData)
+ {
+ $options = $this->stockConfiguration->getConfigItemOptions();
+ foreach ($options as $option) {
+ $useConfig = 'use_config_' . $option;
+ if (isset($inventoryData[$option]) && !isset($inventoryData[$useConfig])) {
+ $inventoryData[$useConfig] = 0;
+ }
+ }
+ return $inventoryData;
+ }
+
+ /**
+ * Update inventory in products
+ *
+ * @param array $productIds
+ * @param int $websiteId
+ * @param array $inventoryData
+ *
+ * @return void
+ */
+ private function updateInventoryInProducts($productIds, $websiteId, $inventoryData): void
+ {
+ foreach ($productIds as $productId) {
+ $stockItemDo = $this->stockRegistry->getStockItem($productId, $websiteId);
+ if (!$stockItemDo->getProductId()) {
+ $inventoryData['product_id'] = $productId;
+ }
+ $stockItemId = $stockItemDo->getId();
+ $this->dataObjectHelper->populateWithArray($stockItemDo, $inventoryData, StockItemInterface::class);
+ $stockItemDo->setItemId($stockItemId);
+ $this->stockItemRepository->save($stockItemDo);
+ }
+ $this->stockIndexerProcessor->reindexList($productIds);
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml
index 51a5b46b59d22..e7d79c593b8c7 100644
--- a/app/code/Magento/CatalogInventory/etc/di.xml
+++ b/app/code/Magento/CatalogInventory/etc/di.xml
@@ -135,4 +135,7 @@
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminCatalogSearchTermActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminCatalogSearchTermActionGroup.xml
new file mode 100644
index 0000000000000..33ffa4fe1b296
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/AdminCatalogSearchTermActionGroup.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
index 6b913e5b458e6..4b52b2c669edf 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
@@ -11,9 +11,9 @@
-
+
-
+
@@ -116,4 +116,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml
new file mode 100644
index 0000000000000..83e4ac50a74e6
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Data/SearchTermData.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Data/SearchTermData.xml
new file mode 100644
index 0000000000000..995b860d107ca
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Data/SearchTermData.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Query text
+ Default Store View
+ http://example.com/
+ No
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermIndexPage.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermIndexPage.xml
new file mode 100644
index 0000000000000..bbafff8ad7739
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermIndexPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermNewPage.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermNewPage.xml
new file mode 100644
index 0000000000000..de7491471741c
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermNewPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
new file mode 100644
index 0000000000000..ac316d060f6e9
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermMessagesSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermMessagesSection.xml
new file mode 100644
index 0000000000000..5d19198a1b94c
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermMessagesSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml
new file mode 100644
index 0000000000000..a7d577a7508c0
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminCreateSearchTermEntityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminCreateSearchTermEntityTest.xml
new file mode 100644
index 0000000000000..2b425f34f8a5b
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminCreateSearchTermEntityTest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminDeleteSearchTermTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminDeleteSearchTermTest.xml
new file mode 100644
index 0000000000000..c72ed424ef307
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminDeleteSearchTermTest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml
index 593df1c5bc6e1..30a4290d882fb 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Mftf/Test/AdminUrlForProductRewrittenCorrectlyTest.xml
@@ -67,7 +67,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniShoppingCartSubTotalActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniShoppingCartSubTotalActionGroup.xml
new file mode 100644
index 0000000000000..8c5c6f41fffa7
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniShoppingCartSubTotalActionGroup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+ $grabMiniCartTotal
+ {{dataQuote.subtotal}}
+
+
+ $grabMiniCartTotal
+ {{dataQuote.currency}}
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontUpdateProductQtyMiniShoppingCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontUpdateProductQtyMiniShoppingCartActionGroup.xml
new file mode 100644
index 0000000000000..ee8b761a452d4
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontUpdateProductQtyMiniShoppingCartActionGroup.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
index 530157851191f..e7a5992ad8943 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
@@ -15,4 +15,26 @@
495.00
Flat Rate - Fixed
+
+ 560.00
+ 2
+ 1,120.00
+ 10.00
+ 1,130.00
+ Flat Rate - Fixed
+ $
+
+
+ 123.00
+ 3
+ 369.00
+ $
+
+
+ 100.00
+ 20
+ 11
+ 1,320.00
+ $
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
index bdb02835c6276..38c88bf4f80bb 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
@@ -25,6 +25,8 @@
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml
new file mode 100644
index 0000000000000..423f4049f6722
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ grabProductQtyInCart
+ {{quoteQty3Price123.qty}}
+
+
+
+
+
+
+
+
+
+ grabProductQtyInMinicart
+ {{quoteQty3Price123.qty}}
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml
new file mode 100644
index 0000000000000..84080b04c80ee
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{quoteQty11Subtotal1320.qty}}
+ grabProductQtyInCart
+
+
+
+
+
+
+
+ {{quoteQty11Subtotal1320.qty}}
+ grabProductQtyInMinicart
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/UpdateProductFromMiniShoppingCartEntityTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/UpdateProductFromMiniShoppingCartEntityTest.xml
new file mode 100644
index 0000000000000..7318f865a0dc1
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/UpdateProductFromMiniShoppingCartEntityTest.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/etc/di.xml b/app/code/Magento/Checkout/etc/di.xml
index 71dfd12bb4779..4ebd594a28562 100644
--- a/app/code/Magento/Checkout/etc/di.xml
+++ b/app/code/Magento/Checkout/etc/di.xml
@@ -49,7 +49,4 @@
-
-
-
diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml
index 00bcd2a27005a..8f35fe9f37abf 100644
--- a/app/code/Magento/Checkout/etc/frontend/di.xml
+++ b/app/code/Magento/Checkout/etc/frontend/di.xml
@@ -96,4 +96,7 @@
+
+
+
diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html b/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html
index a448537d64e83..4b1a68624e547 100644
--- a/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html
+++ b/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html
@@ -5,17 +5,17 @@
*/
-->
-
+
-
+
-
+
-
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml
index b4bcdaadf9a09..e6ab1c130606b 100644
--- a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml
@@ -17,7 +17,6 @@
-
diff --git a/app/code/Magento/Config/App/Config/Type/System.php b/app/code/Magento/Config/App/Config/Type/System.php
index 2c4b8a8dc48d2..c63ccae871657 100644
--- a/app/code/Magento/Config/App/Config/Type/System.php
+++ b/app/code/Magento/Config/App/Config/Type/System.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Config\App\Config\Type;
use Magento\Framework\App\Config\ConfigSourceInterface;
@@ -13,11 +14,12 @@
use Magento\Config\App\Config\Type\System\Reader;
use Magento\Framework\App\ScopeInterface;
use Magento\Framework\Cache\FrontendInterface;
+use Magento\Framework\Cache\LockGuardedCacheLoader;
use Magento\Framework\Lock\LockManagerInterface;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Store\Model\Config\Processor\Fallback;
-use Magento\Store\Model\ScopeInterface as StoreScope;
use Magento\Framework\Encryption\Encryptor;
+use Magento\Store\Model\ScopeInterface as StoreScope;
/**
* System configuration type
@@ -25,6 +27,7 @@
* @api
* @since 100.1.2
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
class System implements ConfigTypeInterface
{
@@ -42,22 +45,6 @@ class System implements ConfigTypeInterface
* @var string
*/
private static $lockName = 'SYSTEM_CONFIG';
- /**
- * Timeout between retrieves to load the configuration from the cache.
- *
- * Value of the variable in microseconds.
- *
- * @var int
- */
- private static $delayTimeout = 100000;
- /**
- * Lifetime of the lock for write in cache.
- *
- * Value of the variable in seconds.
- *
- * @var int
- */
- private static $lockTimeout = 42;
/**
* @var array
@@ -106,9 +93,9 @@ class System implements ConfigTypeInterface
private $encryptor;
/**
- * @var LockManagerInterface
+ * @var LockGuardedCacheLoader
*/
- private $locker;
+ private $lockQuery;
/**
* @param ConfigSourceInterface $source
@@ -122,6 +109,7 @@ class System implements ConfigTypeInterface
* @param Reader|null $reader
* @param Encryptor|null $encryptor
* @param LockManagerInterface|null $locker
+ * @param LockGuardedCacheLoader|null $lockQuery
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -136,7 +124,8 @@ public function __construct(
$configType = self::CONFIG_TYPE,
Reader $reader = null,
Encryptor $encryptor = null,
- LockManagerInterface $locker = null
+ LockManagerInterface $locker = null,
+ LockGuardedCacheLoader $lockQuery = null
) {
$this->postProcessor = $postProcessor;
$this->cache = $cache;
@@ -145,8 +134,8 @@ public function __construct(
$this->reader = $reader ?: ObjectManager::getInstance()->get(Reader::class);
$this->encryptor = $encryptor
?: ObjectManager::getInstance()->get(Encryptor::class);
- $this->locker = $locker
- ?: ObjectManager::getInstance()->get(LockManagerInterface::class);
+ $this->lockQuery = $lockQuery
+ ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class);
}
/**
@@ -225,83 +214,56 @@ private function getWithParts($path)
}
/**
- * Make lock on data load.
- *
- * @param callable $dataLoader
- * @param bool $flush
- * @return array
- */
- private function lockedLoadData(callable $dataLoader, bool $flush = false): array
- {
- $cachedData = $dataLoader(); //optimistic read
-
- while ($cachedData === false && $this->locker->isLocked(self::$lockName)) {
- usleep(self::$delayTimeout);
- $cachedData = $dataLoader();
- }
-
- while ($cachedData === false) {
- try {
- if ($this->locker->lock(self::$lockName, self::$lockTimeout)) {
- if (!$flush) {
- $data = $this->readData();
- $this->cacheData($data);
- $cachedData = $data;
- } else {
- $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
- $cachedData = [];
- }
- }
- } finally {
- $this->locker->unlock(self::$lockName);
- }
-
- if ($cachedData === false) {
- usleep(self::$delayTimeout);
- $cachedData = $dataLoader();
- }
- }
-
- return $cachedData;
- }
-
- /**
- * Load configuration data for all scopes
+ * Load configuration data for all scopes.
*
* @return array
*/
private function loadAllData()
{
- return $this->lockedLoadData(function () {
+ $loadAction = function () {
$cachedData = $this->cache->load($this->configType);
$data = false;
if ($cachedData !== false) {
$data = $this->serializer->unserialize($this->encryptor->decrypt($cachedData));
}
return $data;
- });
+ };
+
+ return $this->lockQuery->lockedLoadData(
+ self::$lockName,
+ $loadAction,
+ \Closure::fromCallable([$this, 'readData']),
+ \Closure::fromCallable([$this, 'cacheData'])
+ );
}
/**
- * Load configuration data for default scope
+ * Load configuration data for default scope.
*
* @param string $scopeType
* @return array
*/
private function loadDefaultScopeData($scopeType)
{
- return $this->lockedLoadData(function () use ($scopeType) {
+ $loadAction = function () use ($scopeType) {
$cachedData = $this->cache->load($this->configType . '_' . $scopeType);
$scopeData = false;
if ($cachedData !== false) {
$scopeData = [$scopeType => $this->serializer->unserialize($this->encryptor->decrypt($cachedData))];
}
return $scopeData;
- });
+ };
+
+ return $this->lockQuery->lockedLoadData(
+ self::$lockName,
+ $loadAction,
+ \Closure::fromCallable([$this, 'readData']),
+ \Closure::fromCallable([$this, 'cacheData'])
+ );
}
/**
- * Load configuration data for a specified scope
+ * Load configuration data for a specified scope.
*
* @param string $scopeType
* @param string $scopeId
@@ -309,7 +271,7 @@ private function loadDefaultScopeData($scopeType)
*/
private function loadScopeData($scopeType, $scopeId)
{
- return $this->lockedLoadData(function () use ($scopeType, $scopeId) {
+ $loadAction = function () use ($scopeType, $scopeId) {
$cachedData = $this->cache->load($this->configType . '_' . $scopeType . '_' . $scopeId);
$scopeData = false;
if ($cachedData === false) {
@@ -329,7 +291,14 @@ private function loadScopeData($scopeType, $scopeId)
}
return $scopeData;
- });
+ };
+
+ return $this->lockQuery->lockedLoadData(
+ self::$lockName,
+ $loadAction,
+ \Closure::fromCallable([$this, 'readData']),
+ \Closure::fromCallable([$this, 'cacheData'])
+ );
}
/**
@@ -371,7 +340,7 @@ private function cacheData(array $data)
}
/**
- * Walk nested hash map by keys from $pathParts
+ * Walk nested hash map by keys from $pathParts.
*
* @param array $data to walk in
* @param array $pathParts keys path
@@ -408,7 +377,7 @@ private function readData(): array
}
/**
- * Clean cache and global variables cache
+ * Clean cache and global variables cache.
*
* Next items cleared:
* - Internal property intended to store already loaded configuration data
@@ -420,11 +389,13 @@ private function readData(): array
public function clean()
{
$this->data = [];
- $this->lockedLoadData(
- function () {
- return false;
- },
- true
+ $cleanAction = function () {
+ $this->cache->clean(\Zend_Cache::CLEANING_MODE_MATCHING_TAG, [self::CACHE_TAG]);
+ };
+
+ $this->lockQuery->lockedCleanData(
+ self::$lockName,
+ $cleanAction
);
}
}
diff --git a/app/code/Magento/Config/Model/Config.php b/app/code/Magento/Config/Model/Config.php
index 6bf191c20a844..bd38d1451e1b6 100644
--- a/app/code/Magento/Config/Model/Config.php
+++ b/app/code/Magento/Config/Model/Config.php
@@ -114,6 +114,11 @@ class Config extends \Magento\Framework\DataObject
*/
private $scopeTypeNormalizer;
+ /**
+ * @var \Magento\MessageQueue\Api\PoisonPillPutInterface
+ */
+ private $pillPut;
+
/**
* @param \Magento\Framework\App\Config\ReinitableConfigInterface $config
* @param \Magento\Framework\Event\ManagerInterface $eventManager
@@ -126,6 +131,7 @@ class Config extends \Magento\Framework\DataObject
* @param array $data
* @param ScopeResolverPool|null $scopeResolverPool
* @param ScopeTypeNormalizer|null $scopeTypeNormalizer
+ * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -139,7 +145,8 @@ public function __construct(
SettingChecker $settingChecker = null,
array $data = [],
ScopeResolverPool $scopeResolverPool = null,
- ScopeTypeNormalizer $scopeTypeNormalizer = null
+ ScopeTypeNormalizer $scopeTypeNormalizer = null,
+ \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null
) {
parent::__construct($data);
$this->_eventManager = $eventManager;
@@ -155,6 +162,8 @@ public function __construct(
?? ObjectManager::getInstance()->get(ScopeResolverPool::class);
$this->scopeTypeNormalizer = $scopeTypeNormalizer
?? ObjectManager::getInstance()->get(ScopeTypeNormalizer::class);
+ $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class);
}
/**
@@ -224,6 +233,8 @@ public function save()
throw $e;
}
+ $this->pillPut->put();
+
return $this;
}
diff --git a/app/code/Magento/Config/composer.json b/app/code/Magento/Config/composer.json
index 57c067d2cae27..3312fb630ccda 100644
--- a/app/code/Magento/Config/composer.json
+++ b/app/code/Magento/Config/composer.json
@@ -7,6 +7,7 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
+ "magento/module-message-queue": "*",
"magento/module-backend": "*",
"magento/module-cron": "*",
"magento/module-deploy": "*",
diff --git a/app/code/Magento/Config/etc/di.xml b/app/code/Magento/Config/etc/di.xml
index 87a0e666d2d7b..920cac382fcbf 100644
--- a/app/code/Magento/Config/etc/di.xml
+++ b/app/code/Magento/Config/etc/di.xml
@@ -90,9 +90,18 @@
Magento\Framework\App\Config\PreProcessorComposite
Magento\Framework\Serialize\Serializer\Serialize
Magento\Config\App\Config\Type\System\Reader\Proxy
- Magento\Framework\Lock\Backend\Cache
+ systemConfigQueryLocker
+
+
+
+ Magento\Framework\Lock\Backend\Cache
+ 42000
+ 100
+
+
+
systemConfigSourceAggregated
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml
index 95b86ec3a8587..43dae2d70d416 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml
@@ -145,8 +145,8 @@
-
-
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml
index af12f49bf86ea..39aa516077c56 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductUpdateTest.xml
@@ -57,7 +57,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml
index 981985b3f4ea8..1db9b3e5b79b2 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml
@@ -144,7 +144,7 @@
-
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml
index e278018330aa6..934a410d58a8a 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml
@@ -126,7 +126,7 @@
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerAccountPageTitleActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerAccountPageTitleActionGroup.xml
new file mode 100644
index 0000000000000..132b5ca81886f
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerAccountPageTitleActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
index 90544dc2673a3..703b9f542f81a 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
@@ -17,5 +17,6 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateThroughCustomerTabsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateThroughCustomerTabsActionGroup.xml
new file mode 100644
index 0000000000000..5591bee529690
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateThroughCustomerTabsActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenMyAccountPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenMyAccountPageActionGroup.xml
new file mode 100644
index 0000000000000..6ca0f612deeaa
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenMyAccountPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml
index 9b6155e982013..28305d37cf77b 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml
@@ -8,6 +8,12 @@
+
+ 0
+ NOT LOGGED IN
+ 3
+ Retail Customer
+
General
3
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupMainSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupMainSection.xml
index 1fdb15f189ace..4cb7f5e3f628e 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupMainSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupMainSection.xml
@@ -15,5 +15,6 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/LoggedInCustomerHeaderLinksSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/LoggedInCustomerHeaderLinksSection.xml
new file mode 100644
index 0000000000000..907551e932fcf
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/LoggedInCustomerHeaderLinksSection.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml
new file mode 100644
index 0000000000000..3a4329969ae2b
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
index bed0c71ae7fad..407c6480e9dde 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
@@ -9,7 +9,7 @@
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml
new file mode 100644
index 0000000000000..648c30b1ca0bb
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Deploy/Console/DeployStaticOptions.php b/app/code/Magento/Deploy/Console/DeployStaticOptions.php
index 89cb3e4b30345..1c02d24f7e99c 100644
--- a/app/code/Magento/Deploy/Console/DeployStaticOptions.php
+++ b/app/code/Magento/Deploy/Console/DeployStaticOptions.php
@@ -6,6 +6,7 @@
namespace Magento\Deploy\Console;
+use Magento\Deploy\Process\Queue;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputArgument;
@@ -57,6 +58,11 @@ class DeployStaticOptions
*/
const JOBS_AMOUNT = 'jobs';
+ /**
+ * Key for max execution time option
+ */
+ const MAX_EXECUTION_TIME = 'max-execution-time';
+
/**
* Force run of static deploy
*/
@@ -150,6 +156,7 @@ public function getOptionsList()
* Basic options
*
* @return array
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
private function getBasicOptions()
{
@@ -216,6 +223,13 @@ private function getBasicOptions()
'Enable parallel processing using the specified number of jobs.',
self::DEFAULT_JOBS_AMOUNT
),
+ new InputOption(
+ self::MAX_EXECUTION_TIME,
+ null,
+ InputOption::VALUE_OPTIONAL,
+ 'The maximum expected execution time of deployment static process (in seconds).',
+ Queue::DEFAULT_MAX_EXEC_TIME
+ ),
new InputOption(
self::SYMLINK_LOCALE,
null,
diff --git a/app/code/Magento/Deploy/Service/DeployStaticContent.php b/app/code/Magento/Deploy/Service/DeployStaticContent.php
index 66ec6e7418afd..854bf50e0af2f 100644
--- a/app/code/Magento/Deploy/Service/DeployStaticContent.php
+++ b/app/code/Magento/Deploy/Service/DeployStaticContent.php
@@ -85,24 +85,26 @@ public function deploy(array $options)
return;
}
- $queue = $this->queueFactory->create(
- [
- 'logger' => $this->logger,
- 'options' => $options,
- 'maxProcesses' => $this->getProcessesAmount($options),
- 'deployPackageService' => $this->objectManager->create(
- \Magento\Deploy\Service\DeployPackage::class,
- [
- 'logger' => $this->logger
- ]
- )
- ]
- );
+ $queueOptions = [
+ 'logger' => $this->logger,
+ 'options' => $options,
+ 'maxProcesses' => $this->getProcessesAmount($options),
+ 'deployPackageService' => $this->objectManager->create(
+ \Magento\Deploy\Service\DeployPackage::class,
+ [
+ 'logger' => $this->logger
+ ]
+ )
+ ];
+
+ if (isset($options[Options::MAX_EXECUTION_TIME])) {
+ $queueOptions['maxExecTime'] = (int)$options[Options::MAX_EXECUTION_TIME];
+ }
$deployStrategy = $this->deployStrategyFactory->create(
$options[Options::STRATEGY],
[
- 'queue' => $queue
+ 'queue' => $this->queueFactory->create($queueOptions)
]
);
@@ -133,6 +135,8 @@ public function deploy(array $options)
}
/**
+ * Returns amount of parallel processes, returns zero if option wasn't set.
+ *
* @param array $options
* @return int
*/
@@ -142,6 +146,8 @@ private function getProcessesAmount(array $options)
}
/**
+ * Checks if need to refresh only version.
+ *
* @param array $options
* @return bool
*/
diff --git a/app/code/Magento/Deploy/Test/Unit/Service/DeployStaticContentTest.php b/app/code/Magento/Deploy/Test/Unit/Service/DeployStaticContentTest.php
index 75edc8cb4f6ee..396381960e544 100644
--- a/app/code/Magento/Deploy/Test/Unit/Service/DeployStaticContentTest.php
+++ b/app/code/Magento/Deploy/Test/Unit/Service/DeployStaticContentTest.php
@@ -5,6 +5,7 @@
*/
namespace Magento\Deploy\Test\Unit\Service;
+use Magento\Deploy\Console\DeployStaticOptions;
use Magento\Deploy\Package\Package;
use Magento\Deploy\Process\Queue;
use Magento\Deploy\Service\Bundle;
@@ -221,4 +222,35 @@ public function deployDataProvider()
]
];
}
+
+ public function testMaxExecutionTimeOptionPassed()
+ {
+ $options = [
+ DeployStaticOptions::MAX_EXECUTION_TIME => 100,
+ DeployStaticOptions::REFRESH_CONTENT_VERSION_ONLY => false,
+ DeployStaticOptions::JOBS_AMOUNT => 3,
+ DeployStaticOptions::STRATEGY => 'compact',
+ DeployStaticOptions::NO_JAVASCRIPT => true,
+ DeployStaticOptions::NO_HTML_MINIFY => true,
+ ];
+
+ $queueMock = $this->createMock(Queue::class);
+ $strategyMock = $this->createMock(CompactDeploy::class);
+ $this->queueFactory->expects($this->once())
+ ->method('create')
+ ->with([
+ 'logger' => $this->logger,
+ 'maxExecTime' => 100,
+ 'maxProcesses' => 3,
+ 'options' => $options,
+ 'deployPackageService' => null
+ ])
+ ->willReturn($queueMock);
+ $this->deployStrategyFactory->expects($this->once())
+ ->method('create')
+ ->with('compact', ['queue' => $queueMock])
+ ->willReturn($strategyMock);
+
+ $this->service->deploy($options);
+ }
}
diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml
index 9999c29c1a257..011dfa1019738 100644
--- a/app/code/Magento/Elasticsearch6/etc/di.xml
+++ b/app/code/Magento/Elasticsearch6/etc/di.xml
@@ -170,4 +170,36 @@
+
+
+
+
+ - elasticsearchCategoryCollectionFactory
+
+
+
+
+
+
+
+ - elasticsearchAdvancedCollectionFactory
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy
+
+
+
+
+
+
+
+ - elasticsearchFulltextSearchCollectionFactory
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml
new file mode 100644
index 0000000000000..a9100b4730b8c
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml
index ad9e7672ce11a..528ad23aaf2bf 100644
--- a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml
+++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml
@@ -11,5 +11,11 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml
new file mode 100644
index 0000000000000..ceb4e93e4e9aa
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SimpleProductForTest1
+ SimpleProductForTest1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml
new file mode 100644
index 0000000000000..d63a5546716b1
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SimpleProductForTest2
+ SimpleProductForTest2
+
+
+
+
+
+ SimpleProductForTest3
+ SimpleProductForTest3
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Indexer/Model/Indexer.php b/app/code/Magento/Indexer/Model/Indexer.php
index 87a7cce58e1a5..2821a46f29416 100644
--- a/app/code/Magento/Indexer/Model/Indexer.php
+++ b/app/code/Magento/Indexer/Model/Indexer.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Indexer\Model;
use Magento\Framework\Indexer\ActionFactory;
@@ -14,6 +15,8 @@
use Magento\Framework\Indexer\StructureFactory;
/**
+ * Indexer model.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Indexer extends \Magento\Framework\DataObject implements IndexerInterface
@@ -361,7 +364,7 @@ public function getLatestUpdated()
return $this->getView()->getUpdated();
}
}
- return $this->getState()->getUpdated();
+ return $this->getState()->getUpdated() ?: '';
}
/**
diff --git a/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php b/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php
index 6b7cc12218990..ca2da9585f934 100644
--- a/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php
+++ b/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php
@@ -164,7 +164,12 @@ public function testGetLatestUpdated($getViewIsEnabled, $getViewGetUpdated, $get
}
}
} else {
- $this->assertEquals($getStateGetUpdated, $this->model->getLatestUpdated());
+ $getLatestUpdated = $this->model->getLatestUpdated();
+ $this->assertEquals($getStateGetUpdated, $getLatestUpdated);
+
+ if ($getStateGetUpdated === null) {
+ $this->assertNotNull($getLatestUpdated);
+ }
}
}
@@ -182,7 +187,8 @@ public function getLatestUpdatedDataProvider()
[true, '', '06-Jan-1944'],
[true, '06-Jan-1944', ''],
[true, '', ''],
- [true, '06-Jan-1944', '05-Jan-1944']
+ [true, '06-Jan-1944', '05-Jan-1944'],
+ [false, null, null],
];
}
diff --git a/app/code/Magento/MessageQueue/Api/PoisonPillCompareInterface.php b/app/code/Magento/MessageQueue/Api/PoisonPillCompareInterface.php
new file mode 100644
index 0000000000000..3d5b895575597
--- /dev/null
+++ b/app/code/Magento/MessageQueue/Api/PoisonPillCompareInterface.php
@@ -0,0 +1,24 @@
+poisonPillRead = $poisonPillRead;
+ $this->poisonPillCompare = $poisonPillCompare;
+ }
+
+ /**
+ * @inheritdoc
+ * @SuppressWarnings(PHPMD.ExitExpression)
+ */
+ public function invoke(QueueInterface $queue, $maxNumberOfMessages, $callback)
+ {
+ $this->poisonPillVersion = $this->poisonPillRead->getLatestVersion();
+ for ($i = $maxNumberOfMessages; $i > 0; $i--) {
+ do {
+ $message = $queue->dequeue();
+ } while ($message === null && (sleep(1) === 0));
+ if (false === $this->poisonPillCompare->isLatestVersion($this->poisonPillVersion)) {
+ $queue->reject($message);
+ exit(0);
+ }
+ $callback($message);
+ }
+ }
+}
diff --git a/app/code/Magento/MessageQueue/Model/PoisonPillCompare.php b/app/code/Magento/MessageQueue/Model/PoisonPillCompare.php
new file mode 100644
index 0000000000000..a8e40ea495002
--- /dev/null
+++ b/app/code/Magento/MessageQueue/Model/PoisonPillCompare.php
@@ -0,0 +1,40 @@
+poisonPillRead = $poisonPillRead;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function isLatestVersion(int $poisonPillVersion): bool
+ {
+ return $poisonPillVersion === $this->poisonPillRead->getLatestVersion();
+ }
+}
diff --git a/app/code/Magento/MessageQueue/Model/ResourceModel/PoisonPill.php b/app/code/Magento/MessageQueue/Model/ResourceModel/PoisonPill.php
new file mode 100644
index 0000000000000..283fff8ace7c7
--- /dev/null
+++ b/app/code/Magento/MessageQueue/Model/ResourceModel/PoisonPill.php
@@ -0,0 +1,75 @@
+_init(self::QUEUE_POISON_PILL_TABLE, 'version');
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function put(): int
+ {
+ $connection = $this->getConnection();
+ $table = $this->getMainTable();
+ $connection->insert($table, []);
+ return (int)$connection->lastInsertId($table);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getLatestVersion() : int
+ {
+ $select = $this->getConnection()->select()->from(
+ $this->getTable(self::QUEUE_POISON_PILL_TABLE),
+ 'version'
+ )->order(
+ 'version ' . \Magento\Framework\DB\Select::SQL_DESC
+ )->limit(
+ 1
+ );
+
+ $version = (int)$this->getConnection()->fetchOne($select);
+
+ return $version;
+ }
+}
diff --git a/app/code/Magento/MessageQueue/etc/db_schema.xml b/app/code/Magento/MessageQueue/etc/db_schema.xml
index 7a20d2bd4df5d..9cdf414dd06e1 100644
--- a/app/code/Magento/MessageQueue/etc/db_schema.xml
+++ b/app/code/Magento/MessageQueue/etc/db_schema.xml
@@ -21,4 +21,12 @@
+
diff --git a/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json b/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json
index f31981d2ec40f..d9d623a994b37 100644
--- a/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json
+++ b/app/code/Magento/MessageQueue/etc/db_schema_whitelist.json
@@ -9,5 +9,13 @@
"PRIMARY": true,
"QUEUE_LOCK_MESSAGE_CODE": true
}
+ },
+ "queue_poison_pill": {
+ "column": {
+ "version": true
+ },
+ "constraint": {
+ "PRIMARY": true
+ }
}
-}
\ No newline at end of file
+}
diff --git a/app/code/Magento/MessageQueue/etc/di.xml b/app/code/Magento/MessageQueue/etc/di.xml
index c8f2edb862613..22cfea976a722 100644
--- a/app/code/Magento/MessageQueue/etc/di.xml
+++ b/app/code/Magento/MessageQueue/etc/di.xml
@@ -13,6 +13,10 @@
+
+
+
+
diff --git a/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml b/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml
index 34a77095d524d..ee0c32633569a 100644
--- a/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml
+++ b/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml
@@ -30,5 +30,6 @@
+
diff --git a/app/code/Magento/PageCache/Test/Mftf/Test/FlushStaticFilesCacheButtonVisibilityTest.xml b/app/code/Magento/PageCache/Test/Mftf/Test/FlushStaticFilesCacheButtonVisibilityTest.xml
new file mode 100644
index 0000000000000..bd6f7ba362bf4
--- /dev/null
+++ b/app/code/Magento/PageCache/Test/Mftf/Test/FlushStaticFilesCacheButtonVisibilityTest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderStatusFormFillAndSaveActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderStatusFormFillAndSaveActionGroup.xml
new file mode 100644
index 0000000000000..8108577145421
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderStatusFormFillAndSaveActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusExistsInGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusExistsInGridActionGroup.xml
new file mode 100644
index 0000000000000..5f69f52987688
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusExistsInGridActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveDuplicateErrorActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveDuplicateErrorActionGroup.xml
new file mode 100644
index 0000000000000..5b4c3115744c9
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveDuplicateErrorActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveSuccessActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveSuccessActionGroup.xml
new file mode 100644
index 0000000000000..d82f4b9dd25e8
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveSuccessActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusData.xml b/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusData.xml
new file mode 100644
index 0000000000000..aecd7fcf1b703
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusData.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ order_status
+ orderLabel
+
+
+ pending
+ orderLabel
+
+
+ order_status
+ Suspected Fraud
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderStatusPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderStatusPage.xml
new file mode 100644
index 0000000000000..b158e4923074a
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderStatusPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusFormSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusFormSection.xml
new file mode 100644
index 0000000000000..1058b2d6f2177
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusFormSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusGridSection.xml
new file mode 100644
index 0000000000000..b624639281187
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusGridSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingCodeTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingCodeTest.xml
new file mode 100644
index 0000000000000..40a731410a899
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingCodeTest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingLabelTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingLabelTest.xml
new file mode 100644
index 0000000000000..d1381bbb1efb0
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingLabelTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusTest.xml
new file mode 100644
index 0000000000000..c2daaac84dd42
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php
index 89ec2b84572fc..cf6301cb31a9c 100644
--- a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php
+++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php
@@ -61,6 +61,7 @@ public function __construct(
public function loadAttributeOptions()
{
$attributes = [
+ 'base_subtotal_with_discount' => __('Subtotal (Excl. Tax)'),
'base_subtotal' => __('Subtotal'),
'total_qty' => __('Total Items Quantity'),
'weight' => __('Total Weight'),
diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml
new file mode 100644
index 0000000000000..e0b3d4b850bbb
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml b/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml
new file mode 100644
index 0000000000000..1518adad01347
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Query text
+ 1
+ http://example.com/
+ 0
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Metadata/search_term-meta.xml b/app/code/Magento/Search/Test/Mftf/Metadata/search_term-meta.xml
new file mode 100644
index 0000000000000..0bd2dc9be4855
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/Metadata/search_term-meta.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ string
+ integer
+ string
+ integer
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml
index 9e5bde9a2be49..81b025c9554e2 100644
--- a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml
+++ b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml
@@ -15,5 +15,6 @@
+
diff --git a/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml b/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml
new file mode 100644
index 0000000000000..67ccb51bf401e
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Model/Group.php b/app/code/Magento/Store/Model/Group.php
index ccc3c65491422..19f104c9f3790 100644
--- a/app/code/Magento/Store/Model/Group.php
+++ b/app/code/Magento/Store/Model/Group.php
@@ -100,18 +100,24 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements
*/
private $eventManager;
+ /**
+ * @var \Magento\MessageQueue\Api\PoisonPillPutInterface
+ */
+ private $pillPut;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
* @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
* @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory
* @param \Magento\Config\Model\ResourceModel\Config\Data $configDataResource
- * @param \Magento\Store\Model\Store $store
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
- * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
+ * @param ResourceModel\Store\CollectionFactory $storeListFactory
+ * @param StoreManagerInterface $storeManager
+ * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
+ * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
* @param array $data
* @param \Magento\Framework\Event\ManagerInterface|null $eventManager
+ * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -125,13 +131,16 @@ public function __construct(
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
array $data = [],
- \Magento\Framework\Event\ManagerInterface $eventManager = null
+ \Magento\Framework\Event\ManagerInterface $eventManager = null,
+ \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null
) {
$this->_configDataResource = $configDataResource;
$this->_storeListFactory = $storeListFactory;
$this->_storeManager = $storeManager;
$this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\Event\ManagerInterface::class);
+ $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class);
parent::__construct(
$context,
$registry,
@@ -244,6 +253,8 @@ public function getStoreCodes()
}
/**
+ * Get stores count
+ *
* @return int
*/
public function getStoresCount()
@@ -349,6 +360,8 @@ public function isCanDelete()
}
/**
+ * Get default store id
+ *
* @return mixed
*/
public function getDefaultStoreId()
@@ -365,6 +378,8 @@ public function setDefaultStoreId($defaultStoreId)
}
/**
+ * Get root category id
+ *
* @return mixed
*/
public function getRootCategoryId()
@@ -381,6 +396,8 @@ public function setRootCategoryId($rootCategoryId)
}
/**
+ * Get website id
+ *
* @return mixed
*/
public function getWebsiteId()
@@ -397,7 +414,7 @@ public function setWebsiteId($websiteId)
}
/**
- * @return $this
+ * @inheritdoc
*/
public function beforeDelete()
{
@@ -445,6 +462,7 @@ public function afterSave()
$this->_storeManager->reinitStores();
$this->eventManager->dispatch($this->_eventPrefix . '_save', ['group' => $group]);
});
+ $this->pillPut->put();
return parent::afterSave();
}
@@ -473,7 +491,7 @@ public function getIdentities()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getName()
{
@@ -507,7 +525,7 @@ public function setCode($code)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getExtensionAttributes()
{
@@ -515,7 +533,7 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setExtensionAttributes(
\Magento\Store\Api\Data\GroupExtensionInterface $extensionAttributes
@@ -524,7 +542,7 @@ public function setExtensionAttributes(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.1.0
*/
public function getScopeType()
@@ -533,7 +551,7 @@ public function getScopeType()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.1.0
*/
public function getScopeTypeName()
diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php
index c1ad5bdcfc068..b2a515b198b11 100644
--- a/app/code/Magento/Store/Model/Store.php
+++ b/app/code/Magento/Store/Model/Store.php
@@ -326,6 +326,11 @@ class Store extends AbstractExtensibleModel implements
*/
private $eventManager;
+ /**
+ * @var \Magento\MessageQueue\Api\PoisonPillPutInterface
+ */
+ private $pillPut;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -352,6 +357,7 @@ class Store extends AbstractExtensibleModel implements
* @param bool $isCustomEntryPoint
* @param array $data optional generic object data
* @param \Magento\Framework\Event\ManagerInterface|null $eventManager
+ * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -380,7 +386,8 @@ public function __construct(
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
$isCustomEntryPoint = false,
array $data = [],
- \Magento\Framework\Event\ManagerInterface $eventManager = null
+ \Magento\Framework\Event\ManagerInterface $eventManager = null,
+ \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null
) {
$this->_coreFileStorageDatabase = $coreFileStorageDatabase;
$this->_config = $config;
@@ -401,6 +408,8 @@ public function __construct(
$this->websiteRepository = $websiteRepository;
$this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\Event\ManagerInterface::class);
+ $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class);
parent::__construct(
$context,
$registry,
@@ -1077,6 +1086,7 @@ public function afterSave()
$this->getResource()->addCommitCallback(function () use ($event, $store) {
$this->eventManager->dispatch($event, ['store' => $store]);
});
+ $this->pillPut->put();
return parent::afterSave();
}
diff --git a/app/code/Magento/Store/Model/Website.php b/app/code/Magento/Store/Model/Website.php
index c9a7d0013fe06..383b36fd63228 100644
--- a/app/code/Magento/Store/Model/Website.php
+++ b/app/code/Magento/Store/Model/Website.php
@@ -159,6 +159,11 @@ class Website extends \Magento\Framework\Model\AbstractExtensibleModel implement
*/
protected $_currencyFactory;
+ /**
+ * @var \Magento\MessageQueue\Api\PoisonPillPutInterface
+ */
+ private $pillPut;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -174,6 +179,7 @@ class Website extends \Magento\Framework\Model\AbstractExtensibleModel implement
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
+ * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -190,7 +196,8 @@ public function __construct(
\Magento\Directory\Model\CurrencyFactory $currencyFactory,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null
) {
parent::__construct(
$context,
@@ -208,10 +215,12 @@ public function __construct(
$this->_websiteFactory = $websiteFactory;
$this->_storeManager = $storeManager;
$this->_currencyFactory = $currencyFactory;
+ $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class);
}
/**
- * init model
+ * Init model
*
* @return void
*/
@@ -495,6 +504,8 @@ public function getWebsiteGroupStore()
}
/**
+ * Get default group id
+ *
* @return mixed
*/
public function getDefaultGroupId()
@@ -511,6 +522,8 @@ public function setDefaultGroupId($defaultGroupId)
}
/**
+ * Get code
+ *
* @return mixed
*/
public function getCode()
@@ -543,7 +556,7 @@ public function setName($name)
}
/**
- * @return $this
+ * @inheritdoc
*/
public function beforeDelete()
{
@@ -581,7 +594,7 @@ public function afterSave()
if ($this->isObjectNew()) {
$this->_storeManager->reinitStores();
}
-
+ $this->pillPut->put();
return parent::afterSave();
}
@@ -635,8 +648,7 @@ public function getDefaultStore()
}
/**
- * Retrieve default stores select object
- * Select fields website_id, store_id
+ * Retrieve default stores select object, select fields website_id, store_id
*
* @param bool $withDefault include/exclude default admin website
* @return \Magento\Framework\DB\Select
@@ -671,7 +683,7 @@ public function getIdentities()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.1.0
*/
public function getScopeType()
@@ -680,7 +692,7 @@ public function getScopeType()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.1.0
*/
public function getScopeTypeName()
@@ -689,7 +701,7 @@ public function getScopeTypeName()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getExtensionAttributes()
{
@@ -697,7 +709,7 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setExtensionAttributes(
\Magento\Store\Api\Data\WebsiteExtensionInterface $extensionAttributes
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml
index ef8d77c8824ff..ca614ec24138c 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml
@@ -36,4 +36,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml
index 58e1781d69eab..cf2cabdcc2399 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml
@@ -26,4 +26,35 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml
index 58fd0a3f0bc2b..1721e3185402e 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml
@@ -19,6 +19,7 @@
+
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreFrontProductValidationActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreFrontProductValidationActionGroup.xml
new file mode 100644
index 0000000000000..f11394c643ad7
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreFrontProductValidationActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml
index f636336524f01..ae605256a2819 100644
--- a/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml
+++ b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml
@@ -23,4 +23,8 @@
Custom Website
custom_website
-
+
+ website_upd
+ code_upd
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml
index 592af42f2de30..d7006fd01b2ff 100644
--- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml
+++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml
@@ -23,5 +23,6 @@
+
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateWebsiteTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateWebsiteTest.xml
new file mode 100644
index 0000000000000..1608d0b7b5a25
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateWebsiteTest.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml
new file mode 100644
index 0000000000000..fc1dcb5ee1a24
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateWebsiteTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateWebsiteTest.xml
new file mode 100644
index 0000000000000..6b666126569ae
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateWebsiteTest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/composer.json b/app/code/Magento/Store/composer.json
index ebaa32b95f48b..da408f105ccb6 100644
--- a/app/code/Magento/Store/composer.json
+++ b/app/code/Magento/Store/composer.json
@@ -7,6 +7,7 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
+ "magento/module-message-queue": "*",
"magento/module-catalog": "*",
"magento/module-config": "*",
"magento/module-directory": "*",
diff --git a/app/code/Magento/Theme/Model/Design/Backend/File.php b/app/code/Magento/Theme/Model/Design/Backend/File.php
index b37628e54aa30..511fe30f79dcd 100644
--- a/app/code/Magento/Theme/Model/Design/Backend/File.php
+++ b/app/code/Magento/Theme/Model/Design/Backend/File.php
@@ -22,6 +22,8 @@
use Magento\Theme\Model\Design\Config\FileUploader\FileProcessor;
/**
+ * File Backend
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class File extends BackendFile
@@ -88,36 +90,29 @@ public function beforeSave()
{
$values = $this->getValue();
$value = reset($values) ?: [];
- if (!isset($value['file'])) {
+
+ // Need to check name when it is uploaded in the media gallary
+ $file = $value['file'] ?? $value['name'] ?? null;
+ if (!isset($file)) {
throw new LocalizedException(
__('%1 does not contain field \'file\'', $this->getData('field_config/field'))
);
}
if (isset($value['exists'])) {
- $this->setValue($value['file']);
+ $this->setValue($file);
return $this;
}
- $filename = basename($value['file']);
- $result = $this->_mediaDirectory->copyFile(
- $this->getTmpMediaPath($filename),
- $this->_getUploadDir() . '/' . $filename
- );
- if ($result) {
- $this->_mediaDirectory->delete($this->getTmpMediaPath($filename));
- if ($this->_addWhetherScopeInfo()) {
- $filename = $this->_prependScopeInfo($filename);
- }
- $this->setValue($filename);
- } else {
- $this->unsValue();
- }
+ $this->updateMediaDirectory(basename($file), $value['url']);
return $this;
}
/**
- * @return array
+ * After Load
+ *
+ * @return File
+ * @throws LocalizedException
*/
public function afterLoad()
{
@@ -166,6 +161,8 @@ protected function getUploadDirPath($uploadDir)
}
/**
+ * Get Value
+ *
* @return array
*/
public function getValue()
@@ -231,4 +228,49 @@ private function getMime()
}
return $this->mime;
}
+
+ /**
+ * Get Relative Media Path
+ *
+ * @param string $path
+ * @return string
+ */
+ private function getRelativeMediaPath(string $path): string
+ {
+ return str_replace('/pub/media/', '', $path);
+ }
+
+ /**
+ * Move file to the correct media directory
+ *
+ * @param string $filename
+ * @param string $url
+ * @throws LocalizedException
+ */
+ private function updateMediaDirectory(string $filename, string $url)
+ {
+ $relativeMediaPath = $this->getRelativeMediaPath($url);
+ $tmpMediaPath = $this->getTmpMediaPath($filename);
+ $mediaPath = $this->_mediaDirectory->isFile($relativeMediaPath) ? $relativeMediaPath : $tmpMediaPath;
+ $destinationMediaPath = $this->_getUploadDir() . '/' . $filename;
+
+ $result = $mediaPath === $destinationMediaPath;
+ if (!$result) {
+ $result = $this->_mediaDirectory->copyFile(
+ $mediaPath,
+ $destinationMediaPath
+ );
+ }
+ if ($result) {
+ if ($mediaPath === $tmpMediaPath) {
+ $this->_mediaDirectory->delete($mediaPath);
+ }
+ if ($this->_addWhetherScopeInfo()) {
+ $filename = $this->_prependScopeInfo($filename);
+ }
+ $this->setValue($filename);
+ } else {
+ $this->unsValue();
+ }
+ }
}
diff --git a/app/code/Magento/Theme/Test/Mftf/ActionGroup/NavigateToFaviconMediaFolderActionGroup.xml b/app/code/Magento/Theme/Test/Mftf/ActionGroup/NavigateToFaviconMediaFolderActionGroup.xml
new file mode 100644
index 0000000000000..6b98686574321
--- /dev/null
+++ b/app/code/Magento/Theme/Test/Mftf/ActionGroup/NavigateToFaviconMediaFolderActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml b/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml
index e90548a7c94e9..c2652f33f7606 100644
--- a/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml
+++ b/app/code/Magento/Theme/Test/Mftf/Section/AdminDesignConfigSection.xml
@@ -14,10 +14,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Theme/Test/Mftf/Test/AdminDesignConfigMediaGalleryImageUploadTest.xml b/app/code/Magento/Theme/Test/Mftf/Test/AdminDesignConfigMediaGalleryImageUploadTest.xml
new file mode 100644
index 0000000000000..f46328ac151b1
--- /dev/null
+++ b/app/code/Magento/Theme/Test/Mftf/Test/AdminDesignConfigMediaGalleryImageUploadTest.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js b/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js
index 1b6dd9f1c57ec..0eaacdc32567b 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js
@@ -20,6 +20,26 @@ define([
}
},
+ /**
+ * Initializes observable properties of instance
+ *
+ * @returns {Abstract} Chainable.
+ */
+ initObservable: function () {
+ this._super();
+
+ /**
+ * equalityComparer function
+ *
+ * @returns boolean.
+ */
+ this.value.equalityComparer = function (oldValue, newValue) {
+ return !oldValue && !newValue || oldValue === newValue;
+ };
+
+ return this;
+ },
+
/**
* @param {String} value
*/
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AdminUrlRewriteActionGroup.xml b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AdminUrlRewriteActionGroup.xml
index e9651a3f26e94..50b83641e19a9 100644
--- a/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AdminUrlRewriteActionGroup.xml
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AdminUrlRewriteActionGroup.xml
@@ -83,10 +83,10 @@
-
+
-
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Data/UrlRewriteData.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Data/UrlRewriteData.xml
index 77cf80ca95ac5..3692e82072afc 100644
--- a/app/code/Magento/UrlRewrite/Test/Mftf/Data/UrlRewriteData.xml
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Data/UrlRewriteData.xml
@@ -24,6 +24,7 @@
Temporary (302)
1
Default Store View
+ Update Url Rewrite
wishlist
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateProductUrlRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateProductUrlRewriteAndAddTemporaryRedirectTest.xml
new file mode 100644
index 0000000000000..ea370d8419583
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateProductUrlRewriteAndAddTemporaryRedirectTest.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Unit/Model/Product/AttributeValueProviderTest.php b/app/code/Magento/Wishlist/Test/Unit/Model/Product/AttributeValueProviderTest.php
new file mode 100644
index 0000000000000..fb0113eb6ae75
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Unit/Model/Product/AttributeValueProviderTest.php
@@ -0,0 +1,177 @@
+productCollectionFactoryMock = $this->createPartialMock(
+ CollectionFactory::class,
+ ['create']
+ );
+ $this->attributeValueProvider = new AttributeValueProvider(
+ $this->productCollectionFactoryMock
+ );
+ }
+
+ /**
+ * Get attribute text when the flat table is disabled
+ *
+ * @param int $productId
+ * @param string $attributeCode
+ * @param string $attributeText
+ * @return void
+ * @dataProvider attributeDataProvider
+ */
+ public function testGetAttributeTextWhenFlatIsDisabled(int $productId, string $attributeCode, string $attributeText)
+ {
+ $this->productMock = $this->getMockBuilder(Product::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getData'])
+ ->getMock();
+
+ $this->productMock->expects($this->any())
+ ->method('getData')
+ ->with($attributeCode)
+ ->willReturn($attributeText);
+
+ $productCollection = $this->getMockBuilder(Collection::class)
+ ->disableOriginalConstructor()
+ ->setMethods([
+ 'addIdFilter', 'addStoreFilter', 'addAttributeToSelect', 'isEnabledFlat', 'getFirstItem'
+ ])->getMock();
+
+ $productCollection->expects($this->any())
+ ->method('addIdFilter')
+ ->willReturnSelf();
+ $productCollection->expects($this->any())
+ ->method('addStoreFilter')
+ ->willReturnSelf();
+ $productCollection->expects($this->any())
+ ->method('addAttributeToSelect')
+ ->willReturnSelf();
+ $productCollection->expects($this->any())
+ ->method('isEnabledFlat')
+ ->willReturn(false);
+ $productCollection->expects($this->any())
+ ->method('getFirstItem')
+ ->willReturn($this->productMock);
+
+ $this->productCollectionFactoryMock->expects($this->atLeastOnce())
+ ->method('create')
+ ->willReturn($productCollection);
+
+ $actual = $this->attributeValueProvider->getRawAttributeValue($productId, $attributeCode);
+
+ $this->assertEquals($attributeText, $actual);
+ }
+
+ /**
+ * Get attribute text when the flat table is enabled
+ *
+ * @dataProvider attributeDataProvider
+ * @param int $productId
+ * @param string $attributeCode
+ * @param string $attributeText
+ * @return void
+ */
+ public function testGetAttributeTextWhenFlatIsEnabled(int $productId, string $attributeCode, string $attributeText)
+ {
+ $this->connectionMock = $this->getMockBuilder(AdapterInterface::class)->getMockForAbstractClass();
+ $this->connectionMock->expects($this->any())
+ ->method('fetchRow')
+ ->willReturn([
+ $attributeCode => $attributeText
+ ]);
+ $this->productMock = $this->getMockBuilder(Product::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getData'])
+ ->getMock();
+ $this->productMock->expects($this->any())
+ ->method('getData')
+ ->with($attributeCode)
+ ->willReturn($attributeText);
+
+ $productCollection = $this->getMockBuilder(Collection::class)
+ ->disableOriginalConstructor()
+ ->setMethods([
+ 'addIdFilter', 'addStoreFilter', 'addAttributeToSelect', 'isEnabledFlat', 'getConnection'
+ ])->getMock();
+
+ $productCollection->expects($this->any())
+ ->method('addIdFilter')
+ ->willReturnSelf();
+ $productCollection->expects($this->any())
+ ->method('addStoreFilter')
+ ->willReturnSelf();
+ $productCollection->expects($this->any())
+ ->method('addAttributeToSelect')
+ ->willReturnSelf();
+ $productCollection->expects($this->any())
+ ->method('isEnabledFlat')
+ ->willReturn(true);
+ $productCollection->expects($this->any())
+ ->method('getConnection')
+ ->willReturn($this->connectionMock);
+
+ $this->productCollectionFactoryMock->expects($this->atLeastOnce())
+ ->method('create')
+ ->willReturn($productCollection);
+
+ $actual = $this->attributeValueProvider->getRawAttributeValue($productId, $attributeCode);
+
+ $this->assertEquals($attributeText, $actual);
+ }
+
+ /**
+ * @return array
+ */
+ public function attributeDataProvider(): array
+ {
+ return [
+ [1, 'attribute_code', 'Attribute Text']
+ ];
+ }
+}
diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_actions-bar.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_actions-bar.less
index 08434727ccc9c..8499ecaa48c12 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_actions-bar.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_actions-bar.less
@@ -45,6 +45,10 @@
.page-actions {
@_page-action__indent: 1.3rem;
+ &.floating-header {
+ &:extend(.page-actions-buttons all);
+ }
+
.page-main-actions & {
&._fixed {
left: @page-wrapper__indent-left;
diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_fields.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_fields.less
index 4479c070a4e17..8dec680b58726 100644
--- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_fields.less
+++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_fields.less
@@ -55,31 +55,3 @@
}
}
}
-
-//
-// Desktop
-// _____________________________________________
-
-.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
- // ToDo UI: remove with global blank theme .field.required update
- .opc-wrapper {
- .fieldset {
- > .field {
- &.required,
- &._required {
- position: relative;
-
- > label {
- padding-right: 25px;
-
- &:after {
- margin-left: @indent__s;
- position: absolute;
- top: 9px;
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less
index 5f8134193c67f..35445b0989e86 100644
--- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less
+++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less
@@ -209,6 +209,13 @@
.fieldset {
> .field {
margin: 0 0 @indent__base;
+
+ &.choice {
+ &:before {
+ padding: 0;
+ width: 0;
+ }
+ }
&.type {
.control {
diff --git a/app/etc/di.xml b/app/etc/di.xml
index 19543375aad58..d0b45ea16c855 100755
--- a/app/etc/di.xml
+++ b/app/etc/di.xml
@@ -38,7 +38,7 @@
-
+
@@ -1757,4 +1757,11 @@
+
+
+ Magento\Framework\Lock\Backend\Cache
+ 10000
+ 20
+
+
diff --git a/dev/tests/acceptance/tests/_data/catalog_import_products.csv b/dev/tests/acceptance/tests/_data/catalog_import_products.csv
new file mode 100644
index 0000000000000..7732f15d4ce3a
--- /dev/null
+++ b/dev/tests/acceptance/tests/_data/catalog_import_products.csv
@@ -0,0 +1,4 @@
+sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,configurable_variations,configurable_variation_labels,associated_skus
+SimpleProductForTest1,,Default,simple,"Default","base,second_website",SimpleProductAfterImport1,,,1.0000,1,"Taxable Goods","Catalog, Search",250.0000,,,,simple-product-for-test-1,,,,,,,,,,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,"Block after Info Column",,,,,,,,,,,"Use config",,,100.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,,,,,,,,,,,,,,,,,,,
+SimpleProductForTest2,,Default,simple,"Default",base,SimpleProductAfterImport2,,,1.0000,1,"Taxable Goods","Catalog, Search",300.0000,,,,simple-product-for-test-2,,,,,,,,,,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,"Block after Info Column",,,,,,,,,,,"Use config",,,100.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,,,,,,,,,,,,,,,,,,,
+SimpleProductForTest3,,Default,simple,"Default","base,second_website",SimpleProductAfterImport3,,,1.0000,1,"Taxable Goods","Catalog, Search",350.0000,,,,simple-product-for-test-3,,,,,,,,,,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,"Block after Info Column",,,,,,,,,,,"Use config",,,100.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,,,,,,,,,,,,,,,,,,,
\ No newline at end of file
diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php
index 82e3e297dc456..03492f7ae1a9e 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php
@@ -195,6 +195,13 @@ class ConditionsElement extends SimpleElement
*/
protected $exception;
+ /**
+ * Condition option text selector.
+ *
+ * @var string
+ */
+ private $conditionOptionTextSelector = '//option[normalize-space(text())="%s"]';
+
/**
* @inheritdoc
*/
@@ -282,10 +289,16 @@ protected function addCondition($type, ElementInterface $context)
$count = 0;
do {
- $newCondition->find($this->addNew, Locator::SELECTOR_XPATH)->click();
-
try {
- $newCondition->find($this->typeNew, Locator::SELECTOR_XPATH, 'select')->setValue($type);
+ $specificType = $newCondition->find(
+ sprintf($this->conditionOptionTextSelector, $type),
+ Locator::SELECTOR_XPATH
+ )->isPresent();
+ $newCondition->find($this->addNew, Locator::SELECTOR_XPATH)->click();
+ $condition = $specificType
+ ? $newCondition->find($this->typeNew, Locator::SELECTOR_XPATH, 'selectcondition')
+ : $newCondition->find($this->typeNew, Locator::SELECTOR_XPATH, 'select');
+ $condition->setValue($type);
$isSetType = true;
} catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
$isSetType = false;
diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/SelectconditionElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/SelectconditionElement.php
new file mode 100644
index 0000000000000..15a799eac5188
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/SelectconditionElement.php
@@ -0,0 +1,20 @@
+stepFactory = $stepFactory;
$this->fixtureFactory = $fixtureFactory;
$this->adminExportIndex = $adminExportIndex;
$this->catalogProductIndex = $catalogProductIndexPage;
+ $this->cron = $cron;
}
/**
@@ -130,8 +140,12 @@ public function test(
if ($website) {
$website->persist();
$this->setupCurrencyForCustomWebsite($website, $currencyCustomWebsite);
+ $this->cron->run();
+ $this->cron->run();
}
$products = $this->prepareProducts($products, $website);
+ $this->cron->run();
+ $this->cron->run();
$this->adminExportIndex->open();
$this->adminExportIndex->getExportedGrid()->deleteAllExportedFiles();
$exportData = $this->fixtureFactory->createByCode(
diff --git a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml
index d069499da4aab..07646c2aceda8 100644
--- a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml
+++ b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.xml
@@ -50,7 +50,6 @@
- MC-13864 Consumer always read config from memory
price_scope_website
csv_with_advanced_pricing
diff --git a/dev/tests/functional/tests/app/Magento/BundleImportExport/Test/TestCase/ExportProductsTest.xml b/dev/tests/functional/tests/app/Magento/BundleImportExport/Test/TestCase/ExportProductsTest.xml
index 3ad8cff31eaf8..bfbe233b9dc1b 100644
--- a/dev/tests/functional/tests/app/Magento/BundleImportExport/Test/TestCase/ExportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/BundleImportExport/Test/TestCase/ExportProductsTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
- bundleProduct
diff --git a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.php b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.php
index e55558482c1f3..b5cd056fb99ad 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.php
+++ b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.php
@@ -104,6 +104,8 @@ public function test(
$exportData->persist();
$this->adminExportIndex->getExportForm()->fill($exportData);
$this->adminExportIndex->getFilterExport()->clickContinue();
+ $this->cron->run();
+ $this->cron->run();
$this->assertExportProduct->processAssert($export, $exportedFields, $products);
}
diff --git a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.xml b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.xml
index b94f21371496a..be22eab8ac717 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
- catalogProductSimple
@@ -27,6 +28,7 @@
+ mftf_migrated:yes
default
- catalogProductSimple
@@ -43,6 +45,7 @@
+ mftf_migrated:yes
default
- catalogProductSimple
@@ -58,7 +61,7 @@
- >MC-13864 Consumer always read config from memory
+ mftf_migrated:yes
default
- catalogProductSimple
diff --git a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml
index edb0aad954fbb..77e5e2b91d93f 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
- Products
- Add/Update
@@ -38,6 +39,7 @@
+ mftf_migrated:yes
Products
Replace
Stop on Error
diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/CreateSearchTermEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/CreateSearchTermEntityTest.xml
index 0437e0a5e999b..8c465544a3283 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/CreateSearchTermEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/CreateSearchTermEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
catalogProductSimple::sku
Main Website/Main Website Store/Default Store View
http://example.com/
diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/DeleteSearchTermEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/DeleteSearchTermEntityTest.xml
index a9cc0dfd34f9f..8fdd7ef715521 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/DeleteSearchTermEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/DeleteSearchTermEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/MassDeleteSearchTermEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/MassDeleteSearchTermEntityTest.xml
index 3bf4e521c4a04..3ef2b65c0224b 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/MassDeleteSearchTermEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/MassDeleteSearchTermEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
catalogSearchQuery::default,catalogSearchQuery::default,catalogSearchQuery::default
diff --git a/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlCategoryEntityTest.xml
index 398054f1f0ed3..8b15da5ecd2ef 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlCategoryEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlCategoryEntityTest.xml
@@ -14,7 +14,7 @@
Yes
Subcategory%isolation%
subcategory-%isolation%
- test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1
+ test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1, mftf_migrated:yes
diff --git a/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlProductEntity.xml b/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlProductEntity.xml
index 1116821f756a9..8110ed1ed00b1 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlProductEntity.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlProductEntity.xml
@@ -8,7 +8,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1
+ test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1, mftf_migrated:yes
simple-product-%isolation%
Simple Product %isolation%
simple_sku_%isolation%
diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml
index 8b2460718097c..b4c97a11b9145 100644
--- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml
@@ -9,7 +9,7 @@
https://github.com/magento-engcom/msi/issues/1624
- test_type:extended_acceptance_test, severity:S0
+ test_type:extended_acceptance_test, severity:S0, mftf_migrated:yes
catalogProductSimple::default
simple_order_qty_2
true
diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.xml
index e0ea721a51f1b..5caa3ba9b924e 100644
--- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.xml
@@ -8,7 +8,7 @@
- severity:S0
+ severity:S0,mftf_migrated:yes
default
100
3
@@ -20,7 +20,7 @@
- severity:S0
+ severity:S0,mftf_migrated:yes
with_two_custom_option
50
11
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableImportExport/Test/TestCase/ExportProductsTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableImportExport/Test/TestCase/ExportProductsTest.xml
index 93240586ec92c..0a2ce7ab7f183 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableImportExport/Test/TestCase/ExportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableImportExport/Test/TestCase/ExportProductsTest.xml
@@ -30,6 +30,7 @@
+ mftf_migrated:yes
default
- configurableProduct
@@ -45,7 +46,7 @@
- >MC-13864 Consumer always read config from memory
+ mftf_migrated:yes
default
- configurableProduct
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/VerifyDisabledCustomerGroupFieldTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/VerifyDisabledCustomerGroupFieldTest.xml
index 70a912a3b5ffe..e88e5161e474e 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/VerifyDisabledCustomerGroupFieldTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/VerifyDisabledCustomerGroupFieldTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
NOT_LOGGED_IN
- customer_group_code
diff --git a/dev/tests/functional/tests/app/Magento/GroupedImportExport/Test/TestCase/ExportProductsTest.xml b/dev/tests/functional/tests/app/Magento/GroupedImportExport/Test/TestCase/ExportProductsTest.xml
index cffcdbf45a6dc..a110dc6a89f8c 100644
--- a/dev/tests/functional/tests/app/Magento/GroupedImportExport/Test/TestCase/ExportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/GroupedImportExport/Test/TestCase/ExportProductsTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
- groupedProduct
diff --git a/dev/tests/functional/tests/app/Magento/PageCache/Test/TestCase/FlushStaticFilesCacheButtonVisibilityTest.xml b/dev/tests/functional/tests/app/Magento/PageCache/Test/TestCase/FlushStaticFilesCacheButtonVisibilityTest.xml
index cbdce59057195..bc529729f1217 100644
--- a/dev/tests/functional/tests/app/Magento/PageCache/Test/TestCase/FlushStaticFilesCacheButtonVisibilityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/PageCache/Test/TestCase/FlushStaticFilesCacheButtonVisibilityTest.xml
@@ -8,7 +8,7 @@
- severity:S3
+ severity:S3, mftf_migrated:yes
diff --git a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCustomOrderStatusEntityTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCustomOrderStatusEntityTest.xml
index 38ce04fa56d81..e05d0fea6b129 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCustomOrderStatusEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCustomOrderStatusEntityTest.xml
@@ -8,17 +8,20 @@
+ mftf_migrated:yes
order_status%isolation%
orderLabel%isolation%
+ mftf_migrated:yes
pending
orderLabel%isolation%
+ mftf_migrated:yes
order_status%isolation%
Suspected Fraud
diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml
index 5a547f69280e1..e35ef853d1b68 100644
--- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml
@@ -8,7 +8,7 @@
- severity:S1
+ severity:S1, mftf_migrated:yes
website_%isolation%
code_%isolation%
diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml
index 306a9fd2024a4..cd37c555fdb1d 100644
--- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml
@@ -8,7 +8,7 @@
- severity:S2
+ severity:S2, mftf_migrated:yes
custom
Yes
diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml
index ac857ad035f44..5db0e7f8baad4 100644
--- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml
@@ -8,7 +8,7 @@
- severity:S2
+ severity:S2, mftf_migrated:yes
custom_website
website_upd%isolation%
code_upd%isolation%
diff --git a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/UpdateProductUrlRewriteEntityTest.xml b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/UpdateProductUrlRewriteEntityTest.xml
index 60de554d594d2..8f12930aa417b 100644
--- a/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/UpdateProductUrlRewriteEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/UrlRewrite/Test/TestCase/UpdateProductUrlRewriteEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
product/%catalogProductSimple::product_100_dollar%
Main Website/Main Website Store/Default Store View
test_%isolation%.html
diff --git a/dev/tests/integration/framework/Magento/TestFramework/MessageQueue/PublisherConsumerController.php b/dev/tests/integration/framework/Magento/TestFramework/MessageQueue/PublisherConsumerController.php
index 9ca351aa1cf98..32240e68ae73e 100644
--- a/dev/tests/integration/framework/Magento/TestFramework/MessageQueue/PublisherConsumerController.php
+++ b/dev/tests/integration/framework/Magento/TestFramework/MessageQueue/PublisherConsumerController.php
@@ -95,17 +95,9 @@ public function initialize()
$this->amqpHelper->deleteConnection($connectionName);
}
$this->amqpHelper->clearQueue("async.operations.all");
- foreach ($this->consumers as $consumer) {
- foreach ($this->getConsumerProcessIds($consumer) as $consumerProcessId) {
- exec("kill {$consumerProcessId}");
- }
- }
- foreach ($this->consumers as $consumer) {
- if (!$this->getConsumerProcessIds($consumer)) {
- exec("{$this->getConsumerStartCommand($consumer, true)} > /dev/null &");
- }
- sleep(5);
- }
+
+ $this->stopConsumers();
+ $this->startConsumers();
if (file_exists($this->logFilePath)) {
// try to remove before failing the test
@@ -230,4 +222,19 @@ public function getPublisher()
{
return $this->publisher;
}
+
+ /**
+ * Start consumers
+ *
+ * @return void
+ */
+ public function startConsumers(): void
+ {
+ foreach ($this->consumers as $consumer) {
+ if (!$this->getConsumerProcessIds($consumer)) {
+ exec("{$this->getConsumerStartCommand($consumer, true)} > /dev/null &");
+ }
+ sleep(5);
+ }
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php
index 07af21505f180..89f1e5e5d53d6 100644
--- a/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php
+++ b/dev/tests/integration/testsuite/Magento/Backend/Controller/Adminhtml/DashboardTest.php
@@ -21,6 +21,8 @@ public function testAjaxBlockAction()
public function testTunnelAction()
{
+ $this->markTestSkipped('MAGETWO-98800: TunnelAction fails when Google Chart API is not available');
+
$testUrl = \Magento\Backend\Block\Dashboard\Graph::API_URL . '?cht=p3&chd=t:60,40&chs=250x100&chl=Hello|World';
$handle = curl_init();
curl_setopt($handle, CURLOPT_URL, $testUrl);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Action/AttributeTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Action/AttributeTest.php
index a2967878402d0..3ec8c806dcbb1 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Action/AttributeTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Controller/Adminhtml/Product/Action/AttributeTest.php
@@ -5,13 +5,49 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Action;
+use Magento\Catalog\Model\Product\Visibility;
+use Magento\Catalog\Model\ProductRepository;
use Magento\Framework\App\Request\Http as HttpRequest;
+use Magento\TestFramework\Helper\Bootstrap;
+use Magento\TestFramework\MessageQueue\PublisherConsumerController;
/**
* @magentoAppArea adminhtml
*/
class AttributeTest extends \Magento\TestFramework\TestCase\AbstractBackendController
{
+ /** @var PublisherConsumerController */
+ private $publisherConsumerController;
+ private $consumers = ['product_action_attribute.update'];
+
+ protected function setUp()
+ {
+ $this->publisherConsumerController = Bootstrap::getObjectManager()->create(PublisherConsumerController::class, [
+ 'consumers' => $this->consumers,
+ 'logFilePath' => TESTS_TEMP_DIR . "/MessageQueueTestLog.txt",
+ 'maxMessages' => null,
+ 'appInitParams' => Bootstrap::getInstance()->getAppInitParams()
+ ]);
+
+ try {
+ $this->publisherConsumerController->startConsumers();
+ } catch (\Magento\TestFramework\MessageQueue\EnvironmentPreconditionException $e) {
+ $this->markTestSkipped($e->getMessage());
+ } catch (\Magento\TestFramework\MessageQueue\PreconditionFailedException $e) {
+ $this->fail(
+ $e->getMessage()
+ );
+ }
+
+ parent::setUp();
+ }
+
+ protected function tearDown()
+ {
+ $this->publisherConsumerController->stopConsumers();
+ parent::tearDown();
+ }
+
/**
* @covers \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Save::execute
*
@@ -20,7 +56,7 @@ class AttributeTest extends \Magento\TestFramework\TestCase\AbstractBackendContr
*/
public function testSaveActionRedirectsSuccessfully()
{
- $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+ $objectManager = Bootstrap::getObjectManager();
/** @var $session \Magento\Backend\Model\Session */
$session = $objectManager->get(\Magento\Backend\Model\Session::class);
@@ -59,13 +95,14 @@ public function testSaveActionRedirectsSuccessfully()
*/
public function testSaveActionChangeVisibility($attributes)
{
- $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
- $repository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
- \Magento\Catalog\Model\ProductRepository::class
+ $objectManager = Bootstrap::getObjectManager();
+ /** @var ProductRepository $repository */
+ $repository = Bootstrap::getObjectManager()->create(
+ ProductRepository::class
);
$product = $repository->get('simple');
$product->setOrigData();
- $product->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE);
+ $product->setVisibility(Visibility::VISIBILITY_NOT_VISIBLE);
$product->save();
/** @var $session \Magento\Backend\Model\Session */
@@ -75,15 +112,29 @@ public function testSaveActionChangeVisibility($attributes)
$this->getRequest()->setMethod(HttpRequest::METHOD_POST);
$this->dispatch('backend/catalog/product_action_attribute/save/store/0');
+
/** @var \Magento\Catalog\Model\Category $category */
- $categoryFactory = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
+ $categoryFactory = Bootstrap::getObjectManager()->get(
\Magento\Catalog\Model\CategoryFactory::class
);
/** @var \Magento\Catalog\Block\Product\ListProduct $listProduct */
- $listProduct = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(
+ $listProduct = Bootstrap::getObjectManager()->get(
\Magento\Catalog\Block\Product\ListProduct::class
);
+ $this->publisherConsumerController->waitForAsynchronousResult(
+ function () use ($repository) {
+ sleep(3);
+ return $repository->get(
+ 'simple',
+ false,
+ null,
+ true
+ )->getVisibility() != Visibility::VISIBILITY_NOT_VISIBLE;
+ },
+ []
+ );
+
$category = $categoryFactory->create()->load(2);
$layer = $listProduct->getLayer();
$layer->setCurrentCategory($category);
@@ -105,7 +156,7 @@ public function testSaveActionChangeVisibility($attributes)
*/
public function testValidateActionWithMassUpdate($attributes)
{
- $objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+ $objectManager = Bootstrap::getObjectManager();
/** @var $session \Magento\Backend\Model\Session */
$session = $objectManager->get(\Magento\Backend\Model\Session::class);
@@ -156,8 +207,8 @@ public function validateActionDataProvider()
public function saveActionVisibilityAttrDataProvider()
{
return [
- ['arguments' => ['visibility' => \Magento\Catalog\Model\Product\Visibility::VISIBILITY_BOTH]],
- ['arguments' => ['visibility' => \Magento\Catalog\Model\Product\Visibility::VISIBILITY_IN_CATALOG]]
+ ['arguments' => ['visibility' => Visibility::VISIBILITY_BOTH]],
+ ['arguments' => ['visibility' => Visibility::VISIBILITY_IN_CATALOG]]
];
}
}
diff --git a/dev/tests/integration/testsuite/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddressesTest.php b/dev/tests/integration/testsuite/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddressesTest.php
index 994076badddae..60ccdb88676aa 100644
--- a/dev/tests/integration/testsuite/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddressesTest.php
+++ b/dev/tests/integration/testsuite/Magento/Checkout/Plugin/Model/Quote/ResetQuoteAddressesTest.php
@@ -22,6 +22,7 @@ class ResetQuoteAddressesTest extends \PHPUnit\Framework\TestCase
/**
* @magentoDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
*
+ * @magentoAppArea frontend
* @return void
*/
public function testAfterRemoveItem(): void
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/FileLockTest.php b/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/FileLockTest.php
new file mode 100644
index 0000000000000..e64b3c505acf1
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/FileLockTest.php
@@ -0,0 +1,55 @@
+objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+ $this->model = $this->objectManager->create(
+ \Magento\Framework\Lock\Backend\FileLock::class,
+ ['path' => '/tmp']
+ );
+ }
+
+ public function testLockAndUnlock()
+ {
+ $name = 'test_lock';
+
+ $this->assertFalse($this->model->isLocked($name));
+
+ $this->assertTrue($this->model->lock($name));
+ $this->assertTrue($this->model->isLocked($name));
+ $this->assertFalse($this->model->lock($name, 2));
+
+ $this->assertTrue($this->model->unlock($name));
+ $this->assertFalse($this->model->isLocked($name));
+ }
+
+ public function testUnlockWithoutExistingLock()
+ {
+ $name = 'test_lock';
+
+ $this->assertFalse($this->model->isLocked($name));
+ $this->assertFalse($this->model->unlock($name));
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/ZookeeperTest.php b/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/ZookeeperTest.php
new file mode 100644
index 0000000000000..8d0caad5d55e4
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/Lock/Backend/ZookeeperTest.php
@@ -0,0 +1,90 @@
+markTestSkipped('php extension Zookeeper is not installed.');
+ }
+
+ $this->objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+ $this->configReader = $this->objectManager->get(FileReader::class);
+ $this->lockBackendFactory = $this->objectManager->create(LockBackendFactory::class);
+ $this->arrayManager = $this->objectManager->create(ArrayManager::class);
+ $config = $this->configReader->load(ConfigFilePool::APP_ENV);
+
+ if ($this->arrayManager->get('lock/provider', $config) !== 'zookeeper') {
+ $this->markTestSkipped('Zookeeper is not configured during installation.');
+ }
+
+ $this->model = $this->lockBackendFactory->create();
+ $this->assertInstanceOf(ZookeeperLock::class, $this->model);
+ }
+
+ public function testLockAndUnlock()
+ {
+ $name = 'test_lock';
+
+ $this->assertFalse($this->model->isLocked($name));
+
+ $this->assertTrue($this->model->lock($name));
+ $this->assertTrue($this->model->isLocked($name));
+ $this->assertFalse($this->model->lock($name, 2));
+
+ $this->assertTrue($this->model->unlock($name));
+ $this->assertFalse($this->model->isLocked($name));
+ }
+
+ public function testUnlockWithoutExistingLock()
+ {
+ $name = 'test_lock';
+
+ $this->assertFalse($this->model->isLocked($name));
+ $this->assertFalse($this->model->unlock($name));
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Wishlist/Controller/ShareTest.php b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/ShareTest.php
new file mode 100644
index 0000000000000..47705262caaf3
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Wishlist/Controller/ShareTest.php
@@ -0,0 +1,92 @@
+login(1);
+ $this->prepareRequestData();
+ $this->dispatch('wishlist/index/send/');
+
+ $this->assertSessionMessages(
+ $this->equalTo(['Your wish list has been shared.']),
+ MessageInterface::TYPE_SUCCESS
+ );
+ }
+
+ /**
+ * Test share wishlist with incorrect data
+ *
+ * @magentoDataFixture Magento/Wishlist/_files/wishlist.php
+ */
+ public function testShareWishlistWithoutEmails()
+ {
+ $this->login(1);
+ $this->prepareRequestData(true);
+ $this->dispatch('wishlist/index/send/');
+
+ $this->assertSessionMessages(
+ $this->equalTo(['Please enter an email address.']),
+ MessageInterface::TYPE_ERROR
+ );
+ }
+
+ /**
+ * Login the user
+ *
+ * @param string $customerId Customer to mark as logged in for the session
+ * @return void
+ */
+ protected function login($customerId)
+ {
+ /** @var Session $session */
+ $session = $this->_objectManager->get(Session::class);
+ $session->loginById($customerId);
+ }
+
+ /**
+ * Prepares the request with data
+ *
+ * @param bool $invalidData
+ * @return void
+ */
+ private function prepareRequestData($invalidData = false)
+ {
+ Bootstrap::getInstance()->loadArea(Area::AREA_FRONTEND);
+ $emails = !$invalidData ? 'email-1@example.com,email-2@example.com' : '';
+
+ /** @var FormKey $formKey */
+ $formKey = $this->_objectManager->get(FormKey::class);
+ $post = [
+ 'emails' => $emails,
+ 'message' => '',
+ 'form_key' => $formKey->getFormKey(),
+ ];
+
+ $this->getRequest()->setMethod(Request::METHOD_POST);
+ $this->getRequest()->setPostValue($post);
+ }
+}
diff --git a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
index 96854aa76281f..35ba5803b09cc 100644
--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
@@ -213,3 +213,4 @@ Magento/CatalogSearch/Model/ResourceModel/Fulltext
Magento/Elasticsearch/Model/Layer/Search
Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver
Magento/Elasticsearch6/Model/Client
+Magento/Config/App/Config/Type
diff --git a/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php b/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php
new file mode 100644
index 0000000000000..216d8e9a0a01b
--- /dev/null
+++ b/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php
@@ -0,0 +1,116 @@
+locker = $locker;
+ $this->lockTimeout = $lockTimeout;
+ $this->delayTimeout = $delayTimeout;
+ }
+
+ /**
+ * Load data.
+ *
+ * @param string $lockName
+ * @param callable $dataLoader
+ * @param callable $dataCollector
+ * @param callable $dataSaver
+ * @return mixed
+ */
+ public function lockedLoadData(
+ string $lockName,
+ callable $dataLoader,
+ callable $dataCollector,
+ callable $dataSaver
+ ) {
+ $cachedData = $dataLoader(); //optimistic read
+
+ while ($cachedData === false && $this->locker->isLocked($lockName)) {
+ usleep($this->delayTimeout * 1000);
+ $cachedData = $dataLoader();
+ }
+
+ while ($cachedData === false) {
+ try {
+ if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) {
+ $data = $dataCollector();
+ $dataSaver($data);
+ $cachedData = $data;
+ }
+ } finally {
+ $this->locker->unlock($lockName);
+ }
+
+ if ($cachedData === false) {
+ usleep($this->delayTimeout * 1000);
+ $cachedData = $dataLoader();
+ }
+ }
+
+ return $cachedData;
+ }
+
+ /**
+ * Clean data.
+ *
+ * @param string $lockName
+ * @param callable $dataCleaner
+ * @return void
+ */
+ public function lockedCleanData(string $lockName, callable $dataCleaner)
+ {
+ while ($this->locker->isLocked($lockName)) {
+ usleep($this->delayTimeout * 1000);
+ }
+ try {
+ if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) {
+ $dataCleaner();
+ }
+ } finally {
+ $this->locker->unlock($lockName);
+ }
+ }
+}
diff --git a/lib/internal/Magento/Framework/Lock/Backend/Cache.php b/lib/internal/Magento/Framework/Lock/Backend/Cache.php
index 61818cbb8c53c..dfe6bbb828352 100644
--- a/lib/internal/Magento/Framework/Lock/Backend/Cache.php
+++ b/lib/internal/Magento/Framework/Lock/Backend/Cache.php
@@ -14,6 +14,11 @@
*/
class Cache implements \Magento\Framework\Lock\LockManagerInterface
{
+ /**
+ * Prefix for marking that key is locked or not.
+ */
+ const LOCK_PREFIX = 'LOCKED_RECORD_INFO_';
+
/**
* @var FrontendInterface
*/
@@ -26,12 +31,13 @@ public function __construct(FrontendInterface $cache)
{
$this->cache = $cache;
}
+
/**
* @inheritdoc
*/
public function lock(string $name, int $timeout = -1): bool
{
- return $this->cache->save('1', $name, [], $timeout);
+ return $this->cache->save('1', $this->getIdentifier($name), [], $timeout);
}
/**
@@ -39,7 +45,7 @@ public function lock(string $name, int $timeout = -1): bool
*/
public function unlock(string $name): bool
{
- return $this->cache->remove($name);
+ return $this->cache->remove($this->getIdentifier($name));
}
/**
@@ -47,6 +53,17 @@ public function unlock(string $name): bool
*/
public function isLocked(string $name): bool
{
- return (bool)$this->cache->test($name);
+ return (bool)$this->cache->test($this->getIdentifier($name));
+ }
+
+ /**
+ * Get cache locked identifier based on cache identifier.
+ *
+ * @param string $cacheIdentifier
+ * @return string
+ */
+ private function getIdentifier(string $cacheIdentifier): string
+ {
+ return self::LOCK_PREFIX . $cacheIdentifier;
}
}
diff --git a/lib/internal/Magento/Framework/Lock/Backend/FileLock.php b/lib/internal/Magento/Framework/Lock/Backend/FileLock.php
new file mode 100644
index 0000000000000..d168e910a4ab7
--- /dev/null
+++ b/lib/internal/Magento/Framework/Lock/Backend/FileLock.php
@@ -0,0 +1,194 @@
+fileDriver = $fileDriver;
+ $this->path = rtrim($path, '/') . '/';
+
+ try {
+ if (!$this->fileDriver->isExists($this->path)) {
+ $this->fileDriver->createDirectory($this->path);
+ }
+ } catch (FileSystemException $exception) {
+ throw new RuntimeException(
+ new Phrase('Cannot create the directory for locks: %1', [$this->path]),
+ $exception
+ );
+ }
+ }
+
+ /**
+ * Acquires a lock by name
+ *
+ * @param string $name The lock name
+ * @param int $timeout Timeout in seconds. A negative timeout value means infinite timeout
+ * @return bool Returns true if the lock is acquired, otherwise returns false
+ * @throws RuntimeException Throws RuntimeException if cannot acquires the lock because FS problems
+ */
+ public function lock(string $name, int $timeout = -1): bool
+ {
+ try {
+ $lockFile = $this->getLockPath($name);
+ $fileResource = $this->fileDriver->fileOpen($lockFile, 'w+');
+ $skipDeadline = $timeout < 0;
+ $deadline = microtime(true) + $timeout;
+
+ while (!$this->tryToLock($fileResource)) {
+ if (!$skipDeadline && $deadline <= microtime(true)) {
+ $this->fileDriver->fileClose($fileResource);
+ return false;
+ }
+ usleep($this->sleepCycle);
+ }
+ } catch (FileSystemException $exception) {
+ throw new RuntimeException(new Phrase('Cannot acquire a lock.'), $exception);
+ }
+
+ $this->locks[$lockFile] = $fileResource;
+ return true;
+ }
+
+ /**
+ * Checks if a lock exists by name
+ *
+ * @param string $name The lock name
+ * @return bool Returns true if the lock exists, otherwise returns false
+ * @throws RuntimeException Throws RuntimeException if cannot check that the lock exists
+ */
+ public function isLocked(string $name): bool
+ {
+ $lockFile = $this->getLockPath($name);
+ $result = false;
+
+ try {
+ if ($this->fileDriver->isExists($lockFile)) {
+ $fileResource = $this->fileDriver->fileOpen($lockFile, 'w+');
+ if ($this->tryToLock($fileResource)) {
+ $result = false;
+ } else {
+ $result = true;
+ }
+ $this->fileDriver->fileClose($fileResource);
+ }
+ } catch (FileSystemException $exception) {
+ throw new RuntimeException(new Phrase('Cannot verify that the lock exists.'), $exception);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Remove the lock by name
+ *
+ * @param string $name The lock name
+ * @return bool If the lock is removed returns true, otherwise returns false
+ */
+ public function unlock(string $name): bool
+ {
+ $lockFile = $this->getLockPath($name);
+
+ if (isset($this->locks[$lockFile]) && $this->tryToUnlock($this->locks[$lockFile])) {
+ unset($this->locks[$lockFile]);
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Returns the full path to the lock file by name
+ *
+ * @param string $name The lock name
+ * @return string The path to the lock file
+ */
+ private function getLockPath(string $name): string
+ {
+ return $this->path . $name;
+ }
+
+ /**
+ * Tries to lock a file resource
+ *
+ * @param resource $resource The file resource
+ * @return bool If the lock is acquired returns true, otherwise returns false
+ */
+ private function tryToLock($resource): bool
+ {
+ try {
+ return $this->fileDriver->fileLock($resource, LOCK_EX | LOCK_NB);
+ } catch (FileSystemException $exception) {
+ return false;
+ }
+ }
+
+ /**
+ * Tries to unlock a file resource
+ *
+ * @param resource $resource The file resource
+ * @return bool If the lock is removed returns true, otherwise returns false
+ */
+ private function tryToUnlock($resource): bool
+ {
+ try {
+ return $this->fileDriver->fileLock($resource, LOCK_UN | LOCK_NB);
+ } catch (FileSystemException $exception) {
+ return false;
+ }
+ }
+}
diff --git a/lib/internal/Magento/Framework/Lock/Backend/Zookeeper.php b/lib/internal/Magento/Framework/Lock/Backend/Zookeeper.php
new file mode 100644
index 0000000000000..cbba981ae1b51
--- /dev/null
+++ b/lib/internal/Magento/Framework/Lock/Backend/Zookeeper.php
@@ -0,0 +1,280 @@
+\Zookeeper::PERM_ALL, 'scheme' => 'world', 'id' => 'anyone']];
+
+ /**
+ * The mapping list of the lock name with the full lock path
+ *
+ * @var array
+ */
+ private $locks = [];
+
+ /**
+ * The default path to storage locks
+ */
+ const DEFAULT_PATH = '/magento/locks';
+
+ /**
+ * @param string $host The host to connect to Zookeeper
+ * @param string $path The base path to locks in Zookeeper
+ * @throws RuntimeException
+ */
+ public function __construct(string $host, string $path = self::DEFAULT_PATH)
+ {
+ if (!$path) {
+ throw new RuntimeException(
+ new Phrase('The path needs to be a non-empty string.')
+ );
+ }
+
+ if (!$host) {
+ throw new RuntimeException(
+ new Phrase('The host needs to be a non-empty string.')
+ );
+ }
+
+ $this->host = $host;
+ $this->path = rtrim($path, '/') . '/';
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * You can see the lock algorithm by the link
+ * @link https://zookeeper.apache.org/doc/r3.1.2/recipes.html#sc_recipes_Locks
+ *
+ * @throws RuntimeException
+ */
+ public function lock(string $name, int $timeout = -1): bool
+ {
+ $skipDeadline = $timeout < 0;
+ $lockPath = $this->getFullPathToLock($name);
+ $deadline = microtime(true) + $timeout;
+
+ if (!$this->checkAndCreateParentNode($lockPath)) {
+ throw new RuntimeException(new Phrase('Failed creating the path %1', [$lockPath]));
+ }
+
+ $lockKey = $this->getProvider()
+ ->create($lockPath, '1', $this->acl, \Zookeeper::EPHEMERAL | \Zookeeper::SEQUENCE);
+
+ if (!$lockKey) {
+ throw new RuntimeException(new Phrase('Failed creating lock %1', [$lockPath]));
+ }
+
+ while ($this->isAnyLock($lockKey, $this->getIndex($lockKey))) {
+ if (!$skipDeadline && $deadline <= microtime(true)) {
+ $this->getProvider()->delete($lockKey);
+ return false;
+ }
+
+ usleep($this->sleepCycle);
+ }
+
+ $this->locks[$name] = $lockKey;
+
+ return true;
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @throws RuntimeException
+ */
+ public function unlock(string $name): bool
+ {
+ if (!isset($this->locks[$name])) {
+ return false;
+ }
+
+ return $this->getProvider()->delete($this->locks[$name]);
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @throws RuntimeException
+ */
+ public function isLocked(string $name): bool
+ {
+ return $this->isAnyLock($this->getFullPathToLock($name));
+ }
+
+ /**
+ * Gets full path to lock by its name
+ *
+ * @param string $name
+ * @return string
+ */
+ private function getFullPathToLock(string $name): string
+ {
+ return $this->path . $name . '/' . $this->lockName;
+ }
+
+ /**
+ * Initiolizes and returns Zookeeper provider
+ *
+ * @return \Zookeeper
+ * @throws RuntimeException
+ */
+ private function getProvider(): \Zookeeper
+ {
+ if (!$this->zookeeper) {
+ $this->zookeeper = new \Zookeeper($this->host);
+ }
+
+ $deadline = microtime(true) + $this->connectionTimeout;
+ while ($this->zookeeper->getState() != \Zookeeper::CONNECTED_STATE) {
+ if ($deadline <= microtime(true)) {
+ throw new RuntimeException(new Phrase('Zookeeper connection timed out!'));
+ }
+ usleep($this->sleepCycle);
+ }
+
+ return $this->zookeeper;
+ }
+
+ /**
+ * Checks and creates base path recursively
+ *
+ * @param string $path
+ * @return bool
+ * @throws RuntimeException
+ */
+ private function checkAndCreateParentNode(string $path): bool
+ {
+ $path = dirname($path);
+ if ($this->getProvider()->exists($path)) {
+ return true;
+ }
+
+ if (!$this->checkAndCreateParentNode($path)) {
+ return false;
+ }
+
+ if ($this->getProvider()->create($path, '1', $this->acl)) {
+ return true;
+ }
+
+ return $this->getProvider()->exists($path);
+ }
+
+ /**
+ * Gets int increment of lock key
+ *
+ * @param string $key
+ * @return int|null
+ */
+ private function getIndex(string $key)
+ {
+ if (!preg_match('/' . $this->lockName . '([0-9]+)$/', $key, $matches)) {
+ return null;
+ }
+
+ return intval($matches[1]);
+ }
+
+ /**
+ * Checks if there is any sequence node under parent of $fullKey.
+ *
+ * At first checks that the $fullKey node is present, if not - returns false.
+ * If $indexKey is non-null and there is a smaller index than $indexKey then returns true,
+ * otherwise returns false.
+ *
+ * @param string $fullKey The full path without any sequence info
+ * @param int|null $indexKey The index to compare
+ * @return bool
+ * @throws RuntimeException
+ */
+ private function isAnyLock(string $fullKey, int $indexKey = null): bool
+ {
+ $parent = dirname($fullKey);
+
+ if (!$this->getProvider()->exists($parent)) {
+ return false;
+ }
+
+ $children = $this->getProvider()->getChildren($parent);
+
+ if (null === $indexKey && !empty($children)) {
+ return true;
+ }
+
+ foreach ($children as $childKey) {
+ $childIndex = $this->getIndex($childKey);
+
+ if (null === $childIndex) {
+ continue;
+ }
+
+ if ($childIndex < $indexKey) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/lib/internal/Magento/Framework/Lock/LockBackendFactory.php b/lib/internal/Magento/Framework/Lock/LockBackendFactory.php
new file mode 100644
index 0000000000000..b142085ef6563
--- /dev/null
+++ b/lib/internal/Magento/Framework/Lock/LockBackendFactory.php
@@ -0,0 +1,111 @@
+ DatabaseLock::class,
+ self::LOCK_ZOOKEEPER => ZookeeperLock::class,
+ self::LOCK_CACHE => CacheLock::class,
+ self::LOCK_FILE => FileLock::class,
+ ];
+
+ /**
+ * @param ObjectManagerInterface $objectManager The Object Manager instance
+ * @param DeploymentConfig $deploymentConfig The Application deployment configuration
+ */
+ public function __construct(
+ ObjectManagerInterface $objectManager,
+ DeploymentConfig $deploymentConfig
+ ) {
+ $this->objectManager = $objectManager;
+ $this->deploymentConfig = $deploymentConfig;
+ }
+
+ /**
+ * Creates an instance of LockManagerInterface using information from deployment config
+ *
+ * @return LockManagerInterface
+ * @throws RuntimeException
+ */
+ public function create(): LockManagerInterface
+ {
+ $provider = $this->deploymentConfig->get('lock/provider', self::LOCK_DB);
+ $config = $this->deploymentConfig->get('lock/config', []);
+
+ if (!isset($this->lockers[$provider])) {
+ throw new RuntimeException(new Phrase('Unknown locks provider: %1', [$provider]));
+ }
+
+ if (self::LOCK_ZOOKEEPER === $provider && !extension_loaded(self::LOCK_ZOOKEEPER)) {
+ throw new RuntimeException(new Phrase('php extension Zookeeper is not installed.'));
+ }
+
+ return $this->objectManager->create($this->lockers[$provider], $config);
+ }
+}
diff --git a/lib/internal/Magento/Framework/Lock/Proxy.php b/lib/internal/Magento/Framework/Lock/Proxy.php
new file mode 100644
index 0000000000000..2718bf6cb3456
--- /dev/null
+++ b/lib/internal/Magento/Framework/Lock/Proxy.php
@@ -0,0 +1,83 @@
+factory = $factory;
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @throws RuntimeException
+ */
+ public function isLocked(string $name): bool
+ {
+ return $this->getLocker()->isLocked($name);
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @throws RuntimeException
+ */
+ public function lock(string $name, int $timeout = -1): bool
+ {
+ return $this->getLocker()->lock($name, $timeout);
+ }
+
+ /**
+ * @inheritdoc
+ *
+ * @throws RuntimeException
+ */
+ public function unlock(string $name): bool
+ {
+ return $this->getLocker()->unlock($name);
+ }
+
+ /**
+ * Gets LockManagerInterface implementation using Factory
+ *
+ * @return LockManagerInterface
+ * @throws RuntimeException
+ */
+ private function getLocker(): LockManagerInterface
+ {
+ if (!$this->locker) {
+ $this->locker = $this->factory->create();
+ }
+
+ return $this->locker;
+ }
+}
diff --git a/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/ZookeeperTest.php b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/ZookeeperTest.php
new file mode 100644
index 0000000000000..62521b9de3082
--- /dev/null
+++ b/lib/internal/Magento/Framework/Lock/Test/Unit/Backend/ZookeeperTest.php
@@ -0,0 +1,68 @@
+markTestSkipped('Test was skipped because php extension Zookeeper is not installed.');
+ }
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Exception\RuntimeException
+ * @expectedExceptionMessage The path needs to be a non-empty string.
+ * @return void
+ */
+ public function testConstructionWithPathException()
+ {
+ $this->zookeeperProvider = new ZookeeperProvider($this->host, '');
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Exception\RuntimeException
+ * @expectedExceptionMessage The host needs to be a non-empty string.
+ * @return void
+ */
+ public function testConstructionWithHostException()
+ {
+ $this->zookeeperProvider = new ZookeeperProvider('', $this->path);
+ }
+
+ /**
+ * @return void
+ */
+ public function testConstructionWithoutException()
+ {
+ $this->zookeeperProvider = new ZookeeperProvider($this->host, $this->path);
+ }
+}
diff --git a/lib/internal/Magento/Framework/Lock/Test/Unit/LockBackendFactoryTest.php b/lib/internal/Magento/Framework/Lock/Test/Unit/LockBackendFactoryTest.php
new file mode 100644
index 0000000000000..ebf2f54f3e093
--- /dev/null
+++ b/lib/internal/Magento/Framework/Lock/Test/Unit/LockBackendFactoryTest.php
@@ -0,0 +1,116 @@
+objectManagerMock = $this->getMockForAbstractClass(ObjectManagerInterface::class);
+ $this->deploymentConfigMock = $this->createMock(DeploymentConfig::class);
+ $this->factory = new LockBackendFactory($this->objectManagerMock, $this->deploymentConfigMock);
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Exception\RuntimeException
+ * @expectedExceptionMessage Unknown locks provider: someProvider
+ */
+ public function testCreateWithException()
+ {
+ $this->deploymentConfigMock->expects($this->exactly(2))
+ ->method('get')
+ ->withConsecutive(['lock/provider', LockBackendFactory::LOCK_DB], ['lock/config', []])
+ ->willReturnOnConsecutiveCalls('someProvider', []);
+
+ $this->factory->create();
+ }
+
+ /**
+ * @param string $lockProvider
+ * @param string $lockProviderClass
+ * @param array $config
+ * @dataProvider createDataProvider
+ */
+ public function testCreate(string $lockProvider, string $lockProviderClass, array $config)
+ {
+ $lockManagerMock = $this->getMockForAbstractClass(LockManagerInterface::class);
+ $this->deploymentConfigMock->expects($this->exactly(2))
+ ->method('get')
+ ->withConsecutive(['lock/provider', LockBackendFactory::LOCK_DB], ['lock/config', []])
+ ->willReturnOnConsecutiveCalls($lockProvider, $config);
+ $this->objectManagerMock->expects($this->once())
+ ->method('create')
+ ->with($lockProviderClass, $config)
+ ->willReturn($lockManagerMock);
+
+ $this->assertSame($lockManagerMock, $this->factory->create());
+ }
+
+ /**
+ * @return array
+ */
+ public function createDataProvider(): array
+ {
+ $data = [
+ 'db' => [
+ 'lockProvider' => LockBackendFactory::LOCK_DB,
+ 'lockProviderClass' => DatabaseLock::class,
+ 'config' => ['prefix' => 'somePrefix'],
+ ],
+ 'cache' => [
+ 'lockProvider' => LockBackendFactory::LOCK_CACHE,
+ 'lockProviderClass' => CacheLock::class,
+ 'config' => [],
+ ],
+ 'file' => [
+ 'lockProvider' => LockBackendFactory::LOCK_FILE,
+ 'lockProviderClass' => FileLock::class,
+ 'config' => ['path' => '/my/path'],
+ ],
+ ];
+
+ if (extension_loaded('zookeeper')) {
+ $data['zookeeper'] = [
+ 'lockProvider' => LockBackendFactory::LOCK_ZOOKEEPER,
+ 'lockProviderClass' => ZookeeperLock::class,
+ 'config' => ['host' => 'some host'],
+ ];
+ }
+
+ return $data;
+ }
+}
diff --git a/lib/internal/Magento/Framework/Lock/Test/Unit/ProxyTest.php b/lib/internal/Magento/Framework/Lock/Test/Unit/ProxyTest.php
new file mode 100644
index 0000000000000..c71dad701d715
--- /dev/null
+++ b/lib/internal/Magento/Framework/Lock/Test/Unit/ProxyTest.php
@@ -0,0 +1,106 @@
+factoryMock = $this->createMock(LockBackendFactory::class);
+ $this->lockerMock = $this->getMockForAbstractClass(LockManagerInterface::class);
+ $this->proxy = new Proxy($this->factoryMock);
+ }
+
+ /**
+ * @return void
+ */
+ public function testIsLocked()
+ {
+ $lockName = 'testLock';
+ $this->factoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->lockerMock);
+ $this->lockerMock->expects($this->exactly(2))
+ ->method('isLocked')
+ ->with($lockName)
+ ->willReturn(true);
+
+ $this->assertTrue($this->proxy->isLocked($lockName));
+
+ // Call one more time to check that method Factory::create is called one time
+ $this->assertTrue($this->proxy->isLocked($lockName));
+ }
+
+ /**
+ * @return void
+ */
+ public function testLock()
+ {
+ $lockName = 'testLock';
+ $timeout = 123;
+ $this->factoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->lockerMock);
+ $this->lockerMock->expects($this->exactly(2))
+ ->method('lock')
+ ->with($lockName, $timeout)
+ ->willReturn(true);
+
+ $this->assertTrue($this->proxy->lock($lockName, $timeout));
+
+ // Call one more time to check that method Factory::create is called one time
+ $this->assertTrue($this->proxy->lock($lockName, $timeout));
+ }
+
+ /**
+ * @return void
+ */
+ public function testUnlock()
+ {
+ $lockName = 'testLock';
+ $this->factoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->lockerMock);
+ $this->lockerMock->expects($this->exactly(2))
+ ->method('unlock')
+ ->with($lockName)
+ ->willReturn(true);
+
+ $this->assertTrue($this->proxy->unlock($lockName));
+
+ // Call one more time to check that method Factory::create is called one time
+ $this->assertTrue($this->proxy->unlock($lockName));
+ }
+}
diff --git a/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php b/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php
index cb80bc4becaec..48c33c48f12e6 100644
--- a/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php
+++ b/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php
@@ -9,7 +9,7 @@
/**
* Class CallbackInvoker to invoke callbacks for consumer classes
*/
-class CallbackInvoker
+class CallbackInvoker implements CallbackInvokerInterface
{
/**
* Run short running process
diff --git a/lib/internal/Magento/Framework/MessageQueue/CallbackInvokerInterface.php b/lib/internal/Magento/Framework/MessageQueue/CallbackInvokerInterface.php
new file mode 100644
index 0000000000000..36658f2e4eebe
--- /dev/null
+++ b/lib/internal/Magento/Framework/MessageQueue/CallbackInvokerInterface.php
@@ -0,0 +1,24 @@
+getMockForAbstractClass();
$configuration->expects($this->atLeastOnce())->method('getHandlers')->willReturn([]);
$this->messageStatusProcessor->expects($this->exactly(2))->method('acknowledgeMessages');
- $mergedMessage = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
+ $mergedMessage = $this->getMockBuilder(\Magento\Framework\Api\CustomAttributesDataInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
$message = $this->getMockBuilder(\Magento\Framework\MessageQueue\EnvelopeInterface::class)
@@ -116,7 +116,7 @@ public function testProcessWithConnectionLostException()
$exception = new \Magento\Framework\MessageQueue\ConnectionLostException(__('Exception Message'));
$configuration->expects($this->atLeastOnce())->method('getHandlers')->willThrowException($exception);
$this->messageStatusProcessor->expects($this->once())->method('acknowledgeMessages');
- $mergedMessage = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
+ $mergedMessage = $this->getMockBuilder(\Magento\Framework\Api\CustomAttributesDataInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
$message = $this->getMockBuilder(\Magento\Framework\MessageQueue\EnvelopeInterface::class)
@@ -158,7 +158,7 @@ public function testProcessWithException()
$configuration->expects($this->atLeastOnce())->method('getHandlers')->willThrowException($exception);
$this->messageStatusProcessor->expects($this->once())->method('acknowledgeMessages');
$this->messageStatusProcessor->expects($this->atLeastOnce())->method('rejectMessages');
- $mergedMessage = $this->getMockBuilder(\Magento\Catalog\Api\Data\ProductInterface::class)
+ $mergedMessage = $this->getMockBuilder(\Magento\Framework\Api\CustomAttributesDataInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
$message = $this->getMockBuilder(\Magento\Framework\MessageQueue\EnvelopeInterface::class)
diff --git a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php
index 335006555d2f1..6c4746d8218ea 100644
--- a/lib/internal/Magento/Framework/View/Element/AbstractBlock.php
+++ b/lib/internal/Magento/Framework/View/Element/AbstractBlock.php
@@ -3,9 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Framework\View\Element;
+use Magento\Framework\Cache\LockGuardedCacheLoader;
use Magento\Framework\DataObject\IdentityInterface;
+use Magento\Framework\App\ObjectManager;
/**
* Base class for all blocks.
@@ -175,14 +178,23 @@ abstract class AbstractBlock extends \Magento\Framework\DataObject implements Bl
*/
protected $_cache;
+ /**
+ * @var LockGuardedCacheLoader
+ */
+ private $lockQuery;
+
/**
* Constructor
*
* @param \Magento\Framework\View\Element\Context $context
* @param array $data
+ * @param LockGuardedCacheLoader|null $lockQuery
*/
- public function __construct(\Magento\Framework\View\Element\Context $context, array $data = [])
- {
+ public function __construct(
+ \Magento\Framework\View\Element\Context $context,
+ array $data = [],
+ LockGuardedCacheLoader $lockQuery = null
+ ) {
$this->_request = $context->getRequest();
$this->_layout = $context->getLayout();
$this->_eventManager = $context->getEventManager();
@@ -204,6 +216,8 @@ public function __construct(\Magento\Framework\View\Element\Context $context, ar
$this->jsLayout = $data['jsLayout'];
unset($data['jsLayout']);
}
+ $this->lockQuery = $lockQuery
+ ?: ObjectManager::getInstance()->get(LockGuardedCacheLoader::class);
parent::__construct($data);
$this->_construct();
}
@@ -658,19 +672,6 @@ public function toHtml()
}
$html = $this->_loadCache();
- if ($html === false) {
- if ($this->hasData('translate_inline')) {
- $this->inlineTranslation->suspend($this->getData('translate_inline'));
- }
-
- $this->_beforeToHtml();
- $html = $this->_toHtml();
- $this->_saveCache($html);
-
- if ($this->hasData('translate_inline')) {
- $this->inlineTranslation->resume();
- }
- }
$html = $this->_afterToHtml($html);
/** @var \Magento\Framework\DataObject */
@@ -1083,23 +1084,54 @@ protected function getCacheLifetime()
/**
* Load block html from cache storage
*
- * @return string|false
+ * @return string
*/
protected function _loadCache()
{
+ $collectAction = function () {
+ if ($this->hasData('translate_inline')) {
+ $this->inlineTranslation->suspend($this->getData('translate_inline'));
+ }
+
+ $this->_beforeToHtml();
+ return $this->_toHtml();
+ };
+
if ($this->getCacheLifetime() === null || !$this->_cacheState->isEnabled(self::CACHE_GROUP)) {
- return false;
- }
- $cacheKey = $this->getCacheKey();
- $cacheData = $this->_cache->load($cacheKey);
- if ($cacheData) {
- $cacheData = str_replace(
- $this->_getSidPlaceholder($cacheKey),
- $this->_sidResolver->getSessionIdQueryParam($this->_session) . '=' . $this->_session->getSessionId(),
- $cacheData
- );
+ $html = $collectAction();
+ if ($this->hasData('translate_inline')) {
+ $this->inlineTranslation->resume();
+ }
+ return $html;
}
- return $cacheData;
+ $loadAction = function () {
+ $cacheKey = $this->getCacheKey();
+ $cacheData = $this->_cache->load($cacheKey);
+ if ($cacheData) {
+ $cacheData = str_replace(
+ $this->_getSidPlaceholder($cacheKey),
+ $this->_sidResolver->getSessionIdQueryParam($this->_session)
+ . '='
+ . $this->_session->getSessionId(),
+ $cacheData
+ );
+ }
+ return $cacheData;
+ };
+
+ $saveAction = function ($data) {
+ $this->_saveCache($data);
+ if ($this->hasData('translate_inline')) {
+ $this->inlineTranslation->resume();
+ }
+ };
+
+ return (string)$this->lockQuery->lockedLoadData(
+ $this->getCacheKey(),
+ $loadAction,
+ $collectAction,
+ $saveAction
+ );
}
/**
diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php
index 5f7508438a6ed..dba775ea894f4 100644
--- a/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php
+++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/AbstractBlockTest.php
@@ -6,6 +6,7 @@
namespace Magento\Framework\View\Test\Unit\Element;
+use Magento\Framework\Cache\LockGuardedCacheLoader;
use Magento\Framework\View\Element\AbstractBlock;
use Magento\Framework\View\Element\Context;
use Magento\Framework\Config\View;
@@ -13,7 +14,6 @@
use Magento\Framework\Event\ManagerInterface as EventManagerInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Cache\StateInterface as CacheStateInterface;
-use Magento\Framework\App\CacheInterface;
use Magento\Framework\Session\SidResolverInterface;
use Magento\Framework\Session\SessionManagerInterface;
@@ -42,11 +42,6 @@ class AbstractBlockTest extends \PHPUnit\Framework\TestCase
*/
private $cacheStateMock;
- /**
- * @var CacheInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $cacheMock;
-
/**
* @var SidResolverInterface|\PHPUnit_Framework_MockObject_MockObject
*/
@@ -57,6 +52,11 @@ class AbstractBlockTest extends \PHPUnit\Framework\TestCase
*/
private $sessionMock;
+ /**
+ * @var LockGuardedCacheLoader|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $lockQuery;
+
/**
* @return void
*/
@@ -65,7 +65,10 @@ protected function setUp()
$this->eventManagerMock = $this->getMockForAbstractClass(EventManagerInterface::class);
$this->scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class);
$this->cacheStateMock = $this->getMockForAbstractClass(CacheStateInterface::class);
- $this->cacheMock = $this->getMockForAbstractClass(CacheInterface::class);
+ $this->lockQuery = $this->getMockBuilder(LockGuardedCacheLoader::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['lockedLoadData'])
+ ->getMockForAbstractClass();
$this->sidResolverMock = $this->getMockForAbstractClass(SidResolverInterface::class);
$this->sessionMock = $this->getMockForAbstractClass(SessionManagerInterface::class);
$contextMock = $this->createMock(Context::class);
@@ -78,9 +81,6 @@ protected function setUp()
$contextMock->expects($this->once())
->method('getCacheState')
->willReturn($this->cacheStateMock);
- $contextMock->expects($this->once())
- ->method('getCache')
- ->willReturn($this->cacheMock);
$contextMock->expects($this->once())
->method('getSidResolver')
->willReturn($this->sidResolverMock);
@@ -89,7 +89,11 @@ protected function setUp()
->willReturn($this->sessionMock);
$this->block = $this->getMockForAbstractClass(
AbstractBlock::class,
- ['context' => $contextMock]
+ [
+ 'context' => $contextMock,
+ 'data' => [],
+ 'lockQuery' => $this->lockQuery
+ ]
);
}
@@ -219,10 +223,7 @@ public function testToHtmlWhenModuleIsDisabled()
/**
* @param string|bool $cacheLifetime
* @param string|bool $dataFromCache
- * @param string $dataForSaveCache
* @param \PHPUnit\Framework\MockObject\Matcher\InvokedCount $expectsDispatchEvent
- * @param \PHPUnit\Framework\MockObject\Matcher\InvokedCount $expectsCacheLoad
- * @param \PHPUnit\Framework\MockObject\Matcher\InvokedCount $expectsCacheSave
* @param string $expectedResult
* @return void
* @dataProvider getCacheLifetimeDataProvider
@@ -230,10 +231,7 @@ public function testToHtmlWhenModuleIsDisabled()
public function testGetCacheLifetimeViaToHtml(
$cacheLifetime,
$dataFromCache,
- $dataForSaveCache,
$expectsDispatchEvent,
- $expectsCacheLoad,
- $expectsCacheSave,
$expectedResult
) {
$moduleName = 'Test';
@@ -252,13 +250,9 @@ public function testGetCacheLifetimeViaToHtml(
->method('isEnabled')
->with(AbstractBlock::CACHE_GROUP)
->willReturn(true);
- $this->cacheMock->expects($expectsCacheLoad)
- ->method('load')
- ->with(AbstractBlock::CACHE_KEY_PREFIX . $cacheKey)
+ $this->lockQuery->expects($this->any())
+ ->method('lockedLoadData')
->willReturn($dataFromCache);
- $this->cacheMock->expects($expectsCacheSave)
- ->method('save')
- ->with($dataForSaveCache, AbstractBlock::CACHE_KEY_PREFIX . $cacheKey);
$this->sidResolverMock->expects($this->any())
->method('getSessionIdQueryParam')
->with($this->sessionMock)
@@ -279,46 +273,31 @@ public function getCacheLifetimeDataProvider()
[
'cacheLifetime' => null,
'dataFromCache' => 'dataFromCache',
- 'dataForSaveCache' => '',
'expectsDispatchEvent' => $this->exactly(2),
- 'expectsCacheLoad' => $this->never(),
- 'expectsCacheSave' => $this->never(),
'expectedResult' => '',
],
[
'cacheLifetime' => false,
'dataFromCache' => 'dataFromCache',
- 'dataForSaveCache' => '',
'expectsDispatchEvent' => $this->exactly(2),
- 'expectsCacheLoad' => $this->never(),
- 'expectsCacheSave' => $this->never(),
'expectedResult' => '',
],
[
'cacheLifetime' => 120,
'dataFromCache' => 'dataFromCache',
- 'dataForSaveCache' => '',
'expectsDispatchEvent' => $this->exactly(2),
- 'expectsCacheLoad' => $this->once(),
- 'expectsCacheSave' => $this->never(),
'expectedResult' => 'dataFromCache',
],
[
'cacheLifetime' => '120string',
'dataFromCache' => 'dataFromCache',
- 'dataForSaveCache' => '',
'expectsDispatchEvent' => $this->exactly(2),
- 'expectsCacheLoad' => $this->once(),
- 'expectsCacheSave' => $this->never(),
'expectedResult' => 'dataFromCache',
],
[
'cacheLifetime' => 120,
'dataFromCache' => false,
- 'dataForSaveCache' => '',
'expectsDispatchEvent' => $this->exactly(2),
- 'expectsCacheLoad' => $this->once(),
- 'expectsCacheSave' => $this->once(),
'expectedResult' => '',
],
];
diff --git a/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/LinkTest.php b/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/LinkTest.php
index b911a38dbb488..4c76087bfea12 100644
--- a/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/LinkTest.php
+++ b/lib/internal/Magento/Framework/View/Test/Unit/Element/Html/LinkTest.php
@@ -3,10 +3,18 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Framework\View\Test\Unit\Element\Html;
class LinkTest extends \PHPUnit\Framework\TestCase
{
+ private $objectManager;
+
+ protected function setUp()
+ {
+ $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+ }
+
/**
* @var array
*/
@@ -24,24 +32,8 @@ class LinkTest extends \PHPUnit\Framework\TestCase
*/
protected $link;
- /**
- * @param \Magento\Framework\View\Element\Html\Link $link
- * @param string $expected
- *
- * @dataProvider getLinkAttributesDataProvider
- */
- public function testGetLinkAttributes($link, $expected)
- {
- $this->assertEquals($expected, $link->getLinkAttributes());
- }
-
- /**
- * @return array
- */
- public function getLinkAttributesDataProvider()
+ public function testGetLinkAttributes()
{
- $objectManagerHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
-
$escaperMock = $this->getMockBuilder(\Magento\Framework\Escaper::class)
->setMethods(['escapeHtml'])->disableOriginalConstructor()->getMock();
@@ -54,13 +46,19 @@ public function getLinkAttributesDataProvider()
$urlBuilderMock->expects($this->any())
->method('getUrl')
- ->will($this->returnArgument('http://site.com/link.html'));
+ ->willReturn('http://site.com/link.html');
$validtorMock = $this->getMockBuilder(\Magento\Framework\View\Element\Template\File\Validator::class)
->setMethods(['isValid'])->disableOriginalConstructor()->getMock();
+ $validtorMock->expects($this->any())
+ ->method('isValid')
+ ->willReturn(false);
$scopeConfigMock = $this->getMockBuilder(\Magento\Framework\App\Config::class)
->setMethods(['isSetFlag'])->disableOriginalConstructor()->getMock();
+ $scopeConfigMock->expects($this->any())
+ ->method('isSetFlag')
+ ->willReturn(true);
$resolverMock = $this->getMockBuilder(\Magento\Framework\View\Element\Template\File\Resolver::class)
->setMethods([])->disableOriginalConstructor()->getMock();
@@ -72,48 +70,48 @@ public function getLinkAttributesDataProvider()
$contextMock->expects($this->any())
->method('getValidator')
- ->will($this->returnValue($validtorMock));
+ ->willReturn($validtorMock);
$contextMock->expects($this->any())
->method('getResolver')
- ->will($this->returnValue($resolverMock));
+ ->willReturn($resolverMock);
$contextMock->expects($this->any())
->method('getEscaper')
- ->will($this->returnValue($escaperMock));
+ ->willReturn($escaperMock);
$contextMock->expects($this->any())
->method('getUrlBuilder')
- ->will($this->returnValue($urlBuilderMock));
+ ->willReturn($urlBuilderMock);
$contextMock->expects($this->any())
->method('getScopeConfig')
- ->will($this->returnValue($scopeConfigMock));
+ ->willReturn($scopeConfigMock);
/** @var \Magento\Framework\View\Element\Html\Link $linkWithAttributes */
- $linkWithAttributes = $objectManagerHelper->getObject(
+ $linkWithAttributes = $this->objectManager->getObject(
\Magento\Framework\View\Element\Html\Link::class,
['context' => $contextMock]
);
+
+ $this->assertEquals(
+ 'href="http://site.com/link.html"',
+ $linkWithAttributes->getLinkAttributes()
+ );
+
/** @var \Magento\Framework\View\Element\Html\Link $linkWithoutAttributes */
- $linkWithoutAttributes = $objectManagerHelper->getObject(
+ $linkWithoutAttributes = $this->objectManager->getObject(
\Magento\Framework\View\Element\Html\Link::class,
['context' => $contextMock]
);
-
foreach ($this->allowedAttributes as $attribute) {
- $linkWithAttributes->setDataUsingMethod($attribute, $attribute);
+ $linkWithoutAttributes->setDataUsingMethod($attribute, $attribute);
}
- return [
- 'full' => [
- 'link' => $linkWithAttributes,
- 'expected' => 'shape="shape" tabindex="tabindex" onfocus="onfocus" onblur="onblur" id="id"',
- ],
- 'empty' => [
- 'link' => $linkWithoutAttributes,
- 'expected' => '',
- ],
- ];
+ $this->assertEquals(
+ 'href="http://site.com/link.html" shape="shape" tabindex="tabindex"'
+ . ' onfocus="onfocus" onblur="onblur" id="id"',
+ $linkWithoutAttributes->getLinkAttributes()
+ );
}
}
diff --git a/lib/web/mage/backend/floating-header.js b/lib/web/mage/backend/floating-header.js
index 06861277559a4..a6f767259488a 100644
--- a/lib/web/mage/backend/floating-header.js
+++ b/lib/web/mage/backend/floating-header.js
@@ -48,6 +48,7 @@ define([
this.element.wrapInner($('
', {
'class': 'page-actions-inner', 'data-title': title
}));
+ this.element.removeClass('floating-header');
},
/**
diff --git a/setup/performance-toolkit/benchmark.jmx b/setup/performance-toolkit/benchmark.jmx
index 765d0a616f77c..0e1860405e946 100644
--- a/setup/performance-toolkit/benchmark.jmx
+++ b/setup/performance-toolkit/benchmark.jmx
@@ -27267,843 +27267,6 @@ if (testLabel
mpaf/tool/fragments/_system/thread_group.jmx
-
- 1
- false
- 1
- ${productGridMassActionPercentage}
- mpaf/tool/fragments/_system/scenario_controller_tmpl.jmx
-
-
-
-var testLabel = "${testLabel}" ? " (${testLabel})" : "";
-if (testLabel
- && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy'
-) {
- if (sampler.getName().indexOf(testLabel) == -1) {
- sampler.setName(sampler.getName() + testLabel);
- }
-} else if (sampler.getName().indexOf("SetUp - ") == -1) {
- sampler.setName("SetUp - " + sampler.getName());
-}
-
- javascript
- mpaf/tool/fragments/_system/setup_label.jmx
-
-
-
- vars.put("testLabel", "Product Grid Mass Actions");
-
- true
-
-
-
-
-
- function getFormKeyFromResponse()
- {
- var url = prev.getUrlAsString(),
- responseCode = prev.getResponseCode(),
- formKey = null;
- searchPattern = /var FORM_KEY = '(.+)'/;
- if (responseCode == "200" && url) {
- response = prev.getResponseDataAsString();
- formKey = response && response.match(searchPattern) ? response.match(searchPattern)[1] : null;
- }
- return formKey;
- }
-
- formKey = vars.get("form_key_storage");
-
- currentFormKey = getFormKeyFromResponse();
-
- if (currentFormKey != null && currentFormKey != formKey) {
- vars.put("form_key_storage", currentFormKey);
- }
-
- javascript
- mpaf/tool/fragments/ce/admin/handle_admin_form_key.jmx
-
-
-
- formKey = vars.get("form_key_storage");
- if (formKey
- && sampler.getClass().getName() == 'org.apache.jmeter.protocol.http.sampler.HTTPSamplerProxy'
- && sampler.getMethod() == "POST")
- {
- arguments = sampler.getArguments();
- for (i=0; i<arguments.getArgumentCount(); i++)
- {
- argument = arguments.getArgument(i);
- if (argument.getName() == 'form_key' && argument.getValue() != formKey) {
- log.info("admin form key updated: " + argument.getValue() + " => " + formKey);
- argument.setValue(formKey);
- }
- }
- }
-
- javascript
-
-
-
-
-
- false
- mpaf/tool/fragments/ce/http_cookie_manager_without_clear_each_iteration.jmx
-
-
-
- mpaf/tool/fragments/ce/simple_controller.jmx
-
-
-
- get-admin-email
- mpaf/tool/fragments/ce/lock_controller.jmx
-
-
-
- mpaf/tool/fragments/ce/get_admin_email.jmx
-
-adminUserList = props.get("adminUserList");
-adminUserListIterator = props.get("adminUserListIterator");
-adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool"));
-
-if (adminUsersDistribution == 1) {
- adminUser = adminUserList.poll();
-} else {
- if (!adminUserListIterator.hasNext()) {
- adminUserListIterator = adminUserList.descendingIterator();
- }
-
- adminUser = adminUserListIterator.next();
-}
-
-if (adminUser == null) {
- SampleResult.setResponseMessage("adminUser list is empty");
- SampleResult.setResponseData("adminUser list is empty","UTF-8");
- IsSuccess=false;
- SampleResult.setSuccessful(false);
- SampleResult.setStopThread(true);
-}
-vars.put("admin_user", adminUser);
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
- 60000
- 200000
- ${request_protocol}
-
- ${base_path}${admin_path}/admin/
- GET
- true
- false
- true
- false
- false
-
- mpaf/tool/fragments/ce/admin_login/admin_login.jmx
-
-
-
- Welcome
- <title>Magento Admin</title>
-
- Assertion.response_data
- false
- 2
-
-
-
- false
- admin_form_key
- <input name="form_key" type="hidden" value="([^'"]+)" />
- $1$
-
- 1
-
-
-
-
- ^.+$
-
- Assertion.response_data
- false
- 1
- variable
- admin_form_key
-
-
-
-
-
-
-
-
- true
-
- =
- true
- dummy
-
-
- true
- ${admin_form_key}
- =
- true
- form_key
-
-
- true
- ${admin_password}
- =
- true
- login[password]
-
-
- true
- ${admin_user}
- =
- true
- login[username]
-
-
-
-
-
- 60000
- 200000
- ${request_protocol}
-
- ${base_path}${admin_path}/admin/dashboard/
- POST
- true
- false
- true
- false
- Java
- false
-
- mpaf/tool/fragments/ce/admin_login/admin_login_submit_form.jmx
-
-
-
- false
- admin_form_key
- <input name="form_key" type="hidden" value="([^'"]+)" />
- $1$
-
- 1
- mpaf/tool/fragments/ce/admin_login/admin_retrieve_form_key.jmx
-
-
-
-
-
- mpaf/tool/fragments/ce/simple_controller.jmx
-
-
-
-
-
-
- true
- ${admin_form_key}
- =
- true
- form_key
-
-
- true
- product_listing
- =
- true
- namespace
- true
-
-
- true
-
- =
- true
- search
- true
-
-
- true
- true
- =
- true
- filters[placeholder]
- true
-
-
- true
- 20
- =
- true
- paging[pageSize]
- true
-
-
- true
- 1
- =
- true
- paging[current]
- true
-
-
- true
- entity_id
- =
- true
- sorting[field]
- true
-
-
- true
- asc
- =
- true
- sorting[direction]
- true
-
-
- true
- true
- =
- true
- isAjax
- true
-
-
-
-
-
- 60000
- 200000
- ${request_protocol}
-
- ${base_path}${admin_path}/mui/index/render/
- GET
- true
- false
- true
- false
- false
-
- mpaf/tool/fragments/ce/admin_browse_products_grid/get_product_pages_count.jmx
-
-
- $.totalRecords
- 0
- true
- false
- true
-
-
-
- products_number
- $.totalRecords
-
-
- BODY
-
-
-
- false
-
-
- var productsPageSize = Integer.parseInt(vars.get("products_page_size"));
-var productsTotal = Integer.parseInt(vars.get("products_number"));
-var pageCountProducts = Math.round(productsTotal/productsPageSize);
-
-vars.put("pages_count_product", String.valueOf(pageCountProducts));
-
-
-
-
-
-
-import java.util.Random;
-Random random = new Random();
-if (${seedForRandom} > 0) {
-random.setSeed(${seedForRandom});
-}
-var productsPageSize = Integer.parseInt(vars.get("products_page_size"));
-var totalNumberOfPages = Integer.parseInt(vars.get("pages_count_product"));
-
-// Randomly select a page.
-var randomProductsPage = random.nextInt(totalNumberOfPages) + 1;
-
-// Get the first and last product id on that page.
-var lastProductIdOnPage = randomProductsPage * productsPageSize;
-var firstProductIdOnPage = lastProductIdOnPage - productsPageSize + 1;
-
-var randomProductId1 = Math.floor(random.nextInt(productsPageSize)) + firstProductIdOnPage;
-var randomProductId2 = Math.floor(random.nextInt(productsPageSize)) + firstProductIdOnPage;
-var randomProductId3 = Math.floor(random.nextInt(productsPageSize)) + firstProductIdOnPage;
-
-vars.put("page_number", String.valueOf(randomProductsPage));
-vars.put("productId1", String.valueOf(randomProductId1));
-vars.put("productId2", String.valueOf(randomProductId2));
-vars.put("productId3", String.valueOf(randomProductId3));
-
-var randomQuantity = random.nextInt(1000) + 1;
-var randomPrice = random.nextInt(500) + 10;
-var randomVisibility = random.nextInt(4) + 1;
-
-vars.put("quantity", String.valueOf(randomQuantity));
-vars.put("price", String.valueOf(randomPrice));
-vars.put("visibility", String.valueOf(randomVisibility));
-
-
-
- false
- mpaf/tool/fragments/ce/admin_browse_products_grid/products_grid_mass_actions/setup.jmx
-
-
-
-
-
-
- true
- ${admin_form_key}
- =
- true
- form_key
- true
-
-
- true
- product_listing
- =
- true
- namespace
- true
-
-
- true
-
- =
- true
- search
- true
-
-
- true
- true
- =
- true
- filters[placeholder]
- true
-
-
- true
- ${products_page_size}
- =
- true
- paging[pageSize]
- true
-
-
- true
- ${page_number}
- =
- true
- paging[current]
- true
-
-
- true
- entity_id
- =
- true
- sorting[field]
- true
-
-
- true
- asc
- =
- true
- sorting[direction]
- true
-
-
- true
- true
- =
- true
- isAjax
- true
-
-
-
-
-
- 60000
- 200000
- ${request_protocol}
-
- ${base_path}${admin_path}/mui/index/render/
- GET
- true
- false
- true
- false
- false
-
- mpaf/tool/fragments/ce/admin_browse_products_grid/products_grid_mass_actions/display_grid.jmx
-
-
-
- totalRecords
-
- Assertion.response_data
- false
- 2
-
-
-
-
-
-
-
-
- true
- ${productId1}
- =
- true
- selected[0]
-
-
- true
- ${productId2}
- =
- true
- selected[1]
-
-
- true
- ${productId3}
- =
- true
- selected[2]
- true
-
-
- true
- true
- =
- true
- filters[placeholder]
- false
-
-
- true
- ${admin_form_key}
- =
- true
- form_key
- false
-
-
- true
- product_listing
- =
- true
- namespace
- false
-
-
-
-
-
- 60000
- 200000
- ${request_protocol}
-
- ${base_path}${admin_path}/catalog/product_action_attribute/edit
- GET
- true
- false
- true
- false
- false
-
- mpaf/tool/fragments/ce/admin_browse_products_grid/products_grid_mass_actions/display_update_attributes.jmx
-
-
-
- Update Attributes
-
- Assertion.response_data
- false
- 2
-
-
-
-
-
-
-
-
- true
- true
- =
- true
- isAjax
- true
-
-
- true
- ${admin_form_key}
- =
- true
- form_key
- true
-
-
- true
- 1
- =
- true
- product[product_has_weight]
- true
-
-
- true
- 1
- =
- true
- product[use_config_gift_message_available]
- true
-
-
- true
- 1
- =
- true
- product[use_config_gift_wrapping_available]
- true
-
-
- true
- ${quantity}
- =
- true
- inventory[qty]
- true
-
-
- true
- ${price}
- =
- true
- attributes[price]
-
-
- true
- ${visibility}
- =
- true
- attributes[visibility]
-
-
-
-
-
- 60000
- 200000
- ${request_protocol}
-
- ${base_path}${admin_path}/catalog/product_action_attribute/validate
- POST
- true
- false
- true
- false
- false
-
- mpaf/tool/fragments/ce/admin_browse_products_grid/products_grid_mass_actions/change_attributes.jmx
-
-
-
- {"error":false}
-
- Assertion.response_data
- false
- 2
-
-
-
-
-
-
-
- true
- true
- =
- true
- isAjax
- false
-
-
- true
- ${admin_form_key}
- =
- true
- form_key
- false
-
-
- true
- 1
- =
- true
- product[product_has_weight]
- true
-
-
- true
- 1
- =
- true
- product[use_config_gift_message_available]
-
-
- true
- 1
- =
- true
- product[use_config_gift_wrapping_available]
- true
-
-
- true
- ${quantity}
- =
- true
- inventory[qty]
-
-
- true
- on
- =
- true
- toggle_price
- true
-
-
- true
- ${price}
- =
- true
- attributes[price]
-
-
- true
- on
- =
- true
- toggle_price
- true
-
-
- true
- ${visibility}
- =
- true
- attributes[visibility]
-
-
- true
- on
- =
- true
- toggle_visibility
- true
-
-
-
-
-
- 60000
- 200000
- ${request_protocol}
-
- ${base_path}${admin_path}/catalog/product_action_attribute/save/store/0/active_tab/attributes
- POST
- true
- false
- true
- true
- false
-
-
-
-
-
- were updated.
-
- Assertion.response_data
- false
- 2
-
-
-
-
-
-
-
-
-
-
-
- 60000
- 200000
- ${request_protocol}
-
- ${base_path}${admin_path}/admin/auth/logout/
- GET
- true
- false
- true
- false
- false
-
- mpaf/tool/fragments/ce/setup/admin_logout.jmx
-
-
-
- false
-
-
-
- adminUsersDistribution = Integer.parseInt(vars.get("admin_users_distribution_per_admin_pool"));
- if (adminUsersDistribution == 1) {
- adminUserList = props.get("adminUserList");
- adminUserList.add(vars.get("admin_user"));
- }
-
- mpaf/tool/fragments/ce/common/return_admin_email_to_pool.jmx
-
-
-
-
-
1
false
diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList.php b/setup/src/Magento/Setup/Model/ConfigOptionsList.php
index afe1a5d9e2591..3f2aedae1373c 100644
--- a/setup/src/Magento/Setup/Model/ConfigOptionsList.php
+++ b/setup/src/Magento/Setup/Model/ConfigOptionsList.php
@@ -50,7 +50,8 @@ class ConfigOptionsList implements ConfigOptionsListInterface
private $configOptionsListClasses = [
\Magento\Setup\Model\ConfigOptionsList\Session::class,
\Magento\Setup\Model\ConfigOptionsList\Cache::class,
- \Magento\Setup\Model\ConfigOptionsList\PageCache::class
+ \Magento\Setup\Model\ConfigOptionsList\PageCache::class,
+ \Magento\Setup\Model\ConfigOptionsList\Lock::class,
];
/**
diff --git a/setup/src/Magento/Setup/Model/ConfigOptionsList/Lock.php b/setup/src/Magento/Setup/Model/ConfigOptionsList/Lock.php
new file mode 100644
index 0000000000000..66f41128c46b1
--- /dev/null
+++ b/setup/src/Magento/Setup/Model/ConfigOptionsList/Lock.php
@@ -0,0 +1,342 @@
+ [
+ self::INPUT_KEY_LOCK_PROVIDER => self::CONFIG_PATH_LOCK_PROVIDER,
+ self::INPUT_KEY_LOCK_DB_PREFIX => self::CONFIG_PATH_LOCK_DB_PREFIX,
+ ],
+ LockBackendFactory::LOCK_ZOOKEEPER => [
+ self::INPUT_KEY_LOCK_PROVIDER => self::CONFIG_PATH_LOCK_PROVIDER,
+ self::INPUT_KEY_LOCK_ZOOKEEPER_HOST => self::CONFIG_PATH_LOCK_ZOOKEEPER_HOST,
+ self::INPUT_KEY_LOCK_ZOOKEEPER_PATH => self::CONFIG_PATH_LOCK_ZOOKEEPER_PATH,
+ ],
+ LockBackendFactory::LOCK_CACHE => [
+ self::INPUT_KEY_LOCK_PROVIDER => self::CONFIG_PATH_LOCK_PROVIDER,
+ ],
+ LockBackendFactory::LOCK_FILE => [
+ self::INPUT_KEY_LOCK_PROVIDER => self::CONFIG_PATH_LOCK_PROVIDER,
+ self::INPUT_KEY_LOCK_FILE_PATH => self::CONFIG_PATH_LOCK_FILE_PATH,
+ ],
+ ];
+
+ /**
+ * The list of default values
+ *
+ * @var array
+ */
+ private $defaultConfigValues = [
+ self::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_DB,
+ self::INPUT_KEY_LOCK_DB_PREFIX => null,
+ self::INPUT_KEY_LOCK_ZOOKEEPER_PATH => ZookeeperLock::DEFAULT_PATH,
+ ];
+
+ /**
+ * @inheritdoc
+ */
+ public function getOptions()
+ {
+ return [
+ new SelectConfigOption(
+ self::INPUT_KEY_LOCK_PROVIDER,
+ SelectConfigOption::FRONTEND_WIZARD_SELECT,
+ $this->validLockProviders,
+ self::CONFIG_PATH_LOCK_PROVIDER,
+ 'Lock provider name',
+ LockBackendFactory::LOCK_DB
+ ),
+ new TextConfigOption(
+ self::INPUT_KEY_LOCK_DB_PREFIX,
+ TextConfigOption::FRONTEND_WIZARD_TEXT,
+ self::CONFIG_PATH_LOCK_DB_PREFIX,
+ 'Installation specific lock prefix to avoid lock conflicts'
+ ),
+ new TextConfigOption(
+ self::INPUT_KEY_LOCK_ZOOKEEPER_HOST,
+ TextConfigOption::FRONTEND_WIZARD_TEXT,
+ self::CONFIG_PATH_LOCK_ZOOKEEPER_HOST,
+ 'Host and port to connect to Zookeeper cluster. For example: 127.0.0.1:2181'
+ ),
+ new TextConfigOption(
+ self::INPUT_KEY_LOCK_ZOOKEEPER_PATH,
+ TextConfigOption::FRONTEND_WIZARD_TEXT,
+ self::CONFIG_PATH_LOCK_ZOOKEEPER_PATH,
+ 'The path where Zookeeper will save locks. The default path is: ' . ZookeeperLock::DEFAULT_PATH
+ ),
+ new TextConfigOption(
+ self::INPUT_KEY_LOCK_FILE_PATH,
+ TextConfigOption::FRONTEND_WIZARD_TEXT,
+ self::CONFIG_PATH_LOCK_FILE_PATH,
+ 'The path where file locks will be saved.'
+ ),
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function createConfig(array $options, DeploymentConfig $deploymentConfig)
+ {
+ $configData = new ConfigData(ConfigFilePool::APP_ENV);
+ $configData->setOverrideWhenSave(true);
+ $lockProvider = $this->getLockProvider($options, $deploymentConfig);
+
+ $this->setDefaultConfiguration($configData, $deploymentConfig, $lockProvider);
+
+ foreach ($this->mappingInputKeyToConfigPath[$lockProvider] as $input => $path) {
+ if (isset($options[$input])) {
+ $configData->set($path, $options[$input]);
+ }
+ }
+
+ return $configData;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(array $options, DeploymentConfig $deploymentConfig)
+ {
+ $lockProvider = $this->getLockProvider($options, $deploymentConfig);
+ switch ($lockProvider) {
+ case LockBackendFactory::LOCK_ZOOKEEPER:
+ $errors = $this->validateZookeeperConfig($options, $deploymentConfig);
+ break;
+ case LockBackendFactory::LOCK_FILE:
+ $errors = $this->validateFileConfig($options, $deploymentConfig);
+ break;
+ case LockBackendFactory::LOCK_CACHE:
+ case LockBackendFactory::LOCK_DB:
+ $errors = [];
+ break;
+ default:
+ $errors[] = 'The lock provider ' . $lockProvider . ' does not exist.';
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Validates File locks configuration
+ *
+ * @param array $options
+ * @param DeploymentConfig $deploymentConfig
+ * @return array
+ */
+ private function validateFileConfig(array $options, DeploymentConfig $deploymentConfig): array
+ {
+ $errors = [];
+
+ $path = $options[self::INPUT_KEY_LOCK_FILE_PATH]
+ ?? $deploymentConfig->get(
+ self::CONFIG_PATH_LOCK_FILE_PATH,
+ $this->getDefaultValue(self::INPUT_KEY_LOCK_FILE_PATH)
+ );
+
+ if (!$path) {
+ $errors[] = 'The path needs to be a non-empty string.';
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Validates Zookeeper configuration
+ *
+ * @param array $options
+ * @param DeploymentConfig $deploymentConfig
+ * @return array
+ */
+ private function validateZookeeperConfig(array $options, DeploymentConfig $deploymentConfig): array
+ {
+ $errors = [];
+
+ if (!extension_loaded(LockBackendFactory::LOCK_ZOOKEEPER)) {
+ $errors[] = 'php extension Zookeeper is not installed.';
+ }
+
+ $host = $options[self::INPUT_KEY_LOCK_ZOOKEEPER_HOST]
+ ?? $deploymentConfig->get(
+ self::CONFIG_PATH_LOCK_ZOOKEEPER_HOST,
+ $this->getDefaultValue(self::INPUT_KEY_LOCK_ZOOKEEPER_HOST)
+ );
+ $path = $options[self::INPUT_KEY_LOCK_ZOOKEEPER_PATH]
+ ?? $deploymentConfig->get(
+ self::CONFIG_PATH_LOCK_ZOOKEEPER_PATH,
+ $this->getDefaultValue(self::INPUT_KEY_LOCK_ZOOKEEPER_PATH)
+ );
+
+ if (!$path) {
+ $errors[] = 'Zookeeper path needs to be a non-empty string.';
+ }
+
+ if (!$host) {
+ $errors[] = 'Zookeeper host is should be set.';
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Returns the name of lock provider
+ *
+ * @param array $options
+ * @param DeploymentConfig $deploymentConfig
+ * @return string
+ */
+ private function getLockProvider(array $options, DeploymentConfig $deploymentConfig): string
+ {
+ if (!isset($options[self::INPUT_KEY_LOCK_PROVIDER])) {
+ return (string) $deploymentConfig->get(
+ self::CONFIG_PATH_LOCK_PROVIDER,
+ $this->getDefaultValue(self::INPUT_KEY_LOCK_PROVIDER)
+ );
+ }
+
+ return (string) $options[self::INPUT_KEY_LOCK_PROVIDER];
+ }
+
+ /**
+ * Sets default configuration for locks
+ *
+ * @param ConfigData $configData
+ * @param DeploymentConfig $deploymentConfig
+ * @param string $lockProvider
+ * @return ConfigData
+ */
+ private function setDefaultConfiguration(
+ ConfigData $configData,
+ DeploymentConfig $deploymentConfig,
+ string $lockProvider
+ ) {
+ foreach ($this->mappingInputKeyToConfigPath[$lockProvider] as $input => $path) {
+ $configData->set($path, $deploymentConfig->get($path, $this->getDefaultValue($input)));
+ }
+
+ return $configData;
+ }
+
+ /**
+ * Returns default value by input key
+ *
+ * If default value is not set returns null
+ *
+ * @param string $inputKey
+ * @return mixed|null
+ */
+ private function getDefaultValue(string $inputKey)
+ {
+ if (isset($this->defaultConfigValues[$inputKey])) {
+ return $this->defaultConfigValues[$inputKey];
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/LockTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/LockTest.php
new file mode 100644
index 0000000000000..1a46bddf5f21a
--- /dev/null
+++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsList/LockTest.php
@@ -0,0 +1,232 @@
+deploymentConfigMock = $this->createMock(DeploymentConfig::class);
+ $this->lockConfigOptionsList = new LockConfigOptionsList();
+ }
+
+ /**
+ * @return void
+ */
+ public function testGetOptions()
+ {
+ $options = $this->lockConfigOptionsList->getOptions();
+ $this->assertSame(5, count($options));
+
+ $this->assertArrayHasKey(0, $options);
+ $this->assertInstanceOf(SelectConfigOption::class, $options[0]);
+ $this->assertEquals(LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER, $options[0]->getName());
+
+ $this->assertArrayHasKey(1, $options);
+ $this->assertInstanceOf(TextConfigOption::class, $options[1]);
+ $this->assertEquals(LockConfigOptionsList::INPUT_KEY_LOCK_DB_PREFIX, $options[1]->getName());
+
+ $this->assertArrayHasKey(2, $options);
+ $this->assertInstanceOf(TextConfigOption::class, $options[2]);
+ $this->assertEquals(LockConfigOptionsList::INPUT_KEY_LOCK_ZOOKEEPER_HOST, $options[2]->getName());
+
+ $this->assertArrayHasKey(3, $options);
+ $this->assertInstanceOf(TextConfigOption::class, $options[3]);
+ $this->assertEquals(LockConfigOptionsList::INPUT_KEY_LOCK_ZOOKEEPER_PATH, $options[3]->getName());
+
+ $this->assertArrayHasKey(4, $options);
+ $this->assertInstanceOf(TextConfigOption::class, $options[4]);
+ $this->assertEquals(LockConfigOptionsList::INPUT_KEY_LOCK_FILE_PATH, $options[4]->getName());
+ }
+
+ /**
+ * @param array $options
+ * @param array $expectedResult
+ * @dataProvider createConfigDataProvider
+ */
+ public function testCreateConfig(array $options, array $expectedResult)
+ {
+ $this->deploymentConfigMock->expects($this->any())
+ ->method('get')
+ ->willReturnArgument(1);
+ $data = $this->lockConfigOptionsList->createConfig($options, $this->deploymentConfigMock);
+ $this->assertInstanceOf(ConfigData::class, $data);
+ $this->assertTrue($data->isOverrideWhenSave());
+ $this->assertSame($expectedResult, $data->getData());
+ }
+
+ /**
+ * @return array
+ */
+ public function createConfigDataProvider(): array
+ {
+ return [
+ 'Check default values' => [
+ 'options' => [],
+ 'expectedResult' => [
+ 'lock' => [
+ 'provider' => LockBackendFactory::LOCK_DB,
+ 'config' => [
+ 'prefix' => null,
+ ],
+ ],
+ ],
+ ],
+ 'Check default value for cache lock' => [
+ 'options' => [
+ LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_CACHE,
+ ],
+ 'expectedResult' => [
+ 'lock' => [
+ 'provider' => LockBackendFactory::LOCK_CACHE,
+ ],
+ ],
+ ],
+ 'Check default value for zookeeper lock' => [
+ 'options' => [
+ LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_ZOOKEEPER,
+ ],
+ 'expectedResult' => [
+ 'lock' => [
+ 'provider' => LockBackendFactory::LOCK_ZOOKEEPER,
+ 'config' => [
+ 'host' => null,
+ 'path' => ZookeeperLock::DEFAULT_PATH,
+ ],
+ ],
+ ],
+ ],
+ 'Check specific db lock options' => [
+ 'options' => [
+ LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_DB,
+ LockConfigOptionsList::INPUT_KEY_LOCK_DB_PREFIX => 'my_prefix'
+ ],
+ 'expectedResult' => [
+ 'lock' => [
+ 'provider' => LockBackendFactory::LOCK_DB,
+ 'config' => [
+ 'prefix' => 'my_prefix',
+ ],
+ ],
+ ],
+ ],
+ 'Check specific zookeeper lock options' => [
+ 'options' => [
+ LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_ZOOKEEPER,
+ LockConfigOptionsList::INPUT_KEY_LOCK_ZOOKEEPER_HOST => '123.45.67.89:10',
+ LockConfigOptionsList::INPUT_KEY_LOCK_ZOOKEEPER_PATH => '/some/path',
+ ],
+ 'expectedResult' => [
+ 'lock' => [
+ 'provider' => LockBackendFactory::LOCK_ZOOKEEPER,
+ 'config' => [
+ 'host' => '123.45.67.89:10',
+ 'path' => '/some/path',
+ ],
+ ],
+ ],
+ ],
+ 'Check specific file lock options' => [
+ 'options' => [
+ LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_FILE,
+ LockConfigOptionsList::INPUT_KEY_LOCK_FILE_PATH => '/my/path'
+ ],
+ 'expectedResult' => [
+ 'lock' => [
+ 'provider' => LockBackendFactory::LOCK_FILE,
+ 'config' => [
+ 'path' => '/my/path',
+ ],
+ ],
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @param array $options
+ * @param array $expectedResult
+ * @dataProvider validateDataProvider
+ */
+ public function testValidate(array $options, array $expectedResult)
+ {
+ $this->deploymentConfigMock->expects($this->any())
+ ->method('get')
+ ->willReturnArgument(1);
+ $this->assertSame(
+ $expectedResult,
+ $this->lockConfigOptionsList->validate($options, $this->deploymentConfigMock)
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function validateDataProvider(): array
+ {
+ return [
+ 'Wrong lock provider' => [
+ 'options' => [
+ LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER => 'SomeProvider',
+ ],
+ 'expectedResult' => [
+ 'The lock provider SomeProvider does not exist.',
+ ],
+ ],
+ 'Empty host and path for Zookeeper' => [
+ 'options' => [
+ LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_ZOOKEEPER,
+ LockConfigOptionsList::INPUT_KEY_LOCK_ZOOKEEPER_HOST => '',
+ LockConfigOptionsList::INPUT_KEY_LOCK_ZOOKEEPER_PATH => '',
+ ],
+ 'expectedResult' => extension_loaded('zookeeper')
+ ? [
+ 'Zookeeper path needs to be a non-empty string.',
+ 'Zookeeper host is should be set.',
+ ]
+ : [
+ 'php extension Zookeeper is not installed.',
+ 'Zookeeper path needs to be a non-empty string.',
+ 'Zookeeper host is should be set.',
+ ],
+ ],
+ 'Empty path for File lock' => [
+ 'options' => [
+ LockConfigOptionsList::INPUT_KEY_LOCK_PROVIDER => LockBackendFactory::LOCK_FILE,
+ LockConfigOptionsList::INPUT_KEY_LOCK_FILE_PATH => '',
+ ],
+ 'expectedResult' => [
+ 'The path needs to be a non-empty string.',
+ ],
+ ],
+ ];
+ }
+}
diff --git a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsListTest.php b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsListTest.php
index d7f680309c9ef..a85b468cebc92 100644
--- a/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsListTest.php
+++ b/setup/src/Magento/Setup/Test/Unit/Model/ConfigOptionsListTest.php
@@ -7,6 +7,7 @@
namespace Magento\Setup\Test\Unit\Model;
use Magento\Framework\Config\ConfigOptionsListConstants;
+use Magento\Setup\Model\ConfigOptionsList\Lock;
use Magento\Setup\Model\ConfigGenerator;
use Magento\Setup\Model\ConfigOptionsList;
use Magento\Setup\Validator\DbValidator;
@@ -82,7 +83,7 @@ public function testCreateOptions()
$this->generator->expects($this->once())->method('createXFrameConfig')->willReturn($configDataMock);
$this->generator->expects($this->once())->method('createCacheHostsConfig')->willReturn($configDataMock);
- $configData = $this->object->createConfig([], $this->deploymentConfig);
+ $configData = $this->object->createConfig([Lock::INPUT_KEY_LOCK_PROVIDER => 'db'], $this->deploymentConfig);
$this->assertGreaterThanOrEqual(6, count($configData));
}
@@ -96,7 +97,7 @@ public function testCreateOptionsWithOptionalNull()
$this->generator->expects($this->once())->method('createXFrameConfig')->willReturn($configDataMock);
$this->generator->expects($this->once())->method('createCacheHostsConfig')->willReturn($configDataMock);
- $configData = $this->object->createConfig([], $this->deploymentConfig);
+ $configData = $this->object->createConfig([Lock::INPUT_KEY_LOCK_PROVIDER => 'db'], $this->deploymentConfig);
$this->assertGreaterThanOrEqual(6, count($configData));
}
@@ -109,7 +110,8 @@ public function testValidateSuccess()
ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'name',
ConfigOptionsListConstants::INPUT_KEY_DB_HOST => 'host',
ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'user',
- ConfigOptionsListConstants::INPUT_KEY_DB_PASSWORD => 'pass'
+ ConfigOptionsListConstants::INPUT_KEY_DB_PASSWORD => 'pass',
+ Lock::INPUT_KEY_LOCK_PROVIDER => 'db'
];
$this->prepareValidationMocks();
@@ -127,7 +129,8 @@ public function testValidateInvalidSessionHandler()
ConfigOptionsListConstants::INPUT_KEY_DB_NAME => 'name',
ConfigOptionsListConstants::INPUT_KEY_DB_HOST => 'host',
ConfigOptionsListConstants::INPUT_KEY_DB_USER => 'user',
- ConfigOptionsListConstants::INPUT_KEY_DB_PASSWORD => 'pass'
+ ConfigOptionsListConstants::INPUT_KEY_DB_PASSWORD => 'pass',
+ Lock::INPUT_KEY_LOCK_PROVIDER => 'db'
];
$this->prepareValidationMocks();
@@ -141,7 +144,8 @@ public function testValidateEmptyEncryptionKey()
{
$options = [
ConfigOptionsListConstants::INPUT_KEY_SKIP_DB_VALIDATION => true,
- ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => ''
+ ConfigOptionsListConstants::INPUT_KEY_ENCRYPTION_KEY => '',
+ Lock::INPUT_KEY_LOCK_PROVIDER => 'db'
];
$this->assertEquals(
['Invalid encryption key. Encryption key must be 32 character string without any white space.'],
@@ -167,7 +171,8 @@ public function testValidateCacheHosts($hosts, $expectedError)
{
$options = [
ConfigOptionsListConstants::INPUT_KEY_SKIP_DB_VALIDATION => true,
- ConfigOptionsListConstants::INPUT_KEY_CACHE_HOSTS => $hosts
+ ConfigOptionsListConstants::INPUT_KEY_CACHE_HOSTS => $hosts,
+ Lock::INPUT_KEY_LOCK_PROVIDER => 'db'
];
$result = $this->object->validate($options, $this->deploymentConfig);
if ($expectedError) {