diff --git a/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php
index 86e691daa4213..af1ef4400e442 100644
--- a/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php
+++ b/app/code/Magento/AsynchronousOperations/Model/MassConsumer.php
@@ -14,7 +14,7 @@
use Magento\Framework\MessageQueue\MessageLockException;
use Magento\Framework\MessageQueue\ConnectionLostException;
use Magento\Framework\Exception\NotFoundException;
-use Magento\Framework\MessageQueue\CallbackInvoker;
+use Magento\Framework\MessageQueue\CallbackInvokerInterface;
use Magento\Framework\MessageQueue\ConsumerConfigurationInterface;
use Magento\Framework\MessageQueue\EnvelopeInterface;
use Magento\Framework\MessageQueue\QueueInterface;
@@ -30,7 +30,7 @@
class MassConsumer implements ConsumerInterface
{
/**
- * @var \Magento\Framework\MessageQueue\CallbackInvoker
+ * @var CallbackInvokerInterface
*/
private $invoker;
@@ -67,7 +67,7 @@ class MassConsumer implements ConsumerInterface
/**
* Initialize dependencies.
*
- * @param CallbackInvoker $invoker
+ * @param CallbackInvokerInterface $invoker
* @param ResourceConnection $resource
* @param MessageController $messageController
* @param ConsumerConfigurationInterface $configuration
@@ -76,7 +76,7 @@ class MassConsumer implements ConsumerInterface
* @param Registry $registry
*/
public function __construct(
- CallbackInvoker $invoker,
+ CallbackInvokerInterface $invoker,
ResourceConnection $resource,
MessageController $messageController,
ConsumerConfigurationInterface $configuration,
diff --git a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php
index eae92e1663fc8..89d468159c6e9 100644
--- a/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php
+++ b/app/code/Magento/AsynchronousOperations/Model/MassSchedule.php
@@ -20,6 +20,7 @@
use Psr\Log\LoggerInterface;
use Magento\AsynchronousOperations\Model\ResourceModel\Operation\OperationRepository;
use Magento\Authorization\Model\UserContextInterface;
+use Magento\Framework\Encryption\Encryptor;
/**
* Class MassSchedule used for adding multiple entities as Operations to Bulk Management with the status tracking
@@ -63,6 +64,11 @@ class MassSchedule
*/
private $userContext;
+ /**
+ * @var Encryptor
+ */
+ private $encryptor;
+
/**
* Initialize dependencies.
*
@@ -73,6 +79,7 @@ class MassSchedule
* @param LoggerInterface $logger
* @param OperationRepository $operationRepository
* @param UserContextInterface $userContext
+ * @param Encryptor|null $encryptor
*/
public function __construct(
IdentityGeneratorInterface $identityService,
@@ -81,7 +88,8 @@ public function __construct(
BulkManagementInterface $bulkManagement,
LoggerInterface $logger,
OperationRepository $operationRepository,
- UserContextInterface $userContext = null
+ UserContextInterface $userContext = null,
+ Encryptor $encryptor = null
) {
$this->identityService = $identityService;
$this->itemStatusInterfaceFactory = $itemStatusInterfaceFactory;
@@ -90,6 +98,7 @@ public function __construct(
$this->logger = $logger;
$this->operationRepository = $operationRepository;
$this->userContext = $userContext ?: ObjectManager::getInstance()->get(UserContextInterface::class);
+ $this->encryptor = $encryptor ?: ObjectManager::getInstance()->get(Encryptor::class);
}
/**
@@ -130,9 +139,13 @@ public function publishMass($topicName, array $entitiesArray, $groupId = null, $
$requestItem = $this->itemStatusInterfaceFactory->create();
try {
- $operations[] = $this->operationRepository->createByTopic($topicName, $entityParams, $groupId);
+ $operation = $this->operationRepository->createByTopic($topicName, $entityParams, $groupId);
+ $operations[] = $operation;
$requestItem->setId($key);
$requestItem->setStatus(ItemStatusInterface::STATUS_ACCEPTED);
+ $requestItem->setDataHash(
+ $this->encryptor->hash($operation->getSerializedData(), Encryptor::HASH_VERSION_SHA256)
+ );
$requestItems[] = $requestItem;
} catch (\Exception $exception) {
$this->logger->error($exception);
diff --git a/app/code/Magento/Authorizenet/Model/Directpost/Request.php b/app/code/Magento/Authorizenet/Model/Directpost/Request.php
index d518af4e04f55..10be4cd5febf6 100644
--- a/app/code/Magento/Authorizenet/Model/Directpost/Request.php
+++ b/app/code/Magento/Authorizenet/Model/Directpost/Request.php
@@ -194,7 +194,7 @@ public function setDataFromOrder(
/**
* Set sign hash into the request object.
*
- * All needed fields should be placed in the object fist.
+ * All needed fields should be placed in the object first.
*
* @return $this
*/
diff --git a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
index 9890a10a4ceb0..891b2a3ada724 100644
--- a/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
+++ b/app/code/Magento/Backend/Block/Widget/Grid/Massaction/AbstractMassaction.php
@@ -282,25 +282,23 @@ public function getGridIdsJson()
if (!$this->getUseSelectAll()) {
return '';
}
- /** @var \Magento\Framework\Data\Collection $allIdsCollection */
- $allIdsCollection = clone $this->getParentBlock()->getCollection();
- if ($this->getMassactionIdField()) {
- $massActionIdField = $this->getMassactionIdField();
+ /** @var \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection $collection */
+ $collection = clone $this->getParentBlock()->getCollection();
+
+ if ($collection instanceof AbstractDb) {
+ $idsSelect = clone $collection->getSelect();
+ $idsSelect->reset(\Magento\Framework\DB\Select::ORDER);
+ $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_COUNT);
+ $idsSelect->reset(\Magento\Framework\DB\Select::LIMIT_OFFSET);
+ $idsSelect->reset(\Magento\Framework\DB\Select::COLUMNS);
+ $idsSelect->columns($this->getMassactionIdField(), 'main_table');
+ $idList = $collection->getConnection()->fetchCol($idsSelect);
} else {
- $massActionIdField = $this->getParentBlock()->getMassactionIdField();
+ $idList = $collection->setPageSize(0)->getColumnValues($this->getMassactionIdField());
}
- if ($allIdsCollection instanceof AbstractDb) {
- $allIdsCollection->getSelect()->limit();
- $allIdsCollection->clear();
- }
-
- $gridIds = $allIdsCollection->setPageSize(0)->getColumnValues($massActionIdField);
- if (!empty($gridIds)) {
- return join(",", $gridIds);
- }
- return '';
+ return implode(',', $idList);
}
/**
diff --git a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php
index e8143b5f6b43a..e62b73f39241d 100644
--- a/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php
+++ b/app/code/Magento/Backend/Test/Unit/Block/Widget/Grid/MassactionTest.php
@@ -269,62 +269,6 @@ public function testGetGridIdsJsonWithoutUseSelectAll()
$this->assertEmpty($this->_block->getGridIdsJson());
}
- /**
- * @param array $items
- * @param string $result
- *
- * @dataProvider dataProviderGetGridIdsJsonWithUseSelectAll
- */
- public function testGetGridIdsJsonWithUseSelectAll(array $items, $result)
- {
- $this->_block->setUseSelectAll(true);
-
- if ($this->_block->getMassactionIdField()) {
- $massActionIdField = $this->_block->getMassactionIdField();
- } else {
- $massActionIdField = $this->_block->getParentBlock()->getMassactionIdField();
- }
-
- $collectionMock = $this->getMockBuilder(\Magento\Framework\Data\Collection::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->_gridMock->expects($this->once())
- ->method('getCollection')
- ->willReturn($collectionMock);
- $collectionMock->expects($this->once())
- ->method('setPageSize')
- ->with(0)
- ->willReturnSelf();
- $collectionMock->expects($this->once())
- ->method('getColumnValues')
- ->with($massActionIdField)
- ->willReturn($items);
-
- $this->assertEquals($result, $this->_block->getGridIdsJson());
- }
-
- /**
- * @return array
- */
- public function dataProviderGetGridIdsJsonWithUseSelectAll()
- {
- return [
- [
- [],
- '',
- ],
- [
- [1],
- '1',
- ],
- [
- [1, 2, 3],
- '1,2,3',
- ],
- ];
- }
-
/**
* @param string $itemId
* @param array|\Magento\Framework\DataObject $item
diff --git a/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml b/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml
index 69d545f12d075..0a1dcb0b626e6 100644
--- a/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml
+++ b/app/code/Magento/Backend/view/adminhtml/templates/pageactions.phtml
@@ -8,7 +8,7 @@
?>
getChildHtml()):?>
-
getUiId('content-header') ?>>
+
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
index 0730e7a7c5dc1..342bbc388f872 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Action/Attribute/Save.php
@@ -6,8 +6,10 @@
*/
namespace Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute;
-use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
+use Magento\AsynchronousOperations\Api\Data\OperationInterface;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Backend\App\Action;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
* Class Save
@@ -16,75 +18,68 @@
class Save extends \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute implements HttpPostActionInterface
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Flat\Processor
+ * @var \Magento\Framework\Bulk\BulkManagementInterface
*/
- protected $_productFlatIndexerProcessor;
+ private $bulkManagement;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\Processor
+ * @var \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory
*/
- protected $_productPriceIndexerProcessor;
+ private $operationFactory;
/**
- * Catalog product
- *
- * @var \Magento\Catalog\Helper\Product
+ * @var \Magento\Framework\DataObject\IdentityGeneratorInterface
*/
- protected $_catalogProduct;
+ private $identityService;
/**
- * @var \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory
+ * @var \Magento\Framework\Serialize\SerializerInterface
*/
- protected $stockItemFactory;
+ private $serializer;
/**
- * Stock Indexer
- *
- * @var \Magento\CatalogInventory\Model\Indexer\Stock\Processor
+ * @var \Magento\Authorization\Model\UserContextInterface
*/
- protected $_stockIndexerProcessor;
+ private $userContext;
/**
- * @var \Magento\Framework\Api\DataObjectHelper
+ * @var int
*/
- protected $dataObjectHelper;
+ private $bulkSize;
/**
* @param Action\Context $context
* @param \Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper
- * @param \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor
- * @param \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor
- * @param \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor
- * @param \Magento\Catalog\Helper\Product $catalogProduct
- * @param \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory
- * @param \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+ * @param \Magento\Framework\Bulk\BulkManagementInterface $bulkManagement
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory $operartionFactory
+ * @param \Magento\Framework\DataObject\IdentityGeneratorInterface $identityService
+ * @param \Magento\Framework\Serialize\SerializerInterface $serializer
+ * @param \Magento\Authorization\Model\UserContextInterface $userContext
+ * @param int $bulkSize
*/
public function __construct(
Action\Context $context,
\Magento\Catalog\Helper\Product\Edit\Action\Attribute $attributeHelper,
- \Magento\Catalog\Model\Indexer\Product\Flat\Processor $productFlatIndexerProcessor,
- \Magento\Catalog\Model\Indexer\Product\Price\Processor $productPriceIndexerProcessor,
- \Magento\CatalogInventory\Model\Indexer\Stock\Processor $stockIndexerProcessor,
- \Magento\Catalog\Helper\Product $catalogProduct,
- \Magento\CatalogInventory\Api\Data\StockItemInterfaceFactory $stockItemFactory,
- \Magento\Framework\Api\DataObjectHelper $dataObjectHelper
+ \Magento\Framework\Bulk\BulkManagementInterface $bulkManagement,
+ \Magento\AsynchronousOperations\Api\Data\OperationInterfaceFactory $operartionFactory,
+ \Magento\Framework\DataObject\IdentityGeneratorInterface $identityService,
+ \Magento\Framework\Serialize\SerializerInterface $serializer,
+ \Magento\Authorization\Model\UserContextInterface $userContext,
+ int $bulkSize = 100
) {
- $this->_productFlatIndexerProcessor = $productFlatIndexerProcessor;
- $this->_productPriceIndexerProcessor = $productPriceIndexerProcessor;
- $this->_stockIndexerProcessor = $stockIndexerProcessor;
- $this->_catalogProduct = $catalogProduct;
- $this->stockItemFactory = $stockItemFactory;
parent::__construct($context, $attributeHelper);
- $this->dataObjectHelper = $dataObjectHelper;
+ $this->bulkManagement = $bulkManagement;
+ $this->operationFactory = $operartionFactory;
+ $this->identityService = $identityService;
+ $this->serializer = $serializer;
+ $this->userContext = $userContext;
+ $this->bulkSize = $bulkSize;
}
/**
* Update product attributes
*
- * @return \Magento\Backend\Model\View\Result\Redirect
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @return \Magento\Framework\Controller\Result\Redirect
*/
public function execute()
{
@@ -93,128 +88,184 @@ public function execute()
}
/* Collect Data */
- $inventoryData = $this->getRequest()->getParam('inventory', []);
$attributesData = $this->getRequest()->getParam('attributes', []);
$websiteRemoveData = $this->getRequest()->getParam('remove_website_ids', []);
$websiteAddData = $this->getRequest()->getParam('add_website_ids', []);
- /* Prepare inventory data item options (use config settings) */
- $options = $this->_objectManager->get(\Magento\CatalogInventory\Api\StockConfigurationInterface::class)
- ->getConfigItemOptions();
- foreach ($options as $option) {
- if (isset($inventoryData[$option]) && !isset($inventoryData['use_config_' . $option])) {
- $inventoryData['use_config_' . $option] = 0;
- }
- }
+ $storeId = $this->attributeHelper->getSelectedStoreId();
+ $websiteId = $this->attributeHelper->getStoreWebsiteId($storeId);
+ $productIds = $this->attributeHelper->getProductIds();
+
+ $attributesData = $this->sanitizeProductAttributes($attributesData);
try {
- $storeId = $this->attributeHelper->getSelectedStoreId();
- if ($attributesData) {
- $dateFormat = $this->_objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class)
- ->getDateFormat(\IntlDateFormatter::SHORT);
-
- foreach ($attributesData as $attributeCode => $value) {
- $attribute = $this->_objectManager->get(\Magento\Eav\Model\Config::class)
- ->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode);
- if (!$attribute->getAttributeId()) {
- unset($attributesData[$attributeCode]);
- continue;
- }
- if ($attribute->getBackendType() == 'datetime') {
- if (!empty($value)) {
- $filterInput = new \Zend_Filter_LocalizedToNormalized(['date_format' => $dateFormat]);
- $filterInternal = new \Zend_Filter_NormalizedToLocalized(
- ['date_format' => \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT]
- );
- $value = $filterInternal->filter($filterInput->filter($value));
- } else {
- $value = null;
- }
- $attributesData[$attributeCode] = $value;
- } elseif ($attribute->getFrontendInput() == 'multiselect') {
- // Check if 'Change' checkbox has been checked by admin for this attribute
- $isChanged = (bool)$this->getRequest()->getPost('toggle_' . $attributeCode);
- if (!$isChanged) {
- unset($attributesData[$attributeCode]);
- continue;
- }
- if (is_array($value)) {
- $value = implode(',', $value);
- }
- $attributesData[$attributeCode] = $value;
- }
- }
+ $this->publish($attributesData, $websiteRemoveData, $websiteAddData, $storeId, $websiteId, $productIds);
+ $this->messageManager->addSuccessMessage(__('Message is added to queue'));
+ } catch (\Magento\Framework\Exception\LocalizedException $e) {
+ $this->messageManager->addErrorMessage($e->getMessage());
+ } catch (\Exception $e) {
+ $this->messageManager->addExceptionMessage(
+ $e,
+ __('Something went wrong while updating the product(s) attributes.')
+ );
+ }
- $this->_objectManager->get(\Magento\Catalog\Model\Product\Action::class)
- ->updateAttributes($this->attributeHelper->getProductIds(), $attributesData, $storeId);
- }
+ return $this->resultRedirectFactory->create()->setPath('catalog/product/', ['store' => $storeId]);
+ }
- if ($inventoryData) {
- // TODO why use ObjectManager?
- /** @var \Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry */
- $stockRegistry = $this->_objectManager
- ->create(\Magento\CatalogInventory\Api\StockRegistryInterface::class);
- /** @var \Magento\CatalogInventory\Api\StockItemRepositoryInterface $stockItemRepository */
- $stockItemRepository = $this->_objectManager
- ->create(\Magento\CatalogInventory\Api\StockItemRepositoryInterface::class);
- foreach ($this->attributeHelper->getProductIds() as $productId) {
- $stockItemDo = $stockRegistry->getStockItem(
- $productId,
- $this->attributeHelper->getStoreWebsiteId($storeId)
- );
- if (!$stockItemDo->getProductId()) {
- $inventoryData['product_id'] = $productId;
- }
-
- $stockItemId = $stockItemDo->getId();
- $this->dataObjectHelper->populateWithArray(
- $stockItemDo,
- $inventoryData,
- \Magento\CatalogInventory\Api\Data\StockItemInterface::class
+ /**
+ * Sanitize product attributes
+ *
+ * @param array $attributesData
+ *
+ * @return array
+ */
+ private function sanitizeProductAttributes($attributesData)
+ {
+ $dateFormat = $this->_objectManager->get(TimezoneInterface::class)->getDateFormat(\IntlDateFormatter::SHORT);
+ $config = $this->_objectManager->get(\Magento\Eav\Model\Config::class);
+
+ foreach ($attributesData as $attributeCode => $value) {
+ $attribute = $config->getAttribute(\Magento\Catalog\Model\Product::ENTITY, $attributeCode);
+ if (!$attribute->getAttributeId()) {
+ unset($attributesData[$attributeCode]);
+ continue;
+ }
+ if ($attribute->getBackendType() === 'datetime') {
+ if (!empty($value)) {
+ $filterInput = new \Zend_Filter_LocalizedToNormalized(['date_format' => $dateFormat]);
+ $filterInternal = new \Zend_Filter_NormalizedToLocalized(
+ ['date_format' => \Magento\Framework\Stdlib\DateTime::DATE_INTERNAL_FORMAT]
);
- $stockItemDo->setItemId($stockItemId);
- $stockItemRepository->save($stockItemDo);
+ $value = $filterInternal->filter($filterInput->filter($value));
+ } else {
+ $value = null;
}
- $this->_stockIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
- }
-
- if ($websiteAddData || $websiteRemoveData) {
- /* @var $actionModel \Magento\Catalog\Model\Product\Action */
- $actionModel = $this->_objectManager->get(\Magento\Catalog\Model\Product\Action::class);
- $productIds = $this->attributeHelper->getProductIds();
-
- if ($websiteRemoveData) {
- $actionModel->updateWebsites($productIds, $websiteRemoveData, 'remove');
+ $attributesData[$attributeCode] = $value;
+ } elseif ($attribute->getFrontendInput() === 'multiselect') {
+ // Check if 'Change' checkbox has been checked by admin for this attribute
+ $isChanged = (bool)$this->getRequest()->getPost('toggle_' . $attributeCode);
+ if (!$isChanged) {
+ unset($attributesData[$attributeCode]);
+ continue;
}
- if ($websiteAddData) {
- $actionModel->updateWebsites($productIds, $websiteAddData, 'add');
+ if (is_array($value)) {
+ $value = implode(',', $value);
}
-
- $this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
+ $attributesData[$attributeCode] = $value;
}
+ }
+ return $attributesData;
+ }
- $this->messageManager->addSuccessMessage(
- __('A total of %1 record(s) were updated.', count($this->attributeHelper->getProductIds()))
- );
-
- $this->_productFlatIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
+ /**
+ * Schedule new bulk
+ *
+ * @param array $attributesData
+ * @param array $websiteRemoveData
+ * @param array $websiteAddData
+ * @param int $storeId
+ * @param int $websiteId
+ * @param array $productIds
+ * @throws \Magento\Framework\Exception\LocalizedException
+ *
+ * @return void
+ */
+ private function publish(
+ $attributesData,
+ $websiteRemoveData,
+ $websiteAddData,
+ $storeId,
+ $websiteId,
+ $productIds
+ ):void {
+ $productIdsChunks = array_chunk($productIds, $this->bulkSize);
+ $bulkUuid = $this->identityService->generateId();
+ $bulkDescription = __('Update attributes for ' . count($productIds) . ' selected products');
+ $operations = [];
+ foreach ($productIdsChunks as $productIdsChunk) {
+ if ($websiteRemoveData || $websiteAddData) {
+ $dataToUpdate = [
+ 'website_assign' => $websiteAddData,
+ 'website_detach' => $websiteRemoveData
+ ];
+ $operations[] = $this->makeOperation(
+ 'Update website assign',
+ 'product_action_attribute.website.update',
+ $dataToUpdate,
+ $storeId,
+ $websiteId,
+ $productIdsChunk,
+ $bulkUuid
+ );
+ }
- if ($this->_catalogProduct->isDataForPriceIndexerWasChanged($attributesData)
- || !empty($websiteRemoveData)
- || !empty($websiteAddData)
- ) {
- $this->_productPriceIndexerProcessor->reindexList($this->attributeHelper->getProductIds());
+ if ($attributesData) {
+ $operations[] = $this->makeOperation(
+ 'Update product attributes',
+ 'product_action_attribute.update',
+ $attributesData,
+ $storeId,
+ $websiteId,
+ $productIdsChunk,
+ $bulkUuid
+ );
}
- } catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addErrorMessage($e->getMessage());
- } catch (\Exception $e) {
- $this->messageManager->addExceptionMessage(
- $e,
- __('Something went wrong while updating the product(s) attributes.')
+ }
+
+ if (!empty($operations)) {
+ $result = $this->bulkManagement->scheduleBulk(
+ $bulkUuid,
+ $operations,
+ $bulkDescription,
+ $this->userContext->getUserId()
);
+ if (!$result) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Something went wrong while processing the request.')
+ );
+ }
}
+ }
+
+ /**
+ * Make asynchronous operation
+ *
+ * @param string $meta
+ * @param string $queue
+ * @param array $dataToUpdate
+ * @param int $storeId
+ * @param int $websiteId
+ * @param array $productIds
+ * @param int $bulkUuid
+ *
+ * @return OperationInterface
+ */
+ private function makeOperation(
+ $meta,
+ $queue,
+ $dataToUpdate,
+ $storeId,
+ $websiteId,
+ $productIds,
+ $bulkUuid
+ ): OperationInterface {
+ $dataToEncode = [
+ 'meta_information' => $meta,
+ 'product_ids' => $productIds,
+ 'store_id' => $storeId,
+ 'website_id' => $websiteId,
+ 'attributes' => $dataToUpdate
+ ];
+ $data = [
+ 'data' => [
+ 'bulk_uuid' => $bulkUuid,
+ 'topic_name' => $queue,
+ 'serialized_data' => $this->serializer->serialize($dataToEncode),
+ 'status' => \Magento\Framework\Bulk\OperationInterface::STATUS_TYPE_OPEN,
+ ]
+ ];
- return $this->resultRedirectFactory->create()
- ->setPath('catalog/product/', ['store' => $this->attributeHelper->getSelectedStoreId()]);
+ return $this->operationFactory->create($data);
}
}
diff --git a/app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php b/app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php
new file mode 100644
index 0000000000000..dc24a3090481e
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Attribute/Backend/Consumer.php
@@ -0,0 +1,163 @@
+catalogProduct = $catalogProduct;
+ $this->productFlatIndexerProcessor = $productFlatIndexerProcessor;
+ $this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
+ $this->productAction = $action;
+ $this->logger = $logger;
+ $this->serializer = $serializer;
+ $this->operationManagement = $operationManagement;
+ $this->entityManager = $entityManager;
+ }
+
+ /**
+ * Process
+ *
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
+ * @throws \Exception
+ *
+ * @return void
+ */
+ public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
+ {
+ try {
+ $serializedData = $operation->getSerializedData();
+ $data = $this->serializer->unserialize($serializedData);
+ $this->execute($data);
+ } catch (\Zend_Db_Adapter_Exception $e) {
+ $this->logger->critical($e->getMessage());
+ if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
+ || $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
+ || $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
+ ) {
+ $status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } else {
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __(
+ 'Sorry, something went wrong during product attributes update. Please see log for details.'
+ );
+ }
+ } catch (NoSuchEntityException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = ($e instanceof TemporaryStateExceptionInterface)
+ ? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
+ : OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (LocalizedException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (\Exception $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
+ }
+
+ $operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
+ ->setErrorCode($errorCode ?? null)
+ ->setResultMessage($message ?? null);
+
+ $this->entityManager->save($operation);
+ }
+
+ /**
+ * Execute
+ *
+ * @param array $data
+ *
+ * @return void
+ */
+ private function execute($data): void
+ {
+ $this->productAction->updateAttributes($data['product_ids'], $data['attributes'], $data['store_id']);
+ if ($this->catalogProduct->isDataForPriceIndexerWasChanged($data['attributes'])) {
+ $this->productPriceIndexerProcessor->reindexList($data['product_ids']);
+ }
+
+ $this->productFlatIndexerProcessor->reindexList($data['product_ids']);
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php b/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php
new file mode 100644
index 0000000000000..32ba39d9afd98
--- /dev/null
+++ b/app/code/Magento/Catalog/Model/Attribute/Backend/ConsumerWebsiteAssign.php
@@ -0,0 +1,168 @@
+productFlatIndexerProcessor = $productFlatIndexerProcessor;
+ $this->productAction = $action;
+ $this->logger = $logger;
+ $this->serializer = $serializer;
+ $this->productPriceIndexerProcessor = $productPriceIndexerProcessor;
+ $this->entityManager = $entityManager;
+ }
+
+ /**
+ * Process
+ *
+ * @param \Magento\AsynchronousOperations\Api\Data\OperationInterface $operation
+ * @throws \Exception
+ *
+ * @return void
+ */
+ public function process(\Magento\AsynchronousOperations\Api\Data\OperationInterface $operation)
+ {
+ try {
+ $serializedData = $operation->getSerializedData();
+ $data = $this->serializer->unserialize($serializedData);
+ $this->execute($data);
+ } catch (\Zend_Db_Adapter_Exception $e) {
+ $this->logger->critical($e->getMessage());
+ if ($e instanceof \Magento\Framework\DB\Adapter\LockWaitException
+ || $e instanceof \Magento\Framework\DB\Adapter\DeadlockException
+ || $e instanceof \Magento\Framework\DB\Adapter\ConnectionException
+ ) {
+ $status = OperationInterface::STATUS_TYPE_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __($e->getMessage());
+ } else {
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __(
+ 'Sorry, something went wrong during product attributes update. Please see log for details.'
+ );
+ }
+ } catch (NoSuchEntityException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = ($e instanceof TemporaryStateExceptionInterface)
+ ? OperationInterface::STATUS_TYPE_RETRIABLY_FAILED
+ : OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (LocalizedException $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = $e->getMessage();
+ } catch (\Exception $e) {
+ $this->logger->critical($e->getMessage());
+ $status = OperationInterface::STATUS_TYPE_NOT_RETRIABLY_FAILED;
+ $errorCode = $e->getCode();
+ $message = __('Sorry, something went wrong during product attributes update. Please see log for details.');
+ }
+
+ $operation->setStatus($status ?? OperationInterface::STATUS_TYPE_COMPLETE)
+ ->setErrorCode($errorCode ?? null)
+ ->setResultMessage($message ?? null);
+
+ $this->entityManager->save($operation);
+ }
+
+ /**
+ * Update website in products
+ *
+ * @param array $productIds
+ * @param array $websiteRemoveData
+ * @param array $websiteAddData
+ *
+ * @return void
+ */
+ private function updateWebsiteInProducts($productIds, $websiteRemoveData, $websiteAddData): void
+ {
+ if ($websiteRemoveData) {
+ $this->productAction->updateWebsites($productIds, $websiteRemoveData, 'remove');
+ }
+ if ($websiteAddData) {
+ $this->productAction->updateWebsites($productIds, $websiteAddData, 'add');
+ }
+ }
+
+ /**
+ * Execute
+ *
+ * @param array $data
+ *
+ * @return void
+ */
+ private function execute($data): void
+ {
+ $this->updateWebsiteInProducts(
+ $data['product_ids'],
+ $data['attributes']['website_detach'],
+ $data['attributes']['website_assign']
+ );
+ $this->productPriceIndexerProcessor->reindexList($data['product_ids']);
+ $this->productFlatIndexerProcessor->reindexList($data['product_ids']);
+ }
+}
diff --git a/app/code/Magento/Catalog/Model/Product/Action.php b/app/code/Magento/Catalog/Model/Product/Action.php
index f78048424b42c..3863cf2457247 100644
--- a/app/code/Magento/Catalog/Model/Product/Action.php
+++ b/app/code/Magento/Catalog/Model/Product/Action.php
@@ -168,5 +168,7 @@ public function updateWebsites($productIds, $websiteIds, $type)
if (!$categoryIndexer->isScheduled()) {
$categoryIndexer->reindexList(array_unique($productIds));
}
+
+ $this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
}
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/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/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index bdbefbd234868..383797933074e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -428,6 +428,11 @@
ProductOptionDropDownWithLongValuesTitle
+
+
+ ProductOptionField
+ ProductOptionArea
+
api-virtual-product
virtual
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/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
index 70edb0ce3ea7d..17769c79677f7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateVirtualProductWithCustomOptionsSuiteAndImportOptionsTest.xml
@@ -139,7 +139,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
index d7607b4b269e8..4d581bae700d7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
@@ -54,7 +54,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
index c0eebd1512d6d..8a44c8093ca5e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
@@ -58,7 +58,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
index 845c47c0e4c20..bee13bec370da 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
@@ -52,7 +52,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
index c9a37ec40e8fa..318ab6555235e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
@@ -135,7 +135,7 @@
-
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
index d67d5b36109e6..34d85e7b46850 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateVirtualProductWithRegularPriceInStockWithCustomOptionsVisibleInSearchOnlyTest.xml
@@ -229,7 +229,7 @@
productPriceAmount
-
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php
deleted file mode 100644
index de44af7f58afc..0000000000000
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Action/Attribute/SaveTest.php
+++ /dev/null
@@ -1,258 +0,0 @@
-attributeHelper = $this->createPartialMock(
- \Magento\Catalog\Helper\Product\Edit\Action\Attribute::class,
- ['getProductIds', 'getSelectedStoreId', 'getStoreWebsiteId']
- );
-
- $this->dataObjectHelperMock = $this->getMockBuilder(\Magento\Framework\Api\DataObjectHelper::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->stockIndexerProcessor = $this->createPartialMock(
- \Magento\CatalogInventory\Model\Indexer\Stock\Processor::class,
- ['reindexList']
- );
-
- $resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->resultRedirectFactory = $this->getMockBuilder(\Magento\Backend\Model\View\Result\RedirectFactory::class)
- ->disableOriginalConstructor()
- ->setMethods(['create'])
- ->getMock();
- $this->resultRedirectFactory->expects($this->atLeastOnce())
- ->method('create')
- ->willReturn($resultRedirect);
-
- $this->prepareContext();
-
- $this->object = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject(
- \Magento\Catalog\Controller\Adminhtml\Product\Action\Attribute\Save::class,
- [
- 'context' => $this->context,
- 'attributeHelper' => $this->attributeHelper,
- 'stockIndexerProcessor' => $this->stockIndexerProcessor,
- 'dataObjectHelper' => $this->dataObjectHelperMock,
- ]
- );
- }
-
- /**
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
- */
- protected function prepareContext()
- {
- $this->stockItemRepository = $this->getMockBuilder(
- \Magento\CatalogInventory\Api\StockItemRepositoryInterface::class
- )->disableOriginalConstructor()->getMock();
-
- $this->request = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class)
- ->disableOriginalConstructor()->getMock();
- $this->response = $this->createMock(\Magento\Framework\App\Response\Http::class);
- $this->objectManager = $this->createMock(\Magento\Framework\ObjectManagerInterface::class);
- $this->eventManager = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
- $this->url = $this->createMock(\Magento\Framework\UrlInterface::class);
- $this->redirect = $this->createMock(\Magento\Framework\App\Response\RedirectInterface::class);
- $this->actionFlag = $this->createMock(\Magento\Framework\App\ActionFlag::class);
- $this->view = $this->createMock(\Magento\Framework\App\ViewInterface::class);
- $this->messageManager = $this->createMock(\Magento\Framework\Message\ManagerInterface::class);
- $this->session = $this->createMock(\Magento\Backend\Model\Session::class);
- $this->authorization = $this->createMock(\Magento\Framework\AuthorizationInterface::class);
- $this->auth = $this->createMock(\Magento\Backend\Model\Auth::class);
- $this->helper = $this->createMock(\Magento\Backend\Helper\Data::class);
- $this->backendUrl = $this->createMock(\Magento\Backend\Model\UrlInterface::class);
- $this->formKeyValidator = $this->createMock(\Magento\Framework\Data\Form\FormKey\Validator::class);
- $this->localeResolver = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class);
-
- $this->context = $this->context = $this->createPartialMock(\Magento\Backend\App\Action\Context::class, [
- 'getRequest',
- 'getResponse',
- 'getObjectManager',
- 'getEventManager',
- 'getUrl',
- 'getRedirect',
- 'getActionFlag',
- 'getView',
- 'getMessageManager',
- 'getSession',
- 'getAuthorization',
- 'getAuth',
- 'getHelper',
- 'getBackendUrl',
- 'getFormKeyValidator',
- 'getLocaleResolver',
- 'getResultRedirectFactory'
- ]);
- $this->context->expects($this->any())->method('getRequest')->willReturn($this->request);
- $this->context->expects($this->any())->method('getResponse')->willReturn($this->response);
- $this->context->expects($this->any())->method('getObjectManager')->willReturn($this->objectManager);
- $this->context->expects($this->any())->method('getEventManager')->willReturn($this->eventManager);
- $this->context->expects($this->any())->method('getUrl')->willReturn($this->url);
- $this->context->expects($this->any())->method('getRedirect')->willReturn($this->redirect);
- $this->context->expects($this->any())->method('getActionFlag')->willReturn($this->actionFlag);
- $this->context->expects($this->any())->method('getView')->willReturn($this->view);
- $this->context->expects($this->any())->method('getMessageManager')->willReturn($this->messageManager);
- $this->context->expects($this->any())->method('getSession')->willReturn($this->session);
- $this->context->expects($this->any())->method('getAuthorization')->willReturn($this->authorization);
- $this->context->expects($this->any())->method('getAuth')->willReturn($this->auth);
- $this->context->expects($this->any())->method('getHelper')->willReturn($this->helper);
- $this->context->expects($this->any())->method('getBackendUrl')->willReturn($this->backendUrl);
- $this->context->expects($this->any())->method('getFormKeyValidator')->willReturn($this->formKeyValidator);
- $this->context->expects($this->any())->method('getLocaleResolver')->willReturn($this->localeResolver);
- $this->context->expects($this->any())
- ->method('getResultRedirectFactory')
- ->willReturn($this->resultRedirectFactory);
-
- $this->product = $this->createPartialMock(
- \Magento\Catalog\Model\Product::class,
- ['isProductsHasSku', '__wakeup']
- );
-
- $this->stockItemService = $this->getMockBuilder(\Magento\CatalogInventory\Api\StockRegistryInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['getStockItem', 'saveStockItem'])
- ->getMockForAbstractClass();
- $this->stockItem = $this->getMockBuilder(\Magento\CatalogInventory\Api\Data\StockItemInterface::class)
- ->setMethods(['getId', 'getProductId'])
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
-
- $this->stockConfig = $this->getMockBuilder(\Magento\CatalogInventory\Api\StockConfigurationInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
-
- $this->objectManager->expects($this->any())->method('create')->will($this->returnValueMap([
- [\Magento\Catalog\Model\Product::class, [], $this->product],
- [\Magento\CatalogInventory\Api\StockRegistryInterface::class, [], $this->stockItemService],
- [\Magento\CatalogInventory\Api\StockItemRepositoryInterface::class, [], $this->stockItemRepository],
- ]));
-
- $this->objectManager->expects($this->any())->method('get')->will($this->returnValueMap([
- [\Magento\CatalogInventory\Api\StockConfigurationInterface::class, $this->stockConfig],
- ]));
- }
-
- public function testExecuteThatProductIdsAreObtainedFromAttributeHelper()
- {
- $this->attributeHelper->expects($this->any())->method('getProductIds')->will($this->returnValue([5]));
- $this->attributeHelper->expects($this->any())->method('getSelectedStoreId')->will($this->returnValue([1]));
- $this->attributeHelper->expects($this->any())->method('getStoreWebsiteId')->will($this->returnValue(1));
- $this->stockConfig->expects($this->any())->method('getConfigItemOptions')->will($this->returnValue([]));
- $this->dataObjectHelperMock->expects($this->any())
- ->method('populateWithArray')
- ->with($this->stockItem, $this->anything(), \Magento\CatalogInventory\Api\Data\StockItemInterface::class)
- ->willReturnSelf();
- $this->product->expects($this->any())->method('isProductsHasSku')->with([5])->will($this->returnValue(true));
- $this->stockItemService->expects($this->any())->method('getStockItem')->with(5, 1)
- ->will($this->returnValue($this->stockItem));
- $this->stockIndexerProcessor->expects($this->any())->method('reindexList')->with([5]);
-
- $this->request->expects($this->any())->method('getParam')->will($this->returnValueMap([
- ['inventory', [], [7]],
- ]));
-
- $this->messageManager->expects($this->never())->method('addErrorMessage');
- $this->messageManager->expects($this->never())->method('addExceptionMessage');
-
- $this->object->execute();
- }
-}
diff --git a/app/code/Magento/Catalog/composer.json b/app/code/Magento/Catalog/composer.json
index 44d051933909b..5c3ee3da8ca81 100644
--- a/app/code/Magento/Catalog/composer.json
+++ b/app/code/Magento/Catalog/composer.json
@@ -7,6 +7,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
+ "magento/module-authorization": "*",
+ "magento/module-asynchronous-operations": "*",
"magento/module-backend": "*",
"magento/module-catalog-inventory": "*",
"magento/module-catalog-rule": "*",
diff --git a/app/code/Magento/Catalog/etc/communication.xml b/app/code/Magento/Catalog/etc/communication.xml
new file mode 100644
index 0000000000000..1a957f6ac9fe5
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/communication.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/di.xml b/app/code/Magento/Catalog/etc/di.xml
index 7d2c3699ee2c2..49447447622f9 100644
--- a/app/code/Magento/Catalog/etc/di.xml
+++ b/app/code/Magento/Catalog/etc/di.xml
@@ -72,6 +72,7 @@
+
diff --git a/app/code/Magento/Catalog/etc/queue.xml b/app/code/Magento/Catalog/etc/queue.xml
new file mode 100644
index 0000000000000..137f34a5c1e25
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_consumer.xml b/app/code/Magento/Catalog/etc/queue_consumer.xml
new file mode 100644
index 0000000000000..d9e66ae69c10c
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_consumer.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_publisher.xml b/app/code/Magento/Catalog/etc/queue_publisher.xml
new file mode 100644
index 0000000000000..1606ea42ec0b3
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_publisher.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_topology.xml b/app/code/Magento/Catalog/etc/queue_topology.xml
new file mode 100644
index 0000000000000..bdac891afbdb8
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_topology.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
index 63248dd53fc1b..b9eea2b114634 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
@@ -56,9 +56,10 @@
-
+
-
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
index 3a4010f417104..1f5ae6b6905bc 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
@@ -115,6 +115,9 @@
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
index 09d9469cb093d..a587d71ba0e68 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
@@ -80,6 +80,9 @@
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
index aaeb0cd38cd99..6f64da4693692 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
@@ -107,9 +107,12 @@
+
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
index 597ee2336b21f..993f1c9cd9da2 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
@@ -123,9 +123,12 @@
+
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
index b1e723e5ee1ff..491d20604a08b 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
@@ -105,6 +105,9 @@
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
index e3c5cd78397f6..f671b54803e35 100644
--- a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
@@ -57,6 +57,9 @@
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php b/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
new file mode 100644
index 0000000000000..334d2b22edbfa
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
@@ -0,0 +1,159 @@
+stockIndexerProcessor = $stockIndexerProcessor;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->stockRegistry = $stockRegistry;
+ $this->stockItemRepository = $stockItemRepository;
+ $this->stockConfiguration = $stockConfiguration;
+ $this->attributeHelper = $attributeHelper;
+ $this->messageManager = $messageManager;
+ }
+
+ /**
+ * Around execute plugin
+ *
+ * @param Save $subject
+ * @param callable $proceed
+ *
+ * @return \Magento\Framework\Controller\ResultInterface
+ */
+ public function aroundExecute(Save $subject, callable $proceed)
+ {
+ try {
+ /** @var \Magento\Framework\App\RequestInterface $request */
+ $request = $subject->getRequest();
+ $inventoryData = $request->getParam('inventory', []);
+ $inventoryData = $this->addConfigSettings($inventoryData);
+
+ $storeId = $this->attributeHelper->getSelectedStoreId();
+ $websiteId = $this->attributeHelper->getStoreWebsiteId($storeId);
+ $productIds = $this->attributeHelper->getProductIds();
+
+ if (!empty($inventoryData)) {
+ $this->updateInventoryInProducts($productIds, $websiteId, $inventoryData);
+ }
+
+ return $proceed();
+ } catch (\Magento\Framework\Exception\LocalizedException $e) {
+ $this->messageManager->addErrorMessage($e->getMessage());
+ return $proceed();
+ } catch (\Exception $e) {
+ $this->messageManager->addExceptionMessage(
+ $e,
+ __('Something went wrong while updating the product(s) attributes.')
+ );
+ return $proceed();
+ }
+ }
+
+ /**
+ * Add config settings
+ *
+ * @param array $inventoryData
+ *
+ * @return array
+ */
+ private function addConfigSettings($inventoryData)
+ {
+ $options = $this->stockConfiguration->getConfigItemOptions();
+ foreach ($options as $option) {
+ $useConfig = 'use_config_' . $option;
+ if (isset($inventoryData[$option]) && !isset($inventoryData[$useConfig])) {
+ $inventoryData[$useConfig] = 0;
+ }
+ }
+ return $inventoryData;
+ }
+
+ /**
+ * Update inventory in products
+ *
+ * @param array $productIds
+ * @param int $websiteId
+ * @param array $inventoryData
+ *
+ * @return void
+ */
+ private function updateInventoryInProducts($productIds, $websiteId, $inventoryData): void
+ {
+ foreach ($productIds as $productId) {
+ $stockItemDo = $this->stockRegistry->getStockItem($productId, $websiteId);
+ if (!$stockItemDo->getProductId()) {
+ $inventoryData['product_id'] = $productId;
+ }
+ $stockItemId = $stockItemDo->getId();
+ $this->dataObjectHelper->populateWithArray($stockItemDo, $inventoryData, StockItemInterface::class);
+ $stockItemDo->setItemId($stockItemId);
+ $this->stockItemRepository->save($stockItemDo);
+ }
+ $this->stockIndexerProcessor->reindexList($productIds);
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml
index 51a5b46b59d22..e7d79c593b8c7 100644
--- a/app/code/Magento/CatalogInventory/etc/di.xml
+++ b/app/code/Magento/CatalogInventory/etc/di.xml
@@ -135,4 +135,7 @@
+
+
+
diff --git a/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php b/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php
new file mode 100644
index 0000000000000..f5851cf1e11b1
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Model/Products/AdaptUrlRewritesToVisibilityAttribute.php
@@ -0,0 +1,127 @@
+productCollectionFactory = $collectionFactory;
+ $this->urlRewriteGenerator = $urlRewriteGenerator;
+ $this->urlPersist = $urlPersist;
+ $this->urlPathGenerator = $urlPathGenerator;
+ }
+
+ /**
+ * Process Url Rewrites according to the products visibility attribute
+ *
+ * @param array $productIds
+ * @param int $visibility
+ * @throws UrlAlreadyExistsException
+ */
+ public function execute(array $productIds, int $visibility): void
+ {
+ $products = $this->getProductsByIds($productIds);
+
+ /** @var Product $product */
+ foreach ($products as $product) {
+ if ($visibility == Visibility::VISIBILITY_NOT_VISIBLE) {
+ $this->urlPersist->deleteByData(
+ [
+ UrlRewrite::ENTITY_ID => $product->getId(),
+ UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
+ ]
+ );
+ } elseif ($visibility !== Visibility::VISIBILITY_NOT_VISIBLE) {
+ $product->setVisibility($visibility);
+ $productUrlPath = $this->urlPathGenerator->getUrlPath($product);
+ $productUrlRewrite = $this->urlRewriteGenerator->generate($product);
+ $product->unsUrlPath();
+ $product->setUrlPath($productUrlPath);
+
+ try {
+ $this->urlPersist->replace($productUrlRewrite);
+ } catch (UrlAlreadyExistsException $e) {
+ throw new UrlAlreadyExistsException(
+ __(
+ 'Can not change the visibility of the product with SKU equals "%1". '
+ . 'URL key "%2" for specified store already exists.',
+ $product->getSku(),
+ $product->getUrlKey()
+ ),
+ $e,
+ $e->getCode(),
+ $e->getUrls()
+ );
+ }
+ }
+ }
+ }
+
+ /**
+ * Get Product Models by Id's
+ *
+ * @param array $productIds
+ * @return array
+ */
+ private function getProductsByIds(array $productIds): array
+ {
+ $productCollection = $this->productCollectionFactory->create();
+ $productCollection->addAttributeToSelect(ProductInterface::VISIBILITY);
+ $productCollection->addAttributeToSelect('url_key');
+ $productCollection->addFieldToFilter(
+ 'entity_id',
+ ['in' => array_unique($productIds)]
+ );
+
+ return $productCollection->getItems();
+ }
+}
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php
new file mode 100644
index 0000000000000..2337bb3646bd7
--- /dev/null
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeProductVisibilityObserver.php
@@ -0,0 +1,54 @@
+adaptUrlRewritesToVisibility = $adaptUrlRewritesToVisibility;
+ }
+
+ /**
+ * Generate urls for UrlRewrites and save it in storage
+ *
+ * @param Observer $observer
+ * @return void
+ * @throws UrlAlreadyExistsException
+ */
+ public function execute(Observer $observer)
+ {
+ $event = $observer->getEvent();
+ $attrData = $event->getAttributesData();
+ $productIds = $event->getProductIds();
+ $visibility = $attrData[ProductInterface::VISIBILITY] ?? 0;
+
+ if (!$visibility || !$productIds) {
+ return;
+ }
+
+ $this->adaptUrlRewritesToVisibility->execute($productIds, (int)$visibility);
+ }
+}
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/CatalogUrlRewrite/etc/events.xml b/app/code/Magento/CatalogUrlRewrite/etc/events.xml
index cc558fe81f16d..728442acf7a44 100644
--- a/app/code/Magento/CatalogUrlRewrite/etc/events.xml
+++ b/app/code/Magento/CatalogUrlRewrite/etc/events.xml
@@ -27,6 +27,9 @@
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
index a14ed147aae22..e7a5992ad8943 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
@@ -24,4 +24,17 @@
Flat Rate - Fixed
$
+
+ 123.00
+ 3
+ 369.00
+ $
+
+
+ 100.00
+ 20
+ 11
+ 1,320.00
+ $
+
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/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml b/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml
index 3f742de0177da..122160f1a10cd 100644
--- a/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml
+++ b/app/code/Magento/CheckoutAgreements/view/frontend/layout/multishipping_checkout_overview.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml b/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml
index 3400770f5cee8..33227f0cdce3c 100644
--- a/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml
+++ b/app/code/Magento/CheckoutAgreements/view/frontend/templates/multishipping_agreements.phtml
@@ -4,6 +4,7 @@
* See COPYING.txt for license details.
*/
+// @deprecated
// @codingStandardsIgnoreFile
?>
diff --git a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
index b2ef78bab9909..ca563bd9d8f61 100644
--- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
+++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
@@ -270,7 +270,8 @@ public function getDirsCollection($path)
$collection = $this->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/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/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/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/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/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/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/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/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/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/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/Multishipping/view/frontend/web/js/overview.js b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
index 9b867cd7217b1..3a6d73e304974 100644
--- a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
+++ b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
@@ -15,7 +15,7 @@ define([
opacity: 0.5, // CSS opacity for the 'Place Order' button when it's clicked and then disabled.
pleaseWaitLoader: 'span.please-wait', // 'Submitting order information...' Ajax loader.
placeOrderSubmit: 'button[type="submit"]', // The 'Place Order' button.
- agreements: '#checkout-agreements' // Container for all of the checkout agreements and terms/conditions
+ agreements: '.checkout-agreements' // Container for all of the checkout agreements and terms/conditions
},
/**
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/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
index da7ab97a1b211..d89a118bff94b 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
@@ -769,11 +769,12 @@ public function addOrdersCount()
*/
public function addRevenueToSelect($convertCurrency = false)
{
- $expr = $this->getTotalsExpression(
+ $expr = $this->getTotalsExpressionWithDiscountRefunded(
!$convertCurrency,
$this->getConnection()->getIfNullSql('main_table.base_subtotal_refunded', 0),
$this->getConnection()->getIfNullSql('main_table.base_subtotal_canceled', 0),
- $this->getConnection()->getIfNullSql('main_table.base_discount_canceled', 0)
+ $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_refunded)', 0),
+ $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_canceled)', 0)
);
$this->getSelect()->columns(['revenue' => $expr]);
@@ -791,11 +792,12 @@ public function addSumAvgTotals($storeId = 0)
/**
* calculate average and total amount
*/
- $expr = $this->getTotalsExpression(
+ $expr = $this->getTotalsExpressionWithDiscountRefunded(
$storeId,
$this->getConnection()->getIfNullSql('main_table.base_subtotal_refunded', 0),
$this->getConnection()->getIfNullSql('main_table.base_subtotal_canceled', 0),
- $this->getConnection()->getIfNullSql('main_table.base_discount_canceled', 0)
+ $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_refunded)', 0),
+ $this->getConnection()->getIfNullSql('ABS(main_table.base_discount_canceled)', 0)
);
$this->getSelect()->columns(
@@ -808,13 +810,15 @@ public function addSumAvgTotals($storeId = 0)
}
/**
- * Get SQL expression for totals
+ * Get SQL expression for totals.
*
* @param int $storeId
* @param string $baseSubtotalRefunded
* @param string $baseSubtotalCanceled
* @param string $baseDiscountCanceled
* @return string
+ * @deprecated
+ * @see getTotalsExpressionWithDiscountRefunded
*/
protected function getTotalsExpression(
$storeId,
@@ -825,10 +829,40 @@ protected function getTotalsExpression(
$template = ($storeId != 0)
? '(main_table.base_subtotal - %2$s - %1$s - ABS(main_table.base_discount_amount) - %3$s)'
: '((main_table.base_subtotal - %1$s - %2$s - ABS(main_table.base_discount_amount) + %3$s) '
- . ' * main_table.base_to_global_rate)';
+ . ' * main_table.base_to_global_rate)';
return sprintf($template, $baseSubtotalRefunded, $baseSubtotalCanceled, $baseDiscountCanceled);
}
+ /**
+ * Get SQL expression for totals with discount refunded.
+ *
+ * @param int $storeId
+ * @param string $baseSubtotalRefunded
+ * @param string $baseSubtotalCanceled
+ * @param string $baseDiscountRefunded
+ * @param string $baseDiscountCanceled
+ * @return string
+ */
+ private function getTotalsExpressionWithDiscountRefunded(
+ $storeId,
+ $baseSubtotalRefunded,
+ $baseSubtotalCanceled,
+ $baseDiscountRefunded,
+ $baseDiscountCanceled
+ ) {
+ $template = ($storeId != 0)
+ ? '(main_table.base_subtotal - %2$s - %1$s - (ABS(main_table.base_discount_amount) - %3$s - %4$s))'
+ : '((main_table.base_subtotal - %1$s - %2$s - (ABS(main_table.base_discount_amount) - %3$s - %4$s)) '
+ . ' * main_table.base_to_global_rate)';
+ return sprintf(
+ $template,
+ $baseSubtotalRefunded,
+ $baseSubtotalCanceled,
+ $baseDiscountRefunded,
+ $baseDiscountCanceled
+ );
+ }
+
/**
* Sort order by total amount
*
diff --git a/app/code/Magento/Review/etc/acl.xml b/app/code/Magento/Review/etc/acl.xml
index 09b80750da14d..46fdb20dee4a1 100644
--- a/app/code/Magento/Review/etc/acl.xml
+++ b/app/code/Magento/Review/etc/acl.xml
@@ -16,8 +16,8 @@
-
+
diff --git a/app/code/Magento/Review/etc/adminhtml/menu.xml b/app/code/Magento/Review/etc/adminhtml/menu.xml
index 0a2e49450e0cf..7376329471921 100644
--- a/app/code/Magento/Review/etc/adminhtml/menu.xml
+++ b/app/code/Magento/Review/etc/adminhtml/menu.xml
@@ -8,8 +8,8 @@
diff --git a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
index 46e794a1954cf..45eee0a4001d1 100644
--- a/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
+++ b/app/code/Magento/Search/Model/ResourceModel/SynonymReader.php
@@ -87,7 +87,7 @@ private function queryByPhrase($phrase)
{
$matchQuery = $this->fullTextSelect->getMatchQuery(
['synonyms' => 'synonyms'],
- $phrase,
+ $this->escapePhrase($phrase),
Fulltext::FULLTEXT_MODE_BOOLEAN
);
$query = $this->getConnection()->select()->from(
@@ -97,6 +97,18 @@ private function queryByPhrase($phrase)
return $this->getConnection()->fetchAll($query);
}
+ /**
+ * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error.
+ *
+ * @see https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html
+ * @param string $phrase
+ * @return string
+ */
+ private function escapePhrase(string $phrase): string
+ {
+ return preg_replace('/@+|[@+-]+$/', '', $phrase);
+ }
+
/**
* A private helper function to retrieve matching synonym groups per scope
*
diff --git a/app/code/Magento/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/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/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/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/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/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/web/css/source/forms/_fields.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less
index d8fd1db5ced47..5698afdaac7ae 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/_fields.less
@@ -545,7 +545,6 @@
& > .admin__field-label {
#mix-grid .column(@field-label-grid__column, @field-grid__columns);
cursor: pointer;
- background: @color-white;
left: 0;
position: absolute;
top: 0;
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/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/GetAvailablePaymentMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php
index a7287ada093b8..ba640bc3402ba 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php
@@ -7,10 +7,8 @@
namespace Magento\GraphQl\Quote\Customer;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\Integration\Api\CustomerTokenServiceInterface;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
@@ -25,19 +23,9 @@ class GetAvailablePaymentMethodsTest extends GraphQlAbstract
private $customerTokenService;
/**
- * @var QuoteResource
+ * @var GetMaskedQuoteIdByReservedOrderId
*/
- private $quoteResource;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
- */
- private $quoteIdToMaskedId;
+ private $getMaskedQuoteIdByReservedOrderId;
/**
* @inheritdoc
@@ -45,55 +33,60 @@ class GetAvailablePaymentMethodsTest extends GraphQlAbstract
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
- $this->quoteResource = $objectManager->get(QuoteResource::class);
- $this->quoteFactory = $objectManager->get(QuoteFactory::class);
- $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
*/
- public function testGetCartWithPaymentMethods()
+ public function testGetAvailablePaymentMethods()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_item_with_items');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = $this->getQuery($maskedQuoteId);
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
self::assertArrayHasKey('cart', $response);
+ self::assertArrayHasKey('available_payment_methods', $response['cart']);
+
self::assertEquals('checkmo', $response['cart']['available_payment_methods'][0]['code']);
self::assertEquals('Check / Money order', $response['cart']['available_payment_methods'][0]['title']);
- self::assertGreaterThan(
- 0,
- count($response['cart']['available_payment_methods']),
- 'There are no available payment methods for customer cart!'
- );
}
/**
+ * _security
* @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
*/
- public function testGetPaymentMethodsFromGuestCart()
+ public function testGetAvailablePaymentMethodsFromGuestCart()
{
- $guestQuoteMaskedId = $this->getMaskedQuoteIdByReservedOrderId(
- 'test_order_with_virtual_product_without_address'
- );
- $query = $this->getQuery($guestQuoteMaskedId);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
$this->expectExceptionMessage(
- "The current user cannot perform operations on cart \"$guestQuoteMaskedId\""
+ "The current user cannot perform operations on cart \"$maskedQuoteId\""
);
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
}
/**
+ * _security
* @magentoApiDataFixture Magento/Customer/_files/three_customers.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
*/
- public function testGetPaymentMethodsFromAnotherCustomerCart()
+ public function testGetAvailablePaymentMethodsFromAnotherCustomerCart()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_item_with_items');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = $this->getQuery($maskedQuoteId);
$this->expectExceptionMessage(
@@ -103,24 +96,31 @@ public function testGetPaymentMethodsFromAnotherCustomerCart()
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
- * @magentoApiDataFixture Magento/Payment/_files/disable_all_active_payment_methods.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php
*/
- public function testGetPaymentMethodsIfPaymentsAreNotSet()
+ public function testGetAvailablePaymentMethodsIfPaymentsAreNotPresent()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_item_with_items');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = $this->getQuery($maskedQuoteId);
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
- self::assertEquals(0, count($response['cart']['available_payment_methods']));
+ self::assertArrayHasKey('cart', $response);
+ self::assertArrayHasKey('available_payment_methods', $response['cart']);
+ self::assertEmpty($response['cart']['available_payment_methods']);
}
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
+ *
* @expectedException \Exception
* @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
*/
- public function testGetPaymentMethodsOfNonExistentCart()
+ public function testGetAvailablePaymentMethodsOfNonExistentCart()
{
$maskedQuoteId = 'non_existent_masked_id';
$query = $this->getQuery($maskedQuoteId);
@@ -132,9 +132,8 @@ public function testGetPaymentMethodsOfNonExistentCart()
* @param string $maskedQuoteId
* @return string
*/
- private function getQuery(
- string $maskedQuoteId
- ): string {
+ private function getQuery(string $maskedQuoteId): string
+ {
return <<
'Bearer ' . $customerToken];
return $headerMap;
}
-
- /**
- * @param string $reservedOrderId
- * @return string
- */
- private function getMaskedQuoteIdByReservedOrderId(string $reservedOrderId): string
- {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php
index 4b1509eef354e..eb62b8c92f310 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php
@@ -7,10 +7,8 @@
namespace Magento\GraphQl\Quote\Customer;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\Integration\Api\CustomerTokenServiceInterface;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
@@ -20,19 +18,9 @@
class GetCartTest extends GraphQlAbstract
{
/**
- * @var QuoteResource
+ * @var GetMaskedQuoteIdByReservedOrderId
*/
- private $quoteResource;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
- */
- private $quoteIdToMaskedId;
+ private $getMaskedQuoteIdByReservedOrderId;
/**
* @var CustomerTokenServiceInterface
@@ -42,19 +30,22 @@ class GetCartTest extends GraphQlAbstract
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
- $this->quoteResource = $objectManager->get(QuoteResource::class);
- $this->quoteFactory = $objectManager->get(QuoteFactory::class);
- $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php
*/
public function testGetCart()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_item_with_items');
- $query = $this->getCartQuery($maskedQuoteId);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
@@ -63,22 +54,23 @@ public function testGetCart()
self::assertCount(2, $response['cart']['items']);
self::assertNotEmpty($response['cart']['items'][0]['id']);
- self::assertEquals($response['cart']['items'][0]['qty'], 2);
- self::assertEquals($response['cart']['items'][0]['product']['sku'], 'simple');
+ self::assertEquals(2, $response['cart']['items'][0]['qty']);
+ self::assertEquals('simple', $response['cart']['items'][0]['product']['sku']);
self::assertNotEmpty($response['cart']['items'][1]['id']);
- self::assertEquals($response['cart']['items'][1]['qty'], 1);
- self::assertEquals($response['cart']['items'][1]['product']['sku'], 'simple_one');
+ self::assertEquals(2, $response['cart']['items'][1]['qty']);
+ self::assertEquals('virtual-product', $response['cart']['items'][1]['product']['sku']);
}
/**
+ * _security
* @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
*/
public function testGetGuestCart()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
- $query = $this->getCartQuery($maskedQuoteId);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
$this->expectExceptionMessage(
"The current user cannot perform operations on cart \"{$maskedQuoteId}\""
@@ -87,13 +79,14 @@ public function testGetGuestCart()
}
/**
+ * _security
* @magentoApiDataFixture Magento/Customer/_files/three_customers.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
*/
public function testGetAnotherCustomerCart()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_item_with_items');
- $query = $this->getCartQuery($maskedQuoteId);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
$this->expectExceptionMessage(
"The current user cannot perform operations on cart \"{$maskedQuoteId}\""
@@ -103,33 +96,30 @@ public function testGetAnotherCustomerCart()
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
+ *
* @expectedException \Exception
* @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
*/
public function testGetNonExistentCart()
{
$maskedQuoteId = 'non_existent_masked_id';
- $query = $this->getCartQuery($maskedQuoteId);
+ $query = $this->getQuery($maskedQuoteId);
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
* @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php
+ *
* @expectedException \Exception
* @expectedExceptionMessage Current user does not have an active cart.
*/
public function testGetInactiveCart()
{
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
- $quote->setCustomerId(1);
- $quote->setIsActive(false);
- $this->quoteResource->save($quote);
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
-
- $query = $this->getCartQuery($maskedQuoteId);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
}
@@ -138,12 +128,11 @@ public function testGetInactiveCart()
* @param string $maskedQuoteId
* @return string
*/
- private function getCartQuery(
- string $maskedQuoteId
- ) : string {
+ private function getQuery(string $maskedQuoteId): string
+ {
return <<quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
-
/**
* @param string $username
* @param string $password
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..129375debe068 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
@@ -7,6 +7,7 @@
namespace Magento\GraphQl\Quote\Customer;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\Integration\Api\CustomerTokenServiceInterface;
use Magento\Quote\Model\QuoteFactory;
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
@@ -19,6 +20,11 @@
*/
class SetBillingAddressOnCartTest extends GraphQlAbstract
{
+ /**
+ * @var GetMaskedQuoteIdByReservedOrderId
+ */
+ private $getMaskedQuoteIdByReservedOrderId;
+
/**
* @var QuoteResource
*/
@@ -42,6 +48,7 @@ class SetBillingAddressOnCartTest extends GraphQlAbstract
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
$this->quoteResource = $objectManager->get(QuoteResource::class);
$this->quoteFactory = $objectManager->get(QuoteFactory::class);
$this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
@@ -49,12 +56,14 @@ protected function setUp()
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
* @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
*/
public function testSetNewBillingAddress()
{
- $maskedQuoteId = $this->assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
}
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
+ * @dataProvider dataProviderSetWithoutRequiredParameters
+ * @param string $input
+ * @param string $message
+ * @throws \Exception
+ */
+ public function testSetBillingAddressWithoutRequiredParameters(string $input, string $message)
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $input = str_replace('cart_id_value', $maskedQuoteId, $input);
+
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderSetWithoutRequiredParameters(): array
+ {
+ 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'
+ ]
+ ];
+ }
+
/**
* Verify the all the whitelisted fields for a New Address Object
*
@@ -480,28 +558,16 @@ private function getHeaderMap(string $username = 'customer@example.com', string
}
/**
- * @param string $reversedQuoteId
- * @return string
- */
- private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string
- {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
-
- /**
- * @param string $reversedQuoteId
+ * @param string $reversedOrderId
* @param int $customerId
* @return string
*/
private function assignQuoteToCustomer(
- string $reversedQuoteId = 'test_order_with_simple_product_without_address',
+ string $reversedOrderId = 'test_order_with_simple_product_without_address',
int $customerId = 1
): string {
$quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
+ $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id');
$quote->setCustomerId($customerId);
$this->quoteResource->save($quote);
return $this->quoteIdToMaskedId->execute((int)$quote->getId());
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..450a22dd6e9c7 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
@@ -7,11 +7,10 @@
namespace Magento\GraphQl\Quote\Customer;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\Integration\Api\CustomerTokenServiceInterface;
+use Magento\OfflinePayments\Model\Cashondelivery;
use Magento\OfflinePayments\Model\Checkmo;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
@@ -21,24 +20,14 @@
class SetPaymentMethodOnCartTest extends GraphQlAbstract
{
/**
- * @var CustomerTokenServiceInterface
- */
- private $customerTokenService;
-
- /**
- * @var QuoteResource
+ * @var GetMaskedQuoteIdByReservedOrderId
*/
- private $quoteResource;
+ private $getMaskedQuoteIdByReservedOrderId;
/**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
+ * @var CustomerTokenServiceInterface
*/
- private $quoteIdToMaskedId;
+ private $customerTokenService;
/**
* @inheritdoc
@@ -46,21 +35,23 @@ class SetPaymentMethodOnCartTest extends GraphQlAbstract
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
- $this->quoteResource = $objectManager->get(QuoteResource::class);
- $this->quoteFactory = $objectManager->get(QuoteFactory::class);
- $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
*/
- public function testSetPaymentWithVirtualProduct()
+ public function testSetPaymentOnCartWithSimpleProduct()
{
$methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_virtual_product');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
self::assertArrayHasKey('setPaymentMethodOnCart', $response);
@@ -70,14 +61,35 @@ public function testSetPaymentWithVirtualProduct()
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage The shipping address is missing. Set the address and try again.
+ */
+ public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress()
+ {
+ $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php
*/
- public function testSetPaymentWithSimpleProduct()
+ public function testSetPaymentOnCartWithVirtualProduct()
{
$methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
self::assertArrayHasKey('setPaymentMethodOnCart', $response);
@@ -88,103 +100,158 @@ public function testSetPaymentWithSimpleProduct()
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ *
* @expectedException \Exception
- * @expectedExceptionMessage The shipping address is missing. Set the address and try again.
+ * @expectedExceptionMessage The requested Payment Method is not available.
*/
- public function testSetPaymentWithSimpleProductWithoutAddress()
+ public function testSetNonExistentPaymentMethod()
{
- $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->assignQuoteToCustomer('test_order_with_simple_product_without_address', 1);
+ $methodCode = 'noway';
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ *
* @expectedException \Exception
- * @expectedExceptionMessage The requested Payment Method is not available.
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
*/
- public function testSetNonExistingPaymentMethod()
+ public function testSetPaymentOnNonExistentCart()
{
- $methodCode = 'noway';
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1');
-
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $maskedQuoteId = 'non_existent_masked_id';
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
}
/**
+ * _security
* @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
*/
public function testSetPaymentMethodToGuestCart()
{
$methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$this->expectExceptionMessage(
"The current user cannot perform operations on cart \"$maskedQuoteId\""
);
-
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
}
/**
+ * _security
* @magentoApiDataFixture Magento/Customer/_files/three_customers.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
*/
public function testSetPaymentMethodToAnotherCustomerCart()
{
$methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$this->expectExceptionMessage(
"The current user cannot perform operations on cart \"$maskedQuoteId\""
);
-
$this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com'));
}
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @expectedException \Exception
- * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
+ * @param string $input
+ * @param string $message
+ * @throws \Exception
+ * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters
*/
- public function testPaymentMethodOnNonExistentCart()
+ public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message)
{
- $maskedQuoteId = 'non_existent_masked_id';
$query = <<expectExceptionMessage($message);
$this->graphQlQuery($query, [], '', $this->getHeaderMap());
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_payment_saved.php
+ * @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/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php
*/
public function testReSetPayment()
{
- /** @var \Magento\Quote\Model\Quote $quote */
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1_with_payment');
- $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $methodCode = Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE;
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
self::assertArrayHasKey('setPaymentMethodOnCart', $response);
self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']);
self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']);
+ self::assertArrayHasKey('code', $response['setPaymentMethodOnCart']['cart']['selected_payment_method']);
self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']);
}
@@ -193,7 +260,7 @@ public function testReSetPayment()
* @param string $methodCode
* @return string
*/
- private function prepareMutationQuery(
+ private function getQuery(
string $maskedQuoteId,
string $methodCode
) : string {
@@ -215,34 +282,6 @@ private function prepareMutationQuery(
QUERY;
}
- /**
- * @param string $reversedQuoteId
- * @return string
- */
- private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string
- {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
-
- /**
- * @param string $reversedQuoteId
- * @param int $customerId
- * @return string
- */
- private function assignQuoteToCustomer(
- string $reversedQuoteId,
- int $customerId
- ): string {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
- $quote->setCustomerId($customerId);
- $this->quoteResource->save($quote);
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
-
/**
* @param string $username
* @param string $password
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php
index ae184b8930c07..5ff29d20b34d7 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php
@@ -7,6 +7,7 @@
namespace Magento\GraphQl\Quote\Customer;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\Integration\Api\CustomerTokenServiceInterface;
use Magento\Quote\Model\QuoteFactory;
use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
@@ -34,6 +35,11 @@ class SetShippingAddressOnCartTest extends GraphQlAbstract
*/
private $quoteIdToMaskedId;
+ /**
+ * @var GetMaskedQuoteIdByReservedOrderId
+ */
+ private $getMaskedQuoteIdByReservedOrderId;
+
/**
* @var CustomerTokenServiceInterface
*/
@@ -45,16 +51,19 @@ protected function setUp()
$this->quoteResource = $objectManager->get(QuoteResource::class);
$this->quoteFactory = $objectManager->get(QuoteFactory::class);
$this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
}
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
*/
- public function testSetNewShippingAddress()
+ public function testSetNewShippingAddressOnCartWithSimpleProduct()
{
- $maskedQuoteId = $this->assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer('test_order_with_virtual_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<expectExceptionMessage(
"The current user cannot perform operations on cart \"$maskedQuoteId\""
);
-
$this->graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com'));
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
* @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
* @dataProvider dataProviderUpdateWithMissedRequiredParameters
* @param string $input
* @param string $message
@@ -357,7 +382,7 @@ public function testSetShippingAddressToAnotherCustomerCart()
*/
public function testSetNewShippingAddressWithMissedRequiredParameters(string $input, string $message)
{
- $maskedQuoteId = $this->assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = << [
@@ -402,13 +427,16 @@ public function dataProviderUpdateWithMissedRequiredParameters()
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
* @expectedException \Exception
* @expectedExceptionMessage You cannot specify multiple shipping addresses.
*/
public function testSetMultipleNewShippingAddresses()
{
- $maskedQuoteId = $this->assignQuoteToCustomer();
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
+ $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id');
$quote->setCustomerId($customerId);
$this->quoteResource->save($quote);
return $this->quoteIdToMaskedId->execute((int)$quote->getId());
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php
index 736ea69440753..b5634f51cb366 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php
@@ -162,30 +162,30 @@ private function prepareMutationQuery(
}
/**
- * @param string $reversedQuoteId
+ * @param string $reversedOrderId
* @return string
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
- private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string
+ private function getMaskedQuoteIdByReservedOrderId(string $reversedOrderId): string
{
$quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
+ $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id');
return $this->quoteIdToMaskedId->execute((int)$quote->getId());
}
/**
- * @param string $reversedQuoteId
+ * @param string $reversedOrderId
* @param int $customerId
* @return string
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
private function assignQuoteToCustomer(
- string $reversedQuoteId,
+ string $reversedOrderId,
int $customerId
): string {
$quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
+ $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id');
$quote->setCustomerId($customerId);
$this->quoteResource->save($quote);
return $this->quoteIdToMaskedId->execute((int)$quote->getId());
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php
new file mode 100644
index 0000000000000..c5a4e8af02a58
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetMaskedQuoteIdByReservedOrderId.php
@@ -0,0 +1,63 @@
+quoteFactory = $quoteFactory;
+ $this->quoteResource = $quoteResource;
+ $this->quoteIdToMaskedId = $quoteIdToMaskedId;
+ }
+
+ /**
+ * Get masked quote id by reserved order id
+ *
+ * @param string $reversedOrderId
+ * @return string
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ public function execute(string $reversedOrderId): string
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id');
+
+ return $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php
index c4dd9af490c99..8271a76d88f12 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php
@@ -7,9 +7,7 @@
namespace Magento\GraphQl\Quote\Guest;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
@@ -19,19 +17,9 @@
class GetAvailablePaymentMethodsTest extends GraphQlAbstract
{
/**
- * @var QuoteResource
+ * @var GetMaskedQuoteIdByReservedOrderId
*/
- private $quoteResource;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
- */
- private $quoteIdToMaskedId;
+ private $getMaskedQuoteIdByReservedOrderId;
/**
* @inheritdoc
@@ -39,43 +27,39 @@ class GetAvailablePaymentMethodsTest extends GraphQlAbstract
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
- $this->quoteResource = $objectManager->get(QuoteResource::class);
- $this->quoteFactory = $objectManager->get(QuoteFactory::class);
- $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
*/
- public function testGetCartWithPaymentMethods()
+ public function testGetAvailablePaymentMethods()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = $this->getQuery($maskedQuoteId);
$response = $this->graphQlQuery($query);
self::assertArrayHasKey('cart', $response);
+ self::assertArrayHasKey('available_payment_methods', $response['cart']);
self::assertEquals('checkmo', $response['cart']['available_payment_methods'][0]['code']);
self::assertEquals('Check / Money order', $response['cart']['available_payment_methods'][0]['title']);
-
- self::assertEquals('free', $response['cart']['available_payment_methods'][1]['code']);
- self::assertEquals(
- 'No Payment Information Required',
- $response['cart']['available_payment_methods'][1]['title']
- );
- self::assertGreaterThan(
- 0,
- count($response['cart']['available_payment_methods']),
- 'There are no available payment methods for guest cart!'
- );
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
*/
- public function testGetPaymentMethodsFromCustomerCart()
+ public function testGetAvailablePaymentMethodsFromCustomerCart()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_1');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = $this->getQuery($maskedQuoteId);
$this->expectExceptionMessage(
@@ -85,23 +69,28 @@ public function testGetPaymentMethodsFromCustomerCart()
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- * @magentoApiDataFixture Magento/Payment/_files/disable_all_active_payment_methods.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php
*/
- public function testGetPaymentMethodsIfPaymentsAreNotSet()
+ public function testGetAvailablePaymentMethodsIfPaymentsAreNotPresent()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = $this->getQuery($maskedQuoteId);
$response = $this->graphQlQuery($query);
- self::assertEquals(0, count($response['cart']['available_payment_methods']));
+ self::assertArrayHasKey('cart', $response);
+ self::assertArrayHasKey('available_payment_methods', $response['cart']);
+ self::assertEmpty($response['cart']['available_payment_methods']);
}
/**
* @expectedException \Exception
* @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
*/
- public function testGetPaymentMethodsOfNonExistentCart()
+ public function testGetAvailablePaymentMethodsOfNonExistentCart()
{
$maskedQuoteId = 'non_existent_masked_id';
$query = $this->getQuery($maskedQuoteId);
@@ -112,9 +101,8 @@ public function testGetPaymentMethodsOfNonExistentCart()
* @param string $maskedQuoteId
* @return string
*/
- private function getQuery(
- string $maskedQuoteId
- ): string {
+ private function getQuery(string $maskedQuoteId): string
+ {
return <<quoteFactory->create();
- $this->quoteResource->load($quote, $reservedOrderId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php
index 916d5951b0ff2..6b1f540e4d47a 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetCartTest.php
@@ -7,10 +7,7 @@
namespace Magento\GraphQl\Quote\Guest;
-use Magento\Integration\Api\CustomerTokenServiceInterface;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
@@ -20,41 +17,27 @@
class GetCartTest extends GraphQlAbstract
{
/**
- * @var QuoteResource
+ * @var GetMaskedQuoteIdByReservedOrderId
*/
- private $quoteResource;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
- */
- private $quoteIdToMaskedId;
-
- /**
- * @var CustomerTokenServiceInterface
- */
- private $customerTokenService;
+ private $getMaskedQuoteIdByReservedOrderId;
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
- $this->quoteResource = $objectManager->get(QuoteResource::class);
- $this->quoteFactory = $objectManager->get(QuoteFactory::class);
- $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
- $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php
*/
public function testGetCart()
{
- $maskedQuoteId = $this->unAssignCustomerFromQuote('test_order_item_with_items');
- $query = $this->getCartQuery($maskedQuoteId);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
$response = $this->graphQlQuery($query);
@@ -63,21 +46,23 @@ public function testGetCart()
self::assertCount(2, $response['cart']['items']);
self::assertNotEmpty($response['cart']['items'][0]['id']);
- self::assertEquals($response['cart']['items'][0]['qty'], 2);
- self::assertEquals($response['cart']['items'][0]['product']['sku'], 'simple');
+ self::assertEquals(2, $response['cart']['items'][0]['qty']);
+ self::assertEquals('simple', $response['cart']['items'][0]['product']['sku']);
self::assertNotEmpty($response['cart']['items'][1]['id']);
- self::assertEquals($response['cart']['items'][1]['qty'], 1);
- self::assertEquals($response['cart']['items'][1]['product']['sku'], 'simple_one');
+ self::assertEquals(2, $response['cart']['items'][1]['qty']);
+ self::assertEquals('virtual-product', $response['cart']['items'][1]['product']['sku']);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
*/
public function testGetCustomerCart()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_item_with_items');
- $query = $this->getCartQuery($maskedQuoteId);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
$this->expectExceptionMessage(
"The current user cannot perform operations on cart \"{$maskedQuoteId}\""
@@ -92,25 +77,22 @@ public function testGetCustomerCart()
public function testGetNonExistentCart()
{
$maskedQuoteId = 'non_existent_masked_id';
- $query = $this->getCartQuery($maskedQuoteId);
+ $query = $this->getQuery($maskedQuoteId);
$this->graphQlQuery($query);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/make_cart_inactive.php
+ *
* @expectedException \Exception
* @expectedExceptionMessage Current user does not have an active cart.
*/
public function testGetInactiveCart()
{
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
- $quote->setIsActive(false);
- $this->quoteResource->save($quote);
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
-
- $query = $this->getCartQuery($maskedQuoteId);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
$this->graphQlQuery($query);
}
@@ -119,12 +101,11 @@ public function testGetInactiveCart()
* @param string $maskedQuoteId
* @return string
*/
- private function getCartQuery(
- string $maskedQuoteId
- ) : string {
+ private function getQuery(string $maskedQuoteId): string
+ {
return <<quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
-
- /**
- * @param string $reversedQuoteId
- * @param int $customerId
- * @return string
- */
- private function unAssignCustomerFromQuote(
- string $reversedQuoteId
- ): string {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
- $quote->setCustomerId(0);
- $this->quoteResource->save($quote);
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
}
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..3808ce38b9d7b 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
@@ -7,9 +7,7 @@
namespace Magento\GraphQl\Quote\Guest;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
@@ -19,34 +17,24 @@
class SetBillingAddressOnCartTest extends GraphQlAbstract
{
/**
- * @var QuoteResource
+ * @var GetMaskedQuoteIdByReservedOrderId
*/
- private $quoteResource;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
- */
- private $quoteIdToMaskedId;
+ private $getMaskedQuoteIdByReservedOrderId;
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
- $this->quoteResource = $objectManager->get(QuoteResource::class);
- $this->quoteFactory = $objectManager->get(QuoteFactory::class);
- $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
*/
public function testSetNewBillingAddress()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<getMaskedQuoteIdByReversedQuoteId('test_order_1');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<graphQlQuery($query);
}
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
+ * @dataProvider dataProviderSetWithoutRequiredParameters
+ * @param string $input
+ * @param string $message
+ * @throws \Exception
+ */
+ public function testSetBillingAddressWithoutRequiredParameters(string $input, string $message)
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $input = str_replace('cart_id_value', $maskedQuoteId, $input);
+
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderSetWithoutRequiredParameters(): array
+ {
+ 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'
+ ]
+ ];
+ }
+
/**
* Verify the all the whitelisted fields for a New Address Object
*
@@ -285,16 +334,4 @@ private function assertNewAddressFields(array $addressResponse, string $addressT
$this->assertResponseFields($addressResponse, $assertionMap);
}
-
- /**
- * @param string $reversedQuoteId
- * @return string
- */
- private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string
- {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
}
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..8f37f00c3db7f 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
@@ -7,10 +7,9 @@
namespace Magento\GraphQl\Quote\Guest;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
+use Magento\OfflinePayments\Model\Cashondelivery;
use Magento\OfflinePayments\Model\Checkmo;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
@@ -20,19 +19,9 @@
class SetPaymentMethodOnCartTest extends GraphQlAbstract
{
/**
- * @var QuoteResource
+ * @var GetMaskedQuoteIdByReservedOrderId
*/
- private $quoteResource;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
- */
- private $quoteIdToMaskedId;
+ private $getMaskedQuoteIdByReservedOrderId;
/**
* @inheritdoc
@@ -40,21 +29,21 @@ class SetPaymentMethodOnCartTest extends GraphQlAbstract
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
- $this->quoteResource = $objectManager->get(QuoteResource::class);
- $this->quoteFactory = $objectManager->get(QuoteFactory::class);
- $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_and_address.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
*/
- public function testSetPaymentWithVirtualProduct()
+ public function testSetPaymentOnCartWithSimpleProduct()
{
$methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_virtual_product');
- $this->unAssignCustomerFromQuote('test_order_with_virtual_product');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$response = $this->graphQlQuery($query);
self::assertArrayHasKey('setPaymentMethodOnCart', $response);
@@ -64,15 +53,33 @@ public function testSetPaymentWithVirtualProduct()
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage The shipping address is missing. Set the address and try again.
*/
- public function testSetPaymentWithSimpleProduct()
+ public function testSetPaymentOnCartWithSimpleProductAndWithoutAddress()
{
$methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1');
- $this->unAssignCustomerFromQuote('test_order_1');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/product_virtual.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_virtual_product.php
+ */
+ public function testSetPaymentOnCartWithVirtualProduct()
+ {
+ $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$response = $this->graphQlQuery($query);
self::assertArrayHasKey('setPaymentMethodOnCart', $response);
@@ -82,43 +89,56 @@ public function testSetPaymentWithSimpleProduct()
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ *
* @expectedException \Exception
- * @expectedExceptionMessage The shipping address is missing. Set the address and try again.
+ * @expectedExceptionMessage The requested Payment Method is not available.
*/
- public function testSetPaymentWithSimpleProductWithoutAddress()
+ public function testSetNonExistentPaymentMethod()
{
- $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $methodCode = 'noway';
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$this->graphQlQuery($query);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
* @expectedException \Exception
- * @expectedExceptionMessage The requested Payment Method is not available.
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
*/
- public function testSetNonExistingPaymentMethod()
+ public function testSetPaymentOnNonExistentCart()
{
- $methodCode = 'noway';
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1');
- $this->unAssignCustomerFromQuote('test_order_1');
-
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $maskedQuoteId = 'non_existent_masked_id';
+ $query = <<graphQlQuery($query);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
*/
public function testSetPaymentMethodToCustomerCart()
{
$methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$this->expectExceptionMessage(
"The current user cannot perform operations on cart \"$maskedQuoteId\""
@@ -127,39 +147,78 @@ public function testSetPaymentMethodToCustomerCart()
}
/**
- * @expectedException \Exception
- * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ *
+ * @param string $input
+ * @param string $message
+ * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters
+ * @throws \Exception
*/
- public function testSetPaymentOnNonExistentCart()
+ public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message)
{
- $maskedQuoteId = 'non_existent_masked_id';
$query = <<expectExceptionMessage($message);
$this->graphQlQuery($query);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_payment_saved.php
+ * @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/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php
*/
public function testReSetPayment()
{
- /** @var \Magento\Quote\Model\Quote $quote */
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_1_with_payment');
- $this->unAssignCustomerFromQuote('test_order_1_with_payment');
- $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
- $query = $this->prepareMutationQuery($maskedQuoteId, $methodCode);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $methodCode = Cashondelivery::PAYMENT_METHOD_CASHONDELIVERY_CODE;
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
$response = $this->graphQlQuery($query);
self::assertArrayHasKey('setPaymentMethodOnCart', $response);
self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']);
self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']);
+ self::assertArrayHasKey('code', $response['setPaymentMethodOnCart']['cart']['selected_payment_method']);
self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']);
}
@@ -168,20 +227,18 @@ public function testReSetPayment()
* @param string $methodCode
* @return string
*/
- private function prepareMutationQuery(
+ private function getQuery(
string $maskedQuoteId,
string $methodCode
) : string {
return <<quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
- $quote->setCustomerId(0);
- $this->quoteResource->save($quote);
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
-
- /**
- * @param string $reversedQuoteId
- * @return string
- */
- private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string
- {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php
index 33aeb2b902a8f..e21d9ed64d491 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingAddressOnCartTest.php
@@ -7,14 +7,9 @@
namespace Magento\GraphQl\Quote\Guest;
-use Magento\Framework\App\Config\ScopeConfigInterface;
-use Magento\Multishipping\Helper\Data;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Quote\Model\QuoteIdToMaskedQuoteIdInterface;
-use Magento\Quote\Model\ResourceModel\Quote as QuoteResource;
+use Magento\GraphQl\Quote\GetMaskedQuoteIdByReservedOrderId;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\TestFramework\TestCase\GraphQlAbstract;
-use Magento\TestFramework\ObjectManager;
/**
* Test for set shipping addresses on cart mutation
@@ -22,34 +17,24 @@
class SetShippingAddressOnCartTest extends GraphQlAbstract
{
/**
- * @var QuoteResource
+ * @var GetMaskedQuoteIdByReservedOrderId
*/
- private $quoteResource;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * @var QuoteIdToMaskedQuoteIdInterface
- */
- private $quoteIdToMaskedId;
+ private $getMaskedQuoteIdByReservedOrderId;
protected function setUp()
{
$objectManager = Bootstrap::getObjectManager();
- $this->quoteResource = $objectManager->get(QuoteResource::class);
- $this->quoteFactory = $objectManager->get(QuoteFactory::class);
- $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
*/
- public function testSetNewShippingAddress()
+ public function testSetNewShippingAddressOnCartWithSimpleProduct()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<getMaskedQuoteIdByReversedQuoteId('test_order_with_virtual_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assignQuoteToCustomer('test_order_with_simple_product_without_address', 1);
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<expectExceptionMessage(
"The current user cannot perform operations on cart \"$maskedQuoteId\""
);
-
$this->graphQlQuery($query);
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
* @dataProvider dataProviderUpdateWithMissedRequiredParameters
* @param string $input
* @param string $message
@@ -222,7 +220,7 @@ public function testSetShippingAddressToCustomerCart()
*/
public function testSetNewShippingAddressWithMissedRequiredParameters(string $input, string $message)
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = << [
@@ -266,13 +264,16 @@ public function dataProviderUpdateWithMissedRequiredParameters()
}
/**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ *
* @expectedException \Exception
* @expectedExceptionMessage You cannot specify multiple shipping addresses.
*/
public function testSetMultipleNewShippingAddresses()
{
- $maskedQuoteId = $this->getMaskedQuoteIdByReversedQuoteId('test_order_with_simple_product_without_address');
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
$query = <<assertResponseFields($shippingAddressResponse, $assertionMap);
}
-
- /**
- * @param string $reversedQuoteId
- * @return string
- */
- private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string
- {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
-
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
-
- /**
- * @param string $reversedQuoteId
- * @param int $customerId
- * @return string
- */
- private function assignQuoteToCustomer(
- string $reversedQuoteId = 'test_order_with_simple_product_without_address',
- int $customerId = 1
- ): string {
- $quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
- $quote->setCustomerId($customerId);
- $this->quoteResource->save($quote);
- return $this->quoteIdToMaskedId->execute((int)$quote->getId());
- }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php
index f159cb6f6151e..bf08ce8adce6c 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php
@@ -153,14 +153,14 @@ private function prepareMutationQuery(
}
/**
- * @param string $reversedQuoteId
+ * @param string $reversedOrderId
* @return string
* @SuppressWarnings(PHPMD.UnusedPrivateMethod)
*/
- private function getMaskedQuoteIdByReversedQuoteId(string $reversedQuoteId): string
+ private function getMaskedQuoteIdByReservedOrderId(string $reversedOrderId): string
{
$quote = $this->quoteFactory->create();
- $this->quoteResource->load($quote, $reversedQuoteId, 'reserved_order_id');
+ $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id');
return $this->quoteIdToMaskedId->execute((int)$quote->getId());
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/OfflineShipping/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php
similarity index 96%
rename from dev/tests/api-functional/testsuite/Magento/GraphQl/OfflineShipping/SetOfflineShippingMethodsOnCartTest.php
rename to dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php
index 80acbbdb64230..cf32003340a66 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/OfflineShipping/SetOfflineShippingMethodsOnCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php
@@ -5,7 +5,7 @@
*/
declare(strict_types=1);
-namespace Magento\GraphQl\OfflineShipping;
+namespace Magento\GraphQl\Quote;
use Magento\Integration\Api\CustomerTokenServiceInterface;
use Magento\Quote\Model\QuoteFactory;
@@ -53,7 +53,7 @@ protected function setUp()
/**
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
- * @magentoApiDataFixture Magento/OfflineShipping/_files/enable_offline_shipping_methods.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php
* @magentoApiDataFixture Magento/OfflineShipping/_files/tablerates_weight.php
*
* @param string $carrierCode
@@ -103,7 +103,7 @@ public function offlineShippingMethodDataProvider()
/**
* @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
- * @magentoApiDataFixture Magento/OfflineShipping/_files/enable_offline_shipping_methods.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php
*/
public function testSetShippingMethodTwiceInOneRequest()
{
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/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.php b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.php
index 3020e69c06399..fefe0d2c126e5 100644
--- a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.php
+++ b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ExportAdvancedPricingTest.php
@@ -65,6 +65,13 @@ class ExportAdvancedPricingTest extends Injectable
*/
private $catalogProductIndex;
+ /**
+ * Cron command
+ *
+ * @var Cron
+ */
+ private $cron;
+
/**
* Run cron before tests running
*
@@ -85,18 +92,21 @@ public function __prepare(
* @param FixtureFactory $fixtureFactory
* @param AdminExportIndex $adminExportIndex
* @param CatalogProductIndex $catalogProductIndexPage
+ * @param Cron $cron
* @return void
*/
public function __inject(
TestStepFactory $stepFactory,
FixtureFactory $fixtureFactory,
AdminExportIndex $adminExportIndex,
- CatalogProductIndex $catalogProductIndexPage
+ CatalogProductIndex $catalogProductIndexPage,
+ Cron $cron
) {
$this->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/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 8fe25614d1d42..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
@@ -62,7 +62,6 @@
mftf_migrated:yes
- >MC-13864 Consumer always read config from memory
default
- catalogProductSimple
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/CheckoutAgreements/Test/Page/MultishippingCheckoutOverview.xml b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/Page/MultishippingCheckoutOverview.xml
index d304d305a7265..a266b09278ddb 100644
--- a/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/Page/MultishippingCheckoutOverview.xml
+++ b/dev/tests/functional/tests/app/Magento/CheckoutAgreements/Test/Page/MultishippingCheckoutOverview.xml
@@ -7,6 +7,6 @@
-->
-
+
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 15dcfd0a9e7e7..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
@@ -8,7 +8,6 @@
- mftf_migrated:yes
default
- configurableProduct
@@ -48,7 +47,6 @@
mftf_migrated:yes
- >MC-13864 Consumer always read config from memory
default
- configurableProduct
diff --git a/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml b/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml
index 8445a21604cdd..4d96ebd2edbe3 100644
--- a/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Review/Test/TestCase/NavigateMenuTest.xml
@@ -8,7 +8,7 @@
- Marketing > Reviews
+ Marketing > All Reviews
Reviews
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/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/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php
new file mode 100644
index 0000000000000..d3f0e9fa2a5ab
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/Observer/ProcessUrlRewriteOnChangeVisibilityObserverTest.php
@@ -0,0 +1,195 @@
+objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
+ $this->productRepository = $this->objectManager->create(ProductRepositoryInterface::class);
+ $this->eventManager = $this->objectManager->create(ManagerInterface::class);
+ }
+
+ /**
+ * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_rewrite_multistore.php
+ * @magentoAppIsolation enabled
+ */
+ public function testMakeProductInvisibleViaMassAction()
+ {
+ /** @var \Magento\Catalog\Model\Product $product*/
+ $product = $this->productRepository->get('product1');
+
+ /** @var StoreManagerInterface $storeManager */
+ $storeManager = $this->objectManager->get(StoreManagerInterface::class);
+ $storeManager->setCurrentStore(0);
+
+ $testStore = $storeManager->getStore('test');
+ $productFilter = [
+ UrlRewrite::ENTITY_TYPE => 'product',
+ ];
+
+ $expected = [
+ [
+ 'request_path' => "product-1.html",
+ 'target_path' => "catalog/product/view/id/" . $product->getId(),
+ 'is_auto_generated' => 1,
+ 'redirect_type' => 0,
+ 'store_id' => '1',
+ ],
+ [
+ 'request_path' => "product-1.html",
+ 'target_path' => "catalog/product/view/id/" . $product->getId(),
+ 'is_auto_generated' => 1,
+ 'redirect_type' => 0,
+ 'store_id' => $testStore->getId(),
+ ]
+ ];
+
+ $actual = $this->getActualResults($productFilter);
+ foreach ($expected as $row) {
+ $this->assertContains($row, $actual);
+ }
+
+ $this->eventManager->dispatch(
+ 'catalog_product_attribute_update_before',
+ [
+ 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_NOT_VISIBLE ],
+ 'product_ids' => [$product->getId()]
+ ]
+ );
+
+ $actual = $this->getActualResults($productFilter);
+ $this->assertCount(0, $actual);
+ }
+
+ /**
+ * @magentoDataFixture Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php
+ * @magentoAppIsolation enabled
+ */
+ public function testMakeProductVisibleViaMassAction()
+ {
+ /** @var \Magento\Catalog\Model\Product $product*/
+ $product = $this->productRepository->get('product1');
+
+ /** @var StoreManagerInterface $storeManager */
+ $storeManager = $this->objectManager->get(StoreManagerInterface::class);
+ $storeManager->setCurrentStore(0);
+
+ $testStore = $storeManager->getStore('test');
+ $productFilter = [
+ UrlRewrite::ENTITY_TYPE => 'product',
+ ];
+
+ $actual = $this->getActualResults($productFilter);
+ $this->assertCount(0, $actual);
+
+ $this->eventManager->dispatch(
+ 'catalog_product_attribute_update_before',
+ [
+ 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH ],
+ 'product_ids' => [$product->getId()]
+ ]
+ );
+
+ $expected = [
+ [
+ 'request_path' => "product-1.html",
+ 'target_path' => "catalog/product/view/id/" . $product->getId(),
+ 'is_auto_generated' => 1,
+ 'redirect_type' => 0,
+ 'store_id' => '1',
+ ],
+ [
+ 'request_path' => "product-1.html",
+ 'target_path' => "catalog/product/view/id/" . $product->getId(),
+ 'is_auto_generated' => 1,
+ 'redirect_type' => 0,
+ 'store_id' => $testStore->getId(),
+ ]
+ ];
+
+ $actual = $this->getActualResults($productFilter);
+ foreach ($expected as $row) {
+ $this->assertContains($row, $actual);
+ }
+ }
+
+ /**
+ * @magentoDataFixture Magento/CatalogUrlRewrite/_files/products_invisible.php
+ * @magentoAppIsolation enabled
+ */
+ public function testErrorOnDuplicatedUrlKey()
+ {
+ $skus = ['product1', 'product2'];
+ foreach ($skus as $sku) {
+ /** @var \Magento\Catalog\Model\Product $product */
+ $productIds[] = $this->productRepository->get($sku)->getId();
+ }
+ $this->expectException(UrlAlreadyExistsException::class);
+ $this->expectExceptionMessage('Can not change the visibility of the product with SKU equals "product2". '
+ . 'URL key "product-1" for specified store already exists.');
+
+ $this->eventManager->dispatch(
+ 'catalog_product_attribute_update_before',
+ [
+ 'attributes_data' => [ ProductInterface::VISIBILITY => Visibility::VISIBILITY_BOTH ],
+ 'product_ids' => $productIds
+ ]
+ );
+ }
+
+ /**
+ * @param array $filter
+ * @return array
+ */
+ private function getActualResults(array $filter)
+ {
+ /** @var \Magento\UrlRewrite\Model\UrlFinderInterface $urlFinder */
+ $urlFinder = $this->objectManager->get(\Magento\UrlRewrite\Model\UrlFinderInterface::class);
+ $actualResults = [];
+ foreach ($urlFinder->findAllByData($filter) as $url) {
+ $actualResults[] = [
+ 'request_path' => $url->getRequestPath(),
+ 'target_path' => $url->getTargetPath(),
+ 'is_auto_generated' => (int)$url->getIsAutogenerated(),
+ 'redirect_type' => $url->getRedirectType(),
+ 'store_id' => $url->getStoreId()
+ ];
+ }
+ return $actualResults;
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php
new file mode 100644
index 0000000000000..d50b29383b1ef
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore.php
@@ -0,0 +1,40 @@
+loadArea(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE);
+
+require __DIR__ . '/../../Store/_files/store.php';
+
+/** @var $installer CategorySetup */
+$objectManager = Bootstrap::getObjectManager();
+$installer = $objectManager->create(CategorySetup::class);
+$storeManager = $objectManager->get(StoreManagerInterface::class);
+$storeManager->setCurrentStore(0);
+
+/** @var $product \Magento\Catalog\Model\Product */
+$product = $objectManager->create(\Magento\Catalog\Model\Product::class);
+$product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
+ ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default'))
+ ->setStoreId(0)
+ ->setWebsiteIds([1])
+ ->setName('Product1')
+ ->setSku('product1')
+ ->setPrice(10)
+ ->setWeight(18)
+ ->setStockData(['use_config_manage_stock' => 0])
+ ->setUrlKey('product-1')
+ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE)
+ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED);
+
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+$productRepository->save($product);
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php
new file mode 100644
index 0000000000000..bcf399cb5e552
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/product_invisible_multistore_rollback.php
@@ -0,0 +1,33 @@
+getInstance()->reinitialize();
+
+/** @var \Magento\Framework\Registry $registry */
+$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+try {
+ $product = $productRepository->get('product1', true);
+ if ($product->getId()) {
+ $productRepository->delete($product);
+ }
+} catch (NoSuchEntityException $e) {
+}
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
+
+require __DIR__ . '/../../Store/_files/store_rollback.php';
+require __DIR__ . '/../../Store/_files/second_store_rollback.php';
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php
new file mode 100644
index 0000000000000..c72d9c8284db3
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible.php
@@ -0,0 +1,38 @@
+loadArea(\Magento\Backend\App\Area\FrontNameResolver::AREA_CODE);
+
+/** @var $installer CategorySetup */
+$objectManager = Bootstrap::getObjectManager();
+$installer = $objectManager->create(CategorySetup::class);
+/** @var ProductRepositoryInterface $productRepository */
+$productRepository = $objectManager->get(ProductRepositoryInterface::class);
+
+$skus = ['product1', 'product2'];
+foreach ($skus as $sku) {
+ /** @var $product \Magento\Catalog\Model\Product */
+ $product = $objectManager->create(\Magento\Catalog\Model\Product::class);
+ $product->setTypeId(\Magento\Catalog\Model\Product\Type::TYPE_SIMPLE)
+ ->setAttributeSetId($installer->getAttributeSetId('catalog_product', 'Default'))
+ ->setStoreId(0)
+ ->setWebsiteIds([1])
+ ->setName('Product1')
+ ->setSku($sku)
+ ->setPrice(10)
+ ->setWeight(18)
+ ->setStockData(['use_config_manage_stock' => 0])
+ ->setUrlKey('product-1')
+ ->setVisibility(\Magento\Catalog\Model\Product\Visibility::VISIBILITY_NOT_VISIBLE)
+ ->setStatus(\Magento\Catalog\Model\Product\Attribute\Source\Status::STATUS_ENABLED);
+ $productRepository->save($product);
+}
diff --git a/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php
new file mode 100644
index 0000000000000..d3d17542aa6ab
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogUrlRewrite/_files/products_invisible_rollback.php
@@ -0,0 +1,37 @@
+getInstance()->reinitialize();
+
+/** @var \Magento\Framework\Registry $registry */
+$registry = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()->get(\Magento\Framework\Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+/** @var \Magento\Catalog\Api\ProductRepositoryInterface $productRepository */
+$productRepository = \Magento\TestFramework\Helper\Bootstrap::getObjectManager()
+ ->get(\Magento\Catalog\Api\ProductRepositoryInterface::class);
+
+$skus = ['product1', 'product2'];
+foreach ($skus as $sku) {
+ try {
+ $product = $productRepository->get($sku, true);
+ if ($product->getId()) {
+ $productRepository->delete($product);
+ }
+ } catch (NoSuchEntityException $e) {
+ }
+}
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
+
+require __DIR__ . '/../../Store/_files/store_rollback.php';
+require __DIR__ . '/../../Store/_files/second_store_rollback.php';
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/GraphQl/Quote/_files/add_simple_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php
new file mode 100644
index 0000000000000..d23381e33d436
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_simple_product.php
@@ -0,0 +1,28 @@
+get(ProductRepositoryInterface::class);
+/** @var QuoteFactory $quoteFactory */
+$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class);
+/** @var QuoteResource $quoteResource */
+$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class);
+/** @var CartRepositoryInterface $cartRepository */
+$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class);
+
+$product = $productRepository->get('simple');
+
+$quote = $quoteFactory->create();
+$quoteResource->load($quote, 'test_quote', 'reserved_order_id');
+$quote->addProduct($product, 2);
+$cartRepository->save($quote);
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_virtual_product.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_virtual_product.php
new file mode 100644
index 0000000000000..f597d0bffa7ce
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/add_virtual_product.php
@@ -0,0 +1,28 @@
+get(ProductRepositoryInterface::class);
+/** @var QuoteFactory $quoteFactory */
+$quoteFactory = Bootstrap::getObjectManager()->get(QuoteFactory::class);
+/** @var QuoteResource $quoteResource */
+$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class);
+/** @var CartRepositoryInterface $cartRepository */
+$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class);
+
+$product = $productRepository->get('virtual-product');
+
+$quote = $quoteFactory->create();
+$quoteResource->load($quote, 'test_quote', 'reserved_order_id');
+$quote->addProduct($product, 2);
+$cartRepository->save($quote);
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
new file mode 100644
index 0000000000000..86174a7753f4e
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
@@ -0,0 +1,29 @@
+get(CartManagementInterface::class);
+/** @var CartRepositoryInterface $cartRepository */
+$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class);
+/** @var QuoteIdMaskFactory $quoteIdMaskFactory */
+$quoteIdMaskFactory = Bootstrap::getObjectManager()->get(QuoteIdMaskFactory::class);
+
+$cartId = $cartManagement->createEmptyCartForCustomer(1);
+$cart = $cartRepository->get($cartId);
+$cart->setReservedOrderId('test_quote');
+$cartRepository->save($cart);
+
+/** @var QuoteIdMask $quoteIdMask */
+$quoteIdMask = $quoteIdMaskFactory->create();
+$quoteIdMask->setQuoteId($cartId)
+ ->save();
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart_rollback.php
new file mode 100644
index 0000000000000..0d5d5f067e35a
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/customer/create_empty_cart_rollback.php
@@ -0,0 +1,28 @@
+get(QuoteFactory::class);
+/** @var QuoteResource $quoteResource */
+$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class);
+/** @var QuoteIdMaskFactory $quoteIdMaskFactory */
+$quoteIdMaskFactory = Bootstrap::getObjectManager()->get(QuoteIdMaskFactory::class);
+
+$quote = $quoteFactory->create();
+$quoteResource->load($quote, 'test_quote', 'reserved_order_id');
+$quoteResource->delete($quote);
+
+/** @var QuoteIdMask $quoteIdMask */
+$quoteIdMask = $quoteIdMaskFactory->create();
+$quoteIdMask->setQuoteId($quote->getId())
+ ->delete();
diff --git a/dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php
similarity index 93%
rename from dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods.php
rename to dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php
index ac811e67c580a..8e6d4b8f74b86 100644
--- a/dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods.php
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167
declare(strict_types=1);
use Magento\Config\Model\Config;
diff --git a/dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods_rollback.php
similarity index 92%
rename from dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods_rollback.php
rename to dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods_rollback.php
index 4a56888058e4d..092826d1fd3f7 100644
--- a/dev/tests/integration/testsuite/Magento/Payment/_files/disable_all_active_payment_methods_rollback.php
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/disable_all_active_payment_methods_rollback.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167
declare(strict_types=1);
use Magento\Framework\App\Config\ScopeConfigInterface;
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php
new file mode 100644
index 0000000000000..9c15589ba82e5
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods.php
@@ -0,0 +1,24 @@
+get(WriterInterface::class);
+
+$configWriter->save('payment/banktransfer/active', 1);
+$configWriter->save('payment/cashondelivery/active', 1);
+$configWriter->save('payment/checkmo/active', 1);
+$configWriter->save('payment/purchaseorder/active', 1);
+
+$scopeConfig = $objectManager->get(ScopeConfigInterface::class);
+$scopeConfig->clean();
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods_rollback.php
new file mode 100644
index 0000000000000..61b7ed9737ff9
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_payment_methods_rollback.php
@@ -0,0 +1,20 @@
+create(WriterInterface::class);
+
+$configWriter->delete('payment/banktransfer/active');
+$configWriter->delete('payment/cashondelivery/active');
+$configWriter->delete('payment/checkmo/active');
+$configWriter->delete('payment/purchaseorder/active');
diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php
similarity index 89%
rename from dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods.php
rename to dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php
index 08694bf8c7d0b..ebc41da9b1b3c 100644
--- a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods.php
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167
declare(strict_types=1);
use Magento\Framework\App\Config\Storage\Writer;
diff --git a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods_rollback.php
similarity index 86%
rename from dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods_rollback.php
rename to dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods_rollback.php
index 31a16f42adfce..384ffbdf51f3c 100644
--- a/dev/tests/integration/testsuite/Magento/OfflineShipping/_files/enable_offline_shipping_methods_rollback.php
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/enable_offline_shipping_methods_rollback.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+// TODO: Should be removed in scope of https://github.com/magento/graphql-ce/issues/167
declare(strict_types=1);
use Magento\Framework\App\Config\Storage\Writer;
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
new file mode 100644
index 0000000000000..6a9ed898c3161
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
@@ -0,0 +1,24 @@
+get(GuestCartManagementInterface::class);
+/** @var CartRepositoryInterface $cartRepository */
+$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class);
+/** @var MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId */
+$maskedQuoteIdToQuoteId = Bootstrap::getObjectManager()->get(MaskedQuoteIdToQuoteIdInterface::class);
+
+$cartHash = $guestCartManagement->createEmptyCart();
+$cartId = $maskedQuoteIdToQuoteId->execute($cartHash);
+$cart = $cartRepository->get($cartId);
+$cart->setReservedOrderId('test_quote');
+$cartRepository->save($cart);
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart_rollback.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart_rollback.php
new file mode 100644
index 0000000000000..0d5d5f067e35a
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/guest/create_empty_cart_rollback.php
@@ -0,0 +1,28 @@
+get(QuoteFactory::class);
+/** @var QuoteResource $quoteResource */
+$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class);
+/** @var QuoteIdMaskFactory $quoteIdMaskFactory */
+$quoteIdMaskFactory = Bootstrap::getObjectManager()->get(QuoteIdMaskFactory::class);
+
+$quote = $quoteFactory->create();
+$quoteResource->load($quote, 'test_quote', 'reserved_order_id');
+$quoteResource->delete($quote);
+
+/** @var QuoteIdMask $quoteIdMask */
+$quoteIdMask = $quoteIdMaskFactory->create();
+$quoteIdMask->setQuoteId($quote->getId())
+ ->delete();
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_cart_inactive.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_cart_inactive.php
new file mode 100644
index 0000000000000..b5704f82879b2
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/make_cart_inactive.php
@@ -0,0 +1,23 @@
+get(QuoteFactory::class);
+/** @var QuoteResource $quoteResource */
+$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class);
+/** @var CartRepositoryInterface $cartRepository */
+$cartRepository = Bootstrap::getObjectManager()->get(CartRepositoryInterface::class);
+
+$quote = $quoteFactory->create();
+$quoteResource->load($quote, 'test_quote', 'reserved_order_id');
+$quote->setIsActive(false);
+$cartRepository->save($quote);
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php
new file mode 100644
index 0000000000000..a973a982b1150
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_checkmo_payment_method.php
@@ -0,0 +1,33 @@
+get(QuoteFactory::class);
+/** @var QuoteResource $quoteResource */
+$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class);
+/** @var PaymentInterfaceFactory $paymentFactory */
+$paymentFactory = Bootstrap::getObjectManager()->get(PaymentInterfaceFactory::class);
+/** @var PaymentMethodManagementInterface $paymentMethodManagement */
+$paymentMethodManagement = Bootstrap::getObjectManager()->get(PaymentMethodManagementInterface::class);
+
+$quote = $quoteFactory->create();
+$quoteResource->load($quote, 'test_quote', 'reserved_order_id');
+
+$payment = $paymentFactory->create([
+ 'data' => [
+ PaymentInterface::KEY_METHOD => Checkmo::PAYMENT_METHOD_CHECKMO_CODE,
+ ]
+]);
+$paymentMethodManagement->set($quote->getId(), $payment);
diff --git a/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php
new file mode 100644
index 0000000000000..e17b9e61f82db
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/GraphQl/Quote/_files/set_new_shipping_address.php
@@ -0,0 +1,43 @@
+get(QuoteFactory::class);
+/** @var QuoteResource $quoteResource */
+$quoteResource = Bootstrap::getObjectManager()->get(QuoteResource::class);
+/** @var AddressInterfaceFactory $quoteAddressFactory */
+$quoteAddressFactory = Bootstrap::getObjectManager()->get(AddressInterfaceFactory::class);
+/** @var DataObjectHelper $dataObjectHelper */
+$dataObjectHelper = Bootstrap::getObjectManager()->get(DataObjectHelper::class);
+/** @var ShippingAddressManagementInterface $shippingAddressManagement */
+$shippingAddressManagement = Bootstrap::getObjectManager()->get(ShippingAddressManagementInterface::class);
+
+$quoteAddressData = [
+ AddressInterface::KEY_TELEPHONE => 3468676,
+ AddressInterface::KEY_POSTCODE => 75477,
+ AddressInterface::KEY_COUNTRY_ID => 'US',
+ AddressInterface::KEY_CITY => 'CityM',
+ AddressInterface::KEY_COMPANY => 'CompanyName',
+ AddressInterface::KEY_STREET => 'Green str, 67',
+ AddressInterface::KEY_LASTNAME => 'Smith',
+ AddressInterface::KEY_FIRSTNAME => 'John',
+ AddressInterface::KEY_REGION_ID => 1,
+];
+$quoteAddress = $quoteAddressFactory->create();
+$dataObjectHelper->populateWithArray($quoteAddress, $quoteAddressData, AddressInterfaceFactory::class);
+
+$quote = $quoteFactory->create();
+$quoteResource->load($quote, 'test_quote', 'reserved_order_id');
+$shippingAddressManagement->assign($quote->getId(), $quoteAddress);
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/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
index 96854aa76281f..35ba5803b09cc 100644
--- a/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
+++ b/dev/tests/static/testsuite/Magento/Test/Php/_files/phpcpd/blacklist/common.txt
@@ -213,3 +213,4 @@ Magento/CatalogSearch/Model/ResourceModel/Fulltext
Magento/Elasticsearch/Model/Layer/Search
Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver
Magento/Elasticsearch6/Model/Client
+Magento/Config/App/Config/Type
diff --git a/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php b/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php
new file mode 100644
index 0000000000000..216d8e9a0a01b
--- /dev/null
+++ b/lib/internal/Magento/Framework/Cache/LockGuardedCacheLoader.php
@@ -0,0 +1,116 @@
+locker = $locker;
+ $this->lockTimeout = $lockTimeout;
+ $this->delayTimeout = $delayTimeout;
+ }
+
+ /**
+ * Load data.
+ *
+ * @param string $lockName
+ * @param callable $dataLoader
+ * @param callable $dataCollector
+ * @param callable $dataSaver
+ * @return mixed
+ */
+ public function lockedLoadData(
+ string $lockName,
+ callable $dataLoader,
+ callable $dataCollector,
+ callable $dataSaver
+ ) {
+ $cachedData = $dataLoader(); //optimistic read
+
+ while ($cachedData === false && $this->locker->isLocked($lockName)) {
+ usleep($this->delayTimeout * 1000);
+ $cachedData = $dataLoader();
+ }
+
+ while ($cachedData === false) {
+ try {
+ if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) {
+ $data = $dataCollector();
+ $dataSaver($data);
+ $cachedData = $data;
+ }
+ } finally {
+ $this->locker->unlock($lockName);
+ }
+
+ if ($cachedData === false) {
+ usleep($this->delayTimeout * 1000);
+ $cachedData = $dataLoader();
+ }
+ }
+
+ return $cachedData;
+ }
+
+ /**
+ * Clean data.
+ *
+ * @param string $lockName
+ * @param callable $dataCleaner
+ * @return void
+ */
+ public function lockedCleanData(string $lockName, callable $dataCleaner)
+ {
+ while ($this->locker->isLocked($lockName)) {
+ usleep($this->delayTimeout * 1000);
+ }
+ try {
+ if ($this->locker->lock($lockName, $this->lockTimeout / 1000)) {
+ $dataCleaner();
+ }
+ } finally {
+ $this->locker->unlock($lockName);
+ }
+ }
+}
diff --git a/lib/internal/Magento/Framework/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/Locale/Test/Unit/FormatTest.php b/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php
index 1141f451c13a5..73a029a5a1411 100644
--- a/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php
+++ b/lib/internal/Magento/Framework/Locale/Test/Unit/FormatTest.php
@@ -68,15 +68,17 @@ protected function setUp()
/**
* @param string $localeCode
+ * @param string $currencyCode
* @param array $expectedResult
* @dataProvider getPriceFormatDataProvider
*/
- public function testGetPriceFormat($localeCode, array $expectedResult): void
+ public function testGetPriceFormat($localeCode, $currencyCode, array $expectedResult): void
{
$this->scope->expects($this->once())
->method('getCurrentCurrency')
->willReturn($this->currency);
+ $this->currency->method('getCode')->willReturn($currencyCode);
$result = $this->formatModel->getPriceFormat($localeCode);
$intersection = array_intersect_assoc($result, $expectedResult);
$this->assertCount(count($expectedResult), $intersection);
@@ -88,18 +90,19 @@ public function testGetPriceFormat($localeCode, array $expectedResult): void
*/
public function getPriceFormatDataProvider(): array
{
+ $swissGroupSymbol = INTL_ICU_VERSION >= 59.1 ? '’' : '\'';
return [
- ['en_US', ['decimalSymbol' => '.', 'groupSymbol' => ',']],
- ['de_DE', ['decimalSymbol' => ',', 'groupSymbol' => '.']],
- ['de_CH', ['decimalSymbol' => '.', 'groupSymbol' => '\'']],
- ['uk_UA', ['decimalSymbol' => ',', 'groupSymbol' => ' ']]
+ ['en_US', 'USD', ['decimalSymbol' => '.', 'groupSymbol' => ',']],
+ ['de_DE', 'EUR', ['decimalSymbol' => ',', 'groupSymbol' => '.']],
+ ['de_CH', 'CHF', ['decimalSymbol' => '.', 'groupSymbol' => $swissGroupSymbol]],
+ ['uk_UA', 'UAH', ['decimalSymbol' => ',', 'groupSymbol' => ' ']]
];
}
/**
*
- * @param mixed $value
- * @param float $expected
+ * @param mixed $value
+ * @param float $expected
* @dataProvider provideNumbers
*/
public function testGetNumber($value, $expected): void
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/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/nginx.conf.sample b/nginx.conf.sample
index ce3891627bc8c..aef22cc55fbbe 100644
--- a/nginx.conf.sample
+++ b/nginx.conf.sample
@@ -159,6 +159,11 @@ location /media/downloadable/ {
location /media/import/ {
deny all;
}
+location /errors/ {
+ location ~* \.xml$ {
+ deny all;
+ }
+}
# PHP entry point for main application
location ~ ^/(index|get|static|errors/report|errors/404|errors/503|health_check)\.php$ {
@@ -198,6 +203,6 @@ gzip_types
gzip_vary on;
# Banned locations (only reached if the earlier PHP entry point regexes don't match)
-location ~* (\.php$|\.htaccess$|\.git) {
+location ~* (\.php$|\.phtml$|\.htaccess$|\.git) {
deny all;
}
diff --git a/pub/errors/.htaccess b/pub/errors/.htaccess
index 3692dd439e2ff..a7b9cbda05893 100644
--- a/pub/errors/.htaccess
+++ b/pub/errors/.htaccess
@@ -1,4 +1,7 @@
Options None
+
+ Deny from all
+
RewriteEngine Off
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) {