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/Braintree/Controller/Paypal/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php
index 14ec829d98024..eb2de7c7b6e39 100644
--- a/app/code/Magento/Braintree/Controller/Paypal/Review.php
+++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php
@@ -13,11 +13,12 @@
use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\App\Action\HttpGetActionInterface;
/**
* Class Review
*/
-class Review extends AbstractAction implements HttpPostActionInterface
+class Review extends AbstractAction implements HttpPostActionInterface, HttpGetActionInterface
{
/**
* @var QuoteUpdater
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/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
index dd09e40ac5b35..1b6756968662f 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
@@ -4,11 +4,6 @@
* See COPYING.txt for license details.
*/
-/**
- * Product attribute add/edit form main tab
- *
- * @author Magento Core Team
- */
namespace Magento\Catalog\Block\Adminhtml\Product\Attribute\Edit\Tab;
use Magento\Backend\Block\Widget\Form\Generic;
@@ -18,6 +13,8 @@
use Magento\Framework\App\ObjectManager;
/**
+ * Product attribute add/edit form main tab
+ *
* @api
* @since 100.0.2
*/
@@ -73,6 +70,7 @@ public function __construct(
* Adding product form elements for editing attribute
*
* @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD)
*/
protected function _prepareForm()
@@ -255,7 +253,7 @@ protected function _prepareForm()
}
/**
- * Initialize form fileds values
+ * Initialize form fields values
*
* @return $this
*/
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/Model/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
index e346c912dccaa..db967052cb7a5 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
@@ -165,19 +165,6 @@ protected function modifyPriceData($object, $data)
/** @var \Magento\Catalog\Model\Product $object */
$data = parent::modifyPriceData($object, $data);
$price = $object->getPrice();
-
- $specialPrice = $object->getSpecialPrice();
- $specialPriceFromDate = $object->getSpecialFromDate();
- $specialPriceToDate = $object->getSpecialToDate();
- $today = time();
-
- if ($specialPrice && ($object->getPrice() > $object->getFinalPrice())) {
- if ($today >= strtotime($specialPriceFromDate) && $today <= strtotime($specialPriceToDate) ||
- $today >= strtotime($specialPriceFromDate) && $specialPriceToDate === null) {
- $price = $specialPrice;
- }
- }
-
foreach ($data as $key => $tierPrice) {
$percentageValue = $this->getPercentage($tierPrice);
if ($percentageValue) {
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/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
new file mode 100644
index 0000000000000..234a7c69913c9
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/Ui/Component/Listing/Columns/Websites.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
index 5af0d71dc246c..494b77724e5b7 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
@@ -3,13 +3,19 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Ui\Component\Listing\Columns;
-use Magento\Framework\View\Element\UiComponentFactory;
+use Magento\Framework\DB\Helper;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
+use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Store\Model\StoreManagerInterface;
/**
+ * Websites listing column component.
+ *
* @api
* @since 100.0.2
*/
@@ -20,6 +26,11 @@ class Websites extends \Magento\Ui\Component\Listing\Columns\Column
*/
const NAME = 'websites';
+ /**
+ * Data for concatenated website names value.
+ */
+ private $websiteNames = 'website_names';
+
/**
* Store manager
*
@@ -27,26 +38,36 @@ class Websites extends \Magento\Ui\Component\Listing\Columns\Column
*/
protected $storeManager;
+ /**
+ * @var \Magento\Framework\DB\Helper
+ */
+ private $resourceHelper;
+
/**
* @param ContextInterface $context
* @param UiComponentFactory $uiComponentFactory
* @param StoreManagerInterface $storeManager
* @param array $components
* @param array $data
+ * @param Helper $resourceHelper
*/
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
StoreManagerInterface $storeManager,
array $components = [],
- array $data = []
+ array $data = [],
+ Helper $resourceHelper = null
) {
parent::__construct($context, $uiComponentFactory, $components, $data);
+ $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$this->storeManager = $storeManager;
+ $this->resourceHelper = $resourceHelper ?: $objectManager->get(Helper::class);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @deprecated 101.0.0
*/
public function prepareDataSource(array $dataSource)
@@ -71,9 +92,10 @@ public function prepareDataSource(array $dataSource)
return $dataSource;
}
-
+
/**
- * Prepare component configuration
+ * Prepare component configuration.
+ *
* @return void
*/
public function prepare()
@@ -83,4 +105,46 @@ public function prepare()
$this->_data['config']['componentDisabled'] = true;
}
}
+
+ /**
+ * Apply sorting.
+ *
+ * @return void
+ */
+ protected function applySorting()
+ {
+ $sorting = $this->getContext()->getRequestParam('sorting');
+ $isSortable = $this->getData('config/sortable');
+ if ($isSortable !== false
+ && !empty($sorting['field'])
+ && !empty($sorting['direction'])
+ && $sorting['field'] === $this->getName()
+ ) {
+ $collection = $this->getContext()->getDataProvider()->getCollection();
+ $collection
+ ->joinField(
+ 'websites_ids',
+ 'catalog_product_website',
+ 'website_id',
+ 'product_id=entity_id',
+ null,
+ 'left'
+ )
+ ->joinTable(
+ 'store_website',
+ 'website_id = websites_ids',
+ ['name'],
+ null,
+ 'left'
+ )
+ ->groupByAttribute('entity_id');
+ $this->resourceHelper->addGroupConcatColumn(
+ $collection->getSelect(),
+ $this->websiteNames,
+ 'name'
+ );
+
+ $collection->getSelect()->order($this->websiteNames . ' ' . $sorting['direction']);
+ }
+ }
}
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/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json
index 5c97261d483d8..805be8a17765f 100644
--- a/app/code/Magento/CatalogAnalytics/composer.json
+++ b/app/code/Magento/CatalogAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-catalog": "*"
+ "magento/module-catalog": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
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 8d57fab843f4c..e7d79c593b8c7 100644
--- a/app/code/Magento/CatalogInventory/etc/di.xml
+++ b/app/code/Magento/CatalogInventory/etc/di.xml
@@ -44,7 +44,7 @@
- Magento\CatalogInventory\Model\ResourceModel\Stock\Proxy
+ Magento\CatalogInventory\Model\ResourceModel\Stock\Item\Proxy
@@ -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 @@
*/
-->
-
+
-
+
-
+
getCollection($path)
->setCollectDirs(true)
->setCollectFiles(false)
- ->setCollectRecursively(false);
+ ->setCollectRecursively(false)
+ ->setOrder('basename', \Magento\Framework\Data\Collection\Filesystem::SORT_ORDER_ASC);
$conditions = $this->getConditionsForExcludeDirs();
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml
index 05b7dfeeb3953..03edc69e6d625 100644
--- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml
@@ -16,7 +16,6 @@
-
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/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php
index 309f08a54aab6..7bec1e3601461 100644
--- a/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php
+++ b/app/code/Magento/Cms/Test/Unit/Model/Wysiwyg/Images/StorageTest.php
@@ -417,6 +417,10 @@ protected function generalTestGetDirsCollection($path, $collectionArray = [], $e
->method('setCollectRecursively')
->with(false)
->willReturnSelf();
+ $storageCollectionMock->expects($this->once())
+ ->method('setOrder')
+ ->with('basename', \Magento\Framework\Data\Collection\Filesystem::SORT_ORDER_ASC)
+ ->willReturnSelf();
$storageCollectionMock->expects($this->once())
->method('getIterator')
->willReturn(new \ArrayIterator($collectionArray));
diff --git a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_listing.xml b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_listing.xml
index 9f886f6f1345e..793fc7d26cb4a 100644
--- a/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_listing.xml
+++ b/app/code/Magento/Cms/view/adminhtml/ui_component/cms_block_listing.xml
@@ -146,7 +146,6 @@
true
- true
text
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/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml
index 06134fcbf09d8..c3ffe988b00d7 100644
--- a/app/code/Magento/ConfigurableProduct/etc/di.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/di.xml
@@ -248,4 +248,11 @@
+
+
+
+ - configurable
+
+
+
diff --git a/app/code/Magento/Customer/Block/Address/Grid.php b/app/code/Magento/Customer/Block/Address/Grid.php
index de6767a0ef92a..963efc648d94b 100644
--- a/app/code/Magento/Customer/Block/Address/Grid.php
+++ b/app/code/Magento/Customer/Block/Address/Grid.php
@@ -1,9 +1,10 @@
addressCollectionFactory->create();
- $collection->setOrder('entity_id', 'desc')
- ->setCustomerFilter([$this->getCustomer()->getId()]);
+ $collection->setOrder('entity_id', 'desc');
+ $collection->addFieldToFilter(
+ 'entity_id',
+ ['nin' => [$this->getDefaultBilling(), $this->getDefaultShipping()]]
+ );
+ $collection->setCustomerFilter([$this->getCustomer()->getId()]);
$this->addressCollection = $collection;
}
return $this->addressCollection;
diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php
index 9caa2988c5a94..4f129f05aa82c 100644
--- a/app/code/Magento/Customer/Model/Visitor.php
+++ b/app/code/Magento/Customer/Model/Visitor.php
@@ -14,6 +14,7 @@
*
* @package Magento\Customer\Model
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class Visitor extends \Magento\Framework\Model\AbstractModel
{
@@ -168,10 +169,6 @@ public function initByRequest($observer)
$this->setLastVisitAt((new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT));
- // prevent saving Visitor for safe methods, e.g. GET request
- if ($this->requestSafety->isSafeMethod()) {
- return $this;
- }
if (!$this->getId()) {
$this->setSessionId($this->session->getSessionId());
$this->save();
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/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
index 54644819e852b..06c23a2864984 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
@@ -46,6 +46,19 @@
0
US_Address_TX
+
+ 1
+ true
+ true
+ John.Doe@example.com
+ LoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsum
+ Doe
+ John Doe
+ pwdTest123!
+ 0
+ 0
+ US_Address_TX
+
1
John.Doe@example.com
@@ -190,4 +203,4 @@
0
0
-
\ No newline at end of file
+
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/Customer/Test/Unit/Block/Address/GridTest.php b/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php
index 31bcc37612302..47f96b132b3db 100644
--- a/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php
@@ -81,7 +81,7 @@ protected function setUp()
public function testGetChildHtml()
{
$customerId = 1;
-
+ $outputString = 'OutputString';
/** @var \Magento\Framework\View\Element\BlockInterface|\PHPUnit_Framework_MockObject_MockObject $block */
$block = $this->getMockBuilder(\Magento\Framework\View\Element\BlockInterface::class)
->setMethods(['setCollection'])
@@ -93,7 +93,7 @@ public function testGetChildHtml()
/** @var \PHPUnit_Framework_MockObject_MockObject */
$addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class)
->disableOriginalConstructor()
- ->setMethods(['setOrder', 'setCustomerFilter', 'load'])
+ ->setMethods(['setOrder', 'setCustomerFilter', 'load','addFieldToFilter'])
->getMock();
$layout->expects($this->atLeastOnce())->method('getChildName')->with('NameInLayout', 'pager')
@@ -108,12 +108,13 @@ public function testGetChildHtml()
->willReturnSelf();
$addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->with([$customerId])
->willReturnSelf();
+ $addressCollection->expects(static::any())->method('addFieldToFilter')->willReturnSelf();
$this->addressCollectionFactory->expects($this->atLeastOnce())->method('create')
->willReturn($addressCollection);
$block->expects($this->atLeastOnce())->method('setCollection')->with($addressCollection)->willReturnSelf();
$this->gridBlock->setNameInLayout('NameInLayout');
$this->gridBlock->setLayout($layout);
- $this->assertEquals('OutputString', $this->gridBlock->getChildHtml('pager'));
+ $this->assertEquals($outputString, $this->gridBlock->getChildHtml('pager'));
}
/**
@@ -137,7 +138,7 @@ public function testGetAdditionalAddresses()
/** @var \PHPUnit_Framework_MockObject_MockObject */
$addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class)
->disableOriginalConstructor()
- ->setMethods(['setOrder', 'setCustomerFilter', 'load', 'getIterator'])
+ ->setMethods(['setOrder', 'setCustomerFilter', 'load', 'getIterator','addFieldToFilter'])
->getMock();
$addressDataModel = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\AddressInterface::class);
$address = $this->getMockBuilder(\Magento\Customer\Model\Address::class)
@@ -157,6 +158,7 @@ public function testGetAdditionalAddresses()
->willReturnSelf();
$addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->with([$customerId])
->willReturnSelf();
+ $addressCollection->expects(static::any())->method('addFieldToFilter')->willReturnSelf();
$addressCollection->expects($this->atLeastOnce())->method('getIterator')
->willReturn(new \ArrayIterator($collection));
$this->addressCollectionFactory->expects($this->atLeastOnce())->method('create')
diff --git a/app/code/Magento/CustomerAnalytics/composer.json b/app/code/Magento/CustomerAnalytics/composer.json
index 7dec4279ee280..3840c534b1964 100644
--- a/app/code/Magento/CustomerAnalytics/composer.json
+++ b/app/code/Magento/CustomerAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-customer": "*"
+ "magento/module-customer": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
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/Directory/Setup/Patch/Data/AddDataForIndia.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForIndia.php
index 69d500960d3f0..47f4fb0a6c7f3 100644
--- a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForIndia.php
+++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForIndia.php
@@ -13,8 +13,7 @@
use Magento\Framework\Setup\Patch\PatchVersionInterface;
/**
- * Class AddDataForIndia
- * @package Magento\Directory\Setup\Patch\Data
+ * Add Regions for India.
*/
class AddDataForIndia implements DataPatchInterface, PatchVersionInterface
{
@@ -29,7 +28,7 @@ class AddDataForIndia implements DataPatchInterface, PatchVersionInterface
private $dataInstallerFactory;
/**
- * AddDataForCroatia constructor.
+ * AddDataForIndia constructor.
*
* @param ModuleDataSetupInterface $moduleDataSetup
* @param \Magento\Directory\Setup\DataInstallerFactory $dataInstallerFactory
@@ -43,7 +42,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function apply()
{
@@ -103,7 +102,7 @@ private function getDataForIndia()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getDependencies()
{
@@ -113,7 +112,7 @@ public static function getDependencies()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getVersion()
{
@@ -121,7 +120,7 @@ public static function getVersion()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAliases()
{
diff --git a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForMexico.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForMexico.php
new file mode 100644
index 0000000000000..32bdf90800d6b
--- /dev/null
+++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForMexico.php
@@ -0,0 +1,127 @@
+moduleDataSetup = $moduleDataSetup;
+ $this->dataInstallerFactory = $dataInstallerFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ /** @var DataInstaller $dataInstaller */
+ $dataInstaller = $this->dataInstallerFactory->create();
+ $dataInstaller->addCountryRegions(
+ $this->moduleDataSetup->getConnection(),
+ $this->getDataForMexico()
+ );
+ }
+
+ /**
+ * Mexican states data.
+ *
+ * @return array
+ */
+ private function getDataForMexico()
+ {
+ return [
+ ['MX', 'AGU', 'Aguascalientes'],
+ ['MX', 'BCN', 'Baja California'],
+ ['MX', 'BCS', 'Baja California Sur'],
+ ['MX', 'CAM', 'Campeche'],
+ ['MX', 'CHP', 'Chiapas'],
+ ['MX', 'CHH', 'Chihuahua'],
+ ['MX', 'CMX', 'Ciudad de México'],
+ ['MX', 'COA', 'Coahuila'],
+ ['MX', 'COL', 'Colima'],
+ ['MX', 'DUR', 'Durango'],
+ ['MX', 'MEX', 'Estado de México'],
+ ['MX', 'GUA', 'Guanajuato'],
+ ['MX', 'GRO', 'Guerrero'],
+ ['MX', 'HID', 'Hidalgo'],
+ ['MX', 'JAL', 'Jalisco'],
+ ['MX', 'MIC', 'Michoacán'],
+ ['MX', 'MOR', 'Morelos'],
+ ['MX', 'NAY', 'Nayarit'],
+ ['MX', 'NLE', 'Nuevo León'],
+ ['MX', 'OAX', 'Oaxaca'],
+ ['MX', 'PUE', 'Puebla'],
+ ['MX', 'QUE', 'Querétaro'],
+ ['MX', 'ROO', 'Quintana Roo'],
+ ['MX', 'SLP', 'San Luis Potosí'],
+ ['MX', 'SIN', 'Sinaloa'],
+ ['MX', 'SON', 'Sonora'],
+ ['MX', 'TAB', 'Tabasco'],
+ ['MX', 'TAM', 'Tamaulipas'],
+ ['MX', 'TLA', 'Tlaxcala'],
+ ['MX', 'VER', 'Veracruz'],
+ ['MX', 'YUC', 'Yucatán'],
+ ['MX', 'ZAC', 'Zacatecas']
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [
+ InitializeDirectoryData::class,
+ AddDataForAustralia::class,
+ AddDataForCroatia::class,
+ AddDataForIndia::class,
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getVersion()
+ {
+ return '2.0.4';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
index 40ef6975fad8b..8da1920f9a444 100644
--- a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
@@ -10,8 +10,10 @@ type Query {
type Currency {
base_currency_code: String
base_currency_symbol: String
- default_display_currecy_code: String
- default_display_currecy_symbol: String
+ default_display_currecy_code: String @deprecated(reason: "Symbol was missed. Use `default_display_currency_code`.")
+ default_display_currency_code: String
+ default_display_currecy_symbol: String @deprecated(reason: "Symbol was missed. Use `default_display_currency_symbol`.")
+ default_display_currency_symbol: String
available_currency_codes: [String]
exchange_rates: [ExchangeRate]
}
diff --git a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Main/AbstractMain.php b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Main/AbstractMain.php
index c5a18a3de99c6..be9d2700664c7 100644
--- a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Main/AbstractMain.php
+++ b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Main/AbstractMain.php
@@ -4,15 +4,13 @@
* See COPYING.txt for license details.
*/
-/**
- * Product attribute add/edit form main tab
- *
- * @author Magento Core Team
- */
namespace Magento\Eav\Block\Adminhtml\Attribute\Edit\Main;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
+/**
+ * Product attribute add/edit form main tab
+ */
abstract class AbstractMain extends \Magento\Backend\Block\Widget\Form\Generic
{
/**
@@ -110,7 +108,6 @@ protected function _prepareForm()
/** @var \Magento\Framework\Data\Form $form */
$form = $this->_formFactory->create(
-
['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
);
@@ -280,10 +277,11 @@ protected function _initFormValues()
}
/**
- * Processing block html after rendering
+ * Processing block html after rendering.
+ *
* Adding js block to the end of this block
*
- * @param string $html
+ * @param string $html
* @return string
*/
protected function _afterToHtml($html)
diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
index d0a5e8de53ae9..1fd71e446e6bb 100644
--- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
+++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
@@ -1683,14 +1683,16 @@ public function saveAttribute(DataObject $object, $attributeCode)
$connection->beginTransaction();
try {
- $select = $connection->select()->from($table, 'value_id')->where($where);
- $origValueId = $connection->fetchOne($select);
+ $select = $connection->select()->from($table, ['value_id', 'value'])->where($where);
+ $origRow = $connection->fetchRow($select);
+ $origValueId = $origRow['value_id'] ?? false;
+ $origValue = $origRow['value'] ?? null;
if ($origValueId === false && $newValue !== null) {
$this->_insertAttribute($object, $attribute, $newValue);
} elseif ($origValueId !== false && $newValue !== null) {
$this->_updateAttribute($object, $attribute, $origValueId, $newValue);
- } elseif ($origValueId !== false && $newValue === null) {
+ } elseif ($origValueId !== false && $newValue === null && $origValue !== null) {
$connection->delete($table, $where);
}
$this->_processAttributeValues();
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
index 0b6ac2b998de7..2e55964560588 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
@@ -3,11 +3,16 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Eav\Model\Entity\Attribute;
+use Magento\Eav\Api\Data\AttributeGroupExtensionInterface;
use Magento\Framework\Api\AttributeValueFactory;
+use Magento\Framework\Exception\LocalizedException;
/**
+ * Entity attribute group model
+ *
* @api
* @method int getSortOrder()
* @method \Magento\Eav\Model\Entity\Attribute\Group setSortOrder(int $value)
@@ -27,6 +32,11 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements
*/
private $translitFilter;
+ /**
+ * @var array
+ */
+ private $reservedSystemNames = [];
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -35,7 +45,8 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements
* @param \Magento\Framework\Filter\Translit $translitFilter
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
- * @param array $data
+ * @param array $data (optional)
+ * @param array $reservedSystemNames (optional)
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -45,7 +56,8 @@ public function __construct(
\Magento\Framework\Filter\Translit $translitFilter,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ array $reservedSystemNames = []
) {
parent::__construct(
$context,
@@ -56,6 +68,7 @@ public function __construct(
$resourceCollection,
$data
);
+ $this->reservedSystemNames = $reservedSystemNames;
$this->translitFilter = $translitFilter;
}
@@ -74,6 +87,7 @@ protected function _construct()
* Checks if current attribute group exists
*
* @return bool
+ * @throws LocalizedException
* @codeCoverageIgnore
*/
public function itemExists()
@@ -85,6 +99,7 @@ public function itemExists()
* Delete groups
*
* @return $this
+ * @throws LocalizedException
* @codeCoverageIgnore
*/
public function deleteGroups()
@@ -110,9 +125,10 @@ public function beforeSave()
),
'-'
);
- if (empty($attributeGroupCode)) {
+ $isReservedSystemName = in_array(strtolower($attributeGroupCode), $this->reservedSystemNames);
+ if (empty($attributeGroupCode) || $isReservedSystemName) {
// in the following code md5 is not used for security purposes
- $attributeGroupCode = md5($groupName);
+ $attributeGroupCode = md5(strtolower($groupName));
}
$this->setAttributeGroupCode($attributeGroupCode);
}
@@ -121,7 +137,8 @@ public function beforeSave()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @codeCoverageIgnoreStart
*/
public function getAttributeGroupId()
@@ -130,7 +147,7 @@ public function getAttributeGroupId()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAttributeGroupName()
{
@@ -138,7 +155,7 @@ public function getAttributeGroupName()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAttributeSetId()
{
@@ -146,7 +163,7 @@ public function getAttributeSetId()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeGroupId($attributeGroupId)
{
@@ -154,7 +171,7 @@ public function setAttributeGroupId($attributeGroupId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeGroupName($attributeGroupName)
{
@@ -162,7 +179,7 @@ public function setAttributeGroupName($attributeGroupName)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeSetId($attributeSetId)
{
@@ -170,9 +187,9 @@ public function setAttributeSetId($attributeSetId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
- * @return \Magento\Eav\Api\Data\AttributeGroupExtensionInterface|null
+ * @return AttributeGroupExtensionInterface|null
*/
public function getExtensionAttributes()
{
@@ -180,14 +197,13 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
- * @param \Magento\Eav\Api\Data\AttributeGroupExtensionInterface $extensionAttributes
+ * @param AttributeGroupExtensionInterface $extensionAttributes
* @return $this
*/
- public function setExtensionAttributes(
- \Magento\Eav\Api\Data\AttributeGroupExtensionInterface $extensionAttributes
- ) {
+ public function setExtensionAttributes(AttributeGroupExtensionInterface $extensionAttributes)
+ {
return $this->_setExtensionAttributes($extensionAttributes);
}
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php b/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
index 56188ab997b76..36ad026029056 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
@@ -73,7 +73,7 @@ public function getOptionText($value)
}
}
// End
- if (isset($options[$value])) {
+ if (is_scalar($value) && isset($options[$value])) {
return $options[$value];
}
return false;
diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php
index 6e81ddc36e9c9..29f9163a6e91d 100644
--- a/app/code/Magento/Eav/Setup/EavSetup.php
+++ b/app/code/Magento/Eav/Setup/EavSetup.php
@@ -10,12 +10,12 @@
use Magento\Eav\Model\Entity\Setup\PropertyMapperInterface;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory;
use Magento\Framework\App\CacheInterface;
-use Magento\Framework\App\ObjectManager;
-use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Setup\ModuleDataSetupInterface;
/**
+ * Base eav setup class.
+ *
* @api
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -101,7 +101,8 @@ public function __construct(
}
/**
- * Gets setup model
+ * Gets setup model.
+ *
* @deprecated
* @return ModuleDataSetupInterface
*/
@@ -568,6 +569,8 @@ public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder = nul
}
/**
+ * Convert group name to attribute group code.
+ *
* @param string $groupName
* @return string
* @since 100.1.0
@@ -1063,7 +1066,7 @@ private function _updateAttributeAdditionalData($entityTypeId, $id, $field, $val
return $this;
}
}
-
+
$attributeId = $this->getAttributeId($entityTypeId, $id);
if (false === $attributeId) {
throw new LocalizedException(__('Attribute with ID: "%1" does not exist', $id));
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
index d4c91e98d9608..1584b922abaa9 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
@@ -40,6 +40,7 @@ protected function setUp()
'resource' => $this->resourceMock,
'translitFilter' => $translitFilter,
'context' => $contextMock,
+ 'reservedSystemNames' => ['configurable'],
];
$objectManager = new ObjectManager($this);
$this->model = $objectManager->getObject(
@@ -67,6 +68,8 @@ public function attributeGroupCodeDataProvider()
{
return [
['General Group', 'general-group'],
+ ['configurable', md5('configurable')],
+ ['configurAble', md5('configurable')],
['///', md5('///')],
];
}
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/BooleanTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/BooleanTest.php
index ee972c27aa8a2..8cf5df877a6eb 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/BooleanTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/BooleanTest.php
@@ -101,13 +101,13 @@ public function addValueSortToCollectionDataProvider()
'expectedJoinCondition' => [
0 => [
'requisites' => ['code_t1' => "table"],
- 'condition' =>
- "e.entity_id=code_t1.entity_id AND code_t1.attribute_id='123' AND code_t1.store_id='0'",
+ 'condition' => "e.entity_id=code_t1.entity_id AND code_t1.attribute_id='123'"
+ . " AND code_t1.store_id='0'",
],
1 => [
'requisites' => ['code_t2' => "table"],
- 'condition' =>
- "e.entity_id=code_t2.entity_id AND code_t2.attribute_id='123' AND code_t2.store_id='12'",
+ 'condition' => "e.entity_id=code_t2.entity_id AND code_t2.attribute_id='123'"
+ . " AND code_t2.store_id='12'",
],
],
'expectedOrder' => 'IF(code_t2.value_id > 0, code_t2.value, code_t1.value) ASC',
@@ -118,13 +118,13 @@ public function addValueSortToCollectionDataProvider()
'expectedJoinCondition' => [
0 => [
'requisites' => ['code_t1' => "table"],
- 'condition' =>
- "e.entity_id=code_t1.entity_id AND code_t1.attribute_id='123' AND code_t1.store_id='0'",
+ 'condition' => "e.entity_id=code_t1.entity_id AND code_t1.attribute_id='123'"
+ . " AND code_t1.store_id='0'",
],
1 => [
'requisites' => ['code_t2' => "table"],
- 'condition' =>
- "e.entity_id=code_t2.entity_id AND code_t2.attribute_id='123' AND code_t2.store_id='12'",
+ 'condition' => "e.entity_id=code_t2.entity_id AND code_t2.attribute_id='123'"
+ . " AND code_t2.store_id='12'",
],
],
'expectedOrder' => 'IF(code_t2.value_id > 0, code_t2.value, code_t1.value) DESC',
@@ -135,8 +135,8 @@ public function addValueSortToCollectionDataProvider()
'expectedJoinCondition' => [
0 => [
'requisites' => ['code_t' => "table"],
- 'condition' =>
- "e.entity_id=code_t.entity_id AND code_t.attribute_id='123' AND code_t.store_id='0'",
+ 'condition' => "e.entity_id=code_t.entity_id AND code_t.attribute_id='123'"
+ . " AND code_t.store_id='0'",
],
],
'expectedOrder' => 'code_t.value DESC',
@@ -147,8 +147,8 @@ public function addValueSortToCollectionDataProvider()
'expectedJoinCondition' => [
0 => [
'requisites' => ['code_t' => "table"],
- 'condition' =>
- "e.entity_id=code_t.entity_id AND code_t.attribute_id='123' AND code_t.store_id='0'",
+ 'condition' => "e.entity_id=code_t.entity_id AND code_t.attribute_id='123'"
+ . " AND code_t.store_id='0'",
],
],
'expectedOrder' => 'code_t.value ASC',
diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml
index a4c89dcfab2af..db6f9b0a64f9f 100644
--- a/app/code/Magento/Eav/etc/di.xml
+++ b/app/code/Magento/Eav/etc/di.xml
@@ -210,4 +210,3 @@
-
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
index 0c03a9df18dc8..eeb48f805bccf 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
@@ -22,13 +22,15 @@ public function build(
array $queryResult,
DataProviderInterface $dataProvider
) {
+ $buckets = $queryResult['aggregations'][$bucket->getName()]['buckets'] ?? [];
$values = [];
- foreach ($queryResult['aggregations'][$bucket->getName()]['buckets'] as $resultBucket) {
+ foreach ($buckets as $resultBucket) {
$values[$resultBucket['key']] = [
'value' => $resultBucket['key'],
'count' => $resultBucket['doc_count'],
];
}
+
return $values;
}
}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
index aaa9d8a88382f..afd383c13421f 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
@@ -5,6 +5,10 @@
*/
namespace Magento\Elasticsearch\SearchAdapter\Query\Builder;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Search\Request\Query\BoolExpression;
use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
@@ -26,20 +30,49 @@ class Match implements QueryInterface
private $fieldMapper;
/**
+ * @deprecated
+ * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
* @var PreprocessorInterface[]
*/
protected $preprocessorContainer;
+ /**
+ * @var AttributeProvider
+ */
+ private $attributeProvider;
+
+ /**
+ * @var TypeResolver
+ */
+ private $fieldTypeResolver;
+
+ /**
+ * @var ValueTransformerPool
+ */
+ private $valueTransformerPool;
+
/**
* @param FieldMapperInterface $fieldMapper
* @param PreprocessorInterface[] $preprocessorContainer
+ * @param AttributeProvider|null $attributeProvider
+ * @param TypeResolver|null $fieldTypeResolver
+ * @param ValueTransformerPool|null $valueTransformerPool
*/
public function __construct(
FieldMapperInterface $fieldMapper,
- array $preprocessorContainer
+ array $preprocessorContainer,
+ AttributeProvider $attributeProvider = null,
+ TypeResolver $fieldTypeResolver = null,
+ ValueTransformerPool $valueTransformerPool = null
) {
$this->fieldMapper = $fieldMapper;
$this->preprocessorContainer = $preprocessorContainer;
+ $this->attributeProvider = $attributeProvider ?? ObjectManager::getInstance()
+ ->get(AttributeProvider::class);
+ $this->fieldTypeResolver = $fieldTypeResolver ?? ObjectManager::getInstance()
+ ->get(TypeResolver::class);
+ $this->valueTransformerPool = $valueTransformerPool ?? ObjectManager::getInstance()
+ ->get(ValueTransformerPool::class);
}
/**
@@ -72,10 +105,6 @@ public function build(array $selectQuery, RequestQueryInterface $requestQuery, $
*/
protected function prepareQuery($queryValue, $conditionType)
{
- $queryValue = $this->escape($queryValue);
- foreach ($this->preprocessorContainer as $preprocessor) {
- $queryValue = $preprocessor->process($queryValue);
- }
$condition = $conditionType === BoolExpression::QUERY_CONDITION_NOT ?
self::QUERY_CONDITION_MUST_NOT : $conditionType;
return [
@@ -104,10 +133,24 @@ protected function buildQueries(array $matches, array $queryValue)
// Checking for quoted phrase \"phrase test\", trim escaped surrounding quotes if found
$count = 0;
- $value = preg_replace('#^\\\\"(.*)\\\\"$#m', '$1', $queryValue['value'], -1, $count);
+ $value = preg_replace('#^"(.*)"$#m', '$1', $queryValue['value'], -1, $count);
$condition = ($count) ? 'match_phrase' : 'match';
+ $transformedTypes = [];
foreach ($matches as $match) {
+ $attributeAdapter = $this->attributeProvider->getByAttributeCode($match['field']);
+ $fieldType = $this->fieldTypeResolver->getFieldType($attributeAdapter);
+ $valueTransformer = $this->valueTransformerPool->get($fieldType ?? 'text');
+ $valueTransformerHash = \spl_object_hash($valueTransformer);
+ if (!isset($transformedTypes[$valueTransformerHash])) {
+ $transformedTypes[$valueTransformerHash] = $valueTransformer->transform($value);
+ }
+ $transformedValue = $transformedTypes[$valueTransformerHash];
+ if (null === $transformedValue) {
+ //Value is incompatible with this field type.
+ continue;
+ }
+
$resolvedField = $this->fieldMapper->getFieldName(
$match['field'],
['type' => FieldMapperInterface::TYPE_QUERY]
@@ -117,8 +160,8 @@ protected function buildQueries(array $matches, array $queryValue)
'body' => [
$condition => [
$resolvedField => [
- 'query' => $value,
- 'boost' => isset($match['boost']) ? $match['boost'] : 1,
+ 'query' => $transformedValue,
+ 'boost' => $match['boost'] ?? 1,
],
],
],
@@ -131,16 +174,13 @@ protected function buildQueries(array $matches, array $queryValue)
/**
* Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
*
- * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error.
- * https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs.
- *
+ * @deprecated
+ * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
* @param string $value
* @return string
*/
protected function escape($value)
{
- $value = preg_replace('/@+|[@+-]+$/', '', $value);
-
$pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
$replace = '\\\$1';
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php
new file mode 100644
index 0000000000000..49eca6e9d82a6
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php
@@ -0,0 +1,44 @@
+dateFieldType = $dateFieldType;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function transform(string $value): ?string
+ {
+ try {
+ $formattedDate = $this->dateFieldType->formatDate(null, $value);
+ } catch (\Exception $e) {
+ $formattedDate = null;
+ }
+
+ return $formattedDate;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php
new file mode 100644
index 0000000000000..5e330076d3df7
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php
@@ -0,0 +1,24 @@
+preprocessors = $preprocessors;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function transform(string $value): string
+ {
+ $value = $this->escape($value);
+ foreach ($this->preprocessors as $preprocessor) {
+ $value = $preprocessor->process($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
+ *
+ * @param string $value
+ * @return string
+ */
+ private function escape(string $value): string
+ {
+ $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
+ $replace = '\\\$1';
+
+ return preg_replace($pattern, $replace, $value);
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php
new file mode 100644
index 0000000000000..c84ddc69cc7a8
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php
@@ -0,0 +1,22 @@
+transformers = $valueTransformers;
+ }
+
+ /**
+ * Get value transformer related to field type.
+ *
+ * @param string $fieldType
+ * @return ValueTransformerInterface
+ */
+ public function get(string $fieldType): ValueTransformerInterface
+ {
+ return $this->transformers[$fieldType] ?? $this->transformers['default'];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
index 8114feb09d35d..d0ffc6debcd8a 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
@@ -5,14 +5,29 @@
*/
namespace Magento\Elasticsearch\Test\Unit\SearchAdapter\Query\Builder;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver;
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
use Magento\Framework\Search\Request\Query\Match as MatchRequestQuery;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use PHPUnit_Framework_MockObject_MockObject as MockObject;
+use PHPUnit\Framework\MockObject\MockObject as MockObject;
class MatchTest extends \PHPUnit\Framework\TestCase
{
+ /**
+ * @var AttributeProvider|MockObject
+ */
+ private $attributeProvider;
+
+ /**
+ * @var TypeResolver|MockObject
+ */
+ private $fieldTypeResolver;
+
/**
* @var MatchQueryBuilder
*/
@@ -23,46 +38,63 @@ class MatchTest extends \PHPUnit\Framework\TestCase
*/
protected function setUp()
{
+ $this->attributeProvider = $this->createMock(AttributeProvider::class);
+ $this->fieldTypeResolver = $this->createMock(TypeResolver::class);
+
+ $valueTransformerPoolMock = $this->createMock(ValueTransformerPool::class);
+ $valueTransformerMock = $this->createMock(ValueTransformerInterface::class);
+ $valueTransformerPoolMock->method('get')
+ ->willReturn($valueTransformerMock);
+ $valueTransformerMock->method('transform')
+ ->willReturnArgument(0);
+
$this->matchQueryBuilder = (new ObjectManager($this))->getObject(
MatchQueryBuilder::class,
[
'fieldMapper' => $this->getFieldMapper(),
'preprocessorContainer' => [],
+ 'attributeProvider' => $this->attributeProvider,
+ 'fieldTypeResolver' => $this->fieldTypeResolver,
+ 'valueTransformerPool' => $valueTransformerPoolMock,
]
);
}
/**
* Tests that method constructs a correct select query.
- * @see MatchQueryBuilder::build
- *
- * @dataProvider queryValuesInvariantsProvider
*
- * @param string $rawQueryValue
- * @param string $errorMessage
+ * @see MatchQueryBuilder::build
*/
- public function testBuild($rawQueryValue, $errorMessage)
+ public function testBuild()
{
- $this->assertSelectQuery(
- $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not'),
- $errorMessage
- );
- }
+ $attributeAdapter = $this->createMock(AttributeAdapter::class);
+ $this->attributeProvider->expects($this->once())
+ ->method('getByAttributeCode')
+ ->with('some_field')
+ ->willReturn($attributeAdapter);
+ $this->fieldTypeResolver->expects($this->once())
+ ->method('getFieldType')
+ ->with($attributeAdapter)
+ ->willReturn('text');
+
+ $rawQueryValue = 'query_value';
+ $selectQuery = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not');
- /**
- * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs.
- *
- * @return array
- */
- public function queryValuesInvariantsProvider()
- {
- return [
- ['query_value', 'Select query field must match simple raw query value.'],
- ['query_value+', 'Specifying a trailing plus sign causes InnoDB to report a syntax error.'],
- ['query_value-', 'Specifying a trailing minus sign causes InnoDB to report a syntax error.'],
- ['query_@value', 'The @ symbol is reserved for use by the @distance proximity search operator.'],
- ['query_value+@', 'The @ symbol is reserved for use by the @distance proximity search operator.'],
+ $expectedSelectQuery = [
+ 'bool' => [
+ 'must_not' => [
+ [
+ 'match' => [
+ 'some_field' => [
+ 'query' => $rawQueryValue,
+ 'boost' => 43,
+ ],
+ ],
+ ],
+ ],
+ ],
];
+ $this->assertEquals($expectedSelectQuery, $selectQuery);
}
/**
@@ -76,6 +108,16 @@ public function queryValuesInvariantsProvider()
*/
public function testBuildMatchQuery($rawQueryValue, $queryValue, $match)
{
+ $attributeAdapter = $this->createMock(AttributeAdapter::class);
+ $this->attributeProvider->expects($this->once())
+ ->method('getByAttributeCode')
+ ->with('some_field')
+ ->willReturn($attributeAdapter);
+ $this->fieldTypeResolver->expects($this->once())
+ ->method('getFieldType')
+ ->with($attributeAdapter)
+ ->willReturn('text');
+
$query = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'should');
$expectedSelectQuery = [
@@ -111,30 +153,6 @@ public function matchProvider()
];
}
- /**
- * @param array $selectQuery
- * @param string $errorMessage
- */
- private function assertSelectQuery($selectQuery, $errorMessage)
- {
- $expectedSelectQuery = [
- 'bool' => [
- 'must_not' => [
- [
- 'match' => [
- 'some_field' => [
- 'query' => 'query_value',
- 'boost' => 43,
- ],
- ],
- ],
- ],
- ],
- ];
-
- $this->assertEquals($expectedSelectQuery, $selectQuery, $errorMessage);
- }
-
/**
* Gets fieldMapper mock object.
*
diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml
index 4a354a2ea528f..9732ae8226431 100644
--- a/app/code/Magento/Elasticsearch/etc/di.xml
+++ b/app/code/Magento/Elasticsearch/etc/di.xml
@@ -540,4 +540,22 @@
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\DateTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\FloatTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\IntegerTransformer
+
+
+
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Query\Preprocessor\Stopwords
+ - Magento\Search\Adapter\Query\Preprocessor\Synonyms
+
+
+
diff --git a/app/code/Magento/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/GiftMessage/Model/CompositeConfigProvider.php b/app/code/Magento/GiftMessage/Model/CompositeConfigProvider.php
index 0fdce9e9090ac..cb370c27863ca 100644
--- a/app/code/Magento/GiftMessage/Model/CompositeConfigProvider.php
+++ b/app/code/Magento/GiftMessage/Model/CompositeConfigProvider.php
@@ -7,6 +7,9 @@
use Magento\Checkout\Model\ConfigProviderInterface;
+/**
+ * Class CompositeConfigProvider
+ */
class CompositeConfigProvider implements ConfigProviderInterface
{
/**
@@ -18,13 +21,13 @@ class CompositeConfigProvider implements ConfigProviderInterface
* @param ConfigProviderInterface[] $configProviders
*/
public function __construct(
- array $configProviders
+ array $configProviders = []
) {
$this->configProviders = $configProviders;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getConfig()
{
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/QuoteAnalytics/composer.json b/app/code/Magento/QuoteAnalytics/composer.json
index 90dae1ec2adca..706bed674b4a9 100644
--- a/app/code/Magento/QuoteAnalytics/composer.json
+++ b/app/code/Magento/QuoteAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-quote": "*"
+ "magento/module-quote": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
index 1b32866ed883c..6868ce3f7f1ff 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
@@ -67,6 +67,11 @@ public function execute(Quote $cart, array $cartItemData): void
{
$sku = $this->extractSku($cartItemData);
$qty = $this->extractQty($cartItemData);
+ if ($qty <= 0) {
+ throw new GraphQlInputException(
+ __('Please enter a number greater than 0 in this field.')
+ );
+ }
$customizableOptions = $this->extractCustomizableOptions($cartItemData);
try {
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
index a93c8032c996a..d1dcb4a48a76b 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
@@ -60,12 +60,12 @@ public function __construct(
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
- throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
+ throw new GraphQlInputException(__('Required parameter "cart_id" is missing.'));
}
$maskedCartId = $args['input']['cart_id'];
if (!isset($args['input']['payment_method']['code']) || empty($args['input']['payment_method']['code'])) {
- throw new GraphQlInputException(__('Required parameter "payment_method" is missing'));
+ throw new GraphQlInputException(__('Required parameter "code" for "payment_method" is missing.'));
}
$paymentMethodCode = $args['input']['payment_method']['code'];
diff --git a/app/code/Magento/ReviewAnalytics/composer.json b/app/code/Magento/ReviewAnalytics/composer.json
index 73f534451580c..a82d4328ca159 100644
--- a/app/code/Magento/ReviewAnalytics/composer.json
+++ b/app/code/Magento/ReviewAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-review": "*"
+ "magento/module-review": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
index d15c218a60b47..6b87c1fe39d8b 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
@@ -6,6 +6,7 @@
namespace Magento\Sales\Block\Adminhtml\Order\Create\Form;
use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Customer\Api\Data\AttributeMetadataInterface;
/**
* Sales Order Create Form Abstract Block
@@ -57,8 +58,7 @@ public function __construct(
}
/**
- * Prepare global layout
- * Add renderers to \Magento\Framework\Data\Form
+ * Prepare global layout. Add renderers to \Magento\Framework\Data\Form
*
* @return $this
*/
@@ -152,7 +152,7 @@ protected function _addAdditionalFormElementData(\Magento\Framework\Data\Form\El
/**
* Add rendering EAV attributes to Form element
*
- * @param \Magento\Customer\Api\Data\AttributeMetadataInterface[] $attributes
+ * @param AttributeMetadataInterface[] $attributes
* @param \Magento\Framework\Data\Form\AbstractForm $form
* @return $this
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -176,8 +176,8 @@ protected function _addAttributesToForm($attributes, \Magento\Framework\Data\For
[
'name' => $attribute->getAttributeCode(),
'label' => __($attribute->getStoreLabel()),
- 'class' => $attribute->getFrontendClass(),
- 'required' => $attribute->isRequired()
+ 'class' => $this->getValidationClasses($attribute),
+ 'required' => $attribute->isRequired(),
]
);
if ($inputType == 'multiline') {
@@ -227,4 +227,58 @@ public function getFormValues()
{
return [];
}
+
+ /**
+ * Retrieve frontend classes according validation rules
+ *
+ * @param AttributeMetadataInterface $attribute
+ *
+ * @return string
+ */
+ private function getValidationClasses(AttributeMetadataInterface $attribute) : string
+ {
+ $out = [];
+ $out[] = $attribute->getFrontendClass();
+
+ $textClasses = $this->getTextLengthValidateClasses($attribute);
+ if (!empty($textClasses)) {
+ $out = array_merge($out, $textClasses);
+ }
+
+ $out = !empty($out) ? implode(' ', array_unique(array_filter($out))) : '';
+ return $out;
+ }
+
+ /**
+ * Retrieve validation classes by min_text_length and max_text_length rules
+ *
+ * @param AttributeMetadataInterface $attribute
+ *
+ * @return array
+ */
+ private function getTextLengthValidateClasses(AttributeMetadataInterface $attribute) : array
+ {
+ $classes = [];
+
+ $validateRules = $attribute->getValidationRules();
+ if (!empty($validateRules)) {
+ foreach ($validateRules as $rule) {
+ switch ($rule->getName()) {
+ case 'min_text_length':
+ $classes[] = 'minimum-length-' . $rule->getValue();
+ break;
+
+ case 'max_text_length':
+ $classes[] = 'maximum-length-' . $rule->getValue();
+ break;
+ }
+ }
+
+ if (!empty($classes)) {
+ $classes[] = 'validate-length';
+ }
+ }
+
+ return $classes;
+ }
}
diff --git a/app/code/Magento/Sales/Model/Order/Address/Validator.php b/app/code/Magento/Sales/Model/Order/Address/Validator.php
index 31cb5bb1f60ca..5d3186781e7d7 100644
--- a/app/code/Magento/Sales/Model/Order/Address/Validator.php
+++ b/app/code/Magento/Sales/Model/Order/Address/Validator.php
@@ -49,8 +49,8 @@ class Validator
/**
* @param DirectoryHelper $directoryHelper
- * @param CountryFactory $countryFactory
- * @param EavConfig $eavConfig
+ * @param CountryFactory $countryFactory
+ * @param EavConfig $eavConfig
*/
public function __construct(
DirectoryHelper $directoryHelper,
@@ -61,6 +61,17 @@ public function __construct(
$this->countryFactory = $countryFactory;
$this->eavConfig = $eavConfig ?: ObjectManager::getInstance()
->get(EavConfig::class);
+ }
+
+ /**
+ * Validate address.
+ *
+ * @param \Magento\Sales\Model\Order\Address $address
+ * @return array
+ */
+ public function validate(Address $address)
+ {
+ $warnings = [];
if ($this->isTelephoneRequired()) {
$this->required['telephone'] = 'Phone Number';
@@ -73,16 +84,7 @@ public function __construct(
if ($this->isFaxRequired()) {
$this->required['fax'] = 'Fax';
}
- }
- /**
- *
- * @param \Magento\Sales\Model\Order\Address $address
- * @return array
- */
- public function validate(Address $address)
- {
- $warnings = [];
foreach ($this->required as $code => $label) {
if (!$address->hasData($code)) {
$warnings[] = sprintf('"%s" is required. Enter and try again.', $label);
@@ -195,7 +197,10 @@ protected function isStateRequired($countryId)
}
/**
+ * Check whether telephone is required for address.
+ *
* @return bool
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function isTelephoneRequired()
{
@@ -203,7 +208,10 @@ protected function isTelephoneRequired()
}
/**
+ * Check whether company is required for address.
+ *
* @return bool
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function isCompanyRequired()
{
@@ -211,7 +219,10 @@ protected function isCompanyRequired()
}
/**
+ * Check whether telephone is required for address.
+ *
* @return bool
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function isFaxRequired()
{
diff --git a/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php b/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php
index 2716e860243bf..a75690536e760 100644
--- a/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php
+++ b/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php
@@ -8,15 +8,15 @@
use Magento\Eav\Model\Config;
use Magento\Framework\App\State;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Sales\Model\OrderFactory;
-use Magento\Sales\Model\ResourceModel\Order\Address\CollectionFactory as AddressCollectionFactory;
-use Magento\Framework\App\ResourceConnection;
-use Magento\Sales\Setup\SalesSetupFactory;
+use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchVersionInterface;
-use Magento\Framework\Setup\ModuleDataSetupInterface;
+use Magento\Sales\Model\Order\Address;
+use Magento\Sales\Setup\SalesSetupFactory;
+/**
+ * Fills quote_address_id in table sales_order_address if it is empty.
+ */
class FillQuoteAddressIdInSalesOrderAddress implements DataPatchInterface, PatchVersionInterface
{
/**
@@ -24,11 +24,6 @@ class FillQuoteAddressIdInSalesOrderAddress implements DataPatchInterface, Patch
*/
private $moduleDataSetup;
- /**
- * @var SalesSetupFactory
- */
- private $salesSetupFactory;
-
/**
* @var State
*/
@@ -40,44 +35,22 @@ class FillQuoteAddressIdInSalesOrderAddress implements DataPatchInterface, Patch
private $eavConfig;
/**
- * @var AddressCollectionFactory
- */
- private $addressCollectionFactory;
-
- /**
- * @var OrderFactory
- */
- private $orderFactory;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * PatchInitial constructor.
* @param ModuleDataSetupInterface $moduleDataSetup
+ * @param State $state
+ * @param Config $eavConfig
*/
public function __construct(
ModuleDataSetupInterface $moduleDataSetup,
- SalesSetupFactory $salesSetupFactory,
State $state,
- Config $eavConfig,
- AddressCollectionFactory $addressCollectionFactory,
- OrderFactory $orderFactory,
- QuoteFactory $quoteFactory
+ Config $eavConfig
) {
$this->moduleDataSetup = $moduleDataSetup;
- $this->salesSetupFactory = $salesSetupFactory;
$this->state = $state;
$this->eavConfig = $eavConfig;
- $this->addressCollectionFactory = $addressCollectionFactory;
- $this->orderFactory = $orderFactory;
- $this->quoteFactory = $quoteFactory;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function apply()
{
@@ -96,32 +69,12 @@ public function apply()
*/
public function fillQuoteAddressIdInSalesOrderAddress(ModuleDataSetupInterface $setup)
{
- $addressTable = $setup->getTable('sales_order_address');
- $updateOrderAddress = $setup->getConnection()
- ->select()
- ->joinInner(
- ['sales_order' => $setup->getTable('sales_order')],
- $addressTable . '.parent_id = sales_order.entity_id',
- ['quote_address_id' => 'quote_address.address_id']
- )
- ->joinInner(
- ['quote_address' => $setup->getTable('quote_address')],
- 'sales_order.quote_id = quote_address.quote_id
- AND ' . $addressTable . '.address_type = quote_address.address_type',
- []
- )
- ->where(
- $addressTable . '.quote_address_id IS NULL'
- );
- $updateOrderAddress = $setup->getConnection()->updateFromSelect(
- $updateOrderAddress,
- $addressTable
- );
- $setup->getConnection()->query($updateOrderAddress);
+ $this->fillQuoteAddressIdInSalesOrderAddressByType($setup, Address::TYPE_SHIPPING);
+ $this->fillQuoteAddressIdInSalesOrderAddressByType($setup, Address::TYPE_BILLING);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getDependencies()
{
@@ -131,7 +84,7 @@ public static function getDependencies()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getVersion()
{
@@ -139,10 +92,99 @@ public static function getVersion()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAliases()
{
return [];
}
+
+ /**
+ * Fill quote_address_id in sales_order_address by type.
+ *
+ * @param ModuleDataSetupInterface $setup
+ * @param string $addressType
+ * @throws \Zend_Db_Statement_Exception
+ */
+ private function fillQuoteAddressIdInSalesOrderAddressByType(ModuleDataSetupInterface $setup, $addressType)
+ {
+ $salesConnection = $setup->getConnection('sales');
+
+ $orderTable = $setup->getTable('sales_order', 'sales');
+ $orderAddressTable = $setup->getTable('sales_order_address', 'sales');
+
+ $query = $salesConnection
+ ->select()
+ ->from(
+ ['sales_order_address' => $orderAddressTable],
+ ['entity_id', 'address_type']
+ )
+ ->joinInner(
+ ['sales_order' => $orderTable],
+ 'sales_order_address.parent_id = sales_order.entity_id',
+ ['quote_id' => 'sales_order.quote_id']
+ )
+ ->where('sales_order_address.quote_address_id IS NULL')
+ ->where('sales_order_address.address_type = ?', $addressType)
+ ->order('sales_order_address.entity_id');
+
+ $batchSize = 5000;
+ $result = $salesConnection->query($query);
+ $count = $result->rowCount();
+ $batches = ceil($count / $batchSize);
+
+ for ($batch = $batches; $batch > 0; $batch--) {
+ $query->limitPage($batch, $batchSize);
+ $result = $salesConnection->fetchAssoc($query);
+
+ $this->fillQuoteAddressIdInSalesOrderAddressProcessBatch($setup, $result, $addressType);
+ }
+ }
+
+ /**
+ * Process filling quote_address_id in sales_order_address in batch.
+ *
+ * @param ModuleDataSetupInterface $setup
+ * @param array $orderAddresses
+ * @param string $addressType
+ */
+ private function fillQuoteAddressIdInSalesOrderAddressProcessBatch(
+ ModuleDataSetupInterface $setup,
+ array $orderAddresses,
+ $addressType
+ ) {
+ $salesConnection = $setup->getConnection('sales');
+ $quoteConnection = $setup->getConnection('checkout');
+
+ $quoteAddressTable = $setup->getTable('quote_address', 'checkout');
+ $quoteTable = $setup->getTable('quote', 'checkout');
+ $salesOrderAddressTable = $setup->getTable('sales_order_address', 'sales');
+
+ $query = $quoteConnection
+ ->select()
+ ->from(
+ ['quote_address' => $quoteAddressTable],
+ ['quote_id', 'address_id']
+ )
+ ->joinInner(
+ ['quote' => $quoteTable],
+ 'quote_address.quote_id = quote.entity_id',
+ []
+ )
+ ->where('quote.entity_id in (?)', array_column($orderAddresses, 'quote_id'))
+ ->where('address_type = ?', $addressType);
+
+ $quoteAddresses = $quoteConnection->fetchAssoc($query);
+
+ foreach ($orderAddresses as $orderAddress) {
+ $bind = [
+ 'quote_address_id' => $quoteAddresses[$orderAddress['quote_id']]['address_id'] ?? null,
+ ];
+ $where = [
+ 'entity_id = ?' => $orderAddress['entity_id']
+ ];
+
+ $salesConnection->update($salesOrderAddressTable, $bind, $where);
+ }
+ }
}
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/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml
new file mode 100644
index 0000000000000..d418751c736e1
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml
index 5a5dd925a3098..68fcd17122bd2 100644
--- a/app/code/Magento/Sales/etc/di.xml
+++ b/app/code/Magento/Sales/etc/di.xml
@@ -1015,4 +1015,9 @@
+
+
+
+
+
diff --git a/app/code/Magento/SalesAnalytics/composer.json b/app/code/Magento/SalesAnalytics/composer.json
index 64424c8f5bc61..b77dcd7e71c65 100644
--- a/app/code/Magento/SalesAnalytics/composer.json
+++ b/app/code/Magento/SalesAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-sales": "*"
+ "magento/module-sales": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
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/Model/ResourceModel/SynonymReader.php b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
index 46e794a1954cf..45eee0a4001d1 100644
--- a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
+++ b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
@@ -87,7 +87,7 @@ private function queryByPhrase($phrase)
{
$matchQuery = $this->fullTextSelect->getMatchQuery(
['synonyms' => 'synonyms'],
- $phrase,
+ $this->escapePhrase($phrase),
Fulltext::FULLTEXT_MODE_BOOLEAN
);
$query = $this->getConnection()->select()->from(
@@ -97,6 +97,18 @@ private function queryByPhrase($phrase)
return $this->getConnection()->fetchAll($query);
}
+ /**
+ * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error.
+ *
+ * @see https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html
+ * @param string $phrase
+ * @return string
+ */
+ private function escapePhrase(string $phrase): string
+ {
+ return preg_replace('/@+|[@+-]+$/', '', $phrase);
+ }
+
/**
* A private helper function to retrieve matching synonym groups per scope
*
diff --git a/app/code/Magento/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/Controller/Adminhtml/Index/Render.php b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php
index b983e56b8aee2..b06c655939b1c 100644
--- a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php
+++ b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php
@@ -86,6 +86,18 @@ public function execute()
$contentType = $this->contentTypeResolver->resolve($component->getContext());
$this->getResponse()->setHeader('Content-Type', $contentType, true);
+ } else {
+ /** @var \Magento\Framework\Controller\Result\Json $resultJson */
+ $resultJson = $this->resultJsonFactory->create();
+ $resultJson->setStatusHeader(
+ \Zend\Http\Response::STATUS_CODE_403,
+ \Zend\Http\AbstractMessage::VERSION_11,
+ 'Forbidden'
+ );
+ return $resultJson->setData([
+ 'error' => $this->escaper->escapeHtml('Forbidden'),
+ 'errorcode' => 403
+ ]);
}
} catch (\Magento\Framework\Exception\LocalizedException $e) {
$this->logger->critical($e);
diff --git a/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php b/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php
index 05b35fb017b4b..2bba8686490b6 100644
--- a/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php
+++ b/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php
@@ -3,12 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Ui\Test\Unit\Controller\Adminhtml\Index;
+use Magento\Framework\Controller\Result\Json;
+use Magento\Framework\Escaper;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Ui\Controller\Adminhtml\Index\Render;
use Magento\Ui\Model\UiComponentTypeResolver;
-use Magento\Framework\View\Element\UiComponent\ContextInterface;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use Zend\Http\AbstractMessage;
+use Zend\Http\Response;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -97,6 +102,11 @@ class RenderTest extends \PHPUnit\Framework\TestCase
*/
private $loggerMock;
+ /**
+ * @var Escaper|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $escaperMock;
+
protected function setUp()
{
$this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class)
@@ -170,6 +180,10 @@ protected function setUp()
$this->uiComponentTypeResolverMock = $this->getMockBuilder(UiComponentTypeResolver::class)
->disableOriginalConstructor()
->getMock();
+ $this->escaperMock = $this->createMock(Escaper::class);
+ $this->escaperMock->expects($this->any())
+ ->method('escapeHtml')
+ ->willReturnArgument(0);
$this->objectManagerHelper = new ObjectManagerHelper($this);
@@ -181,6 +195,7 @@ protected function setUp()
'contentTypeResolver' => $this->uiComponentTypeResolverMock,
'resultJsonFactory' => $this->resultJsonFactoryMock,
'logger' => $this->loggerMock,
+ 'escaper' => $this->escaperMock,
]
);
}
@@ -201,7 +216,7 @@ public function testExecuteAjaxRequestException()
->method('appendBody')
->willThrowException(new \Exception('exception'));
- $jsonResultMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class)
+ $jsonResultMock = $this->getMockBuilder(Json::class)
->disableOriginalConstructor()
->setMethods(['setData'])
->getMock();
@@ -290,6 +305,34 @@ public function testExecuteAjaxRequestWithoutPermissions(array $dataProviderConf
$name = 'test-name';
$renderedData = 'data';
+ if (false === $isAllowed) {
+ $jsonResultMock = $this->getMockBuilder(Json::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['setStatusHeader', 'setData'])
+ ->getMock();
+
+ $jsonResultMock->expects($this->at(0))
+ ->method('setStatusHeader')
+ ->with(
+ Response::STATUS_CODE_403,
+ AbstractMessage::VERSION_11,
+ 'Forbidden'
+ )
+ ->willReturnSelf();
+
+ $jsonResultMock->expects($this->at(1))
+ ->method('setData')
+ ->with([
+ 'error' => 'Forbidden',
+ 'errorcode' => 403
+ ])
+ ->willReturnSelf();
+
+ $this->resultJsonFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($jsonResultMock);
+ }
+
$this->requestMock->expects($this->any())
->method('getParam')
->with('namespace')
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/Model/Storage/DbStorage.php b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php
index d8ceb16d71fdc..2ac1bdd712114 100644
--- a/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php
+++ b/app/code/Magento/UrlRewrite/Model/Storage/DbStorage.php
@@ -105,42 +105,18 @@ protected function doFindOneByData(array $data)
$result = null;
$requestPath = $data[UrlRewrite::REQUEST_PATH];
-
- $data[UrlRewrite::REQUEST_PATH] = [
+ $decodedRequestPath = urldecode($requestPath);
+ $data[UrlRewrite::REQUEST_PATH] = array_unique([
rtrim($requestPath, '/'),
rtrim($requestPath, '/') . '/',
- ];
+ rtrim($decodedRequestPath, '/'),
+ rtrim($decodedRequestPath, '/') . '/',
+ ]);
$resultsFromDb = $this->connection->fetchAll($this->prepareSelect($data));
-
- if (count($resultsFromDb) === 1) {
- $resultFromDb = current($resultsFromDb);
- $redirectTypes = [OptionProvider::TEMPORARY, OptionProvider::PERMANENT];
-
- // If request path matches the DB value or it's redirect - we can return result from DB
- $canReturnResultFromDb = ($resultFromDb[UrlRewrite::REQUEST_PATH] === $requestPath
- || in_array((int)$resultFromDb[UrlRewrite::REDIRECT_TYPE], $redirectTypes, true));
-
- // Otherwise return 301 redirect to request path from DB results
- $result = $canReturnResultFromDb ? $resultFromDb : [
- UrlRewrite::ENTITY_TYPE => 'custom',
- UrlRewrite::ENTITY_ID => '0',
- UrlRewrite::REQUEST_PATH => $requestPath,
- UrlRewrite::TARGET_PATH => $resultFromDb[UrlRewrite::REQUEST_PATH],
- UrlRewrite::REDIRECT_TYPE => OptionProvider::PERMANENT,
- UrlRewrite::STORE_ID => $resultFromDb[UrlRewrite::STORE_ID],
- UrlRewrite::DESCRIPTION => null,
- UrlRewrite::IS_AUTOGENERATED => '0',
- UrlRewrite::METADATA => null,
- ];
- } else {
- // If we have 2 results - return the row that matches request path
- foreach ($resultsFromDb as $resultFromDb) {
- if ($resultFromDb[UrlRewrite::REQUEST_PATH] === $requestPath) {
- $result = $resultFromDb;
- break;
- }
- }
+ if ($resultsFromDb) {
+ $urlRewrite = $this->extractMostRelevantUrlRewrite($requestPath, $resultsFromDb);
+ $result = $this->prepareUrlRewrite($requestPath, $urlRewrite);
}
return $result;
@@ -149,6 +125,75 @@ protected function doFindOneByData(array $data)
return $this->connection->fetchRow($this->prepareSelect($data));
}
+ /**
+ * Extract most relevant url rewrite from url rewrites list
+ *
+ * @param string $requestPath
+ * @param array $urlRewrites
+ * @return array|null
+ */
+ private function extractMostRelevantUrlRewrite(string $requestPath, array $urlRewrites): ?array
+ {
+ $prioritizedUrlRewrites = [];
+ foreach ($urlRewrites as $urlRewrite) {
+ switch (true) {
+ case $urlRewrite[UrlRewrite::REQUEST_PATH] === $requestPath:
+ $priority = 1;
+ break;
+ case $urlRewrite[UrlRewrite::REQUEST_PATH] === urldecode($requestPath):
+ $priority = 2;
+ break;
+ case rtrim($urlRewrite[UrlRewrite::REQUEST_PATH], '/') === rtrim($requestPath, '/'):
+ $priority = 3;
+ break;
+ case rtrim($urlRewrite[UrlRewrite::REQUEST_PATH], '/') === rtrim(urldecode($requestPath), '/'):
+ $priority = 4;
+ break;
+ default:
+ $priority = 5;
+ break;
+ }
+ $prioritizedUrlRewrites[$priority] = $urlRewrite;
+ }
+ ksort($prioritizedUrlRewrites);
+
+ return array_shift($prioritizedUrlRewrites);
+ }
+
+ /**
+ * Prepare url rewrite
+ *
+ * If request path matches the DB value or it's redirect - we can return result from DB
+ * Otherwise return 301 redirect to request path from DB results
+ *
+ * @param string $requestPath
+ * @param array $urlRewrite
+ * @return array
+ */
+ private function prepareUrlRewrite(string $requestPath, array $urlRewrite): array
+ {
+ $redirectTypes = [OptionProvider::TEMPORARY, OptionProvider::PERMANENT];
+ $canReturnResultFromDb = (
+ in_array($urlRewrite[UrlRewrite::REQUEST_PATH], [$requestPath, urldecode($requestPath)], true)
+ || in_array((int) $urlRewrite[UrlRewrite::REDIRECT_TYPE], $redirectTypes, true)
+ );
+ if (!$canReturnResultFromDb) {
+ $urlRewrite = [
+ UrlRewrite::ENTITY_TYPE => 'custom',
+ UrlRewrite::ENTITY_ID => '0',
+ UrlRewrite::REQUEST_PATH => $requestPath,
+ UrlRewrite::TARGET_PATH => $urlRewrite[UrlRewrite::REQUEST_PATH],
+ UrlRewrite::REDIRECT_TYPE => OptionProvider::PERMANENT,
+ UrlRewrite::STORE_ID => $urlRewrite[UrlRewrite::STORE_ID],
+ UrlRewrite::DESCRIPTION => null,
+ UrlRewrite::IS_AUTOGENERATED => '0',
+ UrlRewrite::METADATA => null,
+ ];
+ }
+
+ return $urlRewrite;
+ }
+
/**
* Delete old URLs from DB.
*
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/code/Magento/Wishlist/ViewModel/AllowedQuantity.php b/app/code/Magento/Wishlist/ViewModel/AllowedQuantity.php
new file mode 100644
index 0000000000000..5e4c6b39f3c36
--- /dev/null
+++ b/app/code/Magento/Wishlist/ViewModel/AllowedQuantity.php
@@ -0,0 +1,80 @@
+stockRegistry = $stockRegistry;
+ }
+
+ /**
+ * Set product configuration item
+ *
+ * @param ItemInterface $item
+ * @return self
+ */
+ public function setItem(ItemInterface $item): self
+ {
+ $this->item = $item;
+ return $this;
+ }
+
+ /**
+ * Get product configuration item
+ *
+ * @return ItemInterface
+ */
+ public function getItem(): ItemInterface
+ {
+ return $this->item;
+ }
+
+ /**
+ * Get min and max qty for wishlist form.
+ *
+ * @return array
+ */
+ public function getMinMaxQty(): array
+ {
+ $product = $this->getItem()->getProduct();
+ $stockItem = $this->stockRegistry->getStockItem($product->getId(), $product->getStore()->getWebsiteId());
+ $params = [];
+
+ $params['minAllowed'] = (float)$stockItem->getMinSaleQty();
+ if ($stockItem->getMaxSaleQty()) {
+ $params['maxAllowed'] = (float)$stockItem->getMaxSaleQty();
+ } else {
+ $params['maxAllowed'] = (float)StockDataFilter::MAX_QTY_VALUE;
+ }
+
+ return $params;
+ }
+}
diff --git a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml
index b20bc6a4e00ba..d4c3cc7fadd84 100644
--- a/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml
+++ b/app/code/Magento/Wishlist/view/frontend/layout/wishlist_index_index.xml
@@ -41,6 +41,7 @@
+ Magento\Wishlist\ViewModel\AllowedQuantity
Add to Cart
diff --git a/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml b/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml
index 9ea0d1a823235..6cb32d70ee1d8 100644
--- a/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml
+++ b/app/code/Magento/Wishlist/view/frontend/templates/item/column/cart.phtml
@@ -11,6 +11,9 @@
/** @var \Magento\Wishlist\Model\Item $item */
$item = $block->getItem();
$product = $item->getProduct();
+/** @var \Magento\Wishlist\ViewModel\AllowedQuantity $viewModel */
+$viewModel = $block->getData('allowedQuantityViewModel');
+$allowedQty = $viewModel->setItem($item)->getMinMaxQty();
?>
getChildNames() as $childName): ?>
= /* @noEscape */ $block->getLayout()->renderElement($childName, false) ?>
@@ -21,7 +24,7 @@ $product = $item->getProduct();
diff --git a/app/code/Magento/WishlistAnalytics/composer.json b/app/code/Magento/WishlistAnalytics/composer.json
index fc69afe2907ab..747f2a4baaaa9 100644
--- a/app/code/Magento/WishlistAnalytics/composer.json
+++ b/app/code/Magento/WishlistAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-wishlist": "*"
+ "magento/module-wishlist": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php
index e3a788af2ea7e..792928ab61aaf 100644
--- a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php
+++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php
@@ -13,6 +13,7 @@
use Magento\Wishlist\Model\ResourceModel\Wishlist as WishlistResourceModel;
use Magento\Wishlist\Model\Wishlist;
use Magento\Wishlist\Model\WishlistFactory;
+use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
/**
* Fetches the Wishlist data according to the GraphQL schema
@@ -51,6 +52,10 @@ public function resolve(
) {
$customerId = $context->getUserId();
+ /* Guest checking */
+ if (!$customerId && 0 === $customerId) {
+ throw new GraphQlAuthorizationException(__('The current user cannot perform operations on wishlist'));
+ }
/** @var Wishlist $wishlist */
$wishlist = $this->wishlistFactory->create();
$this->wishlistResource->load($wishlist, $customerId, 'customer_id');
diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/_headings-group.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/_headings-group.less
index 832c66b7988e0..bf7ee7850f9d0 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/_headings-group.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/_headings-group.less
@@ -42,4 +42,5 @@
color: @page-title__color;
font-size: @page-title__font-size;
margin-bottom: 0;
+ word-break: break-all;
}
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/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less
index 6e03e1d0cebaa..e37e08f3b667d 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less
@@ -99,7 +99,7 @@
}
.action-menu {
- max-height: 3.85rem * @data-grid-search-control-action-menu-item__quantity; // ToDo UI: change static item height
+ max-height: 3.85rem * @data-grid-search-control-action-menu-item__quantity; // @todo: change static item height
overflow-y: auto;
z-index: @data-grid-search-menu__z-index;
}
@@ -354,6 +354,7 @@
.admin__current-filters-list-wrap {
width: 100%;
+ word-break: break-all;
}
.admin__current-filters-list {
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/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less
index d477c08fc9553..e5915969c91b9 100644
--- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less
+++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less
@@ -397,7 +397,7 @@
.page-products.page-layout-3columns {
.products-grid {
.product-item {
- margin-left: 2%;
+ margin-left: 0;
width: calc(~'(100% - 4%) / 3');
&:nth-child(3n + 1) {
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/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php
index 1ff0b53dda0bb..ad5d71cb08605 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Directory/CurrencyTest.php
@@ -21,8 +21,8 @@ public function testGetCurrency()
currency {
base_currency_code
base_currency_symbol
- default_display_currecy_code
- default_display_currecy_symbol
+ default_display_currency_code
+ default_display_currency_symbol
available_currency_codes
exchange_rates {
currency_to
@@ -36,8 +36,8 @@ public function testGetCurrency()
$this->assertArrayHasKey('currency', $result);
$this->assertArrayHasKey('base_currency_code', $result['currency']);
$this->assertArrayHasKey('base_currency_symbol', $result['currency']);
- $this->assertArrayHasKey('default_display_currecy_code', $result['currency']);
- $this->assertArrayHasKey('default_display_currecy_symbol', $result['currency']);
+ $this->assertArrayHasKey('default_display_currency_code', $result['currency']);
+ $this->assertArrayHasKey('default_display_currency_symbol', $result['currency']);
$this->assertArrayHasKey('available_currency_codes', $result['currency']);
$this->assertArrayHasKey('exchange_rates', $result['currency']);
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php
index 1e92a2e497bed..d9ab8db62a195 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddSimpleProductToCartTest.php
@@ -59,6 +59,22 @@ public function testAddSimpleProductToCart()
self::assertEquals($sku, $response['addSimpleProductsToCart']['cart']['items'][0]['product']['sku']);
}
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/products.php
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ * @expectedException \Exception
+ * @expectedExceptionMessage Please enter a number greater than 0 in this field.
+ */
+ public function testAddSimpleProductToCartWithNegativeQty()
+ {
+ $sku = 'simple';
+ $qty = -2;
+ $maskedQuoteId = $this->getMaskedQuoteId();
+
+ $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty);
+ $this->graphQlQuery($query);
+ }
+
/**
* @return string
*/
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php
index 67a086311d71a..55a32f7cdf653 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php
@@ -423,6 +423,39 @@ public function testSetBillingAddressOnNonExistentCart()
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
}
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @dataProvider dataProviderSetWithoutRequiredParameters
+ * @param string $input
+ * @param string $message
+ * @throws \Exception
+ */
+ public function testSetBillingAddressWithoutRequiredParameters(string $input, string $message)
+ {
+ $maskedQuoteId = $this->assignQuoteToCustomer();
+ $input = str_replace('cart_id_value', $maskedQuoteId, $input);
+
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+
/**
* Verify the all the whitelisted fields for a New Address Object
*
@@ -506,4 +539,22 @@ private function assignQuoteToCustomer(
$this->quoteResource->save($quote);
return $this->quoteIdToMaskedId->execute((int)$quote->getId());
}
+
+ /**
+ * @return array
+ */
+ public function dataProviderSetWithoutRequiredParameters()
+ {
+ return [
+ 'missed_billing_address' => [
+ 'cart_id: "cart_id_value"',
+ 'Field SetBillingAddressOnCartInput.billing_address of required type BillingAddressInput!'
+ . ' was not provided.',
+ ],
+ 'missed_cart_id' => [
+ 'billing_address: {}',
+ 'Required parameter "cart_id" is missing'
+ ]
+ ];
+ }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php
index c7da2144adb9e..51c48f5041dd9 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php
@@ -171,6 +171,54 @@ public function testPaymentMethodOnNonExistentCart()
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
}
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @param string $input
+ * @param string $message
+ * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters
+ */
+ public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message)
+ {
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+ /**
+ * @return array
+ */
+ public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array
+ {
+ return [
+ 'missed_cart_id' => [
+ 'payment_method: {code: "' . Checkmo::PAYMENT_METHOD_CHECKMO_CODE . '"}',
+ 'Required parameter "cart_id" is missing.'
+ ],
+ 'missed_payment_method' => [
+ 'cart_id: "test"',
+ 'Required parameter "code" for "payment_method" is missing.'
+ ],
+ 'missed_payment_method_code' => [
+ 'cart_id: "test", payment_method: {code: ""}',
+ 'Required parameter "code" for "payment_method" is missing.'
+ ],
+ ];
+ }
+
/**
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_payment_saved.php
*/
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php
index 27de0d12e413d..a2f092b9e0e1d 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php
@@ -263,6 +263,38 @@ public function testSetBillingAddressOnNonExistentCart()
$this->graphQlQuery($query);
}
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @dataProvider dataProviderSetWithoutRequiredParameters
+ * @param string $input
+ * @param string $message
+ * @throws \Exception
+ */
+ public function testSetBillingAddressWithoutRequiredParameters(string $input, string $message)
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $input = str_replace('cart_id_value', $maskedQuoteId, $input);
+
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+
/**
* Verify the all the whitelisted fields for a New Address Object
*
@@ -297,4 +329,22 @@ private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): str
return $this->quoteIdToMaskedId->execute((int)$quote->getId());
}
+
+ /**
+ * @return array
+ */
+ public function dataProviderSetWithoutRequiredParameters()
+ {
+ return [
+ 'missed_billing_address' => [
+ 'cart_id: "cart_id_value"',
+ 'Field SetBillingAddressOnCartInput.billing_address of required type BillingAddressInput!'
+ . ' was not provided.',
+ ],
+ 'missed_cart_id' => [
+ 'billing_address: {}',
+ 'Required parameter "cart_id" is missing'
+ ]
+ ];
+ }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php
index 182bbaf618505..017b85ba17b93 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php
@@ -126,6 +126,53 @@ public function testSetPaymentMethodToCustomerCart()
$this->graphQlQuery($query);
}
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @param string $input
+ * @param string $message
+ * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters
+ */
+ public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message)
+ {
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+ /**
+ * @return array
+ */
+ public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array
+ {
+ return [
+ 'missed_cart_id' => [
+ 'payment_method: {code: "' . Checkmo::PAYMENT_METHOD_CHECKMO_CODE . '"}',
+ 'Required parameter "cart_id" is missing.'
+ ],
+ 'missed_payment_method' => [
+ 'cart_id: "test"',
+ 'Required parameter "code" for "payment_method" is missing.'
+ ],
+ 'missed_payment_method_code' => [
+ 'cart_id: "test", payment_method: {code: ""}',
+ 'Required parameter "code" for "payment_method" is missing.'
+ ],
+ ];
+ }
+
/**
* @expectedException \Exception
* @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php
new file mode 100644
index 0000000000000..463f2c4af101f
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Ups/SetUpsShippingMethodsOnCartTest.php
@@ -0,0 +1,147 @@
+quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Ups/_files/enable_ups_shipping_method.php
+ */
+ public function testSetUpsShippingMethod()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $shippingAddressId = (int)$quote->getShippingAddress()->getId();
+
+ $query = $this->getAddUpsShippingMethodQuery(
+ $maskedQuoteId,
+ $shippingAddressId,
+ self::CARRIER_CODE,
+ self::CARRIER_METHOD_CODE_GROUND
+ );
+
+ $response = $this->sendRequestWithToken($query);
+ $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses'];
+ $expectedResult = [
+ 'carrier_code' => self::CARRIER_CODE,
+ 'method_code' => self::CARRIER_METHOD_CODE_GROUND,
+ 'label' => 'United Parcel Service - Ground',
+ ];
+ self::assertEquals($addressesInformation[0]['selected_shipping_method'], $expectedResult);
+ }
+
+ /**
+ * Generates query for setting the specified shipping method on cart
+ *
+ * @param int $shippingAddressId
+ * @param string $maskedQuoteId
+ * @param string $carrierCode
+ * @param string $methodCode
+ * @return string
+ */
+ private function getAddUpsShippingMethodQuery(
+ string $maskedQuoteId,
+ int $shippingAddressId,
+ string $carrierCode,
+ string $methodCode
+ ): string {
+ return <<customerTokenService->createCustomerAccessToken('customer@example.com', 'password');
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+
+ return $this->graphQlQuery($query, [], '', $headerMap);
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php
index d570fc09b7714..4aac5d9445934 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php
@@ -93,6 +93,36 @@ public function testGetCustomerWishlist(): void
$this->assertEquals($wishlistItemProduct->getName(), $response['wishlist']['items'][0]['product']['name']);
}
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage The current user cannot perform operations on wishlist
+ */
+ public function testGetGuestWishlist()
+ {
+ $query =
+ <<graphQlQuery($query);
+ }
+
/**
* @param string $email
* @param string $password
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 6dbf2b1aa6a12..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
*/
@@ -265,7 +272,7 @@ protected function addSingleCondition($condition, ElementInterface $context)
$this->addCondition($condition['type'], $context);
$createdCondition = $context->find($this->created, Locator::SELECTOR_XPATH);
$this->waitForCondition($createdCondition);
- $this->fillCondition($condition['rules'], $createdCondition);
+ $this->fillCondition($condition['rules'], $createdCondition, $condition['type']);
}
/**
@@ -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;
@@ -306,13 +319,14 @@ protected function addCondition($type, ElementInterface $context)
*
* @param array $rules
* @param ElementInterface $element
+ * @param string|null $type
* @return void
* @throws \Exception
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
- protected function fillCondition(array $rules, ElementInterface $element)
+ protected function fillCondition(array $rules, ElementInterface $element, $type = null)
{
$this->resetKeyParam();
foreach ($rules as $rule) {
@@ -333,7 +347,7 @@ protected function fillCondition(array $rules, ElementInterface $element)
if ($this->fillGrid($rule, $param)) {
$isSet = true;
- } elseif ($this->fillSelect($rule, $param)) {
+ } elseif ($this->fillSelect($rule, $param, $type)) {
$isSet = true;
} elseif ($this->fillText($rule, $param)) {
$isSet = true;
@@ -390,11 +404,15 @@ protected function fillGrid($rule, ElementInterface $param)
*
* @param string $rule
* @param ElementInterface $param
+ * @param string|null $type
* @return bool
*/
- protected function fillSelect($rule, ElementInterface $param)
+ protected function fillSelect($rule, ElementInterface $param, $type = null)
{
- $value = $param->find('select', Locator::SELECTOR_TAG_NAME, 'select');
+ //Avoid confusion between regions like: "Baja California" and "California".
+ $value = strpos($type, 'State/Province') === false
+ ? $param->find('select', Locator::SELECTOR_TAG_NAME, 'select')
+ : $param->find('select', Locator::SELECTOR_TAG_NAME, 'selectstate');
if ($value->isVisible()) {
$value->setValue($rule);
$this->click();
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/Backend/Test/Page/Adminhtml/Dashboard.xml b/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/Dashboard.xml
index adae65a1d06d6..799f9e30fd972 100644
--- a/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/Dashboard.xml
+++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/Dashboard.xml
@@ -17,5 +17,6 @@
+
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/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccess.php b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccess.php
index f7c56ae1b9653..ecfbc8d353888 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccess.php
+++ b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccess.php
@@ -10,6 +10,7 @@
use Magento\Mtf\Client\BrowserInterface;
use Magento\Mtf\Constraint\AbstractConstraint;
use Magento\User\Test\Fixture\User;
+use Magento\User\Test\TestStep\LoginUserOnBackendWithErrorStep;
/**
* Asserts that user has only related permissions.
@@ -18,6 +19,8 @@ class AssertUserRoleRestrictedAccess extends AbstractConstraint
{
const DENIED_ACCESS = 'Sorry, you need permissions to view this content.';
+ protected $loginStep = 'Magento\User\Test\TestStep\LoginUserOnBackendStep';
+
/**
* Asserts that user has only related permissions.
*
@@ -36,7 +39,7 @@ public function processAssert(
$denyUrl
) {
$this->objectManager->create(
- \Magento\User\Test\TestStep\LoginUserOnBackendStep::class,
+ $this->loginStep,
['user' => $user]
)->run();
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccessWithError.php b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccessWithError.php
new file mode 100644
index 0000000000000..b001893abb4c4
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserRoleRestrictedAccessWithError.php
@@ -0,0 +1,17 @@
+objectManager->create(
- \Magento\User\Test\TestStep\LoginUserOnBackendStep::class,
+ $this->loginStep,
['user' => $user]
)->run();
\PHPUnit\Framework\Assert::assertTrue(
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserSuccessLoginWithError.php b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserSuccessLoginWithError.php
new file mode 100644
index 0000000000000..9fed1f4df8573
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/Constraint/AssertUserSuccessLoginWithError.php
@@ -0,0 +1,20 @@
+
-
+
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.php b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.php
index cc1d0fc980fbf..58450abc71633 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.php
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.php
@@ -121,6 +121,11 @@ public function testUpdateAdminUserRolesEntity(
*/
public function tearDown()
{
+ sleep(3);
+ $modalMessage = $this->dashboard->getModalMessage();
+ if ($modalMessage->isVisible()) {
+ $modalMessage->acceptAlert();
+ }
$this->dashboard->getAdminPanelHeader()->logOut();
}
}
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.xml b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.xml
index 224ccbce10f96..db6a13d0f3551 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestCase/UpdateAdminUserRoleEntityTest.xml
@@ -29,8 +29,8 @@
-
-
+
+
custom_admin_with_default_role
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestStep/CloseErrorAlertStep.php b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/CloseErrorAlertStep.php
new file mode 100644
index 0000000000000..51d48058c8ae5
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/CloseErrorAlertStep.php
@@ -0,0 +1,57 @@
+dashboard = $dashboard;
+ $this->browser = $browser;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function run()
+ {
+ $modalMessage = $this->dashboard->getModalMessage();
+ try {
+ $this->browser->waitUntil(
+ function () use ($modalMessage) {
+ return $modalMessage->isVisible() ? true : null;
+ }
+ );
+ $modalMessage->acceptAlert();
+ } catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
+ //There is no modal to accept.
+ }
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LoginUserOnBackendStep.php b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LoginUserOnBackendStep.php
index 4f7e6deed7a85..c244e27d42899 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LoginUserOnBackendStep.php
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LoginUserOnBackendStep.php
@@ -50,7 +50,7 @@ class LoginUserOnBackendStep implements TestStepInterface
*
* @var BrowserInterface
*/
- private $browser;
+ protected $browser;
/**
* Array of error messages on admin login form.
@@ -108,8 +108,6 @@ public function run()
}
}
}
-
- $this->dashboard->getSystemMessageDialog()->closePopup();
}
/**
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LoginUserOnBackendWithErrorStep.php b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LoginUserOnBackendWithErrorStep.php
new file mode 100644
index 0000000000000..094f90d0a5d70
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LoginUserOnBackendWithErrorStep.php
@@ -0,0 +1,53 @@
+closeErrorAlertStep = $closeErrorAlertStep;
+ }
+
+ /**
+ * Run step flow.
+ *
+ * @return void
+ */
+ public function run()
+ {
+ parent::run();
+ $this->closeErrorAlertStep->run();
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LogoutUserOnBackendStep.php b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LogoutUserOnBackendStep.php
index 70a4080a0b4d5..7f366312bba24 100644
--- a/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LogoutUserOnBackendStep.php
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LogoutUserOnBackendStep.php
@@ -48,7 +48,6 @@ public function __construct(AdminAuthLogin $adminAuth, Dashboard $dashboard)
public function run()
{
$this->adminAuth->open();
- $this->dashboard->getSystemMessageDialog()->closePopup();
$this->dashboard->getAdminPanelHeader()->logOut();
}
}
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LogoutUserOnBackendWithErrorStep.php b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LogoutUserOnBackendWithErrorStep.php
new file mode 100644
index 0000000000000..ce49e86afc065
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/TestStep/LogoutUserOnBackendWithErrorStep.php
@@ -0,0 +1,40 @@
+closeErrorAlertStep = $closeErrorAlertStep;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function run()
+ {
+ $this->adminAuth->open();
+ $this->closeErrorAlertStep->run();
+ $this->dashboard->getAdminPanelHeader()->logOut();
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/User/Test/etc/di.xml b/dev/tests/functional/tests/app/Magento/User/Test/etc/di.xml
new file mode 100644
index 0000000000000..1298bd56a8fb0
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/User/Test/etc/di.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ \Magento\User\Test\TestStep\LogoutUserOnBackendWithErrorStep
+
+
+
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/Block/Widget/Grid/MassactionTest.php b/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php
index 8aeee9cf12494..e11c5ce5d9cf3 100644
--- a/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php
+++ b/dev/tests/integration/testsuite/Magento/Backend/Block/Widget/Grid/MassactionTest.php
@@ -87,41 +87,6 @@ public function testMassactionDefaultValues()
$this->assertFalse($blockEmpty->isAvailable());
}
- public function testGetJavaScript()
- {
- $this->loadLayout();
-
- $javascript = $this->_block->getJavaScript();
-
- $expectedItemFirst = '#"option_id1":{"label":"Option One",' .
- '"url":"http:\\\/\\\/localhost\\\/index\.php\\\/(?:key\\\/([\w\d]+)\\\/)?",' .
- '"complete":"Test","id":"option_id1"}#';
- $this->assertRegExp($expectedItemFirst, $javascript);
-
- $expectedItemSecond = '#"option_id2":{"label":"Option Two",' .
- '"url":"http:\\\/\\\/localhost\\\/index\.php\\\/(?:key\\\/([\w\d]+)\\\/)?",' .
- '"confirm":"Are you sure\?","id":"option_id2"}#';
- $this->assertRegExp($expectedItemSecond, $javascript);
- }
-
- public function testGetJavaScriptWithAddedItem()
- {
- $this->loadLayout();
-
- $input = [
- 'id' => 'option_id3',
- 'label' => 'Option Three',
- 'url' => '*/*/option3',
- 'block_name' => 'admin.test.grid.massaction.option3',
- ];
- $expected = '#"option_id3":{"id":"option_id3","label":"Option Three",' .
- '"url":"http:\\\/\\\/localhost\\\/index\.php\\\/(?:key\\\/([\w\d]+)\\\/)?",' .
- '"block_name":"admin.test.grid.massaction.option3"}#';
-
- $this->_block->addItem($input['id'], $input);
- $this->assertRegExp($expected, $this->_block->getJavaScript());
- }
-
/**
* @param string $mageMode
* @param int $expectedCount
@@ -213,21 +178,4 @@ public function getItemsDataProvider()
]
];
}
-
- public function testGridContainsMassactionColumn()
- {
- $this->loadLayout();
- $this->_layout->getBlock('admin.test.grid')->toHtml();
-
- $gridMassactionColumn = $this->_layout->getBlock('admin.test.grid')
- ->getColumnSet()
- ->getChildBlock('massaction');
-
- $this->assertNotNull($gridMassactionColumn, 'Massaction column does not exist in the grid column set');
- $this->assertInstanceOf(
- \Magento\Backend\Block\Widget\Grid\Column::class,
- $gridMassactionColumn,
- 'Massaction column is not an instance of \Magento\Backend\Block\Widget\Column'
- );
- }
}
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/Braintree/Controller/Paypal/ReviewTest.php b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/ReviewTest.php
new file mode 100644
index 0000000000000..fc79048f15f45
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Braintree/Controller/Paypal/ReviewTest.php
@@ -0,0 +1,43 @@
+controller = $this->_objectManager->create(Review::class);
+ }
+
+ /**
+ * Test controller implements correct interfaces
+ *
+ */
+ public function testInterfaceImplementation()
+ {
+ $this->assertInstanceOf(HttpGetActionInterface::class, $this->controller);
+ $this->assertInstanceOf(HttpPostActionInterface::class, $this->controller);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php
index b97bd9f822666..e9cb2f2d6c9d4 100644
--- a/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php
+++ b/dev/tests/integration/testsuite/Magento/Bundle/Model/Product/FixedBundlePriceCalculatorWithDimensionTest.php
@@ -13,7 +13,6 @@
* @magentoDbIsolation disabled
* @magentoIndexerDimensionMode catalog_product_price website_and_customer_group
* @group indexer_dimension
- * @magentoAppArea frontend
*/
class FixedBundlePriceCalculatorWithDimensionTest extends BundlePriceAbstract
{
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/Catalog/Model/ResourceModel/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
index 7954e2c36227f..476f01eb277df 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
@@ -12,6 +12,11 @@
class ProductTest extends TestCase
{
+ /**
+ * @var ProductRepositoryInterface
+ */
+ private $productRepository;
+
/**
* @var Product
*/
@@ -29,7 +34,8 @@ protected function setUp()
{
$this->objectManager = Bootstrap::getObjectManager();
- $this->model = $this->objectManager->get(Product::class);
+ $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
+ $this->model = $this->objectManager->create(Product::class);
}
/**
@@ -42,11 +48,29 @@ public function testGetAttributeRawValue()
$sku = 'simple';
$attribute = 'name';
- /** @var ProductRepositoryInterface $productRepository */
- $productRepository = $this->objectManager->get(ProductRepositoryInterface::class);
- $product = $productRepository->get($sku);
-
+ $product = $this->productRepository->get($sku);
$actual = $this->model->getAttributeRawValue($product->getId(), $attribute, null);
self::assertEquals($product->getName(), $actual);
}
+
+ /**
+ * @magentoAppArea adminhtml
+ * @magentoDataFixture Magento/Catalog/_files/product_special_price.php
+ * @magentoAppIsolation enabled
+ * @magentoConfigFixture default_store catalog/price/scope 1
+ */
+ public function testUpdateStoreSpecificSpecialPrice()
+ {
+ /** @var \Magento\Catalog\Model\Product $product */
+ $product = $this->productRepository->get('simple', true, 1);
+ $this->assertEquals(5.99, $product->getSpecialPrice());
+
+ $product->setSpecialPrice('');
+ $this->model->save($product);
+ $product = $this->productRepository->get('simple', false, 1, true);
+ $this->assertEmpty($product->getSpecialPrice());
+
+ $product = $this->productRepository->get('simple', false, 0, true);
+ $this->assertEquals(5.99, $product->getSpecialPrice());
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php
similarity index 84%
rename from dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php
rename to dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php
index cbc0476efd1b5..a9ab0e11312b2 100644
--- a/dev/tests/integration/testsuite/Magento/Catalog/_files/text_attribute_rollback.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_text_attribute_rollback.php
@@ -3,13 +3,15 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
/* Delete attribute with text_attribute code */
-$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get('Magento\Framework\Registry');
+$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
$registry->unregister('isSecureArea');
$registry->register('isSecureArea', true);
+
/** @var $attribute \Magento\Catalog\Model\ResourceModel\Eav\Attribute */
$attribute = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->create(
- 'Magento\Catalog\Model\ResourceModel\Eav\Attribute'
+ \Magento\Catalog\Model\ResourceModel\Eav\Attribute::class
);
$attribute->load('text_attribute', 'attribute_code');
$attribute->delete();
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php
index 168073bc6ab74..c57c7c3fd6a92 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_rollback.php
@@ -5,10 +5,10 @@
*/
/** Delete all products */
-require dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php';
+include dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php';
/** Delete text attribute */
-require dirname(dirname(__DIR__)) . '/Catalog/_files/text_attribute_rollback.php';
+include dirname(dirname(__DIR__)) . '/Catalog/_files/product_text_attribute_rollback.php';
-require dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php';
+include dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php';
-require dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php';
+include dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php';
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php
index 168073bc6ab74..c57c7c3fd6a92 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/_files/product_export_data_special_chars_rollback.php
@@ -5,10 +5,10 @@
*/
/** Delete all products */
-require dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php';
+include dirname(dirname(__DIR__)) . '/Catalog/_files/products_with_multiselect_attribute_rollback.php';
/** Delete text attribute */
-require dirname(dirname(__DIR__)) . '/Catalog/_files/text_attribute_rollback.php';
+include dirname(dirname(__DIR__)) . '/Catalog/_files/product_text_attribute_rollback.php';
-require dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php';
+include dirname(dirname(__DIR__)) . '/Store/_files/second_store_rollback.php';
-require dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php';
+include dirname(dirname(__DIR__)) . '/Catalog/_files/category_rollback.php';
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/Customer/Controller/AccountTest.php b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php
index ea7a7710acbc3..10b632c002475 100644
--- a/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php
+++ b/dev/tests/integration/testsuite/Magento/Customer/Controller/AccountTest.php
@@ -751,6 +751,21 @@ public function loginPostRedirectDataProvider()
];
}
+ /**
+ * @magentoDataFixture Magento/Customer/_files/customer.php
+ * @magentoDataFixture Magento/Customer/_files/customer_address.php
+ * @magentoAppArea frontend
+ */
+ public function testCheckVisitorModel()
+ {
+ /** @var \Magento\Customer\Model\Visitor $visitor */
+ $visitor = $this->_objectManager->get(\Magento\Customer\Model\Visitor::class);
+ $this->login(1);
+ $this->assertNull($visitor->getId());
+ $this->dispatch('customer/account/index');
+ $this->assertNotNull($visitor->getId());
+ }
+
/**
* @param string $email
* @return void
diff --git a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php
index 6bb7d6ac568fc..a3da32e0d6c40 100644
--- a/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php
+++ b/dev/tests/integration/testsuite/Magento/Elasticsearch/SearchAdapter/AdapterTest.php
@@ -367,4 +367,18 @@ public function dateDataProvider()
[['from' => '2000-02-01T00:00:00Z', 'to' => ''], 0],
];
}
+
+ public function filterByAttributeValuesDataProvider()
+ {
+ $variations = parent::filterByAttributeValuesDataProvider();
+
+ $variations['quick search by date'] = [
+ 'quick_search_container',
+ [
+ 'search_term' => '2000-10-30',
+ ],
+ ];
+
+ return $variations;
+ }
}
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/Framework/Search/_files/filterable_attributes.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php
index b09af48b5f943..f4f3337a253c0 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes.php
@@ -20,6 +20,10 @@
CategorySetup::class,
['resourceName' => 'catalog_setup']
);
+$productEntityTypeId = $installer->getEntityTypeId(
+ \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE
+);
+
$selectOptions = [];
$selectAttributes = [];
foreach (range(1, 2) as $index) {
@@ -30,7 +34,7 @@
$selectAttribute->setData(
[
'attribute_code' => 'select_attribute_' . $index,
- 'entity_type_id' => $installer->getEntityTypeId('catalog_product'),
+ 'entity_type_id' => $productEntityTypeId,
'is_global' => 1,
'is_user_defined' => 1,
'frontend_input' => 'select',
@@ -56,7 +60,8 @@
);
$selectAttribute->save();
/* Assign attribute to attribute set */
- $installer->addAttributeToGroup('catalog_product', 'Default', 'General', $selectAttribute->getId());
+ $installer->addAttributeToGroup($productEntityTypeId, 'Default', 'General', $selectAttribute->getId());
+
/** @var $selectOptions Collection */
$selectOption = Bootstrap::getObjectManager()->create(
Collection::class
@@ -65,6 +70,26 @@
$selectAttributes[$index] = $selectAttribute;
$selectOptions[$index] = $selectOption;
}
+
+$dateAttribute = Bootstrap::getObjectManager()->create(Attribute::class);
+$dateAttribute->setData(
+ [
+ 'attribute_code' => 'date_attribute',
+ 'entity_type_id' => $productEntityTypeId,
+ 'is_global' => 1,
+ 'is_filterable' => 1,
+ 'backend_type' => 'datetime',
+ 'frontend_input' => 'date',
+ 'frontend_label' => 'Test Date',
+ 'is_searchable' => 1,
+ 'is_filterable_in_search' => 1,
+ ]
+);
+$dateAttribute->save();
+/* Assign attribute to attribute set */
+$installer->addAttributeToGroup($productEntityTypeId, 'Default', 'General', $dateAttribute->getId());
+
+$productAttributeSetId = $installer->getAttributeSetId($productEntityTypeId, 'Default');
/* Create simple products per each first attribute option */
foreach ($selectOptions[1] as $option) {
/** @var $product Product */
@@ -74,7 +99,7 @@
$product->setTypeId(
Type::TYPE_SIMPLE
)->setAttributeSetId(
- $installer->getAttributeSetId('catalog_product', 'Default')
+ $productAttributeSetId
)->setWebsiteIds(
[1]
)->setName(
@@ -92,6 +117,7 @@
)->setStockData(
['use_config_manage_stock' => 1, 'qty' => 5, 'is_in_stock' => 1]
)->save();
+
Bootstrap::getObjectManager()->get(
Action::class
)->updateAttributes(
@@ -99,6 +125,7 @@
[
$selectAttributes[1]->getAttributeCode() => $option->getId(),
$selectAttributes[2]->getAttributeCode() => $selectOptions[2]->getLastItem()->getId(),
+ $dateAttribute->getAttributeCode() => '10/30/2000',
],
$product->getStoreId()
);
diff --git a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php
index 18a5372d06d98..fd413726b2637 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/Search/_files/filterable_attributes_rollback.php
@@ -13,6 +13,7 @@
$registry = Bootstrap::getObjectManager()->get(Registry::class);
$registry->unregister('isSecureArea');
$registry->register('isSecureArea', true);
+
/** @var $productCollection \Magento\Catalog\Model\ResourceModel\Product\Collection */
$productCollection = Bootstrap::getObjectManager()
->create(Product::class)
@@ -20,17 +21,26 @@
foreach ($productCollection as $product) {
$product->delete();
}
+
/** @var $attribute Attribute */
$attribute = Bootstrap::getObjectManager()->create(
Attribute::class
);
/** @var $installer CategorySetup */
$installer = Bootstrap::getObjectManager()->create(CategorySetup::class);
+$productEntityTypeId = $installer->getEntityTypeId(
+ \Magento\Catalog\Api\Data\ProductAttributeInterface::ENTITY_TYPE_CODE
+);
foreach (range(1, 2) as $index) {
- $attribute->loadByCode($installer->getEntityTypeId('catalog_product'), 'select_attribute_' . $index);
+ $attribute->loadByCode($productEntityTypeId, 'select_attribute_' . $index);
if ($attribute->getId()) {
$attribute->delete();
}
}
+$attribute->loadByCode($productEntityTypeId, 'date_attribute');
+if ($attribute->getId()) {
+ $attribute->delete();
+}
+
$registry->unregister('isSecureArea');
$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/Newsletter/Block/Adminhtml/Subscriber/GridTest.php b/dev/tests/integration/testsuite/Magento/Newsletter/Block/Adminhtml/Subscriber/GridTest.php
new file mode 100644
index 0000000000000..48d3356525f49
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Newsletter/Block/Adminhtml/Subscriber/GridTest.php
@@ -0,0 +1,105 @@
+objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+
+ $this->layout = $this->objectManager->create(\Magento\Framework\View\LayoutInterface::class);
+ $this->layout->getUpdate()->load('newsletter_subscriber_grid');
+ $this->layout->generateXml();
+ $this->layout->generateElements();
+ }
+
+ /**
+ * Check if mass action block exists.
+ */
+ public function testMassActionBlockExists()
+ {
+ $this->assertNotFalse(
+ $this->getMassActionBlock(),
+ 'Mass action block does not exist in the grid, or it name was changed.'
+ );
+ }
+
+ /**
+ * Check if mass action id field is correct.
+ */
+ public function testMassActionFieldIdIsCorrect()
+ {
+ $this->assertEquals(
+ 'subscriber_id',
+ $this->getMassActionBlock()->getMassactionIdField(),
+ 'Mass action id field is incorrect.'
+ );
+ }
+
+ /**
+ * Check if function returns correct result.
+ *
+ * @magentoDataFixture Magento/Newsletter/_files/subscribers.php
+ */
+ public function testMassActionBlockContainsCorrectIdList()
+ {
+ $this->assertEquals(
+ implode(',', $this->getAllSubscriberIdList()),
+ $this->getMassActionBlock()->getGridIdsJson(),
+ 'Function returns incorrect result.'
+ );
+ }
+
+ /**
+ * Retrieve mass action block.
+ *
+ * @return bool|\Magento\Backend\Block\Widget\Grid\Massaction
+ */
+ private function getMassActionBlock()
+ {
+ return $this->layout->getBlock('adminhtml.newslettrer.subscriber.grid.massaction');
+ }
+
+ /**
+ * Retrieve list of id of all subscribers.
+ *
+ * @return array
+ */
+ private function getAllSubscriberIdList()
+ {
+ /** @var \Magento\Framework\App\ResourceConnection $resourceConnection */
+ $resourceConnection = $this->objectManager->get(\Magento\Framework\App\ResourceConnection::class);
+ $select = $resourceConnection->getConnection()
+ ->select()
+ ->from($resourceConnection->getTableName('newsletter_subscriber'))
+ ->columns(['subscriber_id' => 'subscriber_id']);
+
+ return $resourceConnection->getConnection()->fetchCol($select);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php b/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php
index b9ba89ba53144..2d0020ba22680 100644
--- a/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php
+++ b/dev/tests/integration/testsuite/Magento/Search/Model/SynonymReaderTest.php
@@ -48,7 +48,22 @@ public static function loadByPhraseDataProvider()
['synonyms' => 'queen,monarch', 'store_id' => 1, 'website_id' => 0],
['synonyms' => 'british,english', 'store_id' => 1, 'website_id' => 0]
]
- ]
+ ],
+ [
+ 'query_value', []
+ ],
+ [
+ 'query_value+', []
+ ],
+ [
+ 'query_value-', []
+ ],
+ [
+ 'query_@value', []
+ ],
+ [
+ 'query_value+@', []
+ ],
];
}
diff --git a/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php b/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php
new file mode 100644
index 0000000000000..5c6c60866fafb
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method.php
@@ -0,0 +1,20 @@
+get(WriterInterface::class);
+
+$configWriter->save('carriers/ups/active', 1);
+
+$scopeConfig = $objectManager->get(ScopeConfigInterface::class);
+$scopeConfig->clean();
diff --git a/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php b/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php
new file mode 100644
index 0000000000000..6d7894879f97b
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Ups/_files/enable_ups_shipping_method_rollback.php
@@ -0,0 +1,16 @@
+create(WriterInterface::class);
+
+$configWriter->delete('carriers/ups/active');
diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php
new file mode 100644
index 0000000000000..b6055f14e79d2
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/Model/UrlFinderInterfaceTest.php
@@ -0,0 +1,71 @@
+urlFinder = Bootstrap::getObjectManager()->create(UrlFinderInterface::class);
+ }
+
+ /**
+ * @dataProvider findOneDataProvider
+ * @param string $requestPath
+ * @param string $targetPath
+ * @param int $redirectType
+ */
+ public function testFindOneByData(string $requestPath, string $targetPath, int $redirectType)
+ {
+ $data = [
+ UrlRewrite::REQUEST_PATH => $requestPath,
+ ];
+ $urlRewrite = $this->urlFinder->findOneByData($data);
+ $this->assertEquals($targetPath, $urlRewrite->getTargetPath());
+ $this->assertEquals($redirectType, $urlRewrite->getRedirectType());
+ }
+
+ /**
+ * @return array
+ */
+ public function findOneDataProvider(): array
+ {
+ return [
+ ['string', 'test_page1', 0],
+ ['string/', 'string', 301],
+ ['string_permanent', 'test_page1', 301],
+ ['string_permanent/', 'test_page1', 301],
+ ['string_temporary', 'test_page1', 302],
+ ['string_temporary/', 'test_page1', 302],
+ ['строка', 'test_page1', 0],
+ ['строка/', 'строка', 301],
+ [urlencode('строка'), 'test_page2', 0],
+ [urlencode('строка') . '/', urlencode('строка'), 301],
+ ['другая_строка', 'test_page1', 302],
+ ['другая_строка/', 'test_page1', 302],
+ [urlencode('другая_строка'), 'test_page1', 302],
+ [urlencode('другая_строка') . '/', 'test_page1', 302],
+ ['السلسلة', 'test_page1', 0],
+ [urlencode('السلسلة'), 'test_page1', 0],
+ ];
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php
new file mode 100644
index 0000000000000..9edc6507308ee
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites.php
@@ -0,0 +1,42 @@
+create(\Magento\UrlRewrite\Model\ResourceModel\UrlRewrite::class);
+foreach ($rewritesData as $rewriteData) {
+ list ($requestPath, $targetPath, $redirectType) = $rewriteData;
+ $rewrite = $objectManager->create(\Magento\UrlRewrite\Model\UrlRewrite::class);
+ $rewrite->setEntityType('custom')
+ ->setRequestPath($requestPath)
+ ->setTargetPath($targetPath)
+ ->setRedirectType($redirectType);
+ $rewriteResource->save($rewrite);
+}
diff --git a/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php
new file mode 100644
index 0000000000000..a98f947d614e0
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/UrlRewrite/_files/url_rewrites_rollback.php
@@ -0,0 +1,20 @@
+get(\Magento\Framework\Registry::class);
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+$urlRewriteCollection = $objectManager->create(\Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection::class);
+$collection = $urlRewriteCollection
+ ->addFieldToFilter('target_path', ['test_page1', 'test_page2'])
+ ->load()
+ ->walk('delete');
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
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/framework/Magento/TestFramework/Dependency/AnalyticsConfigRule.php b/dev/tests/static/framework/Magento/TestFramework/Dependency/AnalyticsConfigRule.php
new file mode 100644
index 0000000000000..b1a6da5e43822
--- /dev/null
+++ b/dev/tests/static/framework/Magento/TestFramework/Dependency/AnalyticsConfigRule.php
@@ -0,0 +1,43 @@
+]*class=[\'"]([^\'"]+)[\'"]#i', $contents, $matches)) {
+ $classes = array_pop($matches);
+ foreach ($classes as $class) {
+ $classParts = explode('\\', $class);
+ $module = implode('\\', array_slice($classParts, 0, 2));
+ if (strtolower($currentModule) !== strtolower($module)) {
+ $dependenciesInfo[] = [
+ 'module' => $module,
+ 'type' => RuleInterface::TYPE_HARD,
+ 'source' => $file,
+ ];
+ }
+ }
+ }
+
+ return $dependenciesInfo;
+ }
+}
diff --git a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php
index a4113abed8030..e2e0357a38f77 100644
--- a/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php
+++ b/dev/tests/static/testsuite/Magento/Test/Integrity/DependencyTest.php
@@ -16,6 +16,7 @@
use Magento\TestFramework\Dependency\LayoutRule;
use Magento\TestFramework\Dependency\PhpRule;
use Magento\TestFramework\Dependency\ReportsConfigRule;
+use Magento\TestFramework\Dependency\AnalyticsConfigRule;
use Magento\TestFramework\Dependency\VirtualType\VirtualTypeMapper;
/**
@@ -78,6 +79,17 @@ class DependencyTest extends \PHPUnit\Framework\TestCase
*/
protected static $_listRoutesXml = [];
+ /**
+ * List of analytics.xml
+ *
+ * Format: array(
+ * '{Module_Name}' => '{Filename}'
+ * )
+ *
+ * @var array
+ */
+ protected static $_listAnalyticsXml = [];
+
/**
* List of routers
*
@@ -176,6 +188,7 @@ public static function setUpBeforeClass()
self::_prepareListConfigXml();
self::_prepareListDbSchemaXml();
self::_prepareListRoutesXml();
+ self::_prepareListAnalyticsXml();
self::_prepareMapRouters();
self::_prepareMapLayoutBlocks();
@@ -240,6 +253,7 @@ protected static function _initRules()
),
new DiRule(new VirtualTypeMapper()),
new ReportsConfigRule($dbRuleTables),
+ new AnalyticsConfigRule(),
];
}
@@ -571,6 +585,20 @@ protected static function _prepareListRoutesXml()
}
}
+ /**
+ * Prepare list of analytics.xml files
+ */
+ protected static function _prepareListAnalyticsXml()
+ {
+ $files = Files::init()->getDbSchemaFiles('analytics.xml', [], false);
+ foreach ($files as $file) {
+ if (preg_match('/(?[A-Z][a-z]+)[_\/\\\\](?[A-Z][a-zA-Z]+)/', $file, $matches)) {
+ $module = $matches['namespace'] . '\\' . $matches['module'];
+ self::$_listAnalyticsXml[$module] = $file;
+ }
+ }
+ }
+
/**
* Prepare map of routers
*/
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/App/FrontControllerInterface.php b/lib/internal/Magento/Framework/App/FrontControllerInterface.php
index a552d88e68f50..afd3091097d19 100644
--- a/lib/internal/Magento/Framework/App/FrontControllerInterface.php
+++ b/lib/internal/Magento/Framework/App/FrontControllerInterface.php
@@ -8,7 +8,7 @@
/**
* Application front controller responsible for dispatching application requests.
* Front controller contains logic common for all actions.
- * Evary application area has own front controller
+ * Every application area has own front controller.
*
* @api
*/
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/Locale/Format.php b/lib/internal/Magento/Framework/Locale/Format.php
index ca50cdb2440f4..adcffe01b910e 100644
--- a/lib/internal/Magento/Framework/Locale/Format.php
+++ b/lib/internal/Magento/Framework/Locale/Format.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Framework\Locale;
+/**
+ * Price locale format.
+ */
class Format implements \Magento\Framework\Locale\FormatInterface
{
/**
@@ -38,7 +41,8 @@ public function __construct(
}
/**
- * Returns the first found number from a string
+ * Returns the first found number from a string.
+ *
* Parsing depends on given locale (grouping and decimal)
*
* Examples for input:
@@ -100,7 +104,7 @@ public function getPriceFormat($localeCode = null, $currencyCode = null)
}
$formatter = new \NumberFormatter(
- $localeCode . '@currency=' . $currency->getCode(),
+ $currency->getCode() ? $localeCode . '@currency=' . $currency->getCode() : $localeCode,
\NumberFormatter::CURRENCY
);
$format = $formatter->getPattern();
diff --git a/lib/internal/Magento/Framework/Locale/Resolver.php b/lib/internal/Magento/Framework/Locale/Resolver.php
index b401da8960f05..d058bfd41ab1a 100644
--- a/lib/internal/Magento/Framework/Locale/Resolver.php
+++ b/lib/internal/Magento/Framework/Locale/Resolver.php
@@ -6,7 +6,12 @@
namespace Magento\Framework\Locale;
use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\DeploymentConfig;
+use Magento\Framework\App\ObjectManager;
+/**
+ * Manages locale config information.
+ */
class Resolver implements ResolverInterface
{
/**
@@ -52,26 +57,34 @@ class Resolver implements ResolverInterface
*/
private $defaultLocalePath;
+ /**
+ * @var DeploymentConfig
+ */
+ private $deploymentConfig;
+
/**
* @param ScopeConfigInterface $scopeConfig
* @param string $defaultLocalePath
* @param string $scopeType
* @param mixed $locale
+ * @param DeploymentConfig|null $deploymentConfig
*/
public function __construct(
ScopeConfigInterface $scopeConfig,
$defaultLocalePath,
$scopeType,
- $locale = null
+ $locale = null,
+ DeploymentConfig $deploymentConfig = null
) {
$this->scopeConfig = $scopeConfig;
$this->defaultLocalePath = $defaultLocalePath;
$this->scopeType = $scopeType;
+ $this->deploymentConfig = $deploymentConfig ?: ObjectManager::getInstance()->create(DeploymentConfig::class);
$this->setLocale($locale);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getDefaultLocalePath()
{
@@ -79,7 +92,7 @@ public function getDefaultLocalePath()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setDefaultLocale($locale)
{
@@ -88,12 +101,15 @@ public function setDefaultLocale($locale)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getDefaultLocale()
{
if (!$this->defaultLocale) {
- $locale = $this->scopeConfig->getValue($this->getDefaultLocalePath(), $this->scopeType);
+ $locale = false;
+ if ($this->deploymentConfig->isAvailable() && $this->deploymentConfig->isDbAvailable()) {
+ $locale = $this->scopeConfig->getValue($this->getDefaultLocalePath(), $this->scopeType);
+ }
if (!$locale) {
$locale = self::DEFAULT_LOCALE;
}
@@ -103,7 +119,7 @@ public function getDefaultLocale()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setLocale($locale = null)
{
@@ -116,7 +132,7 @@ public function setLocale($locale = null)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getLocale()
{
@@ -127,7 +143,7 @@ public function getLocale()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function emulate($scopeId)
{
@@ -147,7 +163,7 @@ public function emulate($scopeId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function revert()
{
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/Oauth/Oauth.php b/lib/internal/Magento/Framework/Oauth/Oauth.php
index 5e48fb5ed30f9..919b0e4c86ba0 100644
--- a/lib/internal/Magento/Framework/Oauth/Oauth.php
+++ b/lib/internal/Magento/Framework/Oauth/Oauth.php
@@ -9,6 +9,9 @@
use Magento\Framework\Encryption\Helper\Security;
use Magento\Framework\Phrase;
+/**
+ * Authorization service.
+ */
class Oauth implements OauthInterface
{
/**
@@ -61,7 +64,7 @@ public static function getSupportedSignatureMethods()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getRequestToken($params, $requestUrl, $httpMethod = 'POST')
{
@@ -74,7 +77,7 @@ public function getRequestToken($params, $requestUrl, $httpMethod = 'POST')
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAccessToken($params, $requestUrl, $httpMethod = 'POST')
{
@@ -102,7 +105,7 @@ public function getAccessToken($params, $requestUrl, $httpMethod = 'POST')
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function validateAccessTokenRequest($params, $requestUrl, $httpMethod = 'POST')
{
@@ -125,7 +128,7 @@ public function validateAccessTokenRequest($params, $requestUrl, $httpMethod = '
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function validateAccessToken($accessToken)
{
@@ -133,7 +136,7 @@ public function validateAccessToken($accessToken)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function buildAuthorizationHeader(
$params,
@@ -199,7 +202,7 @@ protected function _validateSignature($params, $consumerSecret, $httpMethod, $re
);
if (!Security::compareStrings($calculatedSign, $params['oauth_signature'])) {
- throw new Exception(new Phrase('The signatire is invalid. Verify and try again.'));
+ throw new Exception(new Phrase('The signature is invalid. Verify and try again.'));
}
}
diff --git a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php
index f4d83ece134cf..e1b423d738a20 100644
--- a/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php
+++ b/lib/internal/Magento/Framework/Search/Adapter/Mysql/Adapter.php
@@ -7,6 +7,7 @@
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Ddl\Table;
+use Magento\Framework\DB\Select;
use Magento\Framework\Search\Adapter\Mysql\Aggregation\Builder as AggregationBuilder;
use Magento\Framework\Search\AdapterInterface;
use Magento\Framework\Search\RequestInterface;
@@ -49,6 +50,16 @@ class Adapter implements AdapterInterface
*/
private $temporaryStorageFactory;
+ /**
+ * Query Select Parts to be skipped when prepare query for count
+ *
+ * @var array
+ */
+ private $countSqlSkipParts = [
+ \Magento\Framework\DB\Select::LIMIT_COUNT => true,
+ \Magento\Framework\DB\Select::LIMIT_OFFSET => true,
+ ];
+
/**
* @param Mapper $mapper
* @param ResponseFactory $responseFactory
@@ -86,7 +97,7 @@ public function query(RequestInterface $request)
$response = [
'documents' => $documents,
'aggregations' => $aggregations,
- 'total' => count($documents)
+ 'total' => $this->getSize($query)
];
return $this->responseFactory->create($response);
}
@@ -115,4 +126,39 @@ private function getConnection()
{
return $this->resource->getConnection();
}
+
+ /**
+ * Get rows size
+ *
+ * @param Select $query
+ * @return int
+ */
+ private function getSize(Select $query): int
+ {
+ $sql = $this->getSelectCountSql($query);
+ $parentSelect = $this->getConnection()->select();
+ $parentSelect->from(['core_select' => $sql]);
+ $parentSelect->reset(\Magento\Framework\DB\Select::COLUMNS);
+ $parentSelect->columns('COUNT(*)');
+ $totalRecords = $this->getConnection()->fetchOne($parentSelect);
+
+ return intval($totalRecords);
+ }
+
+ /**
+ * Reset limit and offset
+ *
+ * @param Select $query
+ * @return Select
+ */
+ private function getSelectCountSql(Select $query): Select
+ {
+ foreach ($this->countSqlSkipParts as $part => $toSkip) {
+ if ($toSkip) {
+ $query->reset($part);
+ }
+ }
+
+ return $query;
+ }
}
diff --git a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php
index a35e1fd8b6151..fbb56361bfe71 100644
--- a/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php
+++ b/lib/internal/Magento/Framework/Search/Test/Unit/Adapter/Mysql/AdapterTest.php
@@ -161,10 +161,16 @@ public function testQuery()
$select = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
->disableOriginalConstructor()
->getMock();
- $this->connectionAdapter->expects($this->once())
+
+ $this->connectionAdapter->expects($this->exactly(2))
->method('select')
->willReturn($select);
+ $this->connectionAdapter->expects($this->once())
+ ->method('fetchOne')
+ ->with($select)
+ ->willReturn($selectResult['total']);
+
$table = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class)
->disableOriginalConstructor()
->getMock();
diff --git a/lib/internal/Magento/Framework/Setup/OldDbValidator.php b/lib/internal/Magento/Framework/Setup/OldDbValidator.php
index 4c224a6c713ef..018b010e8fe4a 100644
--- a/lib/internal/Magento/Framework/Setup/OldDbValidator.php
+++ b/lib/internal/Magento/Framework/Setup/OldDbValidator.php
@@ -13,7 +13,7 @@
/**
* Old Validator for database
*
- * Used in order to support backward compatability of modules that are installed
+ * Used in order to support backward compatibility of modules that are installed
* in old way (with Install/Upgrade Schema/Data scripts)
*/
class OldDbValidator implements UpToDateValidatorInterface
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/css/source/lib/_resets.less b/lib/web/css/source/lib/_resets.less
index 4499c314ce6ca..08d16842b849c 100644
--- a/lib/web/css/source/lib/_resets.less
+++ b/lib/web/css/source/lib/_resets.less
@@ -105,13 +105,6 @@
.lib-css(box-shadow, @focus__box-shadow);
}
}
-
- input[type="radio"],
- input[type="checkbox"] {
- &:focus {
- box-shadow: none;
- }
- }
}
//
diff --git a/lib/web/mage/adminhtml/globals.js b/lib/web/mage/adminhtml/globals.js
index 12c97fdfcd2c5..683606e576497 100644
--- a/lib/web/mage/adminhtml/globals.js
+++ b/lib/web/mage/adminhtml/globals.js
@@ -12,7 +12,7 @@ define([
/**
* Set of a temporary methods used to provide
- * backward compatability with a legacy code.
+ * backward compatibility with a legacy code.
*/
window.setLocation = function (url) {
window.location.href = url;
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) {