getUiId('content-header') ?>>
+
diff --git a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html
index fe30ca7e83f19..0033f4c071e42 100644
--- a/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html
+++ b/app/code/Magento/Backend/view/adminhtml/web/template/dynamic-rows/grid.html
@@ -69,7 +69,7 @@
_resourceDb = $resourceDb;
$this->_resource = $resource;
$this->helper = $helper ?? ObjectManager::getInstance()->get(Helper::class);
+ $this->getListTables = $getListTables ?? ObjectManager::getInstance()->get(GetListTables::class);
+ $this->getViewsBackup = $getViewsBackup ?? ObjectManager::getInstance()->get(CreateViewsBackup::class);
}
/**
@@ -161,7 +180,7 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu
$this->getResource()->beginTransaction();
- $tables = $this->getResource()->getTables();
+ $tables = $this->getListTables->execute();
$backup->write($this->getResource()->getHeader());
@@ -198,6 +217,8 @@ public function createBackup(\Magento\Framework\Backup\Db\BackupInterface $backu
$backup->write($this->getResource()->getTableDataAfterSql($table));
}
}
+ $this->getViewsBackup->execute($backup);
+
$backup->write($this->getResource()->getTableForeignKeysSql());
$backup->write($this->getResource()->getTableTriggersSql());
$backup->write($this->getResource()->getFooter());
diff --git a/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php b/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php
new file mode 100644
index 0000000000000..73c4221feba3f
--- /dev/null
+++ b/app/code/Magento/Backup/Model/ResourceModel/Table/GetListTables.php
@@ -0,0 +1,44 @@
+resource = $resource;
+ }
+
+ /**
+ * Get list of database tables excluding views.
+ *
+ * @return array
+ */
+ public function execute(): array
+ {
+ return $this->resource->getConnection('backup')->fetchCol(
+ "SHOW FULL TABLES WHERE `Table_type` = ?",
+ self::TABLE_TYPE
+ );
+ }
+}
diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php b/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php
new file mode 100644
index 0000000000000..51b49dcb9e48a
--- /dev/null
+++ b/app/code/Magento/Backup/Model/ResourceModel/View/CreateViewsBackup.php
@@ -0,0 +1,116 @@
+getListViews = $getListViews;
+ $this->resourceConnection = $resourceConnection;
+ }
+
+ /**
+ * Write backup data to backup file.
+ *
+ * @param BackupInterface $backup
+ */
+ public function execute(BackupInterface $backup): void
+ {
+ $views = $this->getListViews->execute();
+
+ foreach ($views as $view) {
+ $backup->write($this->getViewHeader($view));
+ $backup->write($this->getDropViewSql($view));
+ $backup->write($this->getCreateView($view));
+ }
+ }
+
+ /**
+ * Retrieve Database connection for Backup.
+ *
+ * @return AdapterInterface
+ */
+ private function getConnection(): AdapterInterface
+ {
+ if (!$this->connection) {
+ $this->connection = $this->resourceConnection->getConnection('backup');
+ }
+
+ return $this->connection;
+ }
+
+ /**
+ * Get CREATE VIEW query for the specific view.
+ *
+ * @param string $viewName
+ * @return string
+ */
+ private function getCreateView(string $viewName): string
+ {
+ $quotedViewName = $this->getConnection()->quoteIdentifier($viewName);
+ $query = 'SHOW CREATE VIEW ' . $quotedViewName;
+ $row = $this->getConnection()->fetchRow($query);
+ $regExp = '/\sDEFINER\=\`([^`]*)\`\@\`([^`]*)\`/';
+ $sql = preg_replace($regExp, '', $row['Create View']);
+
+ return $sql . ';' . "\n";
+ }
+
+ /**
+ * Prepare a header for View being dumped.
+ *
+ * @param string $viewName
+ * @return string
+ */
+ public function getViewHeader(string $viewName): string
+ {
+ $quotedViewName = $this->getConnection()->quoteIdentifier($viewName);
+ return "\n--\n" . "-- Structure for view {$quotedViewName}\n" . "--\n\n";
+ }
+
+ /**
+ * Make sure that View being created is deleted if already exists.
+ *
+ * @param string $viewName
+ * @return string
+ */
+ public function getDropViewSql(string $viewName): string
+ {
+ $quotedViewName = $this->getConnection()->quoteIdentifier($viewName);
+ return sprintf('DROP VIEW IF EXISTS %s;\n', $quotedViewName);
+ }
+}
diff --git a/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php b/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php
new file mode 100644
index 0000000000000..c76ea2842180b
--- /dev/null
+++ b/app/code/Magento/Backup/Model/ResourceModel/View/GetListViews.php
@@ -0,0 +1,44 @@
+resource = $resource;
+ }
+
+ /**
+ * Get list of database views.
+ *
+ * @return array
+ */
+ public function execute(): array
+ {
+ return $this->resource->getConnection('backup')->fetchCol(
+ "SHOW FULL TABLES WHERE `Table_type` = ?",
+ self::TABLE_TYPE
+ );
+ }
+}
diff --git a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
index 4f34f24c3a806..ebc4ac1fb056a 100644
--- a/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
+++ b/app/code/Magento/Backup/Test/Mftf/ActionGroup/DeleteBackupActionGroup.xml
@@ -17,9 +17,10 @@
+
+
-
diff --git a/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml b/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml
index ae97351cafcaf..ad218cdd57500 100644
--- a/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml
+++ b/app/code/Magento/Backup/Test/Mftf/Data/BackupData.xml
@@ -20,4 +20,8 @@
databaseBackup
Database
-
+
+ WebSetupWizard
+ Database
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Braintree/Controller/Paypal/Review.php b/app/code/Magento/Braintree/Controller/Paypal/Review.php
index 14ec829d98024..eb2de7c7b6e39 100644
--- a/app/code/Magento/Braintree/Controller/Paypal/Review.php
+++ b/app/code/Magento/Braintree/Controller/Paypal/Review.php
@@ -13,11 +13,12 @@
use Magento\Braintree\Model\Paypal\Helper\QuoteUpdater;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\App\Action\HttpGetActionInterface;
/**
* Class Review
*/
-class Review extends AbstractAction implements HttpPostActionInterface
+class Review extends AbstractAction implements HttpPostActionInterface, HttpGetActionInterface
{
/**
* @var QuoteUpdater
diff --git a/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php b/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php
index 372415d3530c0..55e76cae9103a 100644
--- a/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php
+++ b/app/code/Magento/Braintree/Test/Unit/Model/Report/BraintreeTransactionStub.php
@@ -40,7 +40,7 @@ public function __get($name)
}
/**
- * Checks for the existance of a property stored in the private $_attributes property
+ * Checks for the existence of a property stored in the private $_attributes property
*
* @ignore
* @param string $name
diff --git a/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
new file mode 100644
index 0000000000000..a00f1e367864e
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductDropdownOrderSection.xml b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
new file mode 100644
index 0000000000000..787f7ade8ffab
--- /dev/null
+++ b/app/code/Magento/Bundle/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml
index 574c0dccdb07f..c922b981aecd9 100644
--- a/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml
+++ b/app/code/Magento/Bundle/Test/Mftf/Test/BundleProductFixedPricingTest.xml
@@ -31,6 +31,10 @@
+
+
+
+
diff --git a/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml b/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml
index 224cd71538b7b..a770ae864a74c 100644
--- a/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml
+++ b/app/code/Magento/Bundle/view/adminhtml/templates/catalog/product/edit/tab/attributes/extend.phtml
@@ -20,9 +20,9 @@ $isElementReadonly = $block->getElement()
->getReadonly();
?>
-getCanReadPrice() === false)) { ?>
+getCanReadPrice() === false)): ?>
= /* @escapeNotVerified */ $elementHtml ?>
-
+
= $block->getExtendedElement($switchAttributeCode)->toHtml() ?>
@@ -43,13 +43,13 @@ $isElementReadonly = $block->getElement()
} else {
if ($attribute) {
getCanEditPrice() && $block->getCanReadPrice()
- && $block->getProduct()->isObjectNew()) { ?>
+ && $block->getProduct()->isObjectNew()): ?>
getDefaultProductPrice() ?: "''"; ?>
$attribute.value = = /* @escapeNotVerified */ $defaultProductPrice ?>;
-
+
$attribute.disabled = false;
$attribute.addClassName('required-entry');
-
+
}
if ($('dynamic-price-warning')) {
$('dynamic-price-warning').hide();
@@ -58,9 +58,9 @@ $isElementReadonly = $block->getElement()
}
getCanEditPrice()
- && !$block->getProduct()->isObjectNew())) { ?>
+ && !$block->getProduct()->isObjectNew())): ?>
$('= /* @escapeNotVerified */ $switchAttributeCode ?>').observe('change', = /* @escapeNotVerified */ $switchAttributeCode ?>_change);
-
+
Event.observe(window, 'load', function(){
= /* @escapeNotVerified */ $switchAttributeCode ?>_change();
});
diff --git a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
index e56cc6f32d804..f8d2f8bc11116 100644
--- a/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
+++ b/app/code/Magento/Bundle/view/base/web/js/price-bundle.js
@@ -374,8 +374,17 @@ define([
function applyTierPrice(oneItemPrice, qty, optionConfig) {
var tiers = optionConfig.tierPrice,
magicKey = _.keys(oneItemPrice)[0],
+ tiersFirstKey = _.keys(optionConfig)[0],
lowest = false;
+ if (!tiers) {//tiers is undefined when options has only one option
+ tiers = optionConfig[tiersFirstKey].tierPrice;
+ }
+
+ tiers.sort(function (a, b) {//sorting based on "price_qty"
+ return a['price_qty'] - b['price_qty'];
+ });
+
_.each(tiers, function (tier, index) {
if (tier['price_qty'] > qty) {
return;
diff --git a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
index 2152f842d4a29..b2aa0d000e9cf 100644
--- a/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
+++ b/app/code/Magento/CacheInvalidate/Model/PurgeCache.php
@@ -118,6 +118,7 @@ private function splitTags($tagsPattern)
private function sendPurgeRequestToServers($socketAdapter, $servers, $formattedTagsChunk)
{
$headers = [self::HEADER_X_MAGENTO_TAGS_PATTERN => $formattedTagsChunk];
+ $unresponsiveServerError = [];
foreach ($servers as $server) {
$headers['Host'] = $server->getHost();
try {
@@ -131,10 +132,30 @@ private function sendPurgeRequestToServers($socketAdapter, $servers, $formattedT
$socketAdapter->read();
$socketAdapter->close();
} catch (\Exception $e) {
- $this->logger->critical($e->getMessage(), compact('server', 'formattedTagsChunk'));
+ $unresponsiveServerError[] = "Cache host: " . $server->getHost() . ":" . $server->getPort() .
+ "resulted in error message: " . $e->getMessage();
+ }
+ }
+
+ $errorCount = count($unresponsiveServerError);
+
+ if ($errorCount > 0) {
+ $loggerMessage = implode(" ", $unresponsiveServerError);
+
+ if ($errorCount == count($servers)) {
+ $this->logger->critical(
+ 'No cache server(s) could be purged ' . $loggerMessage,
+ compact('server', 'formattedTagsChunk')
+ );
return false;
}
+
+ $this->logger->warning(
+ 'Unresponsive cache server(s) hit' . $loggerMessage,
+ compact('server', 'formattedTagsChunk')
+ );
}
+
$this->logger->execute(compact('servers', 'formattedTagsChunk'));
return true;
}
diff --git a/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php b/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
index bdc8dfa218972..dd4974c5d842c 100644
--- a/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
+++ b/app/code/Magento/Captcha/Observer/CheckUserLoginObserver.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Captcha\Observer;
use Magento\Customer\Model\AuthenticationInterface;
@@ -11,7 +12,10 @@
use Magento\Customer\Api\CustomerRepositoryInterface;
/**
+ * Check captcha on user login page observer.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class CheckUserLoginObserver implements ObserverInterface
{
@@ -140,7 +144,7 @@ public function execute(\Magento\Framework\Event\Observer $observer)
$customer = $this->getCustomerRepository()->get($login);
$this->getAuthentication()->processAuthenticationFailure($customer->getId());
} catch (NoSuchEntityException $e) {
- //do nothing as customer existance is validated later in authenticate method
+ //do nothing as customer existence is validated later in authenticate method
}
$this->messageManager->addError(__('Incorrect CAPTCHA'));
$this->_actionFlag->set('', \Magento\Framework\App\Action\Action::FLAG_NO_DISPATCH, true);
diff --git a/app/code/Magento/Catalog/Api/ProductRenderListInterface.php b/app/code/Magento/Catalog/Api/ProductRenderListInterface.php
index f79efa4c814d7..954acd35a07db 100644
--- a/app/code/Magento/Catalog/Api/ProductRenderListInterface.php
+++ b/app/code/Magento/Catalog/Api/ProductRenderListInterface.php
@@ -4,18 +4,22 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Api;
/**
- * Interface which provides product renders information for products
+ * Interface which provides product renders information for products.
+ *
* @api
* @since 101.1.0
*/
interface ProductRenderListInterface
{
/**
- * Collect and retrieve the list of product render info
- * This info contains raw prices and formated prices, product name, stock status, store_id, etc
+ * Collect and retrieve the list of product render info.
+ *
+ * This info contains raw prices and formatted prices, product name, stock status, store_id, etc.
+ *
* @see \Magento\Catalog\Api\Data\ProductRenderInfoDtoInterface
*
* @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
index dd09e40ac5b35..1b6756968662f 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Product/Attribute/Edit/Tab/Advanced.php
@@ -4,11 +4,6 @@
* See COPYING.txt for license details.
*/
-/**
- * Product attribute add/edit form main tab
- *
- * @author Magento Core Team
- */
namespace Magento\Catalog\Block\Adminhtml\Product\Attribute\Edit\Tab;
use Magento\Backend\Block\Widget\Form\Generic;
@@ -18,6 +13,8 @@
use Magento\Framework\App\ObjectManager;
/**
+ * Product attribute add/edit form main tab
+ *
* @api
* @since 100.0.2
*/
@@ -73,6 +70,7 @@ public function __construct(
* Adding product form elements for editing attribute
*
* @return $this
+ * @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD)
*/
protected function _prepareForm()
@@ -255,7 +253,7 @@ protected function _prepareForm()
}
/**
- * Initialize form fileds values
+ * Initialize form fields values
*
* @return $this
*/
diff --git a/app/code/Magento/Catalog/Block/Product/ListProduct.php b/app/code/Magento/Catalog/Block/Product/ListProduct.php
index c1b255c762dbb..c1d79894162ae 100644
--- a/app/code/Magento/Catalog/Block/Product/ListProduct.php
+++ b/app/code/Magento/Catalog/Block/Product/ListProduct.php
@@ -178,8 +178,9 @@ private function getDefaultListingMode()
}
/**
- * Need use as _prepareLayout - but problem in declaring collection from
- * another block (was problem with search result)
+ * Need use as _prepareLayout - but problem in declaring collection from another block.
+ * (was problem with search result)
+ *
* @return $this
*/
protected function _beforeToHtml()
@@ -188,7 +189,9 @@ protected function _beforeToHtml()
$this->addToolbarBlock($collection);
- $collection->load();
+ if (!$collection->isLoaded()) {
+ $collection->load();
+ }
return parent::_beforeToHtml();
}
@@ -262,6 +265,8 @@ public function getToolbarHtml()
}
/**
+ * Set collection.
+ *
* @param AbstractCollection $collection
* @return $this
*/
@@ -272,7 +277,9 @@ public function setCollection($collection)
}
/**
- * @param array|string|integer| Element $code
+ * Add attribute.
+ *
+ * @param array|string|integer|Element $code
* @return $this
*/
public function addAttribute($code)
@@ -282,6 +289,8 @@ public function addAttribute($code)
}
/**
+ * Get price block template.
+ *
* @return mixed
*/
public function getPriceBlockTemplate()
@@ -371,6 +380,8 @@ public function getAddToCartPostParams(Product $product)
}
/**
+ * Get product price.
+ *
* @param Product $product
* @return string
*/
@@ -396,8 +407,8 @@ public function getProductPrice(Product $product)
}
/**
- * Specifies that price rendering should be done for the list of products
- * i.e. rendering happens in the scope of product list, but not single product
+ * Specifies that price rendering should be done for the list of products.
+ * (rendering happens in the scope of product list, but not single product)
*
* @return Render
*/
diff --git a/app/code/Magento/Catalog/Block/Product/View/Attributes.php b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
index 1cf9851d403a0..5b9777cbfd1e7 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Attributes.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
@@ -94,7 +94,7 @@ public function getAdditionalData(array $excludeAttr = [])
if (is_string($value) && strlen(trim($value))) {
$data[$attribute->getAttributeCode()] = [
- 'label' => __($attribute->getStoreLabel()),
+ 'label' => $attribute->getStoreLabel(),
'value' => $value,
'code' => $attribute->getAttributeCode(),
];
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options.php b/app/code/Magento/Catalog/Block/Product/View/Options.php
index 0720c018f6a9b..c457b20cd0904 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options.php
@@ -4,16 +4,15 @@
* See COPYING.txt for license details.
*/
-/**
- * Product options block
- *
- * @author Magento Core Team
- */
namespace Magento\Catalog\Block\Product\View;
use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Option\Value;
/**
+ * Product options block
+ *
+ * @author Magento Core Team
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
@@ -121,6 +120,8 @@ public function setProduct(Product $product = null)
}
/**
+ * Get group of option.
+ *
* @param string $type
* @return string
*/
@@ -142,6 +143,8 @@ public function getOptions()
}
/**
+ * Check if block has options.
+ *
* @return bool
*/
public function hasOptions()
@@ -160,7 +163,10 @@ public function hasOptions()
*/
protected function _getPriceConfiguration($option)
{
- $optionPrice = $this->pricingHelper->currency($option->getPrice(true), false, false);
+ $optionPrice = $option->getPrice(true);
+ if ($option->getPriceType() !== Value::TYPE_PERCENT) {
+ $optionPrice = $this->pricingHelper->currency($optionPrice, false, false);
+ }
$data = [
'prices' => [
'oldPrice' => [
@@ -195,7 +201,7 @@ protected function _getPriceConfiguration($option)
],
],
'type' => $option->getPriceType(),
- 'name' => $option->getTitle()
+ 'name' => $option->getTitle(),
];
return $data;
}
@@ -231,7 +237,7 @@ public function getJsonConfig()
//pass the return array encapsulated in an object for the other modules to be able to alter it eg: weee
$this->_eventManager->dispatch('catalog_product_option_price_configuration_after', ['configObj' => $configObj]);
- $config=$configObj->getConfig();
+ $config = $configObj->getConfig();
return $this->_jsonEncoder->encode($config);
}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
index 181211a0fc4a2..059580b9b5eae 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/AbstractOptions.php
@@ -9,11 +9,14 @@
*
* @author Magento Core Team
*/
+
namespace Magento\Catalog\Block\Product\View\Options;
use Magento\Catalog\Pricing\Price\CustomOptionPriceInterface;
/**
+ * Product aoptions section abstract block.
+ *
* @api
* @since 100.0.2
*/
@@ -46,7 +49,7 @@ abstract class AbstractOptions extends \Magento\Framework\View\Element\Template
/**
* @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Framework\Pricing\Helper\Data $pricingHelper
- * @param \Magento\Catalog\Helper\Data $catalogData,
+ * @param \Magento\Catalog\Helper\Data $catalogData
* @param array $data
*/
public function __construct(
@@ -123,6 +126,8 @@ public function getFormattedPrice()
}
/**
+ * Retrieve formatted price.
+ *
* @return string
*
* @deprecated
@@ -134,7 +139,7 @@ public function getFormatedPrice()
}
/**
- * Return formated price
+ * Return formatted price
*
* @param array $value
* @param bool $flag
diff --git a/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php b/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
index da35b566d7e71..dd2e23e67f3d7 100644
--- a/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
+++ b/app/code/Magento/Catalog/Block/Ui/ProductViewCounter.php
@@ -20,8 +20,8 @@
/**
* Reports Viewed Products Counter
*
- * The main responsilibity of this class is provide necessary data to track viewed products
- * by customer on frontend and data to synchornize this tracks with backend
+ * The main responsibility of this class is provide necessary data to track viewed products
+ * by customer on frontend and data to synchronize this tracks with backend
*
* @api
* @since 101.1.0
@@ -109,6 +109,8 @@ public function __construct(
*
* @return string {JSON encoded data}
* @since 101.1.0
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getCurrentProductData()
{
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/Controller/Adminhtml/Product/Attribute/Validate.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
index 02c12ad185ee8..072f3ff3ac38e 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
@@ -115,7 +115,7 @@ public function execute()
$attributeCode
);
- if ($attribute->getId() && !$attributeId || $attributeCode === 'product_type') {
+ if ($attribute->getId() && !$attributeId || $attributeCode === 'product_type' || $attributeCode === 'type_id') {
$message = strlen($this->getRequest()->getParam('attribute_code'))
? __('An attribute with this code already exists.')
: __('An attribute with the same code (%1) already exists.', $attributeCode);
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php
index 125406061aed7..78ad9f423871f 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Builder.php
@@ -3,8 +3,11 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Controller\Adminhtml\Product;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\ProductFactory;
use Magento\Cms\Model\Wysiwyg as WysiwygModel;
use Magento\Framework\App\RequestInterface;
@@ -15,6 +18,11 @@
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Type as ProductTypes;
+/**
+ * Build a product based on a request
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class Builder
{
/**
@@ -79,10 +87,11 @@ public function __construct(
* Build product based on user request
*
* @param RequestInterface $request
- * @return \Magento\Catalog\Model\Product
+ * @return ProductInterface
* @throws \RuntimeException
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
- public function build(RequestInterface $request)
+ public function build(RequestInterface $request): ProductInterface
{
$productId = (int) $request->getParam('id');
$storeId = $request->getParam('store', 0);
@@ -92,6 +101,9 @@ public function build(RequestInterface $request)
if ($productId) {
try {
$product = $this->productRepository->getById($productId, true, $storeId);
+ if ($attributeSetId) {
+ $product->setAttributeSetId($attributeSetId);
+ }
} catch (\Exception $e) {
$product = $this->createEmptyProduct(ProductTypes::DEFAULT_TYPE, $attributeSetId, $storeId);
$this->logger->critical($e);
@@ -113,6 +125,8 @@ public function build(RequestInterface $request)
}
/**
+ * Create a product with the given properties
+ *
* @param int $typeId
* @param int $attributeSetId
* @param int $storeId
diff --git a/app/code/Magento/Catalog/Controller/Adminhtml/Product/GridOnly.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/GridOnly.php
index 40e62895caffc..51aaa8c178edd 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/GridOnly.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/GridOnly.php
@@ -1,12 +1,16 @@
productBuilder->build($this->getRequest());
$block = $this->getRequest()->getParam('gridOnlyBlock');
- $blockClassSuffix = str_replace(' ', '_', ucwords(str_replace('_', ' ', $block)));
+ $blockClassSuffix = ucwords($block, '_');
/** @var \Magento\Framework\Controller\Result\Raw $resultRaw */
$resultRaw = $this->resultRawFactory->create();
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/Category/Tree.php b/app/code/Magento/Catalog/Model/Category/Tree.php
index 6080f74d5fa06..0a9cb25d7b0e5 100644
--- a/app/code/Magento/Catalog/Model/Category/Tree.php
+++ b/app/code/Magento/Catalog/Model/Category/Tree.php
@@ -32,27 +32,40 @@ class Tree
*/
protected $treeFactory;
+ /**
+ * @var \Magento\Catalog\Model\ResourceModel\Category\TreeFactory
+ */
+ private $treeResourceFactory;
+
/**
* @param \Magento\Catalog\Model\ResourceModel\Category\Tree $categoryTree
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection
* @param \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory $treeFactory
+ * @param \Magento\Catalog\Model\ResourceModel\Category\TreeFactory|null $treeResourceFactory
*/
public function __construct(
\Magento\Catalog\Model\ResourceModel\Category\Tree $categoryTree,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Model\ResourceModel\Category\Collection $categoryCollection,
- \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory $treeFactory
+ \Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory $treeFactory,
+ \Magento\Catalog\Model\ResourceModel\Category\TreeFactory $treeResourceFactory = null
) {
$this->categoryTree = $categoryTree;
$this->storeManager = $storeManager;
$this->categoryCollection = $categoryCollection;
$this->treeFactory = $treeFactory;
+ $this->treeResourceFactory = $treeResourceFactory ?? \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\Catalog\Model\ResourceModel\Category\TreeFactory::class);
}
/**
+ * Get root node by category.
+ *
* @param \Magento\Catalog\Model\Category|null $category
* @return Node|null
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getRootNode($category = null)
{
@@ -71,13 +84,18 @@ public function getRootNode($category = null)
}
/**
+ * Get node by category.
+ *
* @param \Magento\Catalog\Model\Category $category
* @return Node
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function getNode(\Magento\Catalog\Model\Category $category)
{
$nodeId = $category->getId();
- $node = $this->categoryTree->loadNode($nodeId);
+ $categoryTree = $this->treeResourceFactory->create();
+ $node = $categoryTree->loadNode($nodeId);
$node->loadChildren();
$this->prepareCollection();
$this->categoryTree->addCollectionData($this->categoryCollection);
@@ -85,7 +103,11 @@ protected function getNode(\Magento\Catalog\Model\Category $category)
}
/**
+ * Prepare category collection.
+ *
* @return void
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
protected function prepareCollection()
{
@@ -104,6 +126,8 @@ protected function prepareCollection()
}
/**
+ * Get tree by node.
+ *
* @param \Magento\Framework\Data\Tree\Node $node
* @param int $depth
* @param int $currentLevel
@@ -127,6 +151,8 @@ public function getTree($node, $depth = null, $currentLevel = 0)
}
/**
+ * Get node children.
+ *
* @param \Magento\Framework\Data\Tree\Node $node
* @param int $depth
* @param int $currentLevel
diff --git a/app/code/Magento/Catalog/Model/CategoryList.php b/app/code/Magento/Catalog/Model/CategoryList.php
index e3318db505489..cab8e013d9ba1 100644
--- a/app/code/Magento/Catalog/Model/CategoryList.php
+++ b/app/code/Magento/Catalog/Model/CategoryList.php
@@ -76,11 +76,10 @@ public function getList(SearchCriteriaInterface $searchCriteria)
$this->extensionAttributesJoinProcessor->process($collection);
$this->collectionProcessor->process($searchCriteria, $collection);
- $collection->load();
$items = [];
- foreach ($collection->getItems() as $category) {
- $items[] = $this->categoryRepository->get($category->getId());
+ foreach ($collection->getAllIds() as $id) {
+ $items[] = $this->categoryRepository->get($id);
}
/** @var CategorySearchResultsInterface $searchResult */
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php
index 8b952ca844bb9..1506ccf6963bf 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Flat/AbstractAction.php
@@ -8,6 +8,9 @@
use Magento\Framework\App\ResourceConnection;
+/**
+ * Abstract action class for category flat indexers.
+ */
class AbstractAction
{
/**
@@ -130,7 +133,7 @@ protected function getFlatTableStructure($tableName)
$table = $this->connection->newTable(
$tableName
)->setComment(
- sprintf("Catalog Category Flat", $tableName)
+ 'Catalog Category Flat'
);
//Adding columns
@@ -378,7 +381,7 @@ protected function getAttributeValues($entityIds, $storeId)
$linkField = $this->getCategoryMetadata()->getLinkField();
foreach ($attributesType as $type) {
foreach ($this->getAttributeTypeValues($type, $entityIds, $storeId) as $row) {
- if (isset($row[$linkField]) && isset($row['attribute_id'])) {
+ if (isset($row[$linkField], $row['attribute_id'])) {
$attributeId = $row['attribute_id'];
if (isset($attributes[$attributeId])) {
$attributeCode = $attributes[$attributeId]['attribute_code'];
@@ -496,6 +499,8 @@ protected function getTableName($name)
}
/**
+ * Get category metadata instance.
+ *
* @return \Magento\Framework\EntityManager\EntityMetadata
*/
private function getCategoryMetadata()
@@ -509,6 +514,8 @@ private function getCategoryMetadata()
}
/**
+ * Get skip static columns instance.
+ *
* @return array
*/
private function getSkipStaticColumns()
diff --git a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
index f8121b55dbf99..eb59acb56c356 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Category/Product/Action/Full.php
@@ -3,33 +3,46 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Indexer\Category\Product\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Config;
+use Magento\Catalog\Model\Indexer\Category\Product\AbstractAction;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Query\Generator as QueryGenerator;
use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Select;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Indexer\BatchProviderInterface;
+use Magento\Framework\Indexer\BatchSizeManagementInterface;
use Magento\Indexer\Model\ProcessManager;
+use Magento\Store\Model\Store;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class Full reindex action
*
- * @package Magento\Catalog\Model\Indexer\Category\Product\Action
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractAction
+class Full extends AbstractAction
{
/**
- * @var \Magento\Framework\Indexer\BatchSizeManagementInterface
+ * @var BatchSizeManagementInterface
*/
private $batchSizeManagement;
/**
- * @var \Magento\Framework\Indexer\BatchProviderInterface
+ * @var BatchProviderInterface
*/
private $batchProvider;
/**
- * @var \Magento\Framework\EntityManager\MetadataPool
+ * @var MetadataPool
*/
protected $metadataPool;
@@ -52,25 +65,25 @@ class Full extends \Magento\Catalog\Model\Indexer\Category\Product\AbstractActio
/**
* @param ResourceConnection $resource
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Catalog\Model\Config $config
+ * @param StoreManagerInterface $storeManager
+ * @param Config $config
* @param QueryGenerator|null $queryGenerator
- * @param \Magento\Framework\Indexer\BatchSizeManagementInterface|null $batchSizeManagement
- * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
- * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool
+ * @param BatchSizeManagementInterface|null $batchSizeManagement
+ * @param BatchProviderInterface|null $batchProvider
+ * @param MetadataPool|null $metadataPool
* @param int|null $batchRowsCount
* @param ActiveTableSwitcher|null $activeTableSwitcher
* @param ProcessManager $processManager
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Framework\App\ResourceConnection $resource,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Catalog\Model\Config $config,
+ ResourceConnection $resource,
+ StoreManagerInterface $storeManager,
+ Config $config,
QueryGenerator $queryGenerator = null,
- \Magento\Framework\Indexer\BatchSizeManagementInterface $batchSizeManagement = null,
- \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
+ BatchSizeManagementInterface $batchSizeManagement = null,
+ BatchProviderInterface $batchProvider = null,
+ MetadataPool $metadataPool = null,
$batchRowsCount = null,
ActiveTableSwitcher $activeTableSwitcher = null,
ProcessManager $processManager = null
@@ -81,15 +94,15 @@ public function __construct(
$config,
$queryGenerator
);
- $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
+ $objectManager = ObjectManager::getInstance();
$this->batchSizeManagement = $batchSizeManagement ?: $objectManager->get(
- \Magento\Framework\Indexer\BatchSizeManagementInterface::class
+ BatchSizeManagementInterface::class
);
$this->batchProvider = $batchProvider ?: $objectManager->get(
- \Magento\Framework\Indexer\BatchProviderInterface::class
+ BatchProviderInterface::class
);
$this->metadataPool = $metadataPool ?: $objectManager->get(
- \Magento\Framework\EntityManager\MetadataPool::class
+ MetadataPool::class
);
$this->batchRowsCount = $batchRowsCount;
$this->activeTableSwitcher = $activeTableSwitcher ?: $objectManager->get(ActiveTableSwitcher::class);
@@ -97,33 +110,39 @@ public function __construct(
}
/**
+ * Create the store tables
+ *
* @return void
*/
- private function createTables()
+ private function createTables(): void
{
foreach ($this->storeManager->getStores() as $store) {
- $this->tableMaintainer->createTablesForStore($store->getId());
+ $this->tableMaintainer->createTablesForStore((int)$store->getId());
}
}
/**
+ * Truncates the replica tables
+ *
* @return void
*/
- private function clearReplicaTables()
+ private function clearReplicaTables(): void
{
foreach ($this->storeManager->getStores() as $store) {
- $this->connection->truncateTable($this->tableMaintainer->getMainReplicaTable($store->getId()));
+ $this->connection->truncateTable($this->tableMaintainer->getMainReplicaTable((int)$store->getId()));
}
}
/**
+ * Switches the active table
+ *
* @return void
*/
- private function switchTables()
+ private function switchTables(): void
{
$tablesToSwitch = [];
foreach ($this->storeManager->getStores() as $store) {
- $tablesToSwitch[] = $this->tableMaintainer->getMainTable($store->getId());
+ $tablesToSwitch[] = $this->tableMaintainer->getMainTable((int)$store->getId());
}
$this->activeTableSwitcher->switchTable($this->connection, $tablesToSwitch);
}
@@ -133,12 +152,13 @@ private function switchTables()
*
* @return $this
*/
- public function execute()
+ public function execute(): self
{
$this->createTables();
$this->clearReplicaTables();
$this->reindex();
$this->switchTables();
+
return $this;
}
@@ -147,7 +167,7 @@ public function execute()
*
* @return void
*/
- protected function reindex()
+ protected function reindex(): void
{
$userFunctions = [];
@@ -165,9 +185,9 @@ protected function reindex()
/**
* Execute indexation by store
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
*/
- private function reindexStore($store)
+ private function reindexStore($store): void
{
$this->reindexRootCategory($store);
$this->reindexAnchorCategories($store);
@@ -177,31 +197,31 @@ private function reindexStore($store)
/**
* Publish data from tmp to replica table
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return void
*/
- private function publishData($store)
+ private function publishData($store): void
{
- $select = $this->connection->select()->from($this->tableMaintainer->getMainTmpTable($store->getId()));
+ $select = $this->connection->select()->from($this->tableMaintainer->getMainTmpTable((int)$store->getId()));
$columns = array_keys(
- $this->connection->describeTable($this->tableMaintainer->getMainReplicaTable($store->getId()))
+ $this->connection->describeTable($this->tableMaintainer->getMainReplicaTable((int)$store->getId()))
);
- $tableName = $this->tableMaintainer->getMainReplicaTable($store->getId());
+ $tableName = $this->tableMaintainer->getMainReplicaTable((int)$store->getId());
$this->connection->query(
$this->connection->insertFromSelect(
$select,
$tableName,
$columns,
- \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::INSERT_ON_DUPLICATE
)
);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
- protected function reindexRootCategory(\Magento\Store\Model\Store $store)
+ protected function reindexRootCategory(Store $store): void
{
if ($this->isIndexRootCategoryNeeded()) {
$this->reindexCategoriesBySelect($this->getAllProducts($store), 'cp.entity_id IN (?)', $store);
@@ -211,10 +231,10 @@ protected function reindexRootCategory(\Magento\Store\Model\Store $store)
/**
* Reindex products of anchor categories
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return void
*/
- protected function reindexAnchorCategories(\Magento\Store\Model\Store $store)
+ protected function reindexAnchorCategories(Store $store): void
{
$this->reindexCategoriesBySelect($this->getAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store);
}
@@ -222,10 +242,10 @@ protected function reindexAnchorCategories(\Magento\Store\Model\Store $store)
/**
* Reindex products of non anchor categories
*
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return void
*/
- protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store)
+ protected function reindexNonAnchorCategories(Store $store): void
{
$this->reindexCategoriesBySelect($this->getNonAnchorCategoriesSelect($store), 'ccp.product_id IN (?)', $store);
}
@@ -233,40 +253,42 @@ protected function reindexNonAnchorCategories(\Magento\Store\Model\Store $store)
/**
* Reindex categories using given SQL select and condition.
*
- * @param \Magento\Framework\DB\Select $basicSelect
+ * @param Select $basicSelect
* @param string $whereCondition
- * @param \Magento\Store\Model\Store $store
+ * @param Store $store
* @return void
*/
- private function reindexCategoriesBySelect(\Magento\Framework\DB\Select $basicSelect, $whereCondition, $store)
+ private function reindexCategoriesBySelect(Select $basicSelect, $whereCondition, $store): void
{
- $this->tableMaintainer->createMainTmpTable($store->getId());
+ $this->tableMaintainer->createMainTmpTable((int)$store->getId());
- $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
+ $entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
$columns = array_keys(
- $this->connection->describeTable($this->tableMaintainer->getMainTmpTable($store->getId()))
+ $this->connection->describeTable($this->tableMaintainer->getMainTmpTable((int)$store->getId()))
);
$this->batchSizeManagement->ensureBatchSize($this->connection, $this->batchRowsCount);
- $batches = $this->batchProvider->getBatches(
- $this->connection,
- $entityMetadata->getEntityTable(),
+
+ $select = $this->connection->select();
+ $select->distinct(true);
+ $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+
+ $batchQueries = $this->prepareSelectsByRange(
+ $select,
$entityMetadata->getIdentifierField(),
- $this->batchRowsCount
+ (int)$this->batchRowsCount
);
- foreach ($batches as $batch) {
- $this->connection->delete($this->tableMaintainer->getMainTmpTable($store->getId()));
+
+ foreach ($batchQueries as $query) {
+ $this->connection->delete($this->tableMaintainer->getMainTmpTable((int)$store->getId()));
+ $entityIds = $this->connection->fetchCol($query);
$resultSelect = clone $basicSelect;
- $select = $this->connection->select();
- $select->distinct(true);
- $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
- $entityIds = $this->batchProvider->getBatchIds($this->connection, $select, $batch);
$resultSelect->where($whereCondition, $entityIds);
$this->connection->query(
$this->connection->insertFromSelect(
$resultSelect,
- $this->tableMaintainer->getMainTmpTable($store->getId()),
+ $this->tableMaintainer->getMainTmpTable((int)$store->getId()),
$columns,
- \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::INSERT_ON_DUPLICATE
)
);
$this->publishData($store);
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
index 802176092d147..ed8f692885d91 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Eav/Action/Full.php
@@ -7,26 +7,41 @@
namespace Magento\Catalog\Model\Indexer\Product\Eav\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Query\BatchIteratorInterface;
+use Magento\Framework\DB\Query\Generator as QueryGenerator;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Indexer\BatchProviderInterface;
+use Magento\Store\Model\ScopeInterface;
/**
* Class Full reindex action
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction
+class Full extends AbstractAction
{
/**
- * @var \Magento\Framework\EntityManager\MetadataPool
+ * @var MetadataPool
*/
private $metadataPool;
/**
- * @var \Magento\Framework\Indexer\BatchProviderInterface
+ * @var BatchProviderInterface
*/
private $batchProvider;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator
+ * @var BatchSizeCalculator
*/
private $batchSizeCalculator;
@@ -36,44 +51,54 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Eav\AbstractAction
private $activeTableSwitcher;
/**
- * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ * @var ScopeConfigInterface
*/
private $scopeConfig;
/**
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory
- * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool
- * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator
+ * @var QueryGenerator|null
+ */
+ private $batchQueryGenerator;
+
+ /**
+ * @param DecimalFactory $eavDecimalFactory
+ * @param SourceFactory $eavSourceFactory
+ * @param MetadataPool|null $metadataPool
+ * @param BatchProviderInterface|null $batchProvider
+ * @param BatchSizeCalculator $batchSizeCalculator
* @param ActiveTableSwitcher|null $activeTableSwitcher
- * @param \Magento\Framework\App\Config\ScopeConfigInterface|null $scopeConfig
+ * @param ScopeConfigInterface|null $scopeConfig
+ * @param QueryGenerator|null $batchQueryGenerator
*/
public function __construct(
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory $eavDecimalFactory,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory $eavSourceFactory,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
- \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator $batchSizeCalculator = null,
+ DecimalFactory $eavDecimalFactory,
+ SourceFactory $eavSourceFactory,
+ MetadataPool $metadataPool = null,
+ BatchProviderInterface $batchProvider = null,
+ BatchSizeCalculator $batchSizeCalculator = null,
ActiveTableSwitcher $activeTableSwitcher = null,
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null
+ ScopeConfigInterface $scopeConfig = null,
+ QueryGenerator $batchQueryGenerator = null
) {
- $this->scopeConfig = $scopeConfig ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- \Magento\Framework\App\Config\ScopeConfigInterface::class
+ $this->scopeConfig = $scopeConfig ?: ObjectManager::getInstance()->get(
+ ScopeConfigInterface::class
);
parent::__construct($eavDecimalFactory, $eavSourceFactory, $scopeConfig);
- $this->metadataPool = $metadataPool ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- \Magento\Framework\EntityManager\MetadataPool::class
+ $this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(
+ MetadataPool::class
);
- $this->batchProvider = $batchProvider ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- \Magento\Framework\Indexer\BatchProviderInterface::class
+ $this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get(
+ BatchProviderInterface::class
);
- $this->batchSizeCalculator = $batchSizeCalculator ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator::class
+ $this->batchSizeCalculator = $batchSizeCalculator ?: ObjectManager::getInstance()->get(
+ BatchSizeCalculator::class
);
- $this->activeTableSwitcher = $activeTableSwitcher ?: \Magento\Framework\App\ObjectManager::getInstance()->get(
+ $this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get(
ActiveTableSwitcher::class
);
+ $this->batchQueryGenerator = $batchQueryGenerator ?: ObjectManager::getInstance()->get(
+ QueryGenerator::class
+ );
}
/**
@@ -81,10 +106,10 @@ public function __construct(
*
* @param array|int|null $ids
* @return void
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function execute($ids = null)
+ public function execute($ids = null): void
{
if (!$this->isEavIndexerEnabled()) {
return;
@@ -94,20 +119,21 @@ public function execute($ids = null)
$connection = $indexer->getConnection();
$mainTable = $this->activeTableSwitcher->getAdditionalTableName($indexer->getMainTable());
$connection->truncateTable($mainTable);
- $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
- $batches = $this->batchProvider->getBatches(
- $connection,
- $entityMetadata->getEntityTable(),
+ $entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
+
+ $select = $connection->select();
+ $select->distinct(true);
+ $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+
+ $batchQueries = $this->batchQueryGenerator->generate(
$entityMetadata->getIdentifierField(),
- $this->batchSizeCalculator->estimateBatchSize($connection, $indexerName)
+ $select,
+ $this->batchSizeCalculator->estimateBatchSize($connection, $indexerName),
+ BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR
);
- foreach ($batches as $batch) {
- /** @var \Magento\Framework\DB\Select $select */
- $select = $connection->select();
- $select->distinct(true);
- $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
- $entityIds = $this->batchProvider->getBatchIds($connection, $select, $batch);
+ foreach ($batchQueries as $query) {
+ $entityIds = $connection->fetchCol($query);
if (!empty($entityIds)) {
$indexer->reindexEntities($this->processRelations($indexer, $entityIds, true));
$this->syncData($indexer, $mainTable);
@@ -116,14 +142,14 @@ public function execute($ids = null)
$this->activeTableSwitcher->switchTable($indexer->getConnection(), [$indexer->getMainTable()]);
}
} catch (\Exception $e) {
- throw new \Magento\Framework\Exception\LocalizedException(__($e->getMessage()), $e);
+ throw new LocalizedException(__($e->getMessage()), $e);
}
}
/**
* @inheritdoc
*/
- protected function syncData($indexer, $destinationTable, $ids = null)
+ protected function syncData($indexer, $destinationTable, $ids = null): void
{
$connection = $indexer->getConnection();
$connection->beginTransaction();
@@ -136,7 +162,7 @@ protected function syncData($indexer, $destinationTable, $ids = null)
$select,
$destinationTable,
$targetColumns,
- \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::INSERT_ON_DUPLICATE
);
$connection->query($query);
$connection->commit();
@@ -155,7 +181,7 @@ private function isEavIndexerEnabled(): bool
{
$eavIndexerStatus = $this->scopeConfig->getValue(
self::ENABLE_EAV_INDEXER,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE
);
return (bool)$eavIndexerStatus;
diff --git a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
index 1a75751570658..858eba3ab217a 100644
--- a/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
+++ b/app/code/Magento/Catalog/Model/Indexer/Product/Price/Action/Full.php
@@ -3,41 +3,64 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Indexer\Product\Price\Action;
+use Magento\Catalog\Model\Indexer\Product\Price\AbstractAction;
+use Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory;
+use Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer;
+use Magento\Catalog\Model\Product\Type;
+use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory;
+use Magento\Directory\Model\CurrencyFactory;
+use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\PriceInterface;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Query\BatchIterator;
+use Magento\Framework\DB\Query\Generator as QueryGenerator;
+use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\EntityMetadataInterface;
use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Indexer\BatchProviderInterface;
use Magento\Framework\Indexer\DimensionalIndexerInterface;
use Magento\Customer\Model\Indexer\CustomerGroupDimensionProvider;
+use Magento\Framework\Stdlib\DateTime;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Indexer\Model\ProcessManager;
use Magento\Store\Model\Indexer\WebsiteDimensionProvider;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Class Full reindex action
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
+class Full extends AbstractAction
{
/**
- * @var \Magento\Framework\EntityManager\MetadataPool
+ * @var MetadataPool
*/
private $metadataPool;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator
+ * @var BatchSizeCalculator
*/
private $batchSizeCalculator;
/**
- * @var \Magento\Framework\Indexer\BatchProviderInterface
+ * @var BatchProviderInterface
*/
private $batchProvider;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher
+ * @var ActiveTableSwitcher
*/
private $activeTableSwitcher;
@@ -47,54 +70,61 @@ class Full extends \Magento\Catalog\Model\Indexer\Product\Price\AbstractAction
private $productMetaDataCached;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory
+ * @var DimensionCollectionFactory
*/
private $dimensionCollectionFactory;
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer
+ * @var TableMaintainer
*/
private $dimensionTableMaintainer;
/**
- * @var \Magento\Indexer\Model\ProcessManager
+ * @var ProcessManager
*/
private $processManager;
/**
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $config
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Directory\Model\CurrencyFactory $currencyFactory
- * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
- * @param \Magento\Framework\Stdlib\DateTime $dateTime
- * @param \Magento\Catalog\Model\Product\Type $catalogProductType
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource
- * @param \Magento\Framework\EntityManager\MetadataPool|null $metadataPool
- * @param \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator|null $batchSizeCalculator
- * @param \Magento\Framework\Indexer\BatchProviderInterface|null $batchProvider
- * @param \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher|null $activeTableSwitcher
- * @param \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory|null $dimensionCollectionFactory
- * @param \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer|null $dimensionTableMaintainer
- * @param \Magento\Indexer\Model\ProcessManager $processManager
+ * @var QueryGenerator|null
+ */
+ private $batchQueryGenerator;
+
+ /**
+ * @param ScopeConfigInterface $config
+ * @param StoreManagerInterface $storeManager
+ * @param CurrencyFactory $currencyFactory
+ * @param TimezoneInterface $localeDate
+ * @param DateTime $dateTime
+ * @param Type $catalogProductType
+ * @param Factory $indexerPriceFactory
+ * @param DefaultPrice $defaultIndexerResource
+ * @param MetadataPool|null $metadataPool
+ * @param BatchSizeCalculator|null $batchSizeCalculator
+ * @param BatchProviderInterface|null $batchProvider
+ * @param ActiveTableSwitcher|null $activeTableSwitcher
+ * @param DimensionCollectionFactory|null $dimensionCollectionFactory
+ * @param TableMaintainer|null $dimensionTableMaintainer
+ * @param ProcessManager $processManager
+ * @param QueryGenerator|null $batchQueryGenerator
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
- \Magento\Framework\App\Config\ScopeConfigInterface $config,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Directory\Model\CurrencyFactory $currencyFactory,
- \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate,
- \Magento\Framework\Stdlib\DateTime $dateTime,
- \Magento\Catalog\Model\Product\Type $catalogProductType,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\Factory $indexerPriceFactory,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $defaultIndexerResource,
- \Magento\Framework\EntityManager\MetadataPool $metadataPool = null,
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator $batchSizeCalculator = null,
- \Magento\Framework\Indexer\BatchProviderInterface $batchProvider = null,
- \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher $activeTableSwitcher = null,
- \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory $dimensionCollectionFactory = null,
- \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer $dimensionTableMaintainer = null,
- \Magento\Indexer\Model\ProcessManager $processManager = null
+ ScopeConfigInterface $config,
+ StoreManagerInterface $storeManager,
+ CurrencyFactory $currencyFactory,
+ TimezoneInterface $localeDate,
+ DateTime $dateTime,
+ Type $catalogProductType,
+ Factory $indexerPriceFactory,
+ DefaultPrice $defaultIndexerResource,
+ MetadataPool $metadataPool = null,
+ BatchSizeCalculator $batchSizeCalculator = null,
+ BatchProviderInterface $batchProvider = null,
+ ActiveTableSwitcher $activeTableSwitcher = null,
+ DimensionCollectionFactory $dimensionCollectionFactory = null,
+ TableMaintainer $dimensionTableMaintainer = null,
+ ProcessManager $processManager = null,
+ QueryGenerator $batchQueryGenerator = null
) {
parent::__construct(
$config,
@@ -107,26 +137,27 @@ public function __construct(
$defaultIndexerResource
);
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(
- \Magento\Framework\EntityManager\MetadataPool::class
+ MetadataPool::class
);
$this->batchSizeCalculator = $batchSizeCalculator ?: ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\BatchSizeCalculator::class
+ BatchSizeCalculator::class
);
$this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get(
- \Magento\Framework\Indexer\BatchProviderInterface::class
+ BatchProviderInterface::class
);
$this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher::class
+ ActiveTableSwitcher::class
);
$this->dimensionCollectionFactory = $dimensionCollectionFactory ?: ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\Indexer\Product\Price\DimensionCollectionFactory::class
+ DimensionCollectionFactory::class
);
$this->dimensionTableMaintainer = $dimensionTableMaintainer ?: ObjectManager::getInstance()->get(
- \Magento\Catalog\Model\Indexer\Product\Price\TableMaintainer::class
+ TableMaintainer::class
);
$this->processManager = $processManager ?: ObjectManager::getInstance()->get(
- \Magento\Indexer\Model\ProcessManager::class
+ ProcessManager::class
);
+ $this->batchQueryGenerator = $batchQueryGenerator ?? ObjectManager::getInstance()->get(QueryGenerator::class);
}
/**
@@ -137,13 +168,13 @@ public function __construct(
* @throws \Exception
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function execute($ids = null)
+ public function execute($ids = null): void
{
try {
//Prepare indexer tables before full reindex
$this->prepareTables();
- /** @var \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice $indexer */
+ /** @var DefaultPrice $indexer */
foreach ($this->getTypeIndexers(true) as $typeId => $priceIndexer) {
if ($priceIndexer instanceof DimensionalIndexerInterface) {
//New price reindex mechanism
@@ -170,7 +201,7 @@ public function execute($ids = null)
* @return void
* @throws \Exception
*/
- private function prepareTables()
+ private function prepareTables(): void
{
$this->_defaultIndexerResource->getTableStrategy()->setUseIdxTable(false);
@@ -185,7 +216,7 @@ private function prepareTables()
* @return void
* @throws \Exception
*/
- private function truncateReplicaTables()
+ private function truncateReplicaTables(): void
{
foreach ($this->dimensionCollectionFactory->create() as $dimension) {
$dimensionTable = $this->dimensionTableMaintainer->getMainReplicaTable($dimension);
@@ -202,12 +233,12 @@ private function truncateReplicaTables()
* @return void
* @throws \Exception
*/
- private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId)
+ private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $priceIndexer, string $typeId): void
{
$userFunctions = [];
foreach ($this->dimensionCollectionFactory->create() as $dimensions) {
$userFunctions[] = function () use ($priceIndexer, $dimensions, $typeId) {
- return $this->reindexByBatches($priceIndexer, $dimensions, $typeId);
+ $this->reindexByBatches($priceIndexer, $dimensions, $typeId);
};
}
$this->processManager->execute($userFunctions);
@@ -223,10 +254,13 @@ private function reindexProductTypeWithDimensions(DimensionalIndexerInterface $p
* @return void
* @throws \Exception
*/
- private function reindexByBatches(DimensionalIndexerInterface $priceIndexer, array $dimensions, string $typeId)
- {
+ private function reindexByBatches(
+ DimensionalIndexerInterface $priceIndexer,
+ array $dimensions,
+ string $typeId
+ ): void {
foreach ($this->getBatchesForIndexer($typeId) as $batch) {
- $this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions, $typeId);
+ $this->reindexByBatchWithDimensions($priceIndexer, $batch, $dimensions);
}
}
@@ -235,16 +269,20 @@ private function reindexByBatches(DimensionalIndexerInterface $priceIndexer, arr
*
* @param string $typeId
*
- * @return \Generator
+ * @return BatchIterator
* @throws \Exception
*/
- private function getBatchesForIndexer(string $typeId)
+ private function getBatchesForIndexer(string $typeId): BatchIterator
{
$connection = $this->_defaultIndexerResource->getConnection();
- return $this->batchProvider->getBatches(
- $connection,
- $this->getProductMetaData()->getEntityTable(),
+ $entityMetadata = $this->getProductMetaData();
+ $select = $connection->select();
+ $select->distinct(true);
+ $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+
+ return $this->batchQueryGenerator->generate(
$this->getProductMetaData()->getIdentifierField(),
+ $select,
$this->batchSizeCalculator->estimateBatchSize(
$connection,
$typeId
@@ -256,20 +294,18 @@ private function getBatchesForIndexer(string $typeId)
* Reindex by batch for new 'Dimensional' price indexer
*
* @param DimensionalIndexerInterface $priceIndexer
- * @param array $batch
+ * @param Select $batchQuery
* @param array $dimensions
- * @param string $typeId
*
* @return void
* @throws \Exception
*/
private function reindexByBatchWithDimensions(
DimensionalIndexerInterface $priceIndexer,
- array $batch,
- array $dimensions,
- string $typeId
- ) {
- $entityIds = $this->getEntityIdsFromBatch($typeId, $batch);
+ Select $batchQuery,
+ array $dimensions
+ ): void {
+ $entityIds = $this->getEntityIdsFromBatch($batchQuery);
if (!empty($entityIds)) {
$this->dimensionTableMaintainer->createMainTmpTable($dimensions);
@@ -295,10 +331,10 @@ private function reindexByBatchWithDimensions(
* @return void
* @throws \Exception
*/
- private function reindexProductType(PriceInterface $priceIndexer, string $typeId)
+ private function reindexProductType(PriceInterface $priceIndexer, string $typeId): void
{
foreach ($this->getBatchesForIndexer($typeId) as $batch) {
- $this->reindexBatch($priceIndexer, $batch, $typeId);
+ $this->reindexBatch($priceIndexer, $batch);
}
}
@@ -306,15 +342,13 @@ private function reindexProductType(PriceInterface $priceIndexer, string $typeId
* Reindex by batch for old price indexer
*
* @param PriceInterface $priceIndexer
- * @param array $batch
- * @param string $typeId
- *
+ * @param Select $batch
* @return void
* @throws \Exception
*/
- private function reindexBatch(PriceInterface $priceIndexer, array $batch, string $typeId)
+ private function reindexBatch(PriceInterface $priceIndexer, Select $batch): void
{
- $entityIds = $this->getEntityIdsFromBatch($typeId, $batch);
+ $entityIds = $this->getEntityIdsFromBatch($batch);
if (!empty($entityIds)) {
// Temporary table will created if not exists
@@ -339,27 +373,15 @@ private function reindexBatch(PriceInterface $priceIndexer, array $batch, string
/**
* Get Entity Ids from batch
*
- * @param string $typeId
- * @param array $batch
- *
+ * @param Select $batch
* @return array
* @throws \Exception
*/
- private function getEntityIdsFromBatch(string $typeId, array $batch)
+ private function getEntityIdsFromBatch(Select $batch): array
{
$connection = $this->_defaultIndexerResource->getConnection();
- // Get entity ids from batch
- $select = $connection
- ->select()
- ->distinct(true)
- ->from(
- ['e' => $this->getProductMetaData()->getEntityTable()],
- $this->getProductMetaData()->getIdentifierField()
- )
- ->where('type_id = ?', $typeId);
-
- return $this->batchProvider->getBatchIds($connection, $select, $batch);
+ return $connection->fetchCol($batch);
}
/**
@@ -368,7 +390,7 @@ private function getEntityIdsFromBatch(string $typeId, array $batch)
* @return EntityMetadataInterface
* @throws \Exception
*/
- private function getProductMetaData()
+ private function getProductMetaData(): EntityMetadataInterface
{
if ($this->productMetaDataCached === null) {
$this->productMetaDataCached = $this->metadataPool->getMetadata(ProductInterface::class);
@@ -383,7 +405,7 @@ private function getProductMetaData()
* @return string
* @throws \Exception
*/
- private function getReplicaTable()
+ private function getReplicaTable(): string
{
return $this->activeTableSwitcher->getAdditionalTableName(
$this->_defaultIndexerResource->getMainTable()
@@ -394,8 +416,9 @@ private function getReplicaTable()
* Replacement of tables from replica to main
*
* @return void
+ * @throws \Zend_Db_Statement_Exception
*/
- private function switchTables()
+ private function switchTables(): void
{
// Switch dimension tables
$mainTablesByDimension = [];
@@ -417,13 +440,14 @@ private function switchTables()
/**
* Move data from old price indexer mechanism to new indexer mechanism by dimensions.
+ *
* Used only for backward compatibility
*
* @param array $dimensions
- *
* @return void
+ * @throws \Zend_Db_Statement_Exception
*/
- private function moveDataFromReplicaTableToReplicaTables(array $dimensions)
+ private function moveDataFromReplicaTableToReplicaTables(array $dimensions): void
{
if (!$dimensions) {
return;
@@ -455,17 +479,17 @@ private function moveDataFromReplicaTableToReplicaTables(array $dimensions)
$select,
$replicaTablesByDimension,
[],
- \Magento\Framework\DB\Adapter\AdapterInterface::INSERT_ON_DUPLICATE
+ AdapterInterface::INSERT_ON_DUPLICATE
)
);
}
/**
- * @deprecated
+ * Retrieves the index table that should be used
*
- * @inheritdoc
+ * @deprecated
*/
- protected function getIndexTargetTable()
+ protected function getIndexTargetTable(): string
{
return $this->activeTableSwitcher->getAdditionalTableName($this->_defaultIndexerResource->getMainTable());
}
diff --git a/app/code/Magento/Catalog/Model/Product/Action.php b/app/code/Magento/Catalog/Model/Product/Action.php
index f78048424b42c..3863cf2457247 100644
--- a/app/code/Magento/Catalog/Model/Product/Action.php
+++ b/app/code/Magento/Catalog/Model/Product/Action.php
@@ -168,5 +168,7 @@ public function updateWebsites($productIds, $websiteIds, $type)
if (!$categoryIndexer->isScheduled()) {
$categoryIndexer->reindexList(array_unique($productIds));
}
+
+ $this->_eventManager->dispatch('catalog_product_to_website_change', ['products' => $productIds]);
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
index e346c912dccaa..db967052cb7a5 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/Backend/Tierprice.php
@@ -165,19 +165,6 @@ protected function modifyPriceData($object, $data)
/** @var \Magento\Catalog\Model\Product $object */
$data = parent::modifyPriceData($object, $data);
$price = $object->getPrice();
-
- $specialPrice = $object->getSpecialPrice();
- $specialPriceFromDate = $object->getSpecialFromDate();
- $specialPriceToDate = $object->getSpecialToDate();
- $today = time();
-
- if ($specialPrice && ($object->getPrice() > $object->getFinalPrice())) {
- if ($today >= strtotime($specialPriceFromDate) && $today <= strtotime($specialPriceToDate) ||
- $today >= strtotime($specialPriceFromDate) && $specialPriceToDate === null) {
- $price = $specialPrice;
- }
- }
-
foreach ($data as $key => $tierPrice) {
$percentageValue = $this->getPercentage($tierPrice);
if ($percentageValue) {
diff --git a/app/code/Magento/Catalog/Model/ProductRepository.php b/app/code/Magento/Catalog/Model/ProductRepository.php
index d124bf5e42639..48f45d0ce9373 100644
--- a/app/code/Magento/Catalog/Model/ProductRepository.php
+++ b/app/code/Magento/Catalog/Model/ProductRepository.php
@@ -644,10 +644,11 @@ public function delete(ProductInterface $product)
unset($this->instancesById[$product->getId()]);
$this->resourceModel->delete($product);
} catch (ValidatorException $e) {
- throw new CouldNotSaveException(__($e->getMessage()));
+ throw new CouldNotSaveException(__($e->getMessage()), $e);
} catch (\Exception $e) {
throw new \Magento\Framework\Exception\StateException(
- __('The "%1" product couldn\'t be removed.', $sku)
+ __('The "%1" product couldn\'t be removed.', $sku),
+ $e
);
}
$this->removeProductFromLocalCache($sku);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
index 9cf1431317944..3d7f863b7c0d3 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
@@ -99,7 +99,7 @@ protected function _isApplicableAttribute($object, $attribute)
*/
protected function _isCallableAttributeInstance($instance, $method, $args)
{
- if ($instance instanceof \Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
+ if ($instance instanceof AbstractBackend
&& ($method == 'beforeSave' || $method == 'afterSave')
) {
$attributeCode = $instance->getAttribute()->getAttributeCode();
@@ -491,7 +491,7 @@ protected function _canUpdateAttribute(AbstractAttribute $attribute, $value, arr
* Retrieve attribute's raw value from DB.
*
* @param int $entityId
- * @param int|string|array $attribute atrribute's ids or codes
+ * @param int|string|array $attribute attribute's ids or codes
* @param int|\Magento\Store\Model\Store $store
* @return bool|string|array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
index 618abda0a942d..b5668a12f94a5 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Category/Collection.php
@@ -7,6 +7,7 @@
use Magento\CatalogUrlRewrite\Model\CategoryUrlRewriteGenerator;
use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
use Magento\Store\Model\ScopeInterface;
/**
@@ -83,6 +84,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
*
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -97,7 +99,8 @@ public function __construct(
\Magento\Framework\Validator\UniversalFactory $universalFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null
+ \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
parent::__construct(
$entityFactory,
@@ -110,7 +113,8 @@ public function __construct(
$resourceHelper,
$universalFactory,
$storeManager,
- $connection
+ $connection,
+ $resourceModelPool
);
$this->scopeConfig = $scopeConfig ?:
\Magento\Framework\App\ObjectManager::getInstance()->get(ScopeConfigInterface::class);
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php b/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php
index 9ab863cde2704..2e40d13f1ccac 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Collection/AbstractCollection.php
@@ -5,8 +5,11 @@
*/
namespace Magento\Catalog\Model\ResourceModel\Collection;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
* Catalog EAV collection resource abstract model
+ *
* Implement using different stores for retrieve attribute values
*
* @api
@@ -43,6 +46,7 @@ class AbstractCollection extends \Magento\Eav\Model\Entity\Collection\AbstractCo
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
*
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -56,7 +60,8 @@ public function __construct(
\Magento\Eav\Model\ResourceModel\Helper $resourceHelper,
\Magento\Framework\Validator\UniversalFactory $universalFactory,
\Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->_storeManager = $storeManager;
parent::__construct(
@@ -69,7 +74,8 @@ public function __construct(
$eavEntityFactory,
$resourceHelper,
$universalFactory,
- $connection
+ $connection,
+ $resourceModelPool
);
}
@@ -205,10 +211,7 @@ protected function _getLoadAttributesSelect($table, $attributeIds = [])
}
/**
- * @param \Magento\Framework\DB\Select $select
- * @param string $table
- * @param string $type
- * @return \Magento\Framework\DB\Select
+ * @inheritdoc
*/
protected function _addLoadAttributesSelectValues($select, $table, $type)
{
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
index 8aeb52e75c774..136c7e800bf08 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Collection.php
@@ -21,6 +21,7 @@
use Magento\Store\Model\Store;
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
/**
* Product collection
@@ -297,6 +298,7 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
/**
* Collection constructor
+ *
* @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
* @param \Psr\Log\LoggerInterface $logger
* @param \Magento\Framework\Data\Collection\Db\FetchStrategyInterface $fetchStrategy
@@ -322,6 +324,8 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Collection\Abstrac
* @param TableMaintainer|null $tableMaintainer
* @param PriceTableResolver|null $priceTableResolver
* @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface|null $resourceModelPool
+ *
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -349,7 +353,8 @@ public function __construct(
MetadataPool $metadataPool = null,
TableMaintainer $tableMaintainer = null,
PriceTableResolver $priceTableResolver = null,
- DimensionFactory $dimensionFactory = null
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->moduleManager = $moduleManager;
$this->_catalogProductFlatState = $catalogProductFlatState;
@@ -377,7 +382,8 @@ public function __construct(
$resourceHelper,
$universalFactory,
$storeManager,
- $connection
+ $connection,
+ $resourceModelPool
);
$this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class);
$this->priceTableResolver = $priceTableResolver ?: ObjectManager::getInstance()->get(PriceTableResolver::class);
@@ -1437,7 +1443,7 @@ protected function _addUrlRewrite()
'u.url_rewrite_id=cu.url_rewrite_id'
)->where('cu.url_rewrite_id IS NULL');
}
-
+
// more priority is data with category id
$urlRewrites = [];
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php
index 7c78dbca5a004..a45e2060d7c20 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Compare/Item/Collection.php
@@ -5,6 +5,13 @@
*/
namespace Magento\Catalog\Model\ResourceModel\Product\Compare\Item;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
* Catalog Product Compare Items Resource Collection
*
@@ -75,7 +82,12 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param \Magento\Catalog\Model\ResourceModel\Product\Compare\Item $catalogProductCompareItem
* @param \Magento\Catalog\Helper\Product\Compare $catalogProductCompare
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
- *
+ * @param ProductLimitationFactory|null $productLimitationFactory
+ * @param MetadataPool|null $metadataPool
+ * @param TableMaintainer|null $tableMaintainer
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -100,7 +112,13 @@ public function __construct(
\Magento\Customer\Api\GroupManagementInterface $groupManagement,
\Magento\Catalog\Model\ResourceModel\Product\Compare\Item $catalogProductCompareItem,
\Magento\Catalog\Helper\Product\Compare $catalogProductCompare,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ ProductLimitationFactory $productLimitationFactory = null,
+ MetadataPool $metadataPool = null,
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->_catalogProductCompareItem = $catalogProductCompareItem;
$this->_catalogProductCompare = $catalogProductCompare;
@@ -124,7 +142,13 @@ public function __construct(
$customerSession,
$dateTime,
$groupManagement,
- $connection
+ $connection,
+ $productLimitationFactory,
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
}
@@ -403,6 +427,7 @@ public function clear()
/**
* Retrieve is flat enabled flag
+ *
* Overwrite disable flat for compared item if required EAV resource
*
* @return bool
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php
index 47fc6802d7eaf..463da8762b7cf 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/CustomOptionPriceModifier.php
@@ -127,6 +127,8 @@ public function modifyPrice(IndexTableStructure $priceTable, array $entityIds =
}
/**
+ * Check if custom options exist.
+ *
* @param IndexTableStructure $priceTable
* @return bool
* @throws \Exception
@@ -154,6 +156,8 @@ private function checkIfCustomOptionsExist(IndexTableStructure $priceTable): boo
}
/**
+ * Get connection.
+ *
* @return \Magento\Framework\DB\Adapter\AdapterInterface
*/
private function getConnection()
@@ -211,7 +215,7 @@ private function getSelectForOptionsWithMultipleValues(string $sourceTable): Sel
} else {
$select->joinLeft(
['otps' => $this->getTable('catalog_product_option_type_price')],
- 'otps.option_type_id = otpd.option_type_id AND otpd.store_id = cwd.default_store_id',
+ 'otps.option_type_id = otpd.option_type_id AND otps.store_id = cwd.default_store_id',
[]
);
@@ -373,6 +377,8 @@ private function getSelectAggregated(string $sourceTable): Select
}
/**
+ * Get select for update.
+ *
* @param string $sourceTable
* @return \Magento\Framework\DB\Select
*/
@@ -402,6 +408,8 @@ private function getSelectForUpdate(string $sourceTable): Select
}
/**
+ * Get table name.
+ *
* @param string $tableName
* @return string
*/
@@ -411,6 +419,8 @@ private function getTable(string $tableName): string
}
/**
+ * Is price scope global.
+ *
* @return bool
*/
private function isPriceGlobal(): bool
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
index 168fa8f50acc2..3b4c3408e742b 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/Indexer/Price/DefaultPrice.php
@@ -10,6 +10,7 @@
/**
* Default Product Type Price Indexer Resource model
+ *
* For correctly work need define product type id
*
* @api
@@ -208,6 +209,8 @@ public function reindexEntity($entityIds)
}
/**
+ * Reindex prices.
+ *
* @param null|int|array $entityIds
* @return \Magento\Catalog\Model\ResourceModel\Product\Indexer\Price\DefaultPrice
*/
@@ -604,7 +607,7 @@ protected function _applyCustomOption()
[]
)->joinLeft(
['otps' => $this->getTable('catalog_product_option_type_price')],
- 'otps.option_type_id = otpd.option_type_id AND otpd.store_id = cs.store_id',
+ 'otps.option_type_id = otpd.option_type_id AND otps.store_id = cs.store_id',
[]
)->group(
['i.entity_id', 'i.customer_group_id', 'i.website_id', 'o.option_id']
@@ -802,6 +805,8 @@ public function getIdxTable($table = null)
}
/**
+ * Check if product exists.
+ *
* @return bool
*/
protected function hasEntity()
@@ -823,6 +828,8 @@ protected function hasEntity()
}
/**
+ * Get total tier price expression.
+ *
* @param \Zend_Db_Expr $priceExpression
* @return \Zend_Db_Expr
*/
@@ -863,6 +870,8 @@ private function getTotalTierPriceExpression(\Zend_Db_Expr $priceExpression)
}
/**
+ * Get tier price expression for table.
+ *
* @param string $tableAlias
* @param \Zend_Db_Expr $priceExpression
* @return \Zend_Db_Expr
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
index 8841b6059c46f..841fe17bdcf05 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product/LinkedProductSelectBuilderByBasePrice.php
@@ -11,6 +11,9 @@
use Magento\Framework\DB\Select;
use Magento\Store\Model\Store;
+/**
+ * Provide Select object for retrieve product id with minimal price.
+ */
class LinkedProductSelectBuilderByBasePrice implements LinkedProductSelectBuilderInterface
{
/**
@@ -69,7 +72,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function build($productId)
{
@@ -85,7 +88,7 @@ public function build($productId)
[]
)->joinInner(
[BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS => $productTable],
- sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS, $linkField),
+ sprintf('%s.entity_id = link.child_id', BaseSelectProcessorInterface::PRODUCT_TABLE_ALIAS),
['entity_id']
)->joinInner(
['t' => $priceAttribute->getBackendTable()],
diff --git a/app/code/Magento/Catalog/Pricing/Price/MinimalTierPriceCalculator.php b/app/code/Magento/Catalog/Pricing/Price/MinimalTierPriceCalculator.php
index 387ef9416ef68..a5e573caa381e 100644
--- a/app/code/Magento/Catalog/Pricing/Price/MinimalTierPriceCalculator.php
+++ b/app/code/Magento/Catalog/Pricing/Price/MinimalTierPriceCalculator.php
@@ -29,8 +29,10 @@ public function __construct(CalculatorInterface $calculator)
}
/**
- * Get raw value of "as low as" as a minimal among tier prices
- * {@inheritdoc}
+ * Get raw value of "as low as" as a minimal among tier prices{@inheritdoc}
+ *
+ * @param SaleableInterface $saleableItem
+ * @return float|null
*/
public function getValue(SaleableInterface $saleableItem)
{
@@ -49,8 +51,10 @@ public function getValue(SaleableInterface $saleableItem)
}
/**
- * Return calculated amount object that keeps "as low as" value
- * {@inheritdoc}
+ * Return calculated amount object that keeps "as low as" value{@inheritdoc}
+ *
+ * @param SaleableInterface $saleableItem
+ * @return AmountInterface|null
*/
public function getAmount(SaleableInterface $saleableItem)
{
@@ -58,6 +62,6 @@ public function getAmount(SaleableInterface $saleableItem)
return $value === null
? null
- : $this->calculator->getAmount($value, $saleableItem);
+ : $this->calculator->getAmount($value, $saleableItem, 'tax');
}
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
index 692487c1d60cd..a544be434f9c5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AddProductToCartActionGroup.xml
@@ -11,9 +11,13 @@
-
+
-
-
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
index 57f91b78fcbe9..90d732c9654e1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminCategoryActionGroup.xml
@@ -154,7 +154,7 @@
-
+
@@ -231,6 +231,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -263,4 +280,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
index 1eb76e66677c9..da570f9ed99b0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductActionGroup.xml
@@ -15,12 +15,12 @@
-
+
-
+
@@ -34,6 +34,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -77,6 +96,11 @@
+
+
+
+
+
@@ -180,12 +204,14 @@
-
-
+
+
-
+
-
+
+
+
@@ -241,6 +267,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -342,6 +387,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
index 0082b376bc4a6..46329dde278bc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
@@ -148,4 +148,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
index a3b4203b7a69e..2914ecc470220 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeSetActionGroup.xml
@@ -45,6 +45,16 @@
+
+
+
+
+
+
+
+
+
+
@@ -54,4 +64,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
index f0367fb72c6a2..cbdabe6a2d218 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductGridActionGroup.xml
@@ -192,7 +192,7 @@
-
+
@@ -201,8 +201,9 @@
-
-
+
+
+
@@ -272,4 +273,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml
new file mode 100644
index 0000000000000..9402ac05d79a5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertAttributeDeletionErrorMessageActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml
new file mode 100644
index 0000000000000..25390e6b4a80b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductAttributePresenceInCatalogProductGridActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
new file mode 100644
index 0000000000000..7917fe68aaebc
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductInfoOnEditPageActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
new file mode 100644
index 0000000000000..963c9d9f1c911
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AssertProductOnAdminGridActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml
new file mode 100644
index 0000000000000..575cbdcd9b6dc
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/DeleteProductAttributeByAttributeCodeActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml
new file mode 100644
index 0000000000000..31b024c82a9a0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/OpenProductAttributeFromSearchResultInGridActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml
new file mode 100644
index 0000000000000..67cdd8192fcb4
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/SearchAttributeByCodeOnProductAttributeGridActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
new file mode 100644
index 0000000000000..aec21f3bc48c9
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml
new file mode 100644
index 0000000000000..31783526932b6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CatalogSpecialPriceData.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ 99.99
+ 0
+
+
+
+ 55.55
+ 0
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
index a11c3fd0d7afa..27167d03d528e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/CategoryData.xml
@@ -104,4 +104,10 @@
false
true
+
+ InactiveNotInMenu
+ inactivenotinmenu
+ false
+ false
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
index bf0762b4b0319..134abcaa50354 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
@@ -52,6 +52,27 @@
true
ProductAttributeFrontendLabel
+
+ test_attr_
+ select
+ global
+ false
+ false
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ false
+ ProductAttributeFrontendLabel
+
attribute
select
@@ -73,6 +94,27 @@
true
ProductAttributeFrontendLabel
+
+ attribute
+ select
+ global
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
testattribute
select
@@ -115,6 +157,69 @@
true
ProductAttributeFrontendLabel
+
+ attribute
+ multiselect
+ global
+ false
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
+
+ news_from_date
+ Set Product as New from Date
+ date
+ false
+ true
+ website
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ false
+ false
+ true
+ true
+ false
+ ProductAttributeFrontendLabel
+
+
+ attribute
+ text
+ global
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
attribute
boolean
@@ -159,4 +264,46 @@
false
ProductAttributeFrontendLabel
+
+ text
+ defaultValue
+ No
+
+
+ date
+ No
+
+
+ date
+ No
+
+
+ select
+ Dropdown
+ No
+ opt1Admin
+ opt1Front
+ opt2Admin
+ opt2Front
+ opt3Admin
+ opt3Front
+
+
+ multiselect
+ Multiple Select
+ No
+ opt1Admin
+ opt1Front
+ opt2Admin
+ opt2Front
+ opt3Admin
+ opt3Front
+
+
+ select
+ Dropdown
+ No
+ opt1'Admin
+ opt1'Front
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
index 5be2a84f54555..fcb56cf298a98 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeOptionData.xml
@@ -81,4 +81,9 @@
Option9Store0
Option10Store1
+
+
+ White
+ white
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
index d136661e917cb..383797933074e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -60,6 +60,48 @@
EavStockItem
CustomAttributeCategoryIds
+
+ SimpleProductForTest1
+ simple
+ 4
+ SimpleProductAfterImport1
+ 250.00
+ 4
+ 1
+ 100
+ simple-product-for-test-1
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ SimpleProductForTest2
+ simple
+ 4
+ SimpleProductAfterImport2
+ 300.00
+ 4
+ 1
+ 100
+ simple-product-for-test-2
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ SimpleProductForTest3
+ simple
+ 4
+ SimpleProductAfterImport3
+ 350.00
+ 4
+ 1
+ 100
+ simple-product-for-test-3
+ 1
+ EavStockItem
+ CustomAttributeCategoryIds
+
SimpleProduct
simple
@@ -96,6 +138,31 @@
0
CustomAttributeCategoryIds
+
+ testSku
+ simple
+ 4
+ 4
+ OutOfStockProduct
+ 123.00
+ testurlkey
+ 1
+ 0
+
+
+
+ testSku
+ simple
+ 4
+ 4
+ SimpleOffline
+ 123.00
+ 2
+ 100
+ testurlkey
+ EavStockItem
+ CustomAttributeProductAttribute
+
321.00
@@ -110,6 +177,18 @@
EavStockItem
CustomAttributeProductAttribute
+
+ api-simple-product
+ simple
+ 4
+ 4
+ Api Simple Out Of Stock Product
+ 123.00
+ api-simple-product
+ 1
+ 100
+ CustomAttributeProductAttribute
+
api-simple-product
simple
@@ -349,6 +428,11 @@
ProductOptionDropDownWithLongValuesTitle
+
+
+ ProductOptionField
+ ProductOptionArea
+
api-virtual-product
virtual
@@ -708,6 +792,129 @@
virtual-product
virtual
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 325.02
+ 89
+ In Stock
+ IN STOCK
+ 89.0000
+ Search
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 325.03
+ 25
+ Out of Stock
+ OUT OF STOCK
+ 125.0000
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 245.00
+ 200
+ In Stock
+ IN STOCK
+ 120.0000
+ Catalog, Search
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 325.01
+ 125
+ In Stock
+ IN STOCK
+ 25.0000
+ Catalog
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 300.00
+ 34
+ In Stock
+ IN STOCK
+ 1
+ This item has weight
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 1.99
+ Taxable Goods
+ 1000
+ 1
+ IN STOCK
+ 1
+ This item has weight
+ Catalog, Search
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 245.00
+ 343.00
+ 200
+ In Stock
+ IN STOCK
+ 120.0000
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 74.00
+ 87
+ In Stock
+ 333.0000
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 325.00
+ 123
+ In Stock
+ 129.0000
+ Not Visible Individually
+ simple
+ EavStock100
+
+
+ test-simple-product
+ TestSimpleProduct
+ test_simple_product_sku
+ 9.99
+ simple
+ EavStock100
+
simple-product
SimpleProduct
@@ -729,4 +936,37 @@
13
0
+
+ sku_simple_product_
+ simple
+ 4
+ 4
+ Simple Product
+ 560
+ simple-product-
+ 1
+ 25
+ 1
+ 1
+ 1
+ 2
+ EavStockItem
+ CustomAttributeCategoryIds
+
+
+ sku_simple_product_
+ simple
+ 4
+ 4
+ SimpleProduct
+ 560
+ simple-product-
+ 1
+ 25
+ 1
+ 1
+ 1
+ 2
+ EavStockItem
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinkData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinkData.xml
new file mode 100644
index 0000000000000..000bb2095002c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinkData.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ related
+ simple
+ 1
+ Qty1000
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinksData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinksData.xml
new file mode 100644
index 0000000000000..bd4f807880ab8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductLinksData.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ RelatedProductLink
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/SimpleProductOptionData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/SimpleProductOptionData.xml
new file mode 100644
index 0000000000000..157a4d410263b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/SimpleProductOptionData.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+ Test3 option
+ Drop-down
+ 1
+ 40 Percent
+ 40.00
+ Percent
+ sku_drop_down_row_1
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml
index cb8bb47f3cc93..e5070340421a9 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/TierPriceData.xml
@@ -42,4 +42,18 @@
24.00
15
-
+
+ All Websites [USD]
+ ALL GROUPS
+ 500,000.00
+ 1
+
+
+ 90.00
+ fixed
+ 0
+ ALL GROUPS
+ 2
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml
new file mode 100644
index 0000000000000..83f0a56c21545
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/WidgetsData.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+ Catalog Product Link
+ Product Link Block Template
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_special_price-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_special_price-meta.xml
new file mode 100644
index 0000000000000..354277ad056f7
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_special_price-meta.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ application/json
+
+
+ number
+ integer
+ string
+ string
+ string
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_tier_price-meta.xml b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_tier_price-meta.xml
new file mode 100644
index 0000000000000..7aa7530b0fda8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Metadata/catalog_tier_price-meta.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ application/json
+
+
+ number
+ string
+ integer
+ string
+ string
+ integer
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml
new file mode 100644
index 0000000000000..e23a503266e33
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
index b3ed3f478f810..e4c4ece5ac6cf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductCreatePage.xml
@@ -15,8 +15,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductDeletePage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductDeletePage.xml
new file mode 100644
index 0000000000000..1ce53a0ebd54b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminProductDeletePage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml
index 009110a729bde..e8adede5b2de6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMainActionsSection.xml
@@ -14,5 +14,7 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
index fee86ca1caa29..ea4f4bf53eb71 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategoryMessagesSection.xml
@@ -10,5 +10,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
new file mode 100644
index 0000000000000..2de7bf19fd378
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateNewProductAttributeSection.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
index ee6af87b8e2c5..d24c501152b78 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCreateProductAttributeSection.xml
@@ -23,6 +23,16 @@
+
+
+
+
+
+
+
+
@@ -77,10 +88,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSection.xml
new file mode 100644
index 0000000000000..5329ad48c8f43
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSelectProductPopupSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSelectProductPopupSection.xml
new file mode 100644
index 0000000000000..0da67849f85c6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminNewWidgetSelectProductPopupSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAddAttributeModalSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAddAttributeModalSection.xml
new file mode 100644
index 0000000000000..a3c98e43b4510
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAddAttributeModalSection.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml
index d3aaeefdc6bb2..5efd04eacb719 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeGridSection.xml
@@ -10,12 +10,18 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
index b906e2fa9084b..3fad50adb771a 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributeSetGridSection.xml
@@ -14,5 +14,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributesSection.xml
new file mode 100644
index 0000000000000..46a516b538f09
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductAttributesSection.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCrossSellModalSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCrossSellModalSection.xml
new file mode 100644
index 0000000000000..803d72d7a7eca
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCrossSellModalSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductDropdownOrderSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
new file mode 100644
index 0000000000000..b58fb2316f915
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
index aa752e0e2289c..1652546b0acb3 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormActionSection.xml
@@ -10,6 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
index a1bb27bb45a29..bc7c472df6eac 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedInventorySection.xml
@@ -19,9 +19,14 @@
+
+
+
+
+
+
+
+
+
-
-
-
-
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
index 3f67e4b087cc4..da282d06145aa 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
@@ -13,23 +13,31 @@
+
+
+
+
+
+
+
+
@@ -47,17 +55,21 @@
+
+
+
+
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
index 43345c69e6c04..939974248aabf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridFilterSection.xml
@@ -28,6 +28,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
index be046955a66a5..ef596bed186e5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductRelatedUpSellCrossSellSection.xml
@@ -9,8 +9,10 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
index de7ee35a40f8f..1cd64544d9636 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryMainSection.xml
@@ -26,11 +26,13 @@
-
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
index f35eb63ee0e0a..51b5a0242d976 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
@@ -32,5 +32,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml
index 509ad2b8f849c..52a377ad264c0 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontHeaderSection.xml
@@ -9,6 +9,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
index 4dcda8dcd41ae..c58479a7b73e5 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontMessagesSection.xml
@@ -11,6 +11,6 @@
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index 4114b199715cb..8393cee57996f 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -13,7 +13,8 @@
-
+
+
@@ -74,6 +75,11 @@
+
+
+
+
+
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/Section/StorefrontProductPageSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml
index e9c8f53f97e5f..8055ecfe00cde 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductPageSection.xml
@@ -22,5 +22,6 @@
+
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml
new file mode 100644
index 0000000000000..53bb12fda4833
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AddToCartCrossSellTest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
new file mode 100644
index 0000000000000..e3f4d6cbdde0d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminAddInStockProductToTheCartTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml
new file mode 100644
index 0000000000000..3027416ee520b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminChangeProductAttributeSet.xml
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml
new file mode 100644
index 0000000000000..86978a4121a43
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithDisabledChildProductTest.xml
@@ -0,0 +1,180 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10.00
+
+
+
+
+
+
+ 20.00
+
+
+
+
+
+
+ 30.00
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithOutOfStockChildProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithOutOfStockChildProductTest.xml
new file mode 100644
index 0000000000000..8d41b276334a6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckConfigurableProductPriceWithOutOfStockChildProductTest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
new file mode 100644
index 0000000000000..fd22142fcb097
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveAndNotIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml
new file mode 100644
index 0000000000000..b6c76d6577210
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveCategoryAndSubcategoryIsNotVisibleInNavigationMenuTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
new file mode 100644
index 0000000000000..c9cd9acd9708c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckInactiveIncludeInMenuCategoryAndSubcategoryIsNotVisibleInNavigationTest.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsNotVisibleInCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsNotVisibleInCategoryTest.xml
new file mode 100644
index 0000000000000..ee8b48a94b20d
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsNotVisibleInCategoryTest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsVisibleInCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsVisibleInCategoryTest.xml
new file mode 100644
index 0000000000000..e1cb45be22b4e
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckOutOfStockProductIsVisibleInCategoryTest.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckSubCategoryIsNotVisibleInNavigationMenuTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckSubCategoryIsNotVisibleInNavigationMenuTest.xml
new file mode 100644
index 0000000000000..f5872ac3efca0
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCheckSubCategoryIsNotVisibleInNavigationMenuTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
new file mode 100755
index 0000000000000..4deca73504677
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml
new file mode 100644
index 0000000000000..d9e410a9a3009
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateAttributeSetEntityTest.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml
index 7c24a8aba27bd..79eec02a828f6 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCategoryWithProductsGridFilter.xml
@@ -28,6 +28,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
new file mode 100644
index 0000000000000..09b49011938e8
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
new file mode 100644
index 0000000000000..1bc69be642a37
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDropdownProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml
new file mode 100644
index 0000000000000..37ec4e0d32528
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateCategoryTest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml
new file mode 100644
index 0000000000000..575bb56912b25
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateDuplicateProductTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
new file mode 100644
index 0000000000000..1f558568e9248
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateMultipleSelectProductAttributeVisibleInStorefrontAdvancedSearchFormTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml
new file mode 100644
index 0000000000000..5c798db29b976
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeFromProductPageTest.xml
@@ -0,0 +1,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml
new file mode 100644
index 0000000000000..d4d6496e018f5
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateProductAttributeRequiredTextFieldTest.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/AdminDeleteConfigurableChildProductsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml
new file mode 100644
index 0000000000000..0df9dd0b57545
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteConfigurableChildProductsTest.xml
@@ -0,0 +1,121 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml
new file mode 100644
index 0000000000000..3841c061c2629
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteDropdownProductAttributeFromAttributeSetTest.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml
new file mode 100644
index 0000000000000..6de1a5cd359cd
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteSystemProductAttributeTest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml
new file mode 100644
index 0000000000000..c3cafb17c5eac
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminDeleteTextFieldProductAttributeFromAttributeSetTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
index d7607b4b269e8..4d581bae700d7 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassProductPriceUpdateTest.xml
@@ -54,7 +54,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
index c0eebd1512d6d..8a44c8093ca5e 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesGlobalScopeTest.xml
@@ -58,7 +58,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
index 845c47c0e4c20..bee13bec370da 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminMassUpdateProductAttributesStoreViewScopeTest.xml
@@ -52,7 +52,13 @@
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
new file mode 100644
index 0000000000000..234a7c69913c9
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml
new file mode 100644
index 0000000000000..18e4ff9ee2c99
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductNameToVerifyDataOverridingOnStoreViewLevelTest.xml
@@ -0,0 +1,87 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml
new file mode 100644
index 0000000000000..d5fc981b5b2e6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductPriceToVerifyDataOverridingOnStoreViewLevelTest.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
new file mode 100644
index 0000000000000..2c3aa5db75171
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductTieredPriceTest.xml
@@ -0,0 +1,143 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductTierPrice300InStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductTierPrice300InStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml
new file mode 100644
index 0000000000000..6e8f1ba6f12a6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockDisabledProductTest.xml
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
new file mode 100644
index 0000000000000..a042c4d60ae4f
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockEnabledFlatTest.xml
@@ -0,0 +1,141 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductEnabledFlat.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductEnabledFlat.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
new file mode 100644
index 0000000000000..d08ef9c93999c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockNotVisibleIndividuallyTest.xml
@@ -0,0 +1,104 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml
new file mode 100644
index 0000000000000..3433a09117322
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockUnassignFromCategoryTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
new file mode 100644
index 0000000000000..a695982921cfd
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogAndSearchTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPrice245InStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPrice245InStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
new file mode 100644
index 0000000000000..ba52c6d2bc261
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInCatalogOnlyTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPrice32501InStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPrice32501InStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
new file mode 100644
index 0000000000000..cb5c24839e387
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockVisibleInSearchOnlyTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPrice325InStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPrice325InStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
new file mode 100644
index 0000000000000..318ab6555235e
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceInStockWithCustomOptionsTest.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPriceCustomOptions.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPriceCustomOptions.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+ grabFourthOptions
+ ['-- Please Select --', {{simpleProductCustomizableOption.option_0_title}} +$98.00]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
new file mode 100644
index 0000000000000..54ed753b80a1c
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminUpdateSimpleProductWithRegularPriceOutOfStockTest.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{simpleProductRegularPrice32503OutOfStock.storefrontStatus}}
+ productStockAvailableStatus
+
+
+
+ ${{simpleProductRegularPrice32503OutOfStock.price}}
+ productPriceAmount
+
+
+
+
+
+
+
+
+
+
+
+
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/Mftf/Test/AdminVerifyProductOrderTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml
new file mode 100644
index 0000000000000..a81c26b6e6eaf
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminVerifyProductOrderTest.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml
new file mode 100644
index 0000000000000..52022f32fd8ec
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/CreateProductAttributeEntityTest.xml
@@ -0,0 +1,425 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml
new file mode 100644
index 0000000000000..e79e4cea408fb
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/DeleteUsedInConfigurableProductAttributeTest.xml
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml
index 951afa2ddb68b..a3bce2d4fe2f2 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsTest.xml
@@ -20,30 +20,36 @@
+
+
+
+
+ 17
+
+
+
+
+
+
+
+
+
+
+
-
+
-
-
-
- 17
-
-
-
-
-
-
-
-
-
+
+
+
-
+
@@ -80,7 +86,7 @@
-
+
@@ -92,21 +98,21 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -124,13 +130,11 @@
-
-
-
-
-
-
-
+
+
+
+
+
@@ -168,24 +172,18 @@
-
+
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
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/Test/Unit/Model/Category/TreeTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Category/TreeTest.php
index 9fb2adb2b8ecd..97c098ba0ff2e 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Category/TreeTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Category/TreeTest.php
@@ -43,6 +43,11 @@ class TreeTest extends \PHPUnit\Framework\TestCase
*/
protected $node;
+ /**
+ * @var \Magento\Catalog\Model\ResourceModel\Category\TreeFactory
+ */
+ private $treeResourceFactoryMock;
+
protected function setUp()
{
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
@@ -59,6 +64,12 @@ protected function setUp()
\Magento\Store\Model\StoreManagerInterface::class
)->disableOriginalConstructor()->getMock();
+ $this->treeResourceFactoryMock = $this->createMock(
+ \Magento\Catalog\Model\ResourceModel\Category\TreeFactory::class
+ );
+ $this->treeResourceFactoryMock->method('create')
+ ->willReturn($this->categoryTreeMock);
+
$methods = ['create'];
$this->treeFactoryMock =
$this->createPartialMock(\Magento\Catalog\Api\Data\CategoryTreeInterfaceFactory::class, $methods);
@@ -70,7 +81,8 @@ protected function setUp()
'categoryCollection' => $this->categoryCollection,
'categoryTree' => $this->categoryTreeMock,
'storeManager' => $this->storeManagerMock,
- 'treeFactory' => $this->treeFactoryMock
+ 'treeFactory' => $this->treeFactoryMock,
+ 'treeResourceFactory' => $this->treeResourceFactoryMock,
]
);
}
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
index f78c0ad924954..b8b76524099f4 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/CategoryListTest.php
@@ -93,7 +93,7 @@ public function testGetList()
$collection = $this->getMockBuilder(Collection::class)->disableOriginalConstructor()->getMock();
$collection->expects($this->once())->method('getSize')->willReturn($totalCount);
- $collection->expects($this->once())->method('getItems')->willReturn([$categoryFirst, $categorySecond]);
+ $collection->expects($this->once())->method('getAllIds')->willReturn([$categoryIdFirst, $categoryIdSecond]);
$this->collectionProcessorMock->expects($this->once())
->method('process')
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php
index 90c3f999a6a8b..2e1cff834fd34 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Indexer/Product/Eav/Action/FullTest.php
@@ -3,15 +3,29 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Test\Unit\Model\Indexer\Product\Eav\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
+use Magento\Catalog\Model\Indexer\Product\Eav\Action\Full;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Decimal;
+use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Source;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\DB\Adapter\AdapterInterface;
+use Magento\Framework\DB\Query\Generator;
+use Magento\Framework\DB\Select;
+use Magento\Framework\EntityManager\EntityMetadataInterface;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\DecimalFactory;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\SourceFactory;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Indexer\BatchProviderInterface;
use Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\BatchSizeCalculator;
+use PHPUnit\Framework\MockObject\MockObject as MockObject;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -19,45 +33,50 @@
class FullTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full|\PHPUnit_Framework_MockObject_MockObject
+ * @var Full|MockObject
*/
private $model;
/**
- * @var DecimalFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var DecimalFactory|MockObject
*/
private $eavDecimalFactory;
/**
- * @var SourceFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var SourceFactory|MockObject
*/
private $eavSourceFactory;
/**
- * @var MetadataPool|\PHPUnit_Framework_MockObject_MockObject
+ * @var MetadataPool|MockObject
*/
private $metadataPool;
/**
- * @var BatchProviderInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var BatchProviderInterface|MockObject
*/
private $batchProvider;
/**
- * @var BatchSizeCalculator|\PHPUnit_Framework_MockObject_MockObject
+ * @var BatchSizeCalculator|MockObject
*/
private $batchSizeCalculator;
/**
- * @var ActiveTableSwitcher|\PHPUnit_Framework_MockObject_MockObject
+ * @var ActiveTableSwitcher|MockObject
*/
private $activeTableSwitcher;
/**
- * @var \Magento\Framework\App\Config\ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var ScopeConfigInterface|MockObject
*/
private $scopeConfig;
+ /**
+ * @var Generator
+ */
+ private $batchQueryGenerator;
+
/**
* @return void
*/
@@ -67,15 +86,16 @@ protected function setUp()
$this->eavSourceFactory = $this->createPartialMock(SourceFactory::class, ['create']);
$this->metadataPool = $this->createMock(MetadataPool::class);
$this->batchProvider = $this->getMockForAbstractClass(BatchProviderInterface::class);
+ $this->batchQueryGenerator = $this->createMock(Generator::class);
$this->batchSizeCalculator = $this->createMock(BatchSizeCalculator::class);
$this->activeTableSwitcher = $this->createMock(ActiveTableSwitcher::class);
- $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
+ $this->scopeConfig = $this->getMockBuilder(ScopeConfigInterface::class)
->disableOriginalConstructor()
->getMockForAbstractClass();
$objectManager = new ObjectManager($this);
$this->model = $objectManager->getObject(
- \Magento\Catalog\Model\Indexer\Product\Eav\Action\Full::class,
+ Full::class,
[
'eavDecimalFactory' => $this->eavDecimalFactory,
'eavSourceFactory' => $this->eavSourceFactory,
@@ -83,7 +103,8 @@ protected function setUp()
'batchProvider' => $this->batchProvider,
'batchSizeCalculator' => $this->batchSizeCalculator,
'activeTableSwitcher' => $this->activeTableSwitcher,
- 'scopeConfig' => $this->scopeConfig
+ 'scopeConfig' => $this->scopeConfig,
+ 'batchQueryGenerator' => $this->batchQueryGenerator,
]
);
}
@@ -96,15 +117,15 @@ public function testExecute()
$this->scopeConfig->expects($this->once())->method('getValue')->willReturn(1);
$ids = [1, 2, 3];
- $connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)
+ $connectionMock = $this->getMockBuilder(AdapterInterface::class)
->getMockForAbstractClass();
$connectionMock->expects($this->atLeastOnce())->method('describeTable')->willReturn(['id' => []]);
- $eavSource = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Source::class)
+ $eavSource = $this->getMockBuilder(Source::class)
->disableOriginalConstructor()
->getMock();
- $eavDecimal = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Product\Indexer\Eav\Decimal::class)
+ $eavDecimal = $this->getMockBuilder(Decimal::class)
->disableOriginalConstructor()
->getMock();
@@ -125,22 +146,28 @@ public function testExecute()
$this->eavSourceFactory->expects($this->once())->method('create')->will($this->returnValue($eavDecimal));
- $entityMetadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class)
+ $entityMetadataMock = $this->getMockBuilder(EntityMetadataInterface::class)
->getMockForAbstractClass();
$this->metadataPool->expects($this->atLeastOnce())
->method('getMetadata')
- ->with(\Magento\Catalog\Api\Data\ProductInterface::class)
+ ->with(ProductInterface::class)
->willReturn($entityMetadataMock);
- $this->batchProvider->expects($this->atLeastOnce())
- ->method('getBatches')
- ->willReturn([['from' => 10, 'to' => 100]]);
- $this->batchProvider->expects($this->atLeastOnce())
- ->method('getBatchIds')
+ // Super inefficient algorithm in some cases
+ $this->batchProvider->expects($this->never())
+ ->method('getBatches');
+
+ $batchQuery = $this->createMock(Select::class);
+
+ $connectionMock->method('fetchCol')
+ ->with($batchQuery)
->willReturn($ids);
- $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
+ $this->batchQueryGenerator->method('generate')
+ ->willReturn([$batchQuery]);
+
+ $selectMock = $this->getMockBuilder(Select::class)
->disableOriginalConstructor()
->getMock();
@@ -153,7 +180,7 @@ public function testExecute()
/**
* @return void
- * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws LocalizedException
*/
public function testExecuteWithDisabledEavIndexer()
{
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
index 034b04b6a757d..cfb54c3aefd0f 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductOptions/Config/_files/invalidProductOptionsXmlArray.php
@@ -29,12 +29,12 @@
],
],
'renderer_attribute_with_invalid_value' => [
- ' ' .
+ ' ' .
' ',
[
- "Element 'option', attribute 'renderer': [facet 'pattern'] The value 'true12' is not accepted by the " .
- "pattern '[a-zA-Z_\\\\]+'.\nLine: 1\n",
- "Element 'option', attribute 'renderer': 'true12' is not a valid value of the atomic" .
+ "Element 'option', attribute 'renderer': [facet 'pattern'] The value '123true' is not accepted by the " .
+ "pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
+ "Element 'option', attribute 'renderer': '123true' is not a valid value of the atomic" .
" type 'modelName'.\nLine: 1\n"
],
],
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/invalidProductTypesXmlArray.php b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/invalidProductTypesXmlArray.php
index e1847bea53fcb..868252da8190c 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/invalidProductTypesXmlArray.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/invalidProductTypesXmlArray.php
@@ -23,7 +23,7 @@
' ',
[
"Element 'type', attribute 'modelInstance': [facet 'pattern'] The value '123' is not accepted by the" .
- " pattern '[a-zA-Z_\\\\]+'.\nLine: 1\n",
+ " pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
"Element 'type', attribute 'modelInstance': '123' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n"
],
@@ -57,7 +57,7 @@
' ',
[
"Element 'priceModel', attribute 'instance': [facet 'pattern'] The value '123123' is not accepted " .
- "by the pattern '[a-zA-Z_\\\\]+'.\nLine: 1\n",
+ "by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
"Element 'priceModel', attribute 'instance': '123123' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n"
],
@@ -66,7 +66,7 @@
' ',
[
"Element 'indexerModel', attribute 'instance': [facet 'pattern'] The value '123' is not accepted by " .
- "the pattern '[a-zA-Z_\\\\]+'.\nLine: 1\n",
+ "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
"Element 'indexerModel', attribute 'instance': '123' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n"
],
@@ -83,7 +83,7 @@
' ',
[
"Element 'stockIndexerModel', attribute 'instance': [facet 'pattern'] The value '1234' is not " .
- "accepted by the pattern '[a-zA-Z_\\\\]+'.\nLine: 1\n",
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
"Element 'stockIndexerModel', attribute 'instance': '1234' is not a valid value of the atomic " .
"type 'modelName'.\nLine: 1\n"
],
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types_merged.xml b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types_merged.xml
index 7edbc399a9476..701338774baa5 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types_merged.xml
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ProductTypes/Config/_files/valid_product_types_merged.xml
@@ -15,6 +15,14 @@
+
+
+
+
+
+
+
+
@@ -25,5 +33,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
index 3eb219ee2932b..5da5625189ee3 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
@@ -5,13 +5,33 @@
*/
namespace Magento\Catalog\Test\Unit\Model\ResourceModel\Product;
+use Magento\Catalog\Model\Indexer;
+use Magento\Catalog\Model\Product as ProductModel;
+use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
use Magento\Framework\DB\Select;
+use Magento\Eav\Model\Entity\AbstractEntity;
+use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Eav\Model\EntityFactory;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\Data\Collection;
+use Magento\Framework\Data\Collection\Db\FetchStrategyInterface;
+use Magento\Framework\DB;
+use Magento\Framework\EntityManager\EntityMetadataInterface;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Event;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Store\Api\Data\StoreInterface;
+use Magento\Store\Model\StoreManagerInterface;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class CollectionTest extends \PHPUnit\Framework\TestCase
+class CollectionTest extends TestCase
{
/**
* @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
@@ -24,12 +44,12 @@ class CollectionTest extends \PHPUnit\Framework\TestCase
protected $selectMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var \PHPUnit_Framework_MockObject_MockObject|DB\Adapter\AdapterInterface
*/
protected $connectionMock;
/**
- * @var \Magento\Catalog\Model\ResourceModel\Product\Collection
+ * @var ProductResource\Collection
*/
protected $collection;
@@ -70,128 +90,50 @@ protected function setUp()
{
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->entityFactory = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class);
- $logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
- $fetchStrategy = $this->getMockBuilder(\Magento\Framework\Data\Collection\Db\FetchStrategyInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
- $eventManager = $this->getMockBuilder(\Magento\Framework\Event\ManagerInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
- $eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class)
- ->disableOriginalConstructor()
- ->getMock();
- $resource = $this->getMockBuilder(\Magento\Framework\App\ResourceConnection::class)
- ->disableOriginalConstructor()
- ->getMock();
- $eavEntityFactory = $this->getMockBuilder(\Magento\Eav\Model\EntityFactory::class)
- ->disableOriginalConstructor()
- ->getMock();
- $resourceHelper = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Helper::class)
- ->disableOriginalConstructor()
- ->getMock();
- $universalFactory = $this->getMockBuilder(\Magento\Framework\Validator\UniversalFactory::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['getStore', 'getId', 'getWebsiteId'])
- ->getMockForAbstractClass();
- $moduleManager = $this->getMockBuilder(\Magento\Framework\Module\Manager::class)
- ->disableOriginalConstructor()
- ->getMock();
- $catalogProductFlatState = $this->getMockBuilder(\Magento\Catalog\Model\Indexer\Product\Flat\State::class)
- ->disableOriginalConstructor()
- ->getMock();
- $scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
- $productOptionFactory = $this->getMockBuilder(\Magento\Catalog\Model\Product\OptionFactory::class)
- ->disableOriginalConstructor()
- ->getMock();
- $catalogUrl = $this->getMockBuilder(\Magento\Catalog\Model\ResourceModel\Url::class)
- ->disableOriginalConstructor()
- ->getMock();
- $localeDate = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
- $customerSession = $this->getMockBuilder(\Magento\Customer\Model\Session::class)
- ->disableOriginalConstructor()
- ->getMock();
- $dateTime = $this->getMockBuilder(\Magento\Framework\Stdlib\DateTime::class)
- ->disableOriginalConstructor()
- ->getMock();
- $groupManagement = $this->getMockBuilder(\Magento\Customer\Api\GroupManagementInterface::class)
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
-
- $this->connectionMock = $this->getMockBuilder(\Magento\Framework\DB\Adapter\AdapterInterface::class)
- ->setMethods(['getId'])
- ->disableOriginalConstructor()
- ->getMockForAbstractClass();
-
- $this->selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->entityMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class)
- ->disableOriginalConstructor()
- ->getMock();
-
- $this->galleryResourceMock = $this->getMockBuilder(
- \Magento\Catalog\Model\ResourceModel\Product\Gallery::class
- )->disableOriginalConstructor()->getMock();
-
- $this->metadataPoolMock = $this->getMockBuilder(
- \Magento\Framework\EntityManager\MetadataPool::class
- )->disableOriginalConstructor()->getMock();
-
- $this->galleryReadHandlerMock = $this->getMockBuilder(
- \Magento\Catalog\Model\Product\Gallery\ReadHandler::class
- )->disableOriginalConstructor()->getMock();
-
- $this->storeManager->expects($this->any())->method('getId')->willReturn(1);
- $this->storeManager->expects($this->any())->method('getStore')->willReturnSelf();
- $universalFactory->expects($this->exactly(1))->method('create')->willReturnOnConsecutiveCalls(
- $this->entityMock
- );
+ $this->selectMock = $this->createMock(DB\Select::class);
+ $this->connectionMock = $this->createMock(DB\Adapter\AdapterInterface::class);
+ $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock);
+ $this->entityMock = $this->createMock(AbstractEntity::class);
$this->entityMock->expects($this->once())->method('getConnection')->willReturn($this->connectionMock);
$this->entityMock->expects($this->once())->method('getDefaultAttributes')->willReturn([]);
- $this->entityMock->expects($this->any())->method('getTable')->willReturnArgument(0);
- $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock);
+ $this->entityMock->method('getTable')->willReturnArgument(0);
+ $this->galleryResourceMock = $this->createMock(ProductResource\Gallery::class);
+ $this->metadataPoolMock = $this->createMock(MetadataPool::class);
+ $this->galleryReadHandlerMock = $this->createMock(ProductModel\Gallery\ReadHandler::class);
- $productLimitationMock = $this->createMock(
- \Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation::class
- );
- $productLimitationFactoryMock = $this->getMockBuilder(
- ProductLimitationFactory::class
- )->disableOriginalConstructor()->setMethods(['create'])->getMock();
+ $storeStub = $this->createMock(StoreInterface::class);
+ $storeStub->method('getId')->willReturn(1);
+ $storeStub->method('getWebsiteId')->willReturn(1);
+ $this->storeManager = $this->createMock(StoreManagerInterface::class);
+ $this->storeManager->method('getStore')->willReturn($storeStub);
+ $resourceModelPool = $this->createMock(ResourceModelPoolInterface::class);
+ $resourceModelPool->expects($this->exactly(1))->method('get')->willReturn($this->entityMock);
+ $productLimitationFactoryMock = $this->createPartialMock(ProductLimitationFactory::class, ['create']);
$productLimitationFactoryMock->method('create')
- ->willReturn($productLimitationMock);
+ ->willReturn($this->createMock(ProductResource\Collection\ProductLimitation::class));
$this->collection = $this->objectManager->getObject(
- \Magento\Catalog\Model\ResourceModel\Product\Collection::class,
+ ProductResource\Collection::class,
[
'entityFactory' => $this->entityFactory,
- 'logger' => $logger,
- 'fetchStrategy' => $fetchStrategy,
- 'eventManager' => $eventManager,
- 'eavConfig' => $eavConfig,
- 'resource' => $resource,
- 'eavEntityFactory' => $eavEntityFactory,
- 'resourceHelper' => $resourceHelper,
- 'universalFactory' => $universalFactory,
+ 'logger' => $this->createMock(LoggerInterface::class),
+ 'fetchStrategy' => $this->createMock(FetchStrategyInterface::class),
+ 'eventManager' => $this->createMock(Event\ManagerInterface::class),
+ 'eavConfig' => $this->createMock(\Magento\Eav\Model\Config::class),
+ 'resource' => $this->createMock(ResourceConnection::class),
+ 'eavEntityFactory' => $this->createMock(EntityFactory::class),
+ 'resourceHelper' => $this->createMock(\Magento\Catalog\Model\ResourceModel\Helper::class),
+ 'resourceModelPool' => $resourceModelPool,
'storeManager' => $this->storeManager,
- 'moduleManager' => $moduleManager,
- 'catalogProductFlatState' => $catalogProductFlatState,
- 'scopeConfig' => $scopeConfig,
- 'productOptionFactory' => $productOptionFactory,
- 'catalogUrl' => $catalogUrl,
- 'localeDate' => $localeDate,
- 'customerSession' => $customerSession,
- 'dateTime' => $dateTime,
- 'groupManagement' => $groupManagement,
+ 'moduleManager' => $this->createMock(\Magento\Framework\Module\Manager::class),
+ 'catalogProductFlatState' => $this->createMock(Indexer\Product\Flat\State::class),
+ 'scopeConfig' => $this->createMock(ScopeConfigInterface::class),
+ 'productOptionFactory' => $this->createMock(ProductModel\OptionFactory::class),
+ 'catalogUrl' => $this->createMock(\Magento\Catalog\Model\ResourceModel\Url::class),
+ 'localeDate' => $this->createMock(TimezoneInterface::class),
+ 'customerSession' => $this->createMock(\Magento\Customer\Model\Session::class),
+ 'dateTime' => $this->createMock(\Magento\Framework\Stdlib\DateTime::class),
+ 'groupManagement' => $this->createMock(\Magento\Customer\Api\GroupManagementInterface::class),
'connection' => $this->connectionMock,
'productLimitationFactory' => $productLimitationFactoryMock,
'metadataPool' => $this->metadataPoolMock,
@@ -216,9 +158,8 @@ public function testAddProductCategoriesFilter()
$condition = ['in' => [1, 2]];
$values = [1, 2];
$conditionType = 'nin';
- $preparedSql = "category_id IN(1,2)";
- $tableName = "catalog_category_product";
- $this->connectionMock->expects($this->any())->method('getId')->willReturn(1);
+ $preparedSql = 'category_id IN(1,2)';
+ $tableName = 'catalog_category_product';
$this->connectionMock->expects($this->exactly(2))->method('prepareSqlCondition')->withConsecutive(
['cat.category_id', $condition],
['e.entity_id', [$conditionType => $this->selectMock]]
@@ -243,19 +184,14 @@ public function testAddMediaGalleryData()
$rowId = 4;
$linkField = 'row_id';
$mediaGalleriesMock = [[$linkField => $rowId]];
- $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
+ /** @var ProductModel|\PHPUnit_Framework_MockObject_MockObject $itemMock */
+ $itemMock = $this->getMockBuilder(ProductModel::class)
->disableOriginalConstructor()
->setMethods(['getOrigData'])
->getMock();
- $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
- ->disableOriginalConstructor()
- ->getMock();
- $selectMock = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
- ->disableOriginalConstructor()
- ->getMock();
- $metadataMock = $this->getMockBuilder(\Magento\Framework\EntityManager\EntityMetadataInterface::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $attributeMock = $this->createMock(AbstractAttribute::class);
+ $selectMock = $this->createMock(DB\Select::class);
+ $metadataMock = $this->createMock(EntityMetadataInterface::class);
$this->collection->addItem($itemMock);
$this->galleryResourceMock->expects($this->once())->method('createBatchBaseSelect')->willReturn($selectMock);
$attributeMock->expects($this->once())->method('getAttributeId')->willReturn($attributeId);
@@ -285,25 +221,15 @@ public function testAddMediaGalleryData()
public function testAddTierPriceDataByGroupId()
{
$customerGroupId = 2;
- $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
- ->disableOriginalConstructor()
- ->setMethods(['getData'])
- ->getMock();
- $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
+ /** @var ProductModel|\PHPUnit_Framework_MockObject_MockObject $itemMock */
+ $itemMock = $this->createMock(ProductModel::class);
+ $attributeMock = $this->getMockBuilder(AbstractAttribute::class)
->disableOriginalConstructor()
->setMethods(['isScopeGlobal', 'getBackend'])
->getMock();
- $backend = $this->getMockBuilder(\Magento\Catalog\Model\Product\Attribute\Backend\Tierprice::class)
- ->disableOriginalConstructor()
- ->getMock();
- $resource = $this->getMockBuilder(
- \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice::class
- )
- ->disableOriginalConstructor()
- ->getMock();
- $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $backend = $this->createMock(ProductModel\Attribute\Backend\Tierprice::class);
+ $resource = $this->createMock(ProductResource\Attribute\Backend\GroupPrice\AbstractGroupPrice::class);
+ $select = $this->createMock(DB\Select::class);
$this->connectionMock->expects($this->once())->method('getAutoIncrementField')->willReturn('entity_id');
$this->collection->addItem($itemMock);
$itemMock->expects($this->atLeastOnce())->method('getData')->with('entity_id')->willReturn(1);
@@ -313,7 +239,6 @@ public function testAddTierPriceDataByGroupId()
->willReturn($attributeMock);
$attributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn($backend);
$attributeMock->expects($this->once())->method('isScopeGlobal')->willReturn(false);
- $this->storeManager->expects($this->once())->method('getWebsiteId')->willReturn(1);
$backend->expects($this->once())->method('getResource')->willReturn($resource);
$resource->expects($this->once())->method('getSelect')->willReturn($select);
$select->expects($this->once())->method('columns')->with(['product_id' => 'entity_id'])->willReturnSelf();
@@ -340,25 +265,22 @@ public function testAddTierPriceDataByGroupId()
*/
public function testAddTierPriceData()
{
- $itemMock = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
+ /** @var ProductModel|\PHPUnit_Framework_MockObject_MockObject $itemMock */
+ $itemMock = $this->getMockBuilder(ProductModel::class)
->disableOriginalConstructor()
->setMethods(['getData'])
->getMock();
- $attributeMock = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\AbstractAttribute::class)
+ $attributeMock = $this->getMockBuilder(AbstractAttribute::class)
->disableOriginalConstructor()
->setMethods(['isScopeGlobal', 'getBackend'])
->getMock();
- $backend = $this->getMockBuilder(\Magento\Catalog\Model\Product\Attribute\Backend\Tierprice::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $backend = $this->createMock(ProductModel\Attribute\Backend\Tierprice::class);
$resource = $this->getMockBuilder(
- \Magento\Catalog\Model\ResourceModel\Product\Attribute\Backend\GroupPrice\AbstractGroupPrice::class
+ ProductResource\Attribute\Backend\GroupPrice\AbstractGroupPrice::class
)
->disableOriginalConstructor()
->getMock();
- $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $select = $this->createMock(DB\Select::class);
$this->connectionMock->expects($this->once())->method('getAutoIncrementField')->willReturn('entity_id');
$this->collection->addItem($itemMock);
$itemMock->expects($this->atLeastOnce())->method('getData')->with('entity_id')->willReturn(1);
@@ -368,7 +290,6 @@ public function testAddTierPriceData()
->willReturn($attributeMock);
$attributeMock->expects($this->atLeastOnce())->method('getBackend')->willReturn($backend);
$attributeMock->expects($this->once())->method('isScopeGlobal')->willReturn(false);
- $this->storeManager->expects($this->once())->method('getWebsiteId')->willReturn(1);
$backend->expects($this->once())->method('getResource')->willReturn($resource);
$resource->expects($this->once())->method('getSelect')->willReturn($select);
$select->expects($this->once())->method('columns')->with(['product_id' => 'entity_id'])->willReturnSelf();
diff --git a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php
index 596148b627506..80180d2033ce5 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/ResourceModel/Product/Link/Product/CollectionTest.php
@@ -7,6 +7,8 @@
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitation;
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\Framework\Data\Collection\Db\FetchStrategyInterface;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -26,7 +28,7 @@ class CollectionTest extends \PHPUnit\Framework\TestCase
/** @var \Psr\Log\LoggerInterface|\PHPUnit_Framework_MockObject_MockObject */
protected $loggerMock;
- /** @var \Magento\Framework\Data\Collection\Db\FetchStrategyInterface|\PHPUnit_Framework_MockObject_MockObject */
+ /** @var FetchStrategyInterface|\PHPUnit_Framework_MockObject_MockObject */
protected $fetchStrategyMock;
/** @var \Magento\Framework\Event\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject */
@@ -44,8 +46,8 @@ class CollectionTest extends \PHPUnit\Framework\TestCase
/** @var \Magento\Catalog\Model\ResourceModel\Helper|\PHPUnit_Framework_MockObject_MockObject */
protected $helperMock;
- /** @var \Magento\Framework\Validator\UniversalFactory|\PHPUnit_Framework_MockObject_MockObject */
- protected $universalFactoryMock;
+ /** @var ResourceModelPoolInterface|\PHPUnit_Framework_MockObject_MockObject */
+ protected $resourceModelPoolMock;
/** @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject */
protected $storeManagerMock;
@@ -79,29 +81,23 @@ protected function setUp()
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->entityFactoryMock = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class);
$this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class);
- $this->fetchStrategyMock = $this->createMock(
- \Magento\Framework\Data\Collection\Db\FetchStrategyInterface::class
- );
+ $this->fetchStrategyMock = $this->createMock(FetchStrategyInterface::class);
$this->managerInterfaceMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
$this->configMock = $this->createMock(\Magento\Eav\Model\Config::class);
$this->resourceMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class);
$this->entityFactoryMock2 = $this->createMock(\Magento\Eav\Model\EntityFactory::class);
$this->helperMock = $this->createMock(\Magento\Catalog\Model\ResourceModel\Helper::class);
$entity = $this->createMock(\Magento\Eav\Model\Entity\AbstractEntity::class);
- $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
- ->disableOriginalConstructor()
- ->getMock();
- $connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\Pdo\Mysql::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $select = $this->createMock(\Magento\Framework\DB\Select::class);
+ $connection = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class);
$connection->expects($this->any())
->method('select')
->willReturn($select);
$entity->expects($this->any())->method('getConnection')->will($this->returnValue($connection));
$entity->expects($this->any())->method('getDefaultAttributes')->will($this->returnValue([]));
- $this->universalFactoryMock = $this->createMock(\Magento\Framework\Validator\UniversalFactory::class);
- $this->universalFactoryMock->expects($this->any())->method('create')->will($this->returnValue($entity));
- $this->storeManagerMock = $this->getMockForAbstractClass(\Magento\Store\Model\StoreManagerInterface::class);
+ $this->resourceModelPoolMock = $this->createMock(ResourceModelPoolInterface::class);
+ $this->resourceModelPoolMock->expects($this->any())->method('get')->will($this->returnValue($entity));
+ $this->storeManagerMock = $this->createMock(\Magento\Store\Model\StoreManagerInterface::class);
$this->storeManagerMock
->expects($this->any())
->method('getStore')
@@ -118,9 +114,7 @@ function ($store) {
$this->timezoneInterfaceMock = $this->createMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class);
$this->sessionMock = $this->createMock(\Magento\Customer\Model\Session::class);
$this->dateTimeMock = $this->createMock(\Magento\Framework\Stdlib\DateTime::class);
- $productLimitationFactoryMock = $this->getMockBuilder(
- ProductLimitationFactory::class
- )->disableOriginalConstructor()->setMethods(['create'])->getMock();
+ $productLimitationFactoryMock = $this->createPartialMock(ProductLimitationFactory::class, ['create']);
$productLimitationFactoryMock->method('create')
->willReturn($this->createMock(ProductLimitation::class));
@@ -136,7 +130,7 @@ function ($store) {
'resource' => $this->resourceMock,
'eavEntityFactory' => $this->entityFactoryMock2,
'resourceHelper' => $this->helperMock,
- 'universalFactory' => $this->universalFactoryMock,
+ 'resourceModelPool' => $this->resourceModelPoolMock,
'storeManager' => $this->storeManagerMock,
'catalogData' => $this->catalogHelperMock,
'catalogProductFlatState' => $this->stateMock,
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php
new file mode 100644
index 0000000000000..774edcfeb6b64
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/Component/ColumnFactoryTest.php
@@ -0,0 +1,156 @@
+objectManager = new ObjectManager($this);
+
+ $this->attribute = $this->getMockBuilder(ProductAttributeInterface::class)
+ ->setMethods(['usesSource'])
+ ->getMockForAbstractClass();
+ $this->context = $this->createMock(ContextInterface::class);
+ $this->uiComponentFactory = $this->createMock(UiComponentFactory::class);
+ $this->column = $this->getMockForAbstractClass(ColumnInterface::class);
+ $this->uiComponentFactory->method('create')
+ ->willReturn($this->column);
+
+ $this->columnFactory = $this->objectManager->getObject(ColumnFactory::class, [
+ 'componentFactory' => $this->uiComponentFactory
+ ]);
+ }
+
+ /**
+ * Tests the create method will return correct object.
+ *
+ * @return void
+ */
+ public function testCreatedObject(): void
+ {
+ $this->context->method('getRequestParam')
+ ->with(FilterModifier::FILTER_MODIFIER, [])
+ ->willReturn([]);
+
+ $object = $this->columnFactory->create($this->attribute, $this->context);
+ $this->assertEquals(
+ $this->column,
+ $object,
+ 'Object must be the same which the ui component factory creates.'
+ );
+ }
+
+ /**
+ * Tests create method with not filterable in grid attribute.
+ *
+ * @param array $filterModifiers
+ * @param null|string $filter
+ *
+ * @return void
+ * @dataProvider filterModifiersProvider
+ */
+ public function testCreateWithNotFilterableInGridAttribute(array $filterModifiers, ?string $filter): void
+ {
+ $componentFactoryArgument = [
+ 'data' => [
+ 'config' => [
+ 'label' => __(null),
+ 'dataType' => 'text',
+ 'add_field' => true,
+ 'visible' => null,
+ 'filter' => $filter,
+ 'component' => 'Magento_Ui/js/grid/columns/column',
+ ],
+ ],
+ 'context' => $this->context,
+ ];
+
+ $this->context->method('getRequestParam')
+ ->with(FilterModifier::FILTER_MODIFIER, [])
+ ->willReturn($filterModifiers);
+ $this->attribute->method('getIsFilterableInGrid')
+ ->willReturn(false);
+ $this->attribute->method('getAttributeCode')
+ ->willReturn('color');
+
+ $this->uiComponentFactory->expects($this->once())
+ ->method('create')
+ ->with($this->anything(), $this->anything(), $componentFactoryArgument);
+
+ $this->columnFactory->create($this->attribute, $this->context);
+ }
+
+ /**
+ * Filter modifiers data provider.
+ *
+ * @return array
+ */
+ public function filterModifiersProvider(): array
+ {
+ return [
+ 'without' => [
+ 'filter_modifiers' => [],
+ 'filter' => null,
+ ],
+ 'with' => [
+ 'filter_modifiers' => [
+ 'color' => [
+ 'condition_type' => 'notnull',
+ ],
+ ],
+ 'filter' => 'text',
+ ],
+ ];
+ }
+}
diff --git a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php
index 6d7c8814bd474..0e0cb676cdf3e 100644
--- a/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Ui/DataProvider/Product/ProductCustomOptionsDataProviderTest.php
@@ -54,7 +54,16 @@ protected function setUp()
->getMockForAbstractClass();
$this->collectionMock = $this->getMockBuilder(AbstractCollection::class)
->disableOriginalConstructor()
- ->setMethods(['load', 'getSelect', 'getTable', 'getIterator', 'isLoaded', 'toArray', 'getSize'])
+ ->setMethods([
+ 'load',
+ 'getSelect',
+ 'getTable',
+ 'getIterator',
+ 'isLoaded',
+ 'toArray',
+ 'getSize',
+ 'setStoreId'
+ ])
->getMockForAbstractClass();
$this->dbSelectMock = $this->getMockBuilder(DbSelect::class)
->disableOriginalConstructor()
diff --git a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
index 1903bcd144831..ea6b1fd47a0a5 100644
--- a/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
+++ b/app/code/Magento/Catalog/Ui/Component/ColumnFactory.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Catalog\Ui\Component;
+use Magento\Ui\Component\Filters\FilterModifier;
+
/**
* Column Factory
*
@@ -60,13 +62,15 @@ public function __construct(\Magento\Framework\View\Element\UiComponentFactory $
*/
public function create($attribute, $context, array $config = [])
{
+ $filterModifiers = $context->getRequestParam(FilterModifier::FILTER_MODIFIER, []);
+
$columnName = $attribute->getAttributeCode();
$config = array_merge([
'label' => __($attribute->getDefaultFrontendLabel()),
'dataType' => $this->getDataType($attribute),
'add_field' => true,
'visible' => $attribute->getIsVisibleInGrid(),
- 'filter' => ($attribute->getIsFilterableInGrid())
+ 'filter' => ($attribute->getIsFilterableInGrid() || array_key_exists($columnName, $filterModifiers))
? $this->getFilterType($attribute->getFrontendInput())
: null,
], $config);
diff --git a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
index 5af0d71dc246c..494b77724e5b7 100644
--- a/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
+++ b/app/code/Magento/Catalog/Ui/Component/Listing/Columns/Websites.php
@@ -3,13 +3,19 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Catalog\Ui\Component\Listing\Columns;
-use Magento\Framework\View\Element\UiComponentFactory;
+use Magento\Framework\DB\Helper;
use Magento\Framework\View\Element\UiComponent\ContextInterface;
+use Magento\Framework\View\Element\UiComponentFactory;
use Magento\Store\Model\StoreManagerInterface;
/**
+ * Websites listing column component.
+ *
* @api
* @since 100.0.2
*/
@@ -20,6 +26,11 @@ class Websites extends \Magento\Ui\Component\Listing\Columns\Column
*/
const NAME = 'websites';
+ /**
+ * Data for concatenated website names value.
+ */
+ private $websiteNames = 'website_names';
+
/**
* Store manager
*
@@ -27,26 +38,36 @@ class Websites extends \Magento\Ui\Component\Listing\Columns\Column
*/
protected $storeManager;
+ /**
+ * @var \Magento\Framework\DB\Helper
+ */
+ private $resourceHelper;
+
/**
* @param ContextInterface $context
* @param UiComponentFactory $uiComponentFactory
* @param StoreManagerInterface $storeManager
* @param array $components
* @param array $data
+ * @param Helper $resourceHelper
*/
public function __construct(
ContextInterface $context,
UiComponentFactory $uiComponentFactory,
StoreManagerInterface $storeManager,
array $components = [],
- array $data = []
+ array $data = [],
+ Helper $resourceHelper = null
) {
parent::__construct($context, $uiComponentFactory, $components, $data);
+ $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
$this->storeManager = $storeManager;
+ $this->resourceHelper = $resourceHelper ?: $objectManager->get(Helper::class);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @deprecated 101.0.0
*/
public function prepareDataSource(array $dataSource)
@@ -71,9 +92,10 @@ public function prepareDataSource(array $dataSource)
return $dataSource;
}
-
+
/**
- * Prepare component configuration
+ * Prepare component configuration.
+ *
* @return void
*/
public function prepare()
@@ -83,4 +105,46 @@ public function prepare()
$this->_data['config']['componentDisabled'] = true;
}
}
+
+ /**
+ * Apply sorting.
+ *
+ * @return void
+ */
+ protected function applySorting()
+ {
+ $sorting = $this->getContext()->getRequestParam('sorting');
+ $isSortable = $this->getData('config/sortable');
+ if ($isSortable !== false
+ && !empty($sorting['field'])
+ && !empty($sorting['direction'])
+ && $sorting['field'] === $this->getName()
+ ) {
+ $collection = $this->getContext()->getDataProvider()->getCollection();
+ $collection
+ ->joinField(
+ 'websites_ids',
+ 'catalog_product_website',
+ 'website_id',
+ 'product_id=entity_id',
+ null,
+ 'left'
+ )
+ ->joinTable(
+ 'store_website',
+ 'website_id = websites_ids',
+ ['name'],
+ null,
+ 'left'
+ )
+ ->groupByAttribute('entity_id');
+ $this->resourceHelper->addGroupConcatColumn(
+ $collection->getSelect(),
+ $this->websiteNames,
+ 'name'
+ );
+
+ $collection->getSelect()->order($this->websiteNames . ' ' . $sorting['direction']);
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
index 2a4d2ff52d479..00132c6ad89e8 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/AdvancedPricing.php
@@ -389,6 +389,9 @@ private function addAdvancedPriceLink()
'componentType' => Container::NAME,
'component' => 'Magento_Ui/js/form/components/button',
'template' => 'ui/form/components/button/container',
+ 'imports' => [
+ 'childError' => $this->scopeName . '.advanced_pricing_modal.advanced-pricing:error',
+ ],
'actions' => [
[
'targetName' => $this->scopeName . '.advanced_pricing_modal',
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
index f8f82511cc12f..af43c84501f65 100755
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/CustomOptions.php
@@ -871,6 +871,7 @@ protected function getPositionFieldConfig($sortOrder)
'formElement' => Hidden::NAME,
'dataScope' => static::FIELD_SORT_ORDER_NAME,
'dataType' => Number::NAME,
+ 'visible' => false,
'sortOrder' => $sortOrder,
],
],
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
index 6ec1cc6c46d9d..26044eb91a309 100755
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/General.php
@@ -355,8 +355,10 @@ protected function customizeNameListeners(array $meta)
'allowImport' => !$this->locator->getProduct()->getId(),
];
- if (!in_array($listener, $textListeners)) {
- $importsConfig['elementTmpl'] = 'ui/form/element/input';
+ if (in_array($listener, $textListeners)) {
+ $importsConfig['cols'] = 15;
+ $importsConfig['rows'] = 2;
+ $importsConfig['elementTmpl'] = 'ui/form/element/textarea';
}
$meta = $this->arrayManager->merge($listenerPath . static::META_CONFIG_PATH, $meta, $importsConfig);
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php
index 200ecf89641fa..a518afc576d61 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductDataProvider.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\App\ObjectManager;
+use Magento\Store\Model\Store;
use Magento\Ui\DataProvider\Modifier\ModifierInterface;
use Magento\Ui\DataProvider\Modifier\PoolInterface;
@@ -67,6 +68,7 @@ public function __construct(
$this->addFieldStrategies = $addFieldStrategies;
$this->addFilterStrategies = $addFilterStrategies;
$this->modifiersPool = $modifiersPool ?: ObjectManager::getInstance()->get(PoolInterface::class);
+ $this->collection->setStoreId(Store::DEFAULT_STORE_ID);
}
/**
@@ -110,7 +112,7 @@ public function addField($field, $alias = null)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function addFilter(\Magento\Framework\Api\Filter $filter)
{
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/product_options.xsd b/app/code/Magento/Catalog/etc/product_options.xsd
index 3bc24a9099262..734c8f378d5d7 100644
--- a/app/code/Magento/Catalog/etc/product_options.xsd
+++ b/app/code/Magento/Catalog/etc/product_options.xsd
@@ -61,11 +61,11 @@
- Model name can contain only [a-zA-Z_\\].
+ Model name can contain only ([\\]?[a-zA-Z_][a-zA-Z0-9_]*)+.
-
+
diff --git a/app/code/Magento/Catalog/etc/product_types_base.xsd b/app/code/Magento/Catalog/etc/product_types_base.xsd
index 6cc35fd7bee37..dec952bcf492e 100644
--- a/app/code/Magento/Catalog/etc/product_types_base.xsd
+++ b/app/code/Magento/Catalog/etc/product_types_base.xsd
@@ -92,11 +92,11 @@
- Model name can contain only [a-zA-Z_\\].
+ Model name can contain only ([\\]?[a-zA-Z_][a-zA-Z0-9_]*)+.
-
+
diff --git a/app/code/Magento/Catalog/etc/queue.xml b/app/code/Magento/Catalog/etc/queue.xml
new file mode 100644
index 0000000000000..137f34a5c1e25
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_consumer.xml b/app/code/Magento/Catalog/etc/queue_consumer.xml
new file mode 100644
index 0000000000000..d9e66ae69c10c
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_consumer.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_publisher.xml b/app/code/Magento/Catalog/etc/queue_publisher.xml
new file mode 100644
index 0000000000000..1606ea42ec0b3
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_publisher.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/etc/queue_topology.xml b/app/code/Magento/Catalog/etc/queue_topology.xml
new file mode 100644
index 0000000000000..bdac891afbdb8
--- /dev/null
+++ b/app/code/Magento/Catalog/etc/queue_topology.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/edit/attribute.js b/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/edit/attribute.js
index 407fd1fe28e39..e1923dc46d68e 100644
--- a/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/edit/attribute.js
+++ b/app/code/Magento/Catalog/view/adminhtml/web/catalog/product/edit/attribute.js
@@ -5,13 +5,13 @@
define([
'jquery',
- 'mage/mage'
+ 'mage/mage',
+ 'validation'
], function ($) {
'use strict';
return function (config, element) {
-
- $(element).mage('form').mage('validation', {
+ $(element).mage('form').validation({
validationUrl: config.validationUrl
});
};
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml
index 9c18a18ff5837..71452a2d65e97 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/addtocart.phtml
@@ -20,6 +20,7 @@
+ id="product-addtocart-button" disabled>
= /* @escapeNotVerified */ $buttonTitle ?>
= $block->getChildHtml('', true) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml
index c930d2195a01b..1c4a37fedebe3 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/attributes.phtml
@@ -23,8 +23,8 @@
- = $block->escapeHtml(__($_data['label'])) ?>
- = /* @escapeNotVerified */ $_helper->productAttribute($_product, $_data['value'], $_data['code']) ?>
+ = $block->escapeHtml($_data['label']) ?>
+ = /* @escapeNotVerified */ $_helper->productAttribute($_product, $_data['value'], $_data['code']) ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml
index af664051b1431..57eabbf1d8c8a 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/details.phtml
@@ -22,17 +22,17 @@
$label = $block->getChildData($alias, 'title');
?>
-
+
= /* @escapeNotVerified */ $html ?>
diff --git a/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml
index a2b91a5eeb99f..40f86c7e68d6c 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/opengraph/general.phtml
@@ -14,7 +14,7 @@
-getProduct()->getFinalPrice()):?>
+getProduct()->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)->getAmount()):?>
= $block->getChildHtml('meta.currency') ?>
diff --git a/app/code/Magento/Catalog/view/frontend/web/js/validate-product.js b/app/code/Magento/Catalog/view/frontend/web/js/validate-product.js
index c0637cb672dc6..755e777a01f77 100644
--- a/app/code/Magento/Catalog/view/frontend/web/js/validate-product.js
+++ b/app/code/Magento/Catalog/view/frontend/web/js/validate-product.js
@@ -13,7 +13,8 @@ define([
$.widget('mage.productValidate', {
options: {
bindSubmit: false,
- radioCheckboxClosest: '.nested'
+ radioCheckboxClosest: '.nested',
+ addToCartButtonSelector: '.action.tocart'
},
/**
@@ -41,6 +42,7 @@ define([
return false;
}
});
+ $(this.options.addToCartButtonSelector).attr('disabled', false);
}
});
diff --git a/app/code/Magento/CatalogAnalytics/composer.json b/app/code/Magento/CatalogAnalytics/composer.json
index 5c97261d483d8..805be8a17765f 100644
--- a/app/code/Magento/CatalogAnalytics/composer.json
+++ b/app/code/Magento/CatalogAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-catalog": "*"
+ "magento/module-catalog": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
index 4e3a8403f3132..1783a5cd9a7e5 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/CategoryTree.php
@@ -11,6 +11,7 @@
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
/**
@@ -72,11 +73,12 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
$rootCategoryId = $this->getCategoryId($args);
$categoriesTree = $this->categoryTree->getTree($info, $rootCategoryId);
- if (!empty($categoriesTree)) {
- $result = $this->extractDataFromCategoryTree->execute($categoriesTree);
- return current($result);
- } else {
- return null;
+
+ if (empty($categoriesTree) || ($categoriesTree->count() == 0)) {
+ throw new GraphQlNoSuchEntityException(__('Category doesn\'t exist'));
}
+
+ $result = $this->extractDataFromCategoryTree->execute($categoriesTree);
+ return current($result);
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php
index 3c19ce599a9b3..23a8c2d15c09e 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Product/ProductImage/Url.php
@@ -9,6 +9,7 @@
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\ImageFactory;
+use Magento\CatalogGraphQl\Model\Resolver\Products\DataProvider\Image\Placeholder as PlaceholderProvider;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -23,14 +24,21 @@ class Url implements ResolverInterface
* @var ImageFactory
*/
private $productImageFactory;
+ /**
+ * @var PlaceholderProvider
+ */
+ private $placeholderProvider;
/**
* @param ImageFactory $productImageFactory
+ * @param PlaceholderProvider $placeholderProvider
*/
public function __construct(
- ImageFactory $productImageFactory
+ ImageFactory $productImageFactory,
+ PlaceholderProvider $placeholderProvider
) {
$this->productImageFactory = $productImageFactory;
+ $this->placeholderProvider = $placeholderProvider;
}
/**
@@ -55,23 +63,27 @@ public function resolve(
$product = $value['model'];
$imagePath = $product->getData($value['image_type']);
- $imageUrl = $this->getImageUrl($value['image_type'], $imagePath);
- return $imageUrl;
+ return $this->getImageUrl($value['image_type'], $imagePath);
}
/**
- * Get image url
+ * Get image URL
*
* @param string $imageType
- * @param string|null $imagePath Null if image is not set
+ * @param string|null $imagePath
* @return string
+ * @throws \Exception
*/
private function getImageUrl(string $imageType, ?string $imagePath): string
{
$image = $this->productImageFactory->create();
$image->setDestinationSubdir($imageType)
->setBaseFile($imagePath);
- $imageUrl = $image->getUrl();
- return $imageUrl;
+
+ if ($image->isBaseFilePlaceholder()) {
+ return $this->placeholderProvider->getPlaceholder($imageType);
+ }
+
+ return $image->getUrl();
}
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
index e910a5c8be4cd..24c5e664831e4 100644
--- a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products.php
@@ -7,7 +7,6 @@
namespace Magento\CatalogGraphQl\Model\Resolver;
-use Magento\Framework\Exception\InputException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Filter;
use Magento\CatalogGraphQl\Model\Resolver\Products\Query\Search;
@@ -17,7 +16,6 @@
use Magento\Framework\GraphQl\Query\Resolver\Argument\SearchCriteria\SearchFilter;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Catalog\Model\Layer\Resolver;
-use Magento\Framework\Api\Search\SearchCriteriaInterface;
/**
* Products field resolver, used for GraphQL request processing.
@@ -82,10 +80,10 @@ public function resolve(
} elseif (isset($args['search'])) {
$layerType = Resolver::CATALOG_LAYER_SEARCH;
$this->searchFilter->add($args['search'], $searchCriteria);
- $searchResult = $this->getSearchResult($this->searchQuery, $searchCriteria, $info);
+ $searchResult = $this->searchQuery->getResult($searchCriteria, $info);
} else {
$layerType = Resolver::CATALOG_LAYER_CATEGORY;
- $searchResult = $this->getSearchResult($this->filterQuery, $searchCriteria, $info);
+ $searchResult = $this->filterQuery->getResult($searchCriteria, $info);
}
//possible division by 0
if ($searchCriteria->getPageSize()) {
@@ -117,25 +115,4 @@ public function resolve(
return $data;
}
-
- /**
- * Get search result.
- *
- * @param Filter|Search $query
- * @param SearchCriteriaInterface $searchCriteria
- * @param ResolveInfo $info
- *
- * @return \Magento\CatalogGraphQl\Model\Resolver\Products\SearchResult
- * @throws GraphQlInputException
- */
- private function getSearchResult($query, SearchCriteriaInterface $searchCriteria, ResolveInfo $info)
- {
- try {
- $searchResult = $query->getResult($searchCriteria, $info);
- } catch (InputException $e) {
- throw new GraphQlInputException(__($e->getMessage()));
- }
-
- return $searchResult;
- }
}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Image/Placeholder.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Image/Placeholder.php
new file mode 100644
index 0000000000000..f5cf2a9ef82ff
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Image/Placeholder.php
@@ -0,0 +1,59 @@
+placeholderFactory = $placeholderFactory;
+ $this->assetRepository = $assetRepository;
+ }
+
+ /**
+ * Get placeholder
+ *
+ * @param string $imageType
+ * @return string
+ */
+ public function getPlaceholder(string $imageType): string
+ {
+ $imageAsset = $this->placeholderFactory->create(['type' => $imageType]);
+
+ // check if placeholder defined in config
+ if ($imageAsset->getFilePath()) {
+ return $imageAsset->getUrl();
+ }
+
+ return $this->assetRepository->getUrl(
+ "Magento_Catalog::images/product/placeholder/{$imageType}.jpg"
+ );
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Image/Placeholder/Theme.php b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Image/Placeholder/Theme.php
new file mode 100644
index 0000000000000..dc48c5ef69346
--- /dev/null
+++ b/app/code/Magento/CatalogGraphQl/Model/Resolver/Products/DataProvider/Image/Placeholder/Theme.php
@@ -0,0 +1,71 @@
+scopeConfig = $scopeConfig;
+ $this->storeManager = $storeManager;
+ $this->themeProvider = $themeProvider;
+ }
+
+ /**
+ * Get theme model
+ *
+ * @return array
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ public function getThemeData(): array
+ {
+ $themeId = $this->scopeConfig->getValue(
+ \Magento\Framework\View\DesignInterface::XML_PATH_THEME_ID,
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE,
+ $this->storeManager->getStore()->getId()
+ );
+
+ /** @var $theme \Magento\Framework\View\Design\ThemeInterface */
+ $theme = $this->themeProvider->getThemeById($themeId);
+
+ $data = $theme->getData();
+ $data['themeModel'] = $theme;
+
+ return $data;
+ }
+}
diff --git a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
index 7e18ac34f0fcc..b5622e948b7cd 100644
--- a/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/CatalogGraphQl/etc/graphql/di.xml
@@ -38,11 +38,15 @@
-
- CustomizableFieldOption
+ - CustomizableDateOption
- CustomizableDateOption
+ - CustomizableDateOption
- CustomizableFileOption
- CustomizableAreaOption
- CustomizableDropDownOption
+ - CustomizableMultipleOption
- CustomizableRadioOption
+ - CustomizableCheckboxOption
diff --git a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
index 45f3a4c83be7b..778d0783305b7 100644
--- a/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/CatalogGraphQl/etc/schema.graphqls
@@ -323,6 +323,19 @@ type CustomizableDropDownValue @doc(description: "CustomizableDropDownValue defi
sort_order: Int @doc(description: "The order in which the option is displayed")
}
+type CustomizableMultipleOption implements CustomizableOptionInterface @doc(description: "CustomizableMultipleOption contains information about a multiselect that is defined as part of a customizable option") {
+ value: [CustomizableMultipleValue] @doc(description: "An array that defines the set of options for a multiselect")
+}
+
+type CustomizableMultipleValue @doc(description: "CustomizableMultipleValue defines the price and sku of a product whose page contains a customized multiselect") {
+ option_type_id: Int @doc(description: "The ID assigned to the value")
+ price: Float @doc(description: "The price assigned to this option")
+ price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC")
+ sku: String @doc(description: "The Stock Keeping Unit for this option")
+ title: String @doc(description: "The display name for this option")
+ sort_order: Int @doc(description: "The order in which the option is displayed")
+}
+
type CustomizableFieldOption implements CustomizableOptionInterface @doc(description: "CustomizableFieldOption contains information about a text field that is defined as part of a customizable option") {
value: CustomizableFieldValue @doc(description: "An object that defines a text field")
product_sku: String @doc(description: "The Stock Keeping Unit of the base product")
@@ -407,6 +420,19 @@ type CustomizableRadioValue @doc(description: "CustomizableRadioValue defines t
sort_order: Int @doc(description: "The order in which the radio button is displayed")
}
+type CustomizableCheckboxOption implements CustomizableOptionInterface @doc(description: "CustomizableCheckbbixOption contains information about a set of checkbox values that are defined as part of a customizable option") {
+ value: [CustomizableCheckboxValue] @doc(description: "An array that defines a set of checkbox values")
+}
+
+type CustomizableCheckboxValue @doc(description: "CustomizableCheckboxValue defines the price and sku of a product whose page contains a customized set of checkbox values") {
+ option_type_id: Int @doc(description: "The ID assigned to the value")
+ price: Float @doc(description: "The price assigned to this option")
+ price_type: PriceTypeEnum @doc(description: "FIXED, PERCENT, or DYNAMIC")
+ sku: String @doc(description: "The Stock Keeping Unit for this option")
+ title: String @doc(description: "The display name for this option")
+ sort_order: Int @doc(description: "The order in which the checkbox value is displayed")
+}
+
type VirtualProduct implements ProductInterface, CustomizableProductInterface @doc(description: "A virtual product is non-tangible product that does not require shipping and is not kept in inventory") {
}
diff --git a/app/code/Magento/CatalogImportExport/Model/Import/Product.php b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
index 908cf66e3f64d..404c31296e4dd 100644
--- a/app/code/Magento/CatalogImportExport/Model/Import/Product.php
+++ b/app/code/Magento/CatalogImportExport/Model/Import/Product.php
@@ -2546,7 +2546,7 @@ public function validateRow(array $rowData, $rowNum)
$rowNum,
$rowData[self::COL_NAME],
$message,
- ProcessingError::ERROR_LEVEL_NOT_CRITICAL
+ $errorLevel
)
->getErrorAggregator()
->addRowToSkip($rowNum);
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
new file mode 100644
index 0000000000000..b9eea2b114634
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/ActionGroup/AdminExportActionGroup.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
new file mode 100644
index 0000000000000..1f5ae6b6905bc
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportBundleProductTest.xml
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
new file mode 100644
index 0000000000000..a587d71ba0e68
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportGroupedProductWithSpecialPriceTest.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
new file mode 100644
index 0000000000000..6f64da4693692
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleAndConfigurableProductsWithCustomOptionsTest.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
new file mode 100644
index 0000000000000..993f1c9cd9da2
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAndConfigurableProductsWithAssignedImagesTest.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
new file mode 100644
index 0000000000000..491d20604a08b
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductAssignedToMainWebsiteAndConfigurableProductAssignedToCustomWebsiteTest.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
new file mode 100644
index 0000000000000..f671b54803e35
--- /dev/null
+++ b/app/code/Magento/CatalogImportExport/Test/Mftf/Test/AdminExportSimpleProductWithCustomAttributeTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php b/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php
index 2c52b49c1a039..0a02d4eb6a9a6 100644
--- a/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php
+++ b/app/code/Magento/CatalogInventory/Model/AddStockStatusToCollection.php
@@ -7,6 +7,8 @@
namespace Magento\CatalogInventory\Model;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
+use Magento\Framework\Search\EngineResolverInterface;
+use Magento\Search\Model\EngineResolver;
/**
* Catalog inventory module plugin
@@ -17,18 +19,27 @@ class AddStockStatusToCollection
* @var \Magento\CatalogInventory\Helper\Stock
*/
protected $stockHelper;
-
+
+ /**
+ * @var EngineResolverInterface
+ */
+ private $engineResolver;
+
/**
- * @param \Magento\CatalogInventory\Model\Configuration $configuration
* @param \Magento\CatalogInventory\Helper\Stock $stockHelper
+ * @param EngineResolverInterface $engineResolver
*/
public function __construct(
- \Magento\CatalogInventory\Helper\Stock $stockHelper
+ \Magento\CatalogInventory\Helper\Stock $stockHelper,
+ EngineResolverInterface $engineResolver
) {
$this->stockHelper = $stockHelper;
+ $this->engineResolver = $engineResolver;
}
/**
+ * Add stock filter to collection.
+ *
* @param Collection $productCollection
* @param bool $printQuery
* @param bool $logQuery
@@ -36,7 +47,9 @@ public function __construct(
*/
public function beforeLoad(Collection $productCollection, $printQuery = false, $logQuery = false)
{
- $this->stockHelper->addIsInStockFilterToCollection($productCollection);
+ if ($this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE) {
+ $this->stockHelper->addIsInStockFilterToCollection($productCollection);
+ }
return [$printQuery, $logQuery];
}
}
diff --git a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php
index bc10d38173b4d..43a5aabee9779 100644
--- a/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php
+++ b/app/code/Magento/CatalogInventory/Model/Indexer/Stock/Action/Full.php
@@ -6,12 +6,19 @@
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\CatalogInventory\Model\Indexer\Stock\Action;
+use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\ResourceModel\Indexer\ActiveTableSwitcher;
+use Magento\CatalogInventory\Model\Indexer\Stock\BatchSizeManagement;
+use Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\DefaultStock;
use Magento\Framework\App\ResourceConnection;
use Magento\CatalogInventory\Model\ResourceModel\Indexer\StockFactory;
use Magento\Catalog\Model\Product\Type as ProductType;
+use Magento\Framework\DB\Query\BatchIteratorInterface;
+use Magento\Framework\DB\Query\Generator as QueryGenerator;
use Magento\Framework\Indexer\CacheContext;
use Magento\Framework\Event\ManagerInterface as EventManager;
use Magento\Framework\EntityManager\MetadataPool;
@@ -25,7 +32,6 @@
/**
* Class Full reindex action
*
- * @package Magento\CatalogInventory\Model\Indexer\Stock\Action
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Full extends AbstractAction
@@ -60,6 +66,11 @@ class Full extends AbstractAction
*/
private $activeTableSwitcher;
+ /**
+ * @var QueryGenerator|null
+ */
+ private $batchQueryGenerator;
+
/**
* @param ResourceConnection $resource
* @param StockFactory $indexerFactory
@@ -71,7 +82,7 @@ class Full extends AbstractAction
* @param BatchProviderInterface|null $batchProvider
* @param array $batchRowsCount
* @param ActiveTableSwitcher|null $activeTableSwitcher
- *
+ * @param QueryGenerator|null $batchQueryGenerator
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -84,7 +95,8 @@ public function __construct(
BatchSizeManagementInterface $batchSizeManagement = null,
BatchProviderInterface $batchProvider = null,
array $batchRowsCount = [],
- ActiveTableSwitcher $activeTableSwitcher = null
+ ActiveTableSwitcher $activeTableSwitcher = null,
+ QueryGenerator $batchQueryGenerator = null
) {
parent::__construct(
$resource,
@@ -97,11 +109,12 @@ public function __construct(
$this->metadataPool = $metadataPool ?: ObjectManager::getInstance()->get(MetadataPool::class);
$this->batchProvider = $batchProvider ?: ObjectManager::getInstance()->get(BatchProviderInterface::class);
$this->batchSizeManagement = $batchSizeManagement ?: ObjectManager::getInstance()->get(
- \Magento\CatalogInventory\Model\Indexer\Stock\BatchSizeManagement::class
+ BatchSizeManagement::class
);
$this->batchRowsCount = $batchRowsCount;
$this->activeTableSwitcher = $activeTableSwitcher ?: ObjectManager::getInstance()
->get(ActiveTableSwitcher::class);
+ $this->batchQueryGenerator = $batchQueryGenerator ?: ObjectManager::getInstance()->get(QueryGenerator::class);
}
/**
@@ -109,22 +122,20 @@ public function __construct(
*
* @param null|array $ids
* @throws LocalizedException
- *
* @return void
- *
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
- public function execute($ids = null)
+ public function execute($ids = null): void
{
try {
$this->useIdxTable(false);
$this->cleanIndexersTables($this->_getTypeIndexers());
- $entityMetadata = $this->metadataPool->getMetadata(\Magento\Catalog\Api\Data\ProductInterface::class);
+ $entityMetadata = $this->metadataPool->getMetadata(ProductInterface::class);
$columns = array_keys($this->_getConnection()->describeTable($this->_getIdxTable()));
- /** @var \Magento\CatalogInventory\Model\ResourceModel\Indexer\Stock\DefaultStock $indexer */
+ /** @var DefaultStock $indexer */
foreach ($this->_getTypeIndexers() as $indexer) {
$indexer->setActionType(self::ACTION_TYPE);
$connection = $indexer->getConnection();
@@ -135,22 +146,21 @@ public function execute($ids = null)
: $this->batchRowsCount['default'];
$this->batchSizeManagement->ensureBatchSize($connection, $batchRowCount);
- $batches = $this->batchProvider->getBatches(
- $connection,
- $entityMetadata->getEntityTable(),
+
+ $select = $connection->select();
+ $select->distinct(true);
+ $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
+
+ $batchQueries = $this->batchQueryGenerator->generate(
$entityMetadata->getIdentifierField(),
- $batchRowCount
+ $select,
+ $batchRowCount,
+ BatchIteratorInterface::NON_UNIQUE_FIELD_ITERATOR
);
- foreach ($batches as $batch) {
+ foreach ($batchQueries as $query) {
$this->clearTemporaryIndexTable();
- // Get entity ids from batch
- $select = $connection->select();
- $select->distinct(true);
- $select->from(['e' => $entityMetadata->getEntityTable()], $entityMetadata->getIdentifierField());
- $select->where('type_id = ?', $indexer->getTypeId());
-
- $entityIds = $this->batchProvider->getBatchIds($connection, $select, $batch);
+ $entityIds = $connection->fetchCol($query);
if (!empty($entityIds)) {
$indexer->reindexEntity($entityIds);
$select = $connection->select()->from($this->_getIdxTable(), $columns);
@@ -167,12 +177,13 @@ public function execute($ids = null)
/**
* Delete all records from index table
+ *
* Used to clean table before re-indexation
*
* @param array $indexers
* @return void
*/
- private function cleanIndexersTables(array $indexers)
+ private function cleanIndexersTables(array $indexers): void
{
$tables = array_map(
function (StockInterface $indexer) {
diff --git a/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php b/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php
index b8e8e47bb1fb0..168e947b8fa57 100644
--- a/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php
+++ b/app/code/Magento/CatalogInventory/Model/Plugin/Layer.php
@@ -3,8 +3,15 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\CatalogInventory\Model\Plugin;
+use Magento\Framework\Search\EngineResolverInterface;
+use Magento\Search\Model\EngineResolver;
+
+/**
+ * Catalog inventory plugin for layer.
+ */
class Layer
{
/**
@@ -21,16 +28,24 @@ class Layer
*/
protected $scopeConfig;
+ /**
+ * @var EngineResolverInterface
+ */
+ private $engineResolver;
+
/**
* @param \Magento\CatalogInventory\Helper\Stock $stockHelper
* @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
+ * @param EngineResolverInterface $engineResolver
*/
public function __construct(
\Magento\CatalogInventory\Helper\Stock $stockHelper,
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
+ \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
+ EngineResolverInterface $engineResolver
) {
$this->stockHelper = $stockHelper;
$this->scopeConfig = $scopeConfig;
+ $this->engineResolver = $engineResolver;
}
/**
@@ -46,12 +61,22 @@ public function beforePrepareProductCollection(
\Magento\Catalog\Model\Layer $subject,
\Magento\Catalog\Model\ResourceModel\Collection\AbstractCollection $collection
) {
- if ($this->_isEnabledShowOutOfStock()) {
+ if (!$this->isCurrentEngineMysql() || $this->_isEnabledShowOutOfStock()) {
return;
}
$this->stockHelper->addIsInStockFilterToCollection($collection);
}
+ /**
+ * Check if current engine is MYSQL.
+ *
+ * @return bool
+ */
+ private function isCurrentEngineMysql()
+ {
+ return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE;
+ }
+
/**
* Get config value for 'display out of stock' option
*
diff --git a/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php b/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
index f711268bc7930..0fa4b919c40fa 100644
--- a/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
+++ b/app/code/Magento/CatalogInventory/Model/Spi/StockRegistryProviderInterface.php
@@ -7,16 +7,24 @@
/**
* Interface StockRegistryProviderInterface
+ *
+ * @deprecated 2.3.0 Replaced with Multi Source Inventory
+ * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
*/
interface StockRegistryProviderInterface
{
/**
+ * Get stock.
+ *
* @param int $scopeId
* @return \Magento\CatalogInventory\Api\Data\StockInterface
*/
public function getStock($scopeId);
/**
+ * Get stock item.
+ *
* @param int $productId
* @param int $scopeId
* @return \Magento\CatalogInventory\Api\Data\StockItemInterface
@@ -24,6 +32,8 @@ public function getStock($scopeId);
public function getStockItem($productId, $scopeId);
/**
+ * Get stock status.
+ *
* @param int $productId
* @param int $scopeId
* @return \Magento\CatalogInventory\Api\Data\StockStatusInterface
diff --git a/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php b/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
index 89fb54e7e496b..30f703b5b928f 100644
--- a/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
+++ b/app/code/Magento/CatalogInventory/Model/Spi/StockStateProviderInterface.php
@@ -9,22 +9,32 @@
/**
* Interface StockStateProviderInterface
+ *
+ * @deprecated 2.3.0 Replaced with Multi Source Inventory
+ * @link https://devdocs.magento.com/guides/v2.3/inventory/index.html
+ * @link https://devdocs.magento.com/guides/v2.3/inventory/catalog-inventory-replacements.html
*/
interface StockStateProviderInterface
{
/**
+ * Verify stock.
+ *
* @param StockItemInterface $stockItem
* @return bool
*/
public function verifyStock(StockItemInterface $stockItem);
/**
+ * Verify notification.
+ *
* @param StockItemInterface $stockItem
* @return bool
*/
public function verifyNotification(StockItemInterface $stockItem);
/**
+ * Validate quote qty.
+ *
* @param StockItemInterface $stockItem
* @param int|float $itemQty
* @param int|float $qtyToCheck
@@ -44,8 +54,9 @@ public function checkQuoteItemQty(StockItemInterface $stockItem, $itemQty, $qtyT
public function checkQty(StockItemInterface $stockItem, $qty);
/**
- * Returns suggested qty that satisfies qty increments and minQty/maxQty/minSaleQty/maxSaleQty conditions
- * or original qty if such value does not exist
+ * Returns suggested qty or original qty if such value does not exist.
+ *
+ * Suggested qty satisfies qty increments and minQty/maxQty/minSaleQty/maxSaleQty conditions.
*
* @param StockItemInterface $stockItem
* @param int|float $qty
@@ -54,6 +65,8 @@ public function checkQty(StockItemInterface $stockItem, $qty);
public function suggestQty(StockItemInterface $stockItem, $qty);
/**
+ * Check qty increments.
+ *
* @param StockItemInterface $stockItem
* @param int|float $qty
* @return \Magento\Framework\DataObject
diff --git a/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php b/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
new file mode 100644
index 0000000000000..334d2b22edbfa
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Plugin/MassUpdateProductAttribute.php
@@ -0,0 +1,159 @@
+stockIndexerProcessor = $stockIndexerProcessor;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->stockRegistry = $stockRegistry;
+ $this->stockItemRepository = $stockItemRepository;
+ $this->stockConfiguration = $stockConfiguration;
+ $this->attributeHelper = $attributeHelper;
+ $this->messageManager = $messageManager;
+ }
+
+ /**
+ * Around execute plugin
+ *
+ * @param Save $subject
+ * @param callable $proceed
+ *
+ * @return \Magento\Framework\Controller\ResultInterface
+ */
+ public function aroundExecute(Save $subject, callable $proceed)
+ {
+ try {
+ /** @var \Magento\Framework\App\RequestInterface $request */
+ $request = $subject->getRequest();
+ $inventoryData = $request->getParam('inventory', []);
+ $inventoryData = $this->addConfigSettings($inventoryData);
+
+ $storeId = $this->attributeHelper->getSelectedStoreId();
+ $websiteId = $this->attributeHelper->getStoreWebsiteId($storeId);
+ $productIds = $this->attributeHelper->getProductIds();
+
+ if (!empty($inventoryData)) {
+ $this->updateInventoryInProducts($productIds, $websiteId, $inventoryData);
+ }
+
+ return $proceed();
+ } catch (\Magento\Framework\Exception\LocalizedException $e) {
+ $this->messageManager->addErrorMessage($e->getMessage());
+ return $proceed();
+ } catch (\Exception $e) {
+ $this->messageManager->addExceptionMessage(
+ $e,
+ __('Something went wrong while updating the product(s) attributes.')
+ );
+ return $proceed();
+ }
+ }
+
+ /**
+ * Add config settings
+ *
+ * @param array $inventoryData
+ *
+ * @return array
+ */
+ private function addConfigSettings($inventoryData)
+ {
+ $options = $this->stockConfiguration->getConfigItemOptions();
+ foreach ($options as $option) {
+ $useConfig = 'use_config_' . $option;
+ if (isset($inventoryData[$option]) && !isset($inventoryData[$useConfig])) {
+ $inventoryData[$useConfig] = 0;
+ }
+ }
+ return $inventoryData;
+ }
+
+ /**
+ * Update inventory in products
+ *
+ * @param array $productIds
+ * @param int $websiteId
+ * @param array $inventoryData
+ *
+ * @return void
+ */
+ private function updateInventoryInProducts($productIds, $websiteId, $inventoryData): void
+ {
+ foreach ($productIds as $productId) {
+ $stockItemDo = $this->stockRegistry->getStockItem($productId, $websiteId);
+ if (!$stockItemDo->getProductId()) {
+ $inventoryData['product_id'] = $productId;
+ }
+ $stockItemId = $stockItemDo->getId();
+ $this->dataObjectHelper->populateWithArray($stockItemDo, $inventoryData, StockItemInterface::class);
+ $stockItemDo->setItemId($stockItemId);
+ $this->stockItemRepository->save($stockItemDo);
+ }
+ $this->stockIndexerProcessor->reindexList($productIds);
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php
index af35666ced3e5..906df54732775 100644
--- a/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/AddStockStatusToCollectionTest.php
@@ -6,6 +6,7 @@
namespace Magento\CatalogInventory\Test\Unit\Model;
use Magento\CatalogInventory\Model\AddStockStatusToCollection;
+use Magento\Framework\Search\EngineResolverInterface;
class AddStockStatusToCollectionTest extends \PHPUnit\Framework\TestCase
{
@@ -19,13 +20,24 @@ class AddStockStatusToCollectionTest extends \PHPUnit\Framework\TestCase
*/
protected $stockHelper;
+ /**
+ * @var EngineResolverInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $engineResolver;
+
protected function setUp()
{
$this->stockHelper = $this->createMock(\Magento\CatalogInventory\Helper\Stock::class);
+ $this->engineResolver = $this->getMockBuilder(EngineResolverInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCurrentSearchEngine'])
+ ->getMockForAbstractClass();
+
$this->plugin = (new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this))->getObject(
\Magento\CatalogInventory\Model\AddStockStatusToCollection::class,
[
'stockHelper' => $this->stockHelper,
+ 'engineResolver' => $this->engineResolver
]
);
}
@@ -36,6 +48,10 @@ public function testAddStockStatusToCollection()
->disableOriginalConstructor()
->getMock();
+ $this->engineResolver->expects($this->any())
+ ->method('getCurrentSearchEngine')
+ ->willReturn('mysql');
+
$this->stockHelper->expects($this->once())
->method('addIsInStockFilterToCollection')
->with($productCollection)
diff --git a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php
index 287459bd8cbc8..b64563a35176d 100644
--- a/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php
+++ b/app/code/Magento/CatalogInventory/Test/Unit/Model/Plugin/LayerTest.php
@@ -5,6 +5,8 @@
*/
namespace Magento\CatalogInventory\Test\Unit\Model\Plugin;
+use Magento\Framework\Search\EngineResolverInterface;
+
class LayerTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -22,14 +24,24 @@ class LayerTest extends \PHPUnit\Framework\TestCase
*/
protected $_stockHelperMock;
+ /**
+ * @var EngineResolverInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $engineResolver;
+
protected function setUp()
{
$this->_scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
$this->_stockHelperMock = $this->createMock(\Magento\CatalogInventory\Helper\Stock::class);
+ $this->engineResolver = $this->getMockBuilder(EngineResolverInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getCurrentSearchEngine'])
+ ->getMockForAbstractClass();
$this->_model = new \Magento\CatalogInventory\Model\Plugin\Layer(
$this->_stockHelperMock,
- $this->_scopeConfigMock
+ $this->_scopeConfigMock,
+ $this->engineResolver
);
}
@@ -38,6 +50,10 @@ protected function setUp()
*/
public function testAddStockStatusDisabledShow()
{
+ $this->engineResolver->expects($this->any())
+ ->method('getCurrentSearchEngine')
+ ->willReturn('mysql');
+
$this->_scopeConfigMock->expects(
$this->once()
)->method(
@@ -60,6 +76,10 @@ public function testAddStockStatusDisabledShow()
*/
public function testAddStockStatusEnabledShow()
{
+ $this->engineResolver->expects($this->any())
+ ->method('getCurrentSearchEngine')
+ ->willReturn('mysql');
+
$this->_scopeConfigMock->expects(
$this->once()
)->method(
diff --git a/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/AddQuantityAndStockStatusFieldToCollection.php b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/AddQuantityAndStockStatusFieldToCollection.php
new file mode 100644
index 0000000000000..d66a783c6720d
--- /dev/null
+++ b/app/code/Magento/CatalogInventory/Ui/DataProvider/Product/AddQuantityAndStockStatusFieldToCollection.php
@@ -0,0 +1,32 @@
+joinField(
+ 'quantity_and_stock_status',
+ 'cataloginventory_stock_item',
+ 'is_in_stock',
+ 'product_id=entity_id',
+ '{{table}}.stock_id=1',
+ 'left'
+ );
+ }
+}
diff --git a/app/code/Magento/CatalogInventory/composer.json b/app/code/Magento/CatalogInventory/composer.json
index 007d744b2296f..eb6239ea87ef0 100644
--- a/app/code/Magento/CatalogInventory/composer.json
+++ b/app/code/Magento/CatalogInventory/composer.json
@@ -8,6 +8,7 @@
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
"magento/module-catalog": "*",
+ "magento/module-search": "*",
"magento/module-config": "*",
"magento/module-customer": "*",
"magento/module-eav": "*",
diff --git a/app/code/Magento/CatalogInventory/etc/adminhtml/di.xml b/app/code/Magento/CatalogInventory/etc/adminhtml/di.xml
index 3397ef25918cd..28035de29bc2e 100644
--- a/app/code/Magento/CatalogInventory/etc/adminhtml/di.xml
+++ b/app/code/Magento/CatalogInventory/etc/adminhtml/di.xml
@@ -23,6 +23,7 @@
- Magento\CatalogInventory\Ui\DataProvider\Product\AddQuantityFieldToCollection
+ - Magento\CatalogInventory\Ui\DataProvider\Product\AddQuantityAndStockStatusFieldToCollection
- Magento\CatalogInventory\Ui\DataProvider\Product\AddQuantityFilterToCollection
diff --git a/app/code/Magento/CatalogInventory/etc/di.xml b/app/code/Magento/CatalogInventory/etc/di.xml
index 8d57fab843f4c..e7d79c593b8c7 100644
--- a/app/code/Magento/CatalogInventory/etc/di.xml
+++ b/app/code/Magento/CatalogInventory/etc/di.xml
@@ -44,7 +44,7 @@
- Magento\CatalogInventory\Model\ResourceModel\Stock\Proxy
+ Magento\CatalogInventory\Model\ResourceModel\Stock\Item\Proxy
@@ -135,4 +135,7 @@
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php
index 184fd9cfd5b37..384132415a10e 100644
--- a/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php
+++ b/app/code/Magento/CatalogSearch/Controller/Advanced/Result.php
@@ -60,15 +60,9 @@ public function execute()
{
try {
$this->_catalogSearchAdvanced->addFilters($this->getRequest()->getQueryValue());
- $size = $this->_catalogSearchAdvanced->getProductCollection()->getSize();
-
- $handles = null;
- if ($size == 0) {
- $this->_view->getPage()->initLayout();
- $handles = $this->_view->getLayout()->getUpdate()->getHandles();
- $handles[] = static::DEFAULT_NO_RESULT_HANDLE;
- }
-
+ $this->_view->getPage()->initLayout();
+ $handles = $this->_view->getLayout()->getUpdate()->getHandles();
+ $handles[] = static::DEFAULT_NO_RESULT_HANDLE;
$this->_view->loadLayout($handles);
$this->_view->renderLayout();
} catch (\Magento\Framework\Exception\LocalizedException $e) {
diff --git a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/SelectBuilderForAttribute.php b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/SelectBuilderForAttribute.php
index ddb4085fa13d9..00012a78d1003 100644
--- a/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/SelectBuilderForAttribute.php
+++ b/app/code/Magento/CatalogSearch/Model/Adapter/Mysql/Aggregation/DataProvider/SelectBuilderForAttribute.php
@@ -74,6 +74,8 @@ public function __construct(
}
/**
+ * Build select for attribute search
+ *
* @param Select $select
* @param AbstractAttribute $attribute
* @param int $currentScope
@@ -101,7 +103,7 @@ public function build(Select $select, AbstractAttribute $attribute, int $current
$subSelect = $select;
$subSelect->from(['main_table' => $table], ['main_table.entity_id', 'main_table.value'])
->distinct()
- ->where('main_table.attribute_id = ?', $attribute->getAttributeId())
+ ->where('main_table.attribute_id = ?', (int) $attribute->getAttributeId())
->where('main_table.store_id = ? ', $currentScopeId);
if ($this->isAddStockFilter()) {
$subSelect = $this->applyStockConditionToSelect->execute($subSelect);
@@ -116,6 +118,8 @@ public function build(Select $select, AbstractAttribute $attribute, int $current
}
/**
+ * Is add stock filter
+ *
* @return bool
*/
private function isAddStockFilter()
diff --git a/app/code/Magento/CatalogSearch/Model/Advanced.php b/app/code/Magento/CatalogSearch/Model/Advanced.php
index af0e9ff5528cf..5b96a8c21cbea 100644
--- a/app/code/Magento/CatalogSearch/Model/Advanced.php
+++ b/app/code/Magento/CatalogSearch/Model/Advanced.php
@@ -6,6 +6,8 @@
namespace Magento\CatalogSearch\Model;
use Magento\Catalog\Model\Config;
+use Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategyProvider;
+use Magento\CatalogSearch\Model\Search\ItemCollectionProviderInterface;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\ProductFactory;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
@@ -19,6 +21,7 @@
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Registry;
use Magento\Store\Model\StoreManagerInterface;
+use Magento\Framework\App\ObjectManager;
/**
* Catalog advanced search model
@@ -64,6 +67,7 @@ class Advanced extends \Magento\Framework\Model\AbstractModel
/**
* Initialize dependencies
*
+ * @deprecated
* @var Config
*/
protected $_catalogConfig;
@@ -106,10 +110,22 @@ class Advanced extends \Magento\Framework\Model\AbstractModel
/**
* Advanced Collection Factory
*
+ * @deprecated
+ * @see $collectionProvider
* @var ProductCollectionFactory
*/
protected $productCollectionFactory;
+ /**
+ * @var ItemCollectionProviderInterface
+ */
+ private $collectionProvider;
+
+ /**
+ * @var ProductCollectionPrepareStrategyProvider|null
+ */
+ private $productCollectionPrepareStrategyProvider;
+
/**
* Construct
*
@@ -124,7 +140,8 @@ class Advanced extends \Magento\Framework\Model\AbstractModel
* @param ProductCollectionFactory $productCollectionFactory
* @param AdvancedFactory $advancedFactory
* @param array $data
- *
+ * @param ItemCollectionProviderInterface|null $collectionProvider
+ * @param ProductCollectionPrepareStrategyProvider|null $productCollectionPrepareStrategyProvider
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -138,7 +155,9 @@ public function __construct(
StoreManagerInterface $storeManager,
ProductCollectionFactory $productCollectionFactory,
AdvancedFactory $advancedFactory,
- array $data = []
+ array $data = [],
+ ItemCollectionProviderInterface $collectionProvider = null,
+ ProductCollectionPrepareStrategyProvider $productCollectionPrepareStrategyProvider = null
) {
$this->_attributeCollectionFactory = $attributeCollectionFactory;
$this->_catalogProductVisibility = $catalogProductVisibility;
@@ -147,11 +166,14 @@ public function __construct(
$this->_productFactory = $productFactory;
$this->_storeManager = $storeManager;
$this->productCollectionFactory = $productCollectionFactory;
+ $this->collectionProvider = $collectionProvider;
+ $this->productCollectionPrepareStrategyProvider = $productCollectionPrepareStrategyProvider
+ ?: ObjectManager::getInstance()->get(ProductCollectionPrepareStrategyProvider::class);
parent::__construct(
$context,
$registry,
$advancedFactory->create(),
- $this->productCollectionFactory->create(),
+ $this->resolveProductCollection(),
$data
);
}
@@ -266,7 +288,7 @@ public function getAttributes()
public function getProductCollection()
{
if ($this->_productCollection === null) {
- $collection = $this->productCollectionFactory->create();
+ $collection = $this->resolveProductCollection();
$this->prepareProductCollection($collection);
if (!$collection) {
return $collection;
@@ -277,6 +299,18 @@ public function getProductCollection()
return $this->_productCollection;
}
+ /**
+ * Resolve product collection.
+ *
+ * @return \Magento\Catalog\Model\ResourceModel\Product\Collection|\Magento\Framework\Data\Collection
+ */
+ private function resolveProductCollection()
+ {
+ return (null === $this->collectionProvider)
+ ? $this->productCollectionFactory->create()
+ : $this->collectionProvider->getCollection();
+ }
+
/**
* Prepare product collection
*
@@ -285,13 +319,7 @@ public function getProductCollection()
*/
public function prepareProductCollection($collection)
{
- $collection
- ->addAttributeToSelect($this->_catalogConfig->getProductAttributes())
- ->setStore($this->_storeManager->getStore())
- ->addMinimalPrice()
- ->addTaxPercents()
- ->addStoreFilter()
- ->setVisibility($this->_catalogProductVisibility->getVisibleInSearchIds());
+ $this->productCollectionPrepareStrategyProvider->getStrategy()->prepare($collection);
return $this;
}
diff --git a/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategy.php b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategy.php
new file mode 100644
index 0000000000000..e62de9c689fc2
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategy.php
@@ -0,0 +1,61 @@
+catalogConfig = $catalogConfig;
+ $this->storeManager = $storeManager;
+ $this->catalogProductVisibility = $catalogProductVisibility;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function prepare(Collection $collection)
+ {
+ $collection
+ ->addAttributeToSelect($this->catalogConfig->getProductAttributes())
+ ->setStore($this->storeManager->getStore())
+ ->addMinimalPrice()
+ ->addTaxPercents()
+ ->addStoreFilter()
+ ->setVisibility($this->catalogProductVisibility->getVisibleInSearchIds());
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyInterface.php b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyInterface.php
new file mode 100644
index 0000000000000..23719a6713a32
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Advanced/ProductCollectionPrepareStrategyInterface.php
@@ -0,0 +1,22 @@
+engineResolver = $engineResolver;
+ $this->strategies = $strategies;
+ }
+
+ /**
+ * Get strategy provider for product collection prepare process.
+ *
+ * @return ProductCollectionPrepareStrategyInterface
+ */
+ public function getStrategy(): ProductCollectionPrepareStrategyInterface
+ {
+ if (!isset($this->strategies[$this->engineResolver->getCurrentSearchEngine()])) {
+ throw new \DomainException('Undefined strategy ' . $this->engineResolver->getCurrentSearchEngine());
+ }
+ return $this->strategies[$this->engineResolver->getCurrentSearchEngine()];
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php
index 98c0f91668f7a..e3f61af771f8c 100644
--- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Advanced/Collection.php
@@ -3,18 +3,33 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\CatalogSearch\Model\ResourceModel\Advanced;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
use Magento\Catalog\Model\Product;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
+use Magento\Framework\Search\EngineResolverInterface;
+use Magento\Search\Model\EngineResolver;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory;
use Magento\Framework\Api\FilterBuilder;
+use Magento\Framework\DB\Select;
use Magento\Framework\Api\Search\SearchCriteriaBuilder;
use Magento\Framework\Api\Search\SearchResultFactory;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
use Magento\Framework\Search\Request\EmptyRequestDataException;
use Magento\Framework\Search\Request\NonExistingRequestNameException;
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Api\Search\SearchResultInterface;
/**
* Advanced search collection
@@ -60,6 +75,41 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
*/
private $filterBuilder;
+ /**
+ * @var \Magento\Framework\Api\Search\SearchResultInterface
+ */
+ private $searchResult;
+
+ /**
+ * @var string
+ */
+ private $searchRequestName;
+
+ /**
+ * @var SearchCriteriaResolverFactory
+ */
+ private $searchCriteriaResolverFactory;
+
+ /**
+ * @var SearchResultApplierFactory
+ */
+ private $searchResultApplierFactory;
+
+ /**
+ * @var TotalRecordsResolverFactory
+ */
+ private $totalRecordsResolverFactory;
+
+ /**
+ * @var EngineResolverInterface
+ */
+ private $engineResolver;
+
+ /**
+ * @var array
+ */
+ private $searchOrders;
+
/**
* Collection constructor
*
@@ -89,7 +139,15 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param SearchResultFactory|null $searchResultFactory
* @param ProductLimitationFactory|null $productLimitationFactory
* @param MetadataPool|null $metadataPool
- *
+ * @param TableMaintainer|null $tableMaintainer
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface|null $resourceModelPool
+ * @param string $searchRequestName
+ * @param SearchCriteriaResolverFactory|null $searchCriteriaResolverFactory
+ * @param SearchResultApplierFactory|null $searchResultApplierFactory
+ * @param TotalRecordsResolverFactory|null $totalRecordsResolverFactory
+ * @param EngineResolverInterface|null $engineResolver
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -118,15 +176,33 @@ public function __construct(
\Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
SearchResultFactory $searchResultFactory = null,
ProductLimitationFactory $productLimitationFactory = null,
- MetadataPool $metadataPool = null
+ MetadataPool $metadataPool = null,
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null,
+ $searchRequestName = 'advanced_search_container',
+ SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null,
+ SearchResultApplierFactory $searchResultApplierFactory = null,
+ TotalRecordsResolverFactory $totalRecordsResolverFactory = null,
+ EngineResolverInterface $engineResolver = null
) {
$this->requestBuilder = $requestBuilder;
$this->searchEngine = $searchEngine;
$this->temporaryStorageFactory = $temporaryStorageFactory;
+ $this->searchRequestName = $searchRequestName;
if ($searchResultFactory === null) {
$this->searchResultFactory = \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\Api\Search\SearchResultFactory::class);
}
+ $this->searchCriteriaResolverFactory = $searchCriteriaResolverFactory ?: ObjectManager::getInstance()
+ ->get(SearchCriteriaResolverFactory::class);
+ $this->searchResultApplierFactory = $searchResultApplierFactory ?: ObjectManager::getInstance()
+ ->get(SearchResultApplierFactory::class);
+ $this->totalRecordsResolverFactory = $totalRecordsResolverFactory ?: ObjectManager::getInstance()
+ ->get(TotalRecordsResolverFactory::class);
+ $this->engineResolver = $engineResolver ?: ObjectManager::getInstance()
+ ->get(EngineResolverInterface::class);
parent::__construct(
$entityFactory,
$logger,
@@ -149,7 +225,11 @@ public function __construct(
$groupManagement,
$connection,
$productLimitationFactory,
- $metadataPool
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
}
@@ -168,11 +248,89 @@ public function addFieldsToFilter($fields)
return $this;
}
+ /**
+ * @inheritdoc
+ */
+ public function setOrder($attribute, $dir = Select::SQL_DESC)
+ {
+ $this->setSearchOrder($attribute, $dir);
+ if ($this->isCurrentEngineMysql()) {
+ parent::setOrder($attribute, $dir);
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function addCategoryFilter(\Magento\Catalog\Model\Category $category)
+ {
+ /**
+ * This changes need in backward compatible reasons for support dynamic improved algorithm
+ * for price aggregation process.
+ */
+ if ($this->isCurrentEngineMysql()) {
+ parent::addCategoryFilter($category);
+ } else {
+ $this->addFieldToFilter('category_ids', $category->getId());
+ $this->_productLimitationPrice();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setVisibility($visibility)
+ {
+ /**
+ * This changes need in backward compatible reasons for support dynamic improved algorithm
+ * for price aggregation process.
+ */
+ if ($this->isCurrentEngineMysql()) {
+ parent::setVisibility($visibility);
+ } else {
+ $this->addFieldToFilter('visibility', $visibility);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set sort order for search query.
+ *
+ * @param string $field
+ * @param string $direction
+ * @return void
+ */
+ private function setSearchOrder($field, $direction)
+ {
+ $field = (string)$this->_getMappedField($field);
+ $direction = strtoupper($direction) == self::SORT_ORDER_ASC ? self::SORT_ORDER_ASC : self::SORT_ORDER_DESC;
+
+ $this->searchOrders[$field] = $direction;
+ }
+
+ /**
+ * Check if current engine is MYSQL.
+ *
+ * @return bool
+ */
+ private function isCurrentEngineMysql()
+ {
+ return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE;
+ }
+
/**
* @inheritdoc
*/
protected function _renderFiltersBefore()
{
+ if ($this->isLoaded()) {
+ return;
+ }
if ($this->filters) {
foreach ($this->filters as $attributes) {
foreach ($attributes as $attributeCode => $attributeValue) {
@@ -180,33 +338,70 @@ protected function _renderFiltersBefore()
$this->addAttributeToSearch($attributeCode, $attributeValue);
}
}
- $searchCriteria = $this->getSearchCriteriaBuilder()->create();
- $searchCriteria->setRequestName('advanced_search_container');
+ $searchCriteria = $this->getSearchCriteriaResolver()->resolve();
try {
- $searchResult = $this->getSearch()->search($searchCriteria);
+ $this->searchResult = $this->getSearch()->search($searchCriteria);
+ $this->_totalRecords = $this->getTotalRecordsResolver($this->searchResult)->resolve();
} catch (EmptyRequestDataException $e) {
/** @var \Magento\Framework\Api\Search\SearchResultInterface $searchResult */
- $searchResult = $this->searchResultFactory->create()->setItems([]);
+ $this->searchResult = $this->searchResultFactory->create()->setItems([]);
} catch (NonExistingRequestNameException $e) {
$this->_logger->error($e->getMessage());
throw new LocalizedException(
__('An error occurred. For details, see the error log.')
);
}
- $temporaryStorage = $this->temporaryStorageFactory->create();
- $table = $temporaryStorage->storeApiDocuments($searchResult->getItems());
-
- $this->getSelect()->joinInner(
- [
- 'search_result' => $table->getName(),
- ],
- 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID,
- []
- );
+ $this->getSearchResultApplier($this->searchResult)->apply();
}
parent::_renderFiltersBefore();
}
+ /**
+ * Get total records resolver.
+ *
+ * @param SearchResultInterface $searchResult
+ * @return TotalRecordsResolverInterface
+ */
+ private function getTotalRecordsResolver(SearchResultInterface $searchResult): TotalRecordsResolverInterface
+ {
+ return $this->totalRecordsResolverFactory->create([
+ 'searchResult' => $searchResult,
+ ]);
+ }
+
+ /**
+ * Get search criteria resolver.
+ *
+ * @return SearchCriteriaResolverInterface
+ */
+ private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface
+ {
+ return $this->searchCriteriaResolverFactory->create([
+ 'builder' => $this->getSearchCriteriaBuilder(),
+ 'collection' => $this,
+ 'searchRequestName' => $this->searchRequestName,
+ 'currentPage' => $this->_curPage,
+ 'size' => $this->getPageSize(),
+ 'orders' => $this->searchOrders,
+ ]);
+ }
+
+ /**
+ * Get search result applier.
+ *
+ * @param SearchResultInterface $searchResult
+ * @return SearchResultApplierInterface
+ */
+ private function getSearchResultApplier(SearchResultInterface $searchResult): SearchResultApplierInterface
+ {
+ return $this->searchResultApplierFactory->create([
+ 'collection' => $this,
+ 'searchResult' => $searchResult,
+ /** This variable sets by serOrder method, but doesn't have a getter method. */
+ 'orders' => $this->_orders
+ ]);
+ }
+
/**
* Get attribute code.
*
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
index e6cfe8ca112f7..2c36b150fed07 100644
--- a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection.php
@@ -3,13 +3,26 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\CatalogSearch\Model\ResourceModel\Fulltext;
-use Magento\CatalogSearch\Model\Search\RequestGenerator;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
+use Magento\Framework\Search\EngineResolverInterface;
+use Magento\Framework\Data\Collection\Db\SizeResolverInterfaceFactory;
use Magento\Framework\DB\Select;
+use Magento\Framework\Api\Search\SearchResultInterface;
+use Magento\CatalogSearch\Model\Search\RequestGenerator;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\StateException;
-use Magento\Framework\Search\Adapter\Mysql\TemporaryStorage;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
use Magento\Framework\Search\Response\QueryResponse;
use Magento\Framework\Search\Request\EmptyRequestDataException;
use Magento\Framework\Search\Request\NonExistingRequestNameException;
@@ -17,6 +30,7 @@
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\Search\Model\EngineResolver;
/**
* Fulltext Collection
@@ -26,6 +40,7 @@
* @api
* @since 100.0.2
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.TooManyFields)
*/
class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
{
@@ -60,11 +75,6 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
*/
private $queryText;
- /**
- * @var string|null
- */
- private $relevanceOrderDirection = null;
-
/**
* @var string
*/
@@ -101,6 +111,31 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
*/
private $filterBuilder;
+ /**
+ * @var SearchCriteriaResolverFactory
+ */
+ private $searchCriteriaResolverFactory;
+
+ /**
+ * @var SearchResultApplierFactory
+ */
+ private $searchResultApplierFactory;
+
+ /**
+ * @var TotalRecordsResolverFactory
+ */
+ private $totalRecordsResolverFactory;
+
+ /**
+ * @var EngineResolverInterface
+ */
+ private $engineResolver;
+
+ /**
+ * @var array
+ */
+ private $searchOrders;
+
/**
* Collection constructor
*
@@ -132,8 +167,19 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param SearchResultFactory|null $searchResultFactory
* @param ProductLimitationFactory|null $productLimitationFactory
* @param MetadataPool|null $metadataPool
- *
+ * @param TableMaintainer|null $tableMaintainer
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface|null $resourceModelPool
+ * @param \Magento\Search\Api\SearchInterface|null $search
+ * @param \Magento\Framework\Api\Search\SearchCriteriaBuilder|null $searchCriteriaBuilder
+ * @param \Magento\Framework\Api\FilterBuilder|null $filterBuilder
+ * @param SearchCriteriaResolverFactory|null $searchCriteriaResolverFactory
+ * @param SearchResultApplierFactory|null $searchResultApplierFactory
+ * @param TotalRecordsResolverFactory|null $totalRecordsResolverFactory
+ * @param EngineResolverInterface|null $engineResolver
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function __construct(
\Magento\Framework\Data\Collection\EntityFactory $entityFactory,
@@ -163,7 +209,18 @@ public function __construct(
$searchRequestName = 'catalog_view_container',
SearchResultFactory $searchResultFactory = null,
ProductLimitationFactory $productLimitationFactory = null,
- MetadataPool $metadataPool = null
+ MetadataPool $metadataPool = null,
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null,
+ \Magento\Search\Api\SearchInterface $search = null,
+ \Magento\Framework\Api\Search\SearchCriteriaBuilder $searchCriteriaBuilder = null,
+ \Magento\Framework\Api\FilterBuilder $filterBuilder = null,
+ SearchCriteriaResolverFactory $searchCriteriaResolverFactory = null,
+ SearchResultApplierFactory $searchResultApplierFactory = null,
+ TotalRecordsResolverFactory $totalRecordsResolverFactory = null,
+ EngineResolverInterface $engineResolver = null
) {
$this->queryFactory = $catalogSearchData;
if ($searchResultFactory === null) {
@@ -192,12 +249,29 @@ public function __construct(
$groupManagement,
$connection,
$productLimitationFactory,
- $metadataPool
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
$this->requestBuilder = $requestBuilder;
$this->searchEngine = $searchEngine;
$this->temporaryStorageFactory = $temporaryStorageFactory;
$this->searchRequestName = $searchRequestName;
+ $this->search = $search ?: ObjectManager::getInstance()->get(\Magento\Search\Api\SearchInterface::class);
+ $this->searchCriteriaBuilder = $searchCriteriaBuilder ?: ObjectManager::getInstance()
+ ->get(\Magento\Framework\Api\Search\SearchCriteriaBuilder::class);
+ $this->filterBuilder = $filterBuilder ?: ObjectManager::getInstance()
+ ->get(\Magento\Framework\Api\FilterBuilder::class);
+ $this->searchCriteriaResolverFactory = $searchCriteriaResolverFactory ?: ObjectManager::getInstance()
+ ->get(SearchCriteriaResolverFactory::class);
+ $this->searchResultApplierFactory = $searchResultApplierFactory ?: ObjectManager::getInstance()
+ ->get(SearchResultApplierFactory::class);
+ $this->totalRecordsResolverFactory = $totalRecordsResolverFactory ?: ObjectManager::getInstance()
+ ->get(TotalRecordsResolverFactory::class);
+ $this->engineResolver = $engineResolver ?: ObjectManager::getInstance()
+ ->get(EngineResolverInterface::class);
}
/**
@@ -331,32 +405,32 @@ public function addSearchFilter($query)
/**
* @inheritdoc
*/
- protected function _renderFiltersBefore()
+ public function setOrder($attribute, $dir = Select::SQL_DESC)
{
- $this->getSearchCriteriaBuilder();
- $this->getFilterBuilder();
- $this->getSearch();
-
- if ($this->queryText) {
- $this->filterBuilder->setField('search_term');
- $this->filterBuilder->setValue($this->queryText);
- $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
+ $this->setSearchOrder($attribute, $dir);
+ if ($this->isCurrentEngineMysql()) {
+ parent::setOrder($attribute, $dir);
}
- $priceRangeCalculation = $this->_scopeConfig->getValue(
- \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
- );
- if ($priceRangeCalculation) {
- $this->filterBuilder->setField('price_dynamic_algorithm');
- $this->filterBuilder->setValue($priceRangeCalculation);
- $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
+ return $this;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function _renderFiltersBefore()
+ {
+ if ($this->isLoaded()) {
+ return;
}
- $searchCriteria = $this->searchCriteriaBuilder->create();
- $searchCriteria->setRequestName($this->searchRequestName);
+ $this->prepareSearchTermFilter();
+ $this->preparePriceAggregation();
+
+ $searchCriteria = $this->getSearchCriteriaResolver()->resolve();
try {
$this->searchResult = $this->getSearch()->search($searchCriteria);
+ $this->_totalRecords = $this->getTotalRecordsResolver($this->searchResult)->resolve();
} catch (EmptyRequestDataException $e) {
/** @var \Magento\Framework\Api\Search\SearchResultInterface $searchResult */
$this->searchResult = $this->searchResultFactory->create()->setItems([]);
@@ -365,23 +439,79 @@ protected function _renderFiltersBefore()
throw new LocalizedException(__('An error occurred. For details, see the error log.'));
}
- $temporaryStorage = $this->temporaryStorageFactory->create();
- $table = $temporaryStorage->storeApiDocuments($this->searchResult->getItems());
+ $this->getSearchResultApplier($this->searchResult)->apply();
+ parent::_renderFiltersBefore();
+ }
- $this->getSelect()->joinInner(
- [
- 'search_result' => $table->getName(),
- ],
- 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID,
- []
- );
+ /**
+ * Set sort order for search query.
+ *
+ * @param string $field
+ * @param string $direction
+ * @return void
+ */
+ private function setSearchOrder($field, $direction)
+ {
+ $field = (string)$this->_getMappedField($field);
+ $direction = strtoupper($direction) == self::SORT_ORDER_ASC ? self::SORT_ORDER_ASC : self::SORT_ORDER_DESC;
- if ($this->relevanceOrderDirection) {
- $this->getSelect()->order(
- 'search_result.'. TemporaryStorage::FIELD_SCORE . ' ' . $this->relevanceOrderDirection
- );
- }
- return parent::_renderFiltersBefore();
+ $this->searchOrders[$field] = $direction;
+ }
+
+ /**
+ * Check if current engine is MYSQL.
+ *
+ * @return bool
+ */
+ private function isCurrentEngineMysql()
+ {
+ return $this->engineResolver->getCurrentSearchEngine() === EngineResolver::CATALOG_SEARCH_MYSQL_ENGINE;
+ }
+
+ /**
+ * Get total records resolver.
+ *
+ * @param SearchResultInterface $searchResult
+ * @return TotalRecordsResolverInterface
+ */
+ private function getTotalRecordsResolver(SearchResultInterface $searchResult): TotalRecordsResolverInterface
+ {
+ return $this->totalRecordsResolverFactory->create([
+ 'searchResult' => $searchResult,
+ ]);
+ }
+
+ /**
+ * Get search criteria resolver.
+ *
+ * @return SearchCriteriaResolverInterface
+ */
+ private function getSearchCriteriaResolver(): SearchCriteriaResolverInterface
+ {
+ return $this->searchCriteriaResolverFactory->create([
+ 'builder' => $this->getSearchCriteriaBuilder(),
+ 'collection' => $this,
+ 'searchRequestName' => $this->searchRequestName,
+ 'currentPage' => $this->_curPage,
+ 'size' => $this->getPageSize(),
+ 'orders' => $this->searchOrders,
+ ]);
+ }
+
+ /**
+ * Get search result applier.
+ *
+ * @param SearchResultInterface $searchResult
+ * @return SearchResultApplierInterface
+ */
+ private function getSearchResultApplier(SearchResultInterface $searchResult): SearchResultApplierInterface
+ {
+ return $this->searchResultApplierFactory->create([
+ 'collection' => $this,
+ 'searchResult' => $searchResult,
+ /** This variable sets by serOrder method, but doesn't have a getter method. */
+ 'orders' => $this->_orders,
+ ]);
}
/**
@@ -409,24 +539,6 @@ protected function _renderFilters()
return parent::_renderFilters();
}
- /**
- * Set Order field
- *
- * @param string $attribute
- * @param string $dir
- * @return $this
- */
- public function setOrder($attribute, $dir = Select::SQL_DESC)
- {
- if ($attribute === 'relevance') {
- $this->relevanceOrderDirection = $dir;
- } else {
- parent::setOrder($attribute, $dir);
- }
-
- return $this;
- }
-
/**
* Stub method for compatibility with other search engines
*
@@ -473,7 +585,17 @@ public function getFacetedData($field)
public function addCategoryFilter(\Magento\Catalog\Model\Category $category)
{
$this->addFieldToFilter('category_ids', $category->getId());
- return parent::addCategoryFilter($category);
+ /**
+ * This changes need in backward compatible reasons for support dynamic improved algorithm
+ * for price aggregation process.
+ */
+ if ($this->isCurrentEngineMysql()) {
+ parent::addCategoryFilter($category);
+ } else {
+ $this->_productLimitationPrice();
+ }
+
+ return $this;
}
/**
@@ -485,6 +607,46 @@ public function addCategoryFilter(\Magento\Catalog\Model\Category $category)
public function setVisibility($visibility)
{
$this->addFieldToFilter('visibility', $visibility);
- return parent::setVisibility($visibility);
+ /**
+ * This changes need in backward compatible reasons for support dynamic improved algorithm
+ * for price aggregation process.
+ */
+ if ($this->isCurrentEngineMysql()) {
+ parent::setVisibility($visibility);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Prepare search term filter for text query.
+ *
+ * @return void
+ */
+ private function prepareSearchTermFilter(): void
+ {
+ if ($this->queryText) {
+ $this->filterBuilder->setField('search_term');
+ $this->filterBuilder->setValue($this->queryText);
+ $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
+ }
+ }
+
+ /**
+ * Prepare price aggregation algorithm.
+ *
+ * @return void
+ */
+ private function preparePriceAggregation(): void
+ {
+ $priceRangeCalculation = $this->_scopeConfig->getValue(
+ \Magento\Catalog\Model\Layer\Filter\Dynamic\AlgorithmFactory::XML_PATH_RANGE_CALCULATION,
+ \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ );
+ if ($priceRangeCalculation) {
+ $this->filterBuilder->setField('price_dynamic_algorithm');
+ $this->filterBuilder->setValue($priceRangeCalculation);
+ $this->searchCriteriaBuilder->addFilter($this->filterBuilder->create());
+ }
}
}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php
new file mode 100644
index 0000000000000..632e1ab9270df
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php
@@ -0,0 +1,51 @@
+builder = $builder;
+ $this->searchRequestName = $searchRequestName;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve() : SearchCriteria
+ {
+ $searchCriteria = $this->builder->create();
+ $searchCriteria->setRequestName($this->searchRequestName);
+
+ return $searchCriteria;
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php
new file mode 100644
index 0000000000000..047fa7f71e400
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolverInterface.php
@@ -0,0 +1,22 @@
+collection = $collection;
+ $this->searchResult = $searchResult;
+ $this->temporaryStorageFactory = $temporaryStorageFactory;
+ $this->orders = $orders;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ $temporaryStorage = $this->temporaryStorageFactory->create();
+ $table = $temporaryStorage->storeApiDocuments($this->searchResult->getItems());
+
+ $this->collection->getSelect()->joinInner(
+ [
+ 'search_result' => $table->getName(),
+ ],
+ 'e.entity_id = search_result.' . TemporaryStorage::FIELD_ENTITY_ID,
+ []
+ );
+
+ if (isset($this->orders['relevance'])) {
+ $this->collection->getSelect()->order(
+ 'search_result.' . TemporaryStorage::FIELD_SCORE . ' ' . $this->orders['relevance']
+ );
+ }
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php
new file mode 100644
index 0000000000000..1b3e2a6bbac71
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplierInterface.php
@@ -0,0 +1,20 @@
+_attributeCollectionFactory = $attributeCollectionFactory;
parent::__construct(
@@ -107,7 +125,13 @@ public function __construct(
$customerSession,
$dateTime,
$groupManagement,
- $connection
+ $connection,
+ $productLimitationFactory,
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
}
diff --git a/app/code/Magento/CatalogSearch/Model/ResourceModel/Setup/PropertyMapper.php b/app/code/Magento/CatalogSearch/Model/ResourceModel/Setup/PropertyMapper.php
new file mode 100644
index 0000000000000..f72e1536f4710
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/ResourceModel/Setup/PropertyMapper.php
@@ -0,0 +1,34 @@
+ $this->_getValue($input, 'search_weight', 1),
+ ];
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProvider.php b/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProvider.php
new file mode 100644
index 0000000000000..f621bcbf91835
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProvider.php
@@ -0,0 +1,50 @@
+engineResolver = $engineResolver;
+ $this->factories = $factories;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCollection(): Collection
+ {
+ if (!isset($this->factories[$this->engineResolver->getCurrentSearchEngine()])) {
+ throw new \DomainException('Undefined factory ' . $this->engineResolver->getCurrentSearchEngine());
+ }
+ return $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create();
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProviderInterface.php b/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProviderInterface.php
new file mode 100644
index 0000000000000..db02d5ac5f519
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Model/Search/ItemCollectionProviderInterface.php
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
index 387a7547f4daf..4b52b2c669edf 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchActionGroup.xml
@@ -11,9 +11,9 @@
-
+
-
+
@@ -48,7 +48,7 @@
-
+
@@ -116,4 +116,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml
new file mode 100644
index 0000000000000..83e4ac50a74e6
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/ActionGroup/StorefrontCatalogSearchTermActionGroup.xml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Data/SearchTermData.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Data/SearchTermData.xml
new file mode 100644
index 0000000000000..995b860d107ca
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Data/SearchTermData.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Query text
+ Default Store View
+ http://example.com/
+ No
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermIndexPage.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermIndexPage.xml
new file mode 100644
index 0000000000000..bbafff8ad7739
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermIndexPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermNewPage.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermNewPage.xml
new file mode 100644
index 0000000000000..de7491471741c
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Page/AdminCatalogSearchTermNewPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
new file mode 100644
index 0000000000000..ac316d060f6e9
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermMessagesSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermMessagesSection.xml
new file mode 100644
index 0000000000000..5d19198a1b94c
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermMessagesSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml
new file mode 100644
index 0000000000000..a7d577a7508c0
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermNewSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminCreateSearchTermEntityTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminCreateSearchTermEntityTest.xml
new file mode 100644
index 0000000000000..2b425f34f8a5b
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminCreateSearchTermEntityTest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminDeleteSearchTermTest.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminDeleteSearchTermTest.xml
new file mode 100644
index 0000000000000..c72ed424ef307
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Test/AdminDeleteSearchTermTest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php
index 2faacea24262c..8d1dbbd6dd6df 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Controller/Advanced/ResultTest.php
@@ -20,7 +20,17 @@ public function testResultActionFiltersSetBeforeLoadLayout()
$filters = null;
$expectedQuery = 'filtersData';
- $view = $this->createPartialMock(\Magento\Framework\App\View::class, ['loadLayout', 'renderLayout']);
+ $view = $this->createPartialMock(
+ \Magento\Framework\App\View::class,
+ ['loadLayout', 'renderLayout', 'getPage', 'getLayout']
+ );
+ $update = $this->createPartialMock(\Magento\Framework\View\Model\Layout\Merge::class, ['getHandles']);
+ $update->expects($this->once())->method('getHandles')->will($this->returnValue([]));
+ $layout = $this->createPartialMock(\Magento\Framework\View\Result\Layout::class, ['getUpdate']);
+ $layout->expects($this->once())->method('getUpdate')->will($this->returnValue($update));
+ $view->expects($this->once())->method('getLayout')->will($this->returnValue($layout));
+ $page = $this->createPartialMock(\Magento\Framework\View\Result\Page::class, ['initLayout']);
+ $view->expects($this->once())->method('getPage')->will($this->returnValue($page));
$view->expects($this->once())->method('loadLayout')->will(
$this->returnCallback(
function () use (&$filters, $expectedQuery) {
@@ -32,15 +42,9 @@ function () use (&$filters, $expectedQuery) {
$request = $this->createPartialMock(\Magento\Framework\App\Console\Request::class, ['getQueryValue']);
$request->expects($this->once())->method('getQueryValue')->will($this->returnValue($expectedQuery));
- $collection = $this->createPartialMock(
- \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class,
- ['getSize']
- );
- $collection->expects($this->once())->method('getSize')->will($this->returnValue(1));
-
$catalogSearchAdvanced = $this->createPartialMock(
\Magento\CatalogSearch\Model\Advanced::class,
- ['addFilters', '__wakeup', 'getProductCollection']
+ ['addFilters', '__wakeup']
);
$catalogSearchAdvanced->expects($this->once())->method('addFilters')->will(
$this->returnCallback(
@@ -49,8 +53,6 @@ function ($added) use (&$filters) {
}
)
);
- $catalogSearchAdvanced->expects($this->once())->method('getProductCollection')
- ->will($this->returnValue($collection));
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$context = $objectManager->getObject(
@@ -189,20 +191,11 @@ public function testNoResultsHandle()
$request = $this->createPartialMock(\Magento\Framework\App\Console\Request::class, ['getQueryValue']);
$request->expects($this->once())->method('getQueryValue')->will($this->returnValue($expectedQuery));
- $collection = $this->createPartialMock(
- \Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class,
- ['getSize']
- );
- $collection->expects($this->once())->method('getSize')->will($this->returnValue(0));
-
$catalogSearchAdvanced = $this->createPartialMock(
\Magento\CatalogSearch\Model\Advanced::class,
['addFilters', '__wakeup', 'getProductCollection']
);
- $catalogSearchAdvanced->expects($this->once())->method('getProductCollection')
- ->will($this->returnValue($collection));
-
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$context = $objectManager->getObject(
\Magento\Framework\App\Action\Context::class,
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/AdvancedTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/AdvancedTest.php
index fc5915bb3cdff..a4f62ce83a1b8 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/AdvancedTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/AdvancedTest.php
@@ -250,6 +250,7 @@ public function testAddFiltersVerifyAddConditionsToRegistry(
'productCollectionFactory' => $productCollectionFactory,
'storeManager' => $this->storeManager,
'currencyFactory' => $currencyFactory,
+ 'collectionProvider' => null
]
);
$instance->addFilters($values);
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php
index b65a0d6ca47a0..fe29fa1ece011 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Advanced/CollectionTest.php
@@ -7,7 +7,13 @@
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface;
use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory;
/**
* Tests Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection
@@ -61,7 +67,7 @@ protected function setUp()
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->eavConfig = $this->createMock(\Magento\Eav\Model\Config::class);
$storeManager = $this->getStoreManager();
- $universalFactory = $this->getUniversalFactory();
+ $resourceModelPool = $this->getResourceModelPool();
$this->criteriaBuilder = $this->getCriteriaBuilder();
$this->filterBuilder = $this->createMock(\Magento\Framework\Api\FilterBuilder::class);
$this->temporaryStorageFactory = $this->createMock(
@@ -79,17 +85,57 @@ protected function setUp()
$productLimitationFactoryMock->method('create')
->willReturn($productLimitationMock);
+ $searchCriteriaResolver = $this->getMockBuilder(SearchCriteriaResolverInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['resolve'])
+ ->getMockForAbstractClass();
+ $searchCriteriaResolverFactory = $this->getMockBuilder(SearchCriteriaResolverFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $searchCriteriaResolverFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($searchCriteriaResolver);
+
+ $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['apply'])
+ ->getMockForAbstractClass();
+ $searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $searchResultApplierFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($searchResultApplier);
+
+ $totalRecordsResolver = $this->getMockBuilder(TotalRecordsResolverInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['resolve'])
+ ->getMockForAbstractClass();
+ $totalRecordsResolverFactory = $this->getMockBuilder(TotalRecordsResolverFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $totalRecordsResolverFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($totalRecordsResolver);
+
$this->advancedCollection = $this->objectManager->getObject(
\Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection::class,
[
'eavConfig' => $this->eavConfig,
'storeManager' => $storeManager,
- 'universalFactory' => $universalFactory,
+ 'resourceModelPool' => $resourceModelPool,
'searchCriteriaBuilder' => $this->criteriaBuilder,
'filterBuilder' => $this->filterBuilder,
'temporaryStorageFactory' => $this->temporaryStorageFactory,
'search' => $this->search,
'productLimitationFactory' => $productLimitationFactoryMock,
+ 'collectionProvider' => null,
+ 'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory,
+ 'searchResultApplierFactory' => $searchResultApplierFactory,
+ 'totalRecordsResolverFactory' => $totalRecordsResolverFactory
]
);
}
@@ -117,18 +163,8 @@ public function testLike()
->willReturn($this->filterBuilder);
$filter = $this->createMock(\Magento\Framework\Api\Filter::class);
- $this->filterBuilder->expects($this->once())->method('create')->willReturn($filter);
-
- $criteria = $this->createMock(\Magento\Framework\Api\Search\SearchCriteria::class);
- $this->criteriaBuilder->expects($this->once())->method('create')->willReturn($criteria);
- $criteria->expects($this->once())
- ->method('setRequestName')
- ->with('advanced_search_container');
-
- $tempTable = $this->createMock(\Magento\Framework\DB\Ddl\Table::class);
- $temporaryStorage = $this->createMock(\Magento\Framework\Search\Adapter\Mysql\TemporaryStorage::class);
- $temporaryStorage->expects($this->once())->method('storeApiDocuments')->willReturn($tempTable);
- $this->temporaryStorageFactory->expects($this->once())->method('create')->willReturn($temporaryStorage);
+ $this->filterBuilder->expects($this->any())->method('create')->willReturn($filter);
+
$searchResult = $this->createMock(\Magento\Framework\Api\Search\SearchResultInterface::class);
$this->search->expects($this->once())->method('search')->willReturn($searchResult);
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/BaseCollection.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/BaseCollection.php
index 9ea103e23d2a7..5a5106593af8b 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/BaseCollection.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/BaseCollection.php
@@ -5,6 +5,8 @@
*/
namespace Magento\CatalogSearch\Test\Unit\Model\ResourceModel;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
* Base class for Collection tests.
*
@@ -42,19 +44,17 @@ protected function getStoreManager()
}
/**
- * Get mock for UniversalFactory so Collection can be used.
+ * Get mock for ResourceModelPool so Collection can be used.
*
- * @return \PHPUnit_Framework_MockObject_MockObject
+ * @return \PHPUnit_Framework_MockObject_MockObject|ResourceModelPoolInterface
*/
- protected function getUniversalFactory()
+ protected function getResourceModelPool()
{
$connection = $this->getMockBuilder(\Magento\Framework\DB\Adapter\Pdo\Mysql::class)
->disableOriginalConstructor()
->setMethods(['select'])
->getMockForAbstractClass();
- $select = $this->getMockBuilder(\Magento\Framework\DB\Select::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $select = $this->createMock(\Magento\Framework\DB\Select::class);
$connection->expects($this->any())->method('select')->willReturn($select);
$entity = $this->getMockBuilder(\Magento\Eav\Model\Entity\AbstractEntity::class)
@@ -74,14 +74,14 @@ protected function getUniversalFactory()
->method('getEntityTable')
->willReturn('table');
- $universalFactory = $this->getMockBuilder(\Magento\Framework\Validator\UniversalFactory::class)
- ->setMethods(['create'])
+ $resourceModelPool = $this->getMockBuilder(ResourceModelPoolInterface::class)
+ ->setMethods(['get'])
->disableOriginalConstructor()
->getMock();
- $universalFactory->expects($this->once())
- ->method('create')
+ $resourceModelPool->expects($this->once())
+ ->method('get')
->willReturn($entity);
- return $universalFactory;
+ return $resourceModelPool;
}
}
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php
index a3b1d2fd0f2b6..f1c2161a052e0 100644
--- a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Fulltext/CollectionTest.php
@@ -5,6 +5,12 @@
*/
namespace Magento\CatalogSearch\Test\Unit\Model\ResourceModel\Fulltext;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolverInterface;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverFactory;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplierInterface;
+use Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolverInterface;
use Magento\CatalogSearch\Test\Unit\Model\ResourceModel\BaseCollection;
use Magento\Framework\Search\Adapter\Mysql\TemporaryStorageFactory;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
@@ -43,7 +49,7 @@ class CollectionTest extends BaseCollection
/**
* @var MockObject
*/
- private $universalFactory;
+ private $resourceModelPool;
/**
* @var MockObject
@@ -72,7 +78,7 @@ protected function setUp()
{
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->storeManager = $this->getStoreManager();
- $this->universalFactory = $this->getUniversalFactory();
+ $this->resourceModelPool = $this->getResourceModelPool();
$this->scopeConfig = $this->getScopeConfig();
$this->criteriaBuilder = $this->getCriteriaBuilder();
$this->filterBuilder = $this->getFilterBuilder();
@@ -97,15 +103,53 @@ protected function setUp()
$temporaryStorageFactory->expects($this->any())
->method('create')
->willReturn($this->temporaryStorage);
+ $searchCriteriaResolver = $this->getMockBuilder(SearchCriteriaResolverInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['resolve'])
+ ->getMockForAbstractClass();
+ $searchCriteriaResolverFactory = $this->getMockBuilder(SearchCriteriaResolverFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $searchCriteriaResolverFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($searchCriteriaResolver);
+
+ $searchResultApplier = $this->getMockBuilder(SearchResultApplierInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['apply'])
+ ->getMockForAbstractClass();
+ $searchResultApplierFactory = $this->getMockBuilder(SearchResultApplierFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $searchResultApplierFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($searchResultApplier);
+
+ $totalRecordsResolver = $this->getMockBuilder(TotalRecordsResolverInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['resolve'])
+ ->getMockForAbstractClass();
+ $totalRecordsResolverFactory = $this->getMockBuilder(TotalRecordsResolverFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+ $totalRecordsResolverFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($totalRecordsResolver);
$this->model = $this->objectManager->getObject(
\Magento\CatalogSearch\Model\ResourceModel\Fulltext\Collection::class,
[
'storeManager' => $this->storeManager,
- 'universalFactory' => $this->universalFactory,
+ 'resourceModelPool' => $this->resourceModelPool,
'scopeConfig' => $this->scopeConfig,
'temporaryStorageFactory' => $temporaryStorageFactory,
'productLimitationFactory' => $productLimitationFactoryMock,
+ 'searchCriteriaResolverFactory' => $searchCriteriaResolverFactory,
+ 'searchResultApplierFactory' => $searchResultApplierFactory,
+ 'totalRecordsResolverFactory' => $totalRecordsResolverFactory,
]
);
@@ -124,37 +168,10 @@ protected function tearDown()
$reflectionProperty->setValue(null);
}
- /**
- * @expectedException \Exception
- * @expectedExceptionCode 333
- * @expectedExceptionMessage setRequestName
- */
- public function testGetFacetedDataWithException()
- {
- $criteria = $this->createMock(\Magento\Framework\Api\Search\SearchCriteria::class);
- $this->criteriaBuilder->expects($this->once())->method('create')->willReturn($criteria);
- $criteria->expects($this->once())
- ->method('setRequestName')
- ->withConsecutive(['catalog_view_container'])
- ->willThrowException(new \Exception('setRequestName', 333));
- $this->model->getFacetedData('field');
- }
-
public function testGetFacetedDataWithEmptyAggregations()
{
- $criteria = $this->createMock(\Magento\Framework\Api\Search\SearchCriteria::class);
- $this->criteriaBuilder->expects($this->once())->method('create')->willReturn($criteria);
- $criteria->expects($this->once())
- ->method('setRequestName')
- ->withConsecutive(['catalog_view_container']);
$searchResult = $this->getMockBuilder(\Magento\Framework\Api\Search\SearchResultInterface::class)
->getMockForAbstractClass();
- $table = $this->getMockBuilder(\Magento\Framework\DB\Ddl\Table::class)
- ->setMethods(['getName'])
- ->getMock();
- $this->temporaryStorage->expects($this->once())
- ->method('storeApiDocuments')
- ->willReturn($table);
$this->search->expects($this->once())
->method('search')
->willReturn($searchResult);
diff --git a/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Setup/PropertyMapperTest.php b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Setup/PropertyMapperTest.php
new file mode 100644
index 0000000000000..5c917b360f147
--- /dev/null
+++ b/app/code/Magento/CatalogSearch/Test/Unit/Model/ResourceModel/Setup/PropertyMapperTest.php
@@ -0,0 +1,65 @@
+propertyMapper = new PropertyMapper();
+ }
+
+ /**
+ * @return array
+ */
+ public function caseProvider(): array
+ {
+ return [
+ [
+ ['search_weight' => 9, 'something_other' => '3'],
+ ['search_weight' => 9]
+ ],
+ [
+ ['something' => 3],
+ ['search_weight' => 1]
+ ]
+ ];
+ }
+
+ /**
+ * @dataProvider caseProvider
+ *
+ * @test
+ *
+ * @param array $input
+ * @param array $result
+ * @return void
+ */
+ public function testMapCorrectlyMapsValue(array $input, array $result): void
+ {
+ //Second parameter doesn't matter as it is not used
+ $this->assertSame($result, $this->propertyMapper->map($input, 4));
+ }
+}
diff --git a/app/code/Magento/CatalogSearch/etc/di.xml b/app/code/Magento/CatalogSearch/etc/di.xml
index cc07384d4c525..7359bd6b454b9 100644
--- a/app/code/Magento/CatalogSearch/etc/di.xml
+++ b/app/code/Magento/CatalogSearch/etc/di.xml
@@ -14,6 +14,10 @@
+
+
+
+
@@ -176,8 +180,13 @@
Magento\CatalogSearch\Model\ResourceModel\Fulltext\SearchCollectionFactory
-
+
+
+
+ Magento\CatalogSearch\Model\ResourceModel\Advanced\CollectionFactory
+
+
+
Magento\CatalogSearch\Model\ResourceModel\Advanced\Collection
@@ -185,6 +194,21 @@
Magento\CatalogSearch\Model\ResourceModel\Advanced\CollectionFactory
+ Magento\CatalogSearch\Model\Search\ItemCollectionProviderInterface
+
+
+
+
+
+ - Magento\CatalogSearch\Model\ResourceModel\Advanced\CollectionFactory
+
+
+
+
+
+
+ - Magento\CatalogSearch\Model\Advanced\ProductCollectionPrepareStrategy
+
@@ -340,4 +364,11 @@
+
+
+
+ - Magento\CatalogSearch\Model\ResourceModel\Setup\PropertyMapper
+
+
+
diff --git a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml
index 83808df5b95e4..3f616ab791dfe 100644
--- a/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml
+++ b/app/code/Magento/CatalogSearch/view/frontend/templates/advanced/result.phtml
@@ -11,6 +11,8 @@
/**
* @var $block \Magento\CatalogSearch\Block\Advanced\Result
*/
+/** this changes need for valid apply filters and configuration before search process is started */
+$productList = $block->getProductListHtml();
?>
getResultCount()): ?>
@@ -49,6 +51,6 @@
getResultCount()): ?>
- = $block->getProductListHtml() ?>
+ = /* @escapeNotVerified */ $productList ?>
getSearchCriterias(); ?>
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/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
index 9aaa384776855..7b60c85049767 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
@@ -263,6 +263,7 @@ protected function _populateForUrlGeneration($rowData)
if ($this->isGlobalScope($product->getStoreId())) {
$this->populateGlobalProduct($product);
} else {
+ $this->storesCache[$product->getStoreId()] = true;
$this->addProductToImport($product, $product->getStoreId());
}
return $this;
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/Observer/ProductToWebsiteChangeObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/ProductToWebsiteChangeObserver.php
index cacc761dbee36..44b47faf3d4b8 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/ProductToWebsiteChangeObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/ProductToWebsiteChangeObserver.php
@@ -13,6 +13,8 @@
use Magento\Store\Model\Store;
use Magento\UrlRewrite\Model\UrlPersistInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
+use Magento\Store\Api\StoreWebsiteRelationInterface;
+use Magento\Framework\App\ObjectManager;
/**
* Observer to assign the products to website
@@ -39,22 +41,31 @@ class ProductToWebsiteChangeObserver implements ObserverInterface
*/
protected $request;
+ /**
+ * @var StoreWebsiteRelationInterface
+ */
+ private $storeWebsiteRelation;
+
/**
* @param ProductUrlRewriteGenerator $productUrlRewriteGenerator
* @param UrlPersistInterface $urlPersist
* @param ProductRepositoryInterface $productRepository
* @param RequestInterface $request
+ * @param StoreWebsiteRelationInterface $storeWebsiteRelation
*/
public function __construct(
ProductUrlRewriteGenerator $productUrlRewriteGenerator,
UrlPersistInterface $urlPersist,
ProductRepositoryInterface $productRepository,
- RequestInterface $request
+ RequestInterface $request,
+ StoreWebsiteRelationInterface $storeWebsiteRelation = null
) {
$this->productUrlRewriteGenerator = $productUrlRewriteGenerator;
$this->urlPersist = $urlPersist;
$this->productRepository = $productRepository;
$this->request = $request;
+ $this->storeWebsiteRelation = $storeWebsiteRelation ?:
+ ObjectManager::getInstance()->get(StoreWebsiteRelationInterface::class);
}
/**
@@ -73,10 +84,17 @@ public function execute(\Magento\Framework\Event\Observer $observer)
);
if (!empty($this->productUrlRewriteGenerator->generate($product))) {
- $this->urlPersist->deleteByData([
- UrlRewrite::ENTITY_ID => $product->getId(),
- UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
- ]);
+ if ($this->request->getParam('remove_website_ids')) {
+ foreach ($this->request->getParam('remove_website_ids') as $webId) {
+ foreach ($this->storeWebsiteRelation->getStoreByWebsiteId($webId) as $storeId) {
+ $this->urlPersist->deleteByData([
+ UrlRewrite::ENTITY_ID => $product->getId(),
+ UrlRewrite::ENTITY_TYPE => ProductUrlRewriteGenerator::ENTITY_TYPE,
+ UrlRewrite::STORE_ID => $storeId
+ ]);
+ }
+ }
+ }
if ($product->getVisibility() != Visibility::VISIBILITY_NOT_VISIBLE) {
$this->urlPersist->replace($this->productUrlRewriteGenerator->generate($product));
}
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/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
index fbc620a6d741a..294cf8562906d 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Category/CurrentUrlRewritesRegeneratorTest.php
@@ -252,7 +252,7 @@ protected function getCurrentRewritesMocks($currentRewrites)
->disableOriginalConstructor()->getMock();
foreach ($urlRewrite as $key => $value) {
$url->expects($this->any())
- ->method('get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))))
+ ->method('get' . str_replace('_', '', ucwords($key, '_')))
->will($this->returnValue($value));
}
$rewrites[] = $url;
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
index 4855478b8488a..c431743fc0b51 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Model/Product/CurrentUrlRewritesRegeneratorTest.php
@@ -294,7 +294,7 @@ protected function getCurrentRewritesMocks($currentRewrites)
->disableOriginalConstructor()->getMock();
foreach ($urlRewrite as $key => $value) {
$url->expects($this->any())
- ->method('get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))))
+ ->method('get' . str_replace('_', '', ucwords($key, '_')))
->will($this->returnValue($value));
}
$rewrites[] = $url;
diff --git a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
index fd9ab10537f1c..3984d949332d3 100644
--- a/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
+++ b/app/code/Magento/CatalogUrlRewrite/Test/Unit/Observer/AfterImportDataObserverTest.php
@@ -694,7 +694,7 @@ protected function currentUrlRewritesRegeneratorGetCurrentRewritesMocks($current
->disableOriginalConstructor()->getMock();
foreach ($urlRewrite as $key => $value) {
$url->expects($this->any())
- ->method('get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))))
+ ->method('get' . str_replace('_', '', ucwords($key, '_')))
->will($this->returnValue($value));
}
$rewrites[] = $url;
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/Block/Cart/Totals.php b/app/code/Magento/Checkout/Block/Cart/Totals.php
index 375c564f29059..7ac5c1c149cc6 100644
--- a/app/code/Magento/Checkout/Block/Cart/Totals.php
+++ b/app/code/Magento/Checkout/Block/Cart/Totals.php
@@ -3,12 +3,15 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Checkout\Block\Cart;
use Magento\Framework\View\Element\BlockInterface;
use Magento\Checkout\Block\Checkout\LayoutProcessorInterface;
/**
+ * Totals cart block.
+ *
* @api
*/
class Totals extends \Magento\Checkout\Block\Cart\AbstractCart
@@ -62,6 +65,8 @@ public function __construct(
}
/**
+ * Retrieve encoded js layout.
+ *
* @return string
*/
public function getJsLayout()
@@ -74,6 +79,8 @@ public function getJsLayout()
}
/**
+ * Retrieve totals from cache.
+ *
* @return array
*/
public function getTotals()
@@ -85,6 +92,8 @@ public function getTotals()
}
/**
+ * Set totals to cache.
+ *
* @param array $value
* @return $this
* @codeCoverageIgnore
@@ -96,6 +105,8 @@ public function setTotals($value)
}
/**
+ * Create totals block and set totals.
+ *
* @param string $code
* @return BlockInterface
*/
@@ -121,6 +132,8 @@ protected function _getTotalRenderer($code)
}
/**
+ * Get totals html.
+ *
* @param mixed $total
* @param int|null $area
* @param int $colspan
@@ -177,7 +190,7 @@ public function needDisplayBaseGrandtotal()
}
/**
- * Get formated in base currency base grand total value
+ * Get formatted in base currency base grand total value
*
* @return string
*/
diff --git a/app/code/Magento/Checkout/Controller/Cart/Addgroup.php b/app/code/Magento/Checkout/Controller/Cart/Addgroup.php
index c205f3c16072f..7eb9362031258 100644
--- a/app/code/Magento/Checkout/Controller/Cart/Addgroup.php
+++ b/app/code/Magento/Checkout/Controller/Cart/Addgroup.php
@@ -4,17 +4,21 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Checkout\Controller\Cart;
use Magento\Checkout\Model\Cart as CustomerCart;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Escaper;
use Magento\Framework\App\ObjectManager;
use Magento\Sales\Model\Order\Item;
/**
+ * Add grouped items controller.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Addgroup extends \Magento\Checkout\Controller\Cart
+class Addgroup extends \Magento\Checkout\Controller\Cart implements HttpPostActionInterface
{
/**
* @var Escaper
@@ -44,6 +48,8 @@ public function __construct(
}
/**
+ * Add items in group.
+ *
* @return \Magento\Framework\Controller\Result\Redirect
*/
public function execute()
@@ -74,6 +80,8 @@ public function execute()
}
}
$this->cart->save();
+ } else {
+ $this->messageManager->addErrorMessage(__('Please select at least one product to add to cart'));
}
return $this->_goBack();
}
diff --git a/app/code/Magento/Checkout/Model/Cart.php b/app/code/Magento/Checkout/Model/Cart.php
index eff07af0e6a3e..cec99909dc999 100644
--- a/app/code/Magento/Checkout/Model/Cart.php
+++ b/app/code/Magento/Checkout/Model/Cart.php
@@ -16,6 +16,7 @@
* Shopping cart model
*
* @api
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @deprecated 100.1.0 Use \Magento\Quote\Model\Quote instead
* @see \Magento\Quote\Api\Data\CartInterface
@@ -365,20 +366,10 @@ protected function _getProductRequest($requestInfo)
public function addProduct($productInfo, $requestInfo = null)
{
$product = $this->_getProduct($productInfo);
- $request = $this->_getProductRequest($requestInfo);
$productId = $product->getId();
if ($productId) {
- $stockItem = $this->stockRegistry->getStockItem($productId, $product->getStore()->getWebsiteId());
- $minimumQty = $stockItem->getMinSaleQty();
- //If product quantity is not specified in request and there is set minimal qty for it
- if ($minimumQty
- && $minimumQty > 0
- && !$request->getQty()
- ) {
- $request->setQty($minimumQty);
- }
-
+ $request = $this->getQtyRequest($product, $requestInfo);
try {
$this->_eventManager->dispatch(
'checkout_cart_product_add_before',
@@ -438,8 +429,9 @@ public function addProductsByIds($productIds)
}
$product = $this->_getProduct($productId);
if ($product->getId() && $product->isVisibleInCatalog()) {
+ $request = $this->getQtyRequest($product);
try {
- $this->getQuote()->addProduct($product);
+ $this->getQuote()->addProduct($product, $request);
} catch (\Exception $e) {
$allAdded = false;
}
@@ -762,4 +754,27 @@ private function getRequestInfoFilter()
}
return $this->requestInfoFilter;
}
+
+ /**
+ * Get request quantity
+ *
+ * @param Product $product
+ * @param \Magento\Framework\DataObject|int|array $request
+ * @return int|DataObject
+ */
+ private function getQtyRequest($product, $request = 0)
+ {
+ $request = $this->_getProductRequest($request);
+ $stockItem = $this->stockRegistry->getStockItem($product->getId(), $product->getStore()->getWebsiteId());
+ $minimumQty = $stockItem->getMinSaleQty();
+ //If product quantity is not specified in request and there is set minimal qty for it
+ if ($minimumQty
+ && $minimumQty > 0
+ && !$request->getQty()
+ ) {
+ $request->setQty($minimumQty);
+ }
+
+ return $request;
+ }
}
diff --git a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
index 333226b7d216f..da29482f0123f 100644
--- a/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
+++ b/app/code/Magento/Checkout/Model/GuestPaymentInformationManagement.php
@@ -14,6 +14,8 @@
use Magento\Quote\Model\Quote;
/**
+ * Guest payment information management model.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPaymentInformationManagementInterface
@@ -66,7 +68,7 @@ class GuestPaymentInformationManagement implements \Magento\Checkout\Api\GuestPa
* @param \Magento\Checkout\Api\PaymentInformationManagementInterface $paymentInformationManagement
* @param \Magento\Quote\Model\QuoteIdMaskFactory $quoteIdMaskFactory
* @param CartRepositoryInterface $cartRepository
- * @param ResourceConnection|null
+ * @param ResourceConnection $connectionPool
* @codeCoverageIgnore
*/
public function __construct(
@@ -88,7 +90,7 @@ public function __construct(
}
/**
- * {@inheritDoc}
+ * @inheritdoc
*/
public function savePaymentInformationAndPlaceOrder(
$cartId,
@@ -129,7 +131,7 @@ public function savePaymentInformationAndPlaceOrder(
}
/**
- * {@inheritDoc}
+ * @inheritdoc
*/
public function savePaymentInformation(
$cartId,
@@ -156,7 +158,7 @@ public function savePaymentInformation(
}
/**
- * {@inheritDoc}
+ * @inheritdoc
*/
public function getPaymentInformation($cartId)
{
@@ -190,9 +192,8 @@ private function limitShippingCarrier(Quote $quote) : void
{
$shippingAddress = $quote->getShippingAddress();
if ($shippingAddress && $shippingAddress->getShippingMethod()) {
- $shippingDataArray = explode('_', $shippingAddress->getShippingMethod());
- $shippingCarrier = array_shift($shippingDataArray);
- $shippingAddress->setLimitCarrier($shippingCarrier);
+ $shippingRate = $shippingAddress->getShippingRateByCode($shippingAddress->getShippingMethod());
+ $shippingAddress->setLimitCarrier($shippingRate->getCarrier());
}
}
}
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniShoppingCartSubTotalActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniShoppingCartSubTotalActionGroup.xml
new file mode 100644
index 0000000000000..8c5c6f41fffa7
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/AssertMiniShoppingCartSubTotalActionGroup.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+ $grabMiniCartTotal
+ {{dataQuote.subtotal}}
+
+
+ $grabMiniCartTotal
+ {{dataQuote.currency}}
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/ClearShippingAddressActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/ClearShippingAddressActionGroup.xml
new file mode 100644
index 0000000000000..0e6994e8feaa4
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/ClearShippingAddressActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingAddressOneStreetActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingAddressOneStreetActionGroup.xml
new file mode 100644
index 0000000000000..cbc6c5320b01c
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/FillShippingAddressOneStreetActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddSimpleProductToCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddSimpleProductToCartActionGroup.xml
new file mode 100644
index 0000000000000..7bfc87cd8d6f9
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontAddSimpleProductToCartActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml
index 7a5c5e1d15872..e4a388d2c3a58 100644
--- a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontMiniCartActionGroup.xml
@@ -35,4 +35,11 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontUpdateProductQtyMiniShoppingCartActionGroup.xml b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontUpdateProductQtyMiniShoppingCartActionGroup.xml
new file mode 100644
index 0000000000000..ee8b761a452d4
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/ActionGroup/StorefrontUpdateProductQtyMiniShoppingCartActionGroup.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
index 530157851191f..e7a5992ad8943 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Data/QuoteData.xml
@@ -15,4 +15,26 @@
495.00
Flat Rate - Fixed
+
+ 560.00
+ 2
+ 1,120.00
+ 10.00
+ 1,130.00
+ Flat Rate - Fixed
+ $
+
+
+ 123.00
+ 3
+ 369.00
+ $
+
+
+ 100.00
+ 20
+ 11
+ 1,320.00
+ $
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml
index b0acc64c77727..bf17800f29ad1 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Page/CheckoutCartPage.xml
@@ -11,5 +11,6 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartCrossSellSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartCrossSellSection.xml
new file mode 100644
index 0000000000000..aa23f3364771f
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutCartCrossSellSection.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml
index a182a3357a9ce..d825e10395145 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/CheckoutShippingSection.xml
@@ -27,6 +27,7 @@
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
index bdb02835c6276..38c88bf4f80bb 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Section/StorefrontMiniCartSection.xml
@@ -25,6 +25,8 @@
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/EditShippingAddressOnePageCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/EditShippingAddressOnePageCheckoutTest.xml
new file mode 100644
index 0000000000000..20015f76e08e3
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/EditShippingAddressOnePageCheckoutTest.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml
index 7b81f12624864..ff61b3be08af1 100644
--- a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontGuestCheckoutTest.xml
@@ -63,7 +63,7 @@
-
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml
new file mode 100644
index 0000000000000..423f4049f6722
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleProductQtyTest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ grabProductQtyInCart
+ {{quoteQty3Price123.qty}}
+
+
+
+
+
+
+
+
+
+ grabProductQtyInMinicart
+ {{quoteQty3Price123.qty}}
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml
new file mode 100644
index 0000000000000..84080b04c80ee
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/StorefrontUpdateShoppingCartSimpleWithCustomOptionsProductQtyTest.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{quoteQty11Subtotal1320.qty}}
+ grabProductQtyInCart
+
+
+
+
+
+
+
+ {{quoteQty11Subtotal1320.qty}}
+ grabProductQtyInMinicart
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Mftf/Test/UpdateProductFromMiniShoppingCartEntityTest.xml b/app/code/Magento/Checkout/Test/Mftf/Test/UpdateProductFromMiniShoppingCartEntityTest.xml
new file mode 100644
index 0000000000000..7318f865a0dc1
--- /dev/null
+++ b/app/code/Magento/Checkout/Test/Mftf/Test/UpdateProductFromMiniShoppingCartEntityTest.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php
index 853ae0157e64a..1de0ebce10f51 100644
--- a/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php
+++ b/app/code/Magento/Checkout/Test/Unit/Model/GuestPaymentInformationManagementTest.php
@@ -273,7 +273,7 @@ public function testSavePaymentInformationAndPlaceOrderWithLocalizedException()
*/
private function getMockForAssignBillingAddress(
int $cartId,
- \PHPUnit_Framework_MockObject_MockObject $billingAddressMock
+ \PHPUnit_Framework_MockObject_MockObject $billingAddressMock
) : void {
$quoteIdMask = $this->createPartialMock(QuoteIdMask::class, ['getQuoteId', 'load']);
$this->quoteIdMaskFactoryMock->method('create')
@@ -287,9 +287,11 @@ private function getMockForAssignBillingAddress(
$billingAddressId = 1;
$quote = $this->createMock(Quote::class);
$quoteBillingAddress = $this->createMock(Address::class);
+ $shippingRate = $this->createPartialMock(\Magento\Quote\Model\Quote\Address\Rate::class, []);
+ $shippingRate->setCarrier('flatrate');
$quoteShippingAddress = $this->createPartialMock(
Address::class,
- ['setLimitCarrier', 'getShippingMethod']
+ ['setLimitCarrier', 'getShippingMethod', 'getShippingRateByCode']
);
$this->cartRepositoryMock->method('getActive')
->with($cartId)
@@ -309,6 +311,9 @@ private function getMockForAssignBillingAddress(
$quote->expects($this->once())
->method('setBillingAddress')
->with($billingAddressMock);
+ $quoteShippingAddress->expects($this->any())
+ ->method('getShippingRateByCode')
+ ->willReturn($shippingRate);
$quote->expects($this->once())
->method('setDataChanges')
->willReturnSelf();
diff --git a/app/code/Magento/Checkout/etc/di.xml b/app/code/Magento/Checkout/etc/di.xml
index 71dfd12bb4779..4ebd594a28562 100644
--- a/app/code/Magento/Checkout/etc/di.xml
+++ b/app/code/Magento/Checkout/etc/di.xml
@@ -49,7 +49,4 @@
-
-
-
diff --git a/app/code/Magento/Checkout/etc/frontend/di.xml b/app/code/Magento/Checkout/etc/frontend/di.xml
index 00bcd2a27005a..8f35fe9f37abf 100644
--- a/app/code/Magento/Checkout/etc/frontend/di.xml
+++ b/app/code/Magento/Checkout/etc/frontend/di.xml
@@ -96,4 +96,7 @@
+
+
+
diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml
index 1005c11e44d95..84ab9b13d8f3a 100644
--- a/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml
+++ b/app/code/Magento/Checkout/view/frontend/templates/cart/form.phtml
@@ -14,7 +14,8 @@
method="post"
id="form-validate"
data-mage-init='{"Magento_Checkout/js/action/update-shopping-cart":
- {"validationURL" : "/checkout/cart/updateItemQty"}
+ {"validationURL" : "/checkout/cart/updateItemQty",
+ "updateCartActionContainer": "#update_cart_action_container"}
}'
class="form form-cart">
= $block->getBlockHtml('formkey') ?>
diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/configure/updatecart.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/configure/updatecart.phtml
index c1db2f7775ca8..bfb7ddc55cda6 100644
--- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/configure/updatecart.phtml
+++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/configure/updatecart.phtml
@@ -20,6 +20,7 @@
escapeUrl($block->getContinueShoppingUrl())) ?>
= $block->getChildHtml('shopping.cart.table.after') ?>
+
\ No newline at end of file
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
index ce1527b3d72d6..1920bc4d7ac41 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/action/update-shopping-cart.js
@@ -14,7 +14,8 @@ define([
$.widget('mage.updateShoppingCart', {
options: {
validationURL: '',
- eventName: 'updateCartItemQty'
+ eventName: 'updateCartItemQty',
+ updateCartActionContainer: ''
},
/** @inheritdoc */
@@ -31,7 +32,9 @@ define([
* @return {Boolean}
*/
onSubmit: function (event) {
- if (!this.options.validationURL) {
+ var action = this.element.find(this.options.updateCartActionContainer).val();
+
+ if (!this.options.validationURL || action === 'empty_cart') {
return true;
}
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/empty-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/empty-cart.js
new file mode 100644
index 0000000000000..4b30ad8075274
--- /dev/null
+++ b/app/code/Magento/Checkout/view/frontend/web/js/empty-cart.js
@@ -0,0 +1,16 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'Magento_Customer/js/customer-data'
+], function (customerData) {
+ 'use strict';
+
+ var cartData = customerData.get('cart');
+
+ if (cartData().items && cartData().items.length !== 0) {
+ customerData.reload(['cart'], false);
+ }
+});
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js b/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js
index 2510d1aced3d3..3486a92736617 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/quote.js
@@ -7,7 +7,8 @@
*/
define([
'ko',
- 'underscore'
+ 'underscore',
+ 'domReady!'
], function (ko, _) {
'use strict';
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js b/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js
index 3ea49cd981d90..eecfa65b189d1 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/shopping-cart.js
@@ -14,7 +14,11 @@ define([
_create: function () {
var items, i, reload;
- $(this.options.emptyCartButton).on('click', $.proxy(function () {
+ $(this.options.emptyCartButton).on('click', $.proxy(function (event) {
+ if (event.detail === 0) {
+ return;
+ }
+
$(this.options.emptyCartButton).attr('name', 'update_cart_action_temp');
$(this.options.updateCartActionContainer)
.attr('name', 'update_cart_action').attr('value', 'empty_cart');
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
index 6f9a1a46826da..d68b0682eb511 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/billing-address.js
@@ -202,6 +202,13 @@ function (
}
},
+ /**
+ * Manage cancel button visibility
+ */
+ canUseCancelBillingAddress: ko.computed(function () {
+ return quote.billingAddress() || lastSelectedBillingAddress;
+ }),
+
/**
* Restore billing address
*/
diff --git a/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html b/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html
index 5f735fbb4daa9..63edb5057b933 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/billing-address.html
@@ -22,7 +22,7 @@
-
+
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/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html b/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html
index a448537d64e83..4b1a68624e547 100644
--- a/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html
+++ b/app/code/Magento/CheckoutAgreements/view/frontend/web/template/checkout/checkout-agreements.html
@@ -5,17 +5,17 @@
*/
-->
-
+
-
+
-
+
_pageFactory->create();
- if ($pageId !== null && $pageId !== $page->getId()) {
+ if ($pageId !== null) {
$page->setStoreId($this->_storeManager->getStore()->getId());
$page->load($pageId);
}
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/Mftf/ActionGroup/AssertCMSPageContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageContentActionGroup.xml
index 58318660d2c42..dde6237390257 100644
--- a/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageContentActionGroup.xml
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/AssertCMSPageContentActionGroup.xml
@@ -23,4 +23,14 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml b/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml
index 885310d9399ae..73db6b61343b1 100644
--- a/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml
@@ -7,7 +7,7 @@
-->
-
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml
index 280c7dfd8263e..4ce8842c1ad87 100644
--- a/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Section/StorefrontCMSPageSection.xml
@@ -14,5 +14,6 @@
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml
index 05b7dfeeb3953..03edc69e6d625 100644
--- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddImageToWYSIWYGBlockTest.xml
@@ -16,7 +16,6 @@
-
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml
index b4bcdaadf9a09..e6ab1c130606b 100644
--- a/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Test/CheckStaticBlocksTest.xml
@@ -17,7 +17,6 @@
-
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml
index 65fabfe25e817..2c351a12af72e 100644
--- a/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Test/StoreViewLanguageCorrectSwitchTest.xml
@@ -11,13 +11,12 @@
-
-
+
@@ -37,7 +36,7 @@
-
+
@@ -51,10 +50,18 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
+
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 b1074e92cc949..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;
}
@@ -424,6 +435,11 @@ protected function _processGroup(
if (!isset($fieldData['value'])) {
$fieldData['value'] = null;
}
+
+ if ($field->getType() == 'multiline' && is_array($fieldData['value'])) {
+ $fieldData['value'] = trim(implode(PHP_EOL, $fieldData['value']));
+ }
+
$data = [
'field' => $fieldId,
'groups' => $groups,
@@ -520,24 +536,29 @@ public function setDataByPath($path, $value)
if ($path === '') {
throw new \UnexpectedValueException('Path must not be empty');
}
+
$pathParts = explode('/', $path);
$keyDepth = count($pathParts);
- if ($keyDepth !== 3) {
+ if ($keyDepth < 3) {
throw new \UnexpectedValueException(
- "Allowed depth of configuration is 3 (//). Your configuration depth is "
- . $keyDepth . " for path '$path'"
+ 'Minimal depth of configuration is 3. Your configuration depth is ' . $keyDepth
);
}
+
+ $section = array_shift($pathParts);
$data = [
- 'section' => $pathParts[0],
- 'groups' => [
- $pathParts[1] => [
- 'fields' => [
- $pathParts[2] => ['value' => $value],
- ],
- ],
+ 'fields' => [
+ array_pop($pathParts) => ['value' => $value],
],
];
+ while ($pathParts) {
+ $data = [
+ 'groups' => [
+ array_pop($pathParts) => $data,
+ ],
+ ];
+ }
+ $data['section'] = $section;
$this->addData($data);
}
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
index 1a7b641070ad8..4e9319351a130 100644
--- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
+++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
@@ -33,7 +33,7 @@
-
+
@@ -49,11 +49,11 @@
-
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php
index bdcb44b756bb2..66163e354cc06 100644
--- a/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php
+++ b/app/code/Magento/Config/Test/Unit/Model/ConfigTest.php
@@ -330,22 +330,59 @@ public function testSaveToCheckScopeDataSet()
$this->model->save();
}
- public function testSetDataByPath()
+ /**
+ * @param string $path
+ * @param string $value
+ * @param string $section
+ * @param array $groups
+ * @dataProvider setDataByPathDataProvider
+ */
+ public function testSetDataByPath(string $path, string $value, string $section, array $groups)
{
- $value = 'value';
- $path = '//';
$this->model->setDataByPath($path, $value);
- $expected = [
- 'section' => '',
- 'groups' => [
- '' => [
- 'fields' => [
- '' => ['value' => $value],
+ $this->assertEquals($section, $this->model->getData('section'));
+ $this->assertEquals($groups, $this->model->getData('groups'));
+ }
+
+ /**
+ * @return array
+ */
+ public function setDataByPathDataProvider(): array
+ {
+ return [
+ 'depth 3' => [
+ 'a/b/c',
+ 'value1',
+ 'a',
+ [
+ 'b' => [
+ 'fields' => [
+ 'c' => ['value' => 'value1'],
+ ],
+ ],
+ ],
+ ],
+ 'depth 5' => [
+ 'a/b/c/d/e',
+ 'value1',
+ 'a',
+ [
+ 'b' => [
+ 'groups' => [
+ 'c' => [
+ 'groups' => [
+ 'd' => [
+ 'fields' => [
+ 'e' => ['value' => 'value1'],
+ ],
+ ],
+ ],
+ ],
+ ],
],
],
],
];
- $this->assertSame($expected, $this->model->getData());
}
/**
@@ -359,14 +396,13 @@ public function testSetDataByPathEmpty()
/**
* @param string $path
- * @param string $expectedException
- *
* @dataProvider setDataByPathWrongDepthDataProvider
*/
- public function testSetDataByPathWrongDepth($path, $expectedException)
+ public function testSetDataByPathWrongDepth(string $path)
{
- $expectedException = 'Allowed depth of configuration is 3 (//). ' . $expectedException;
- $this->expectException('\UnexpectedValueException');
+ $currentDepth = count(explode('/', $path));
+ $expectedException = 'Minimal depth of configuration is 3. Your configuration depth is ' . $currentDepth;
+ $this->expectException(\UnexpectedValueException::class);
$this->expectExceptionMessage($expectedException);
$value = 'value';
$this->model->setDataByPath($path, $value);
@@ -375,13 +411,11 @@ public function testSetDataByPathWrongDepth($path, $expectedException)
/**
* @return array
*/
- public function setDataByPathWrongDepthDataProvider()
+ public function setDataByPathWrongDepthDataProvider(): array
{
return [
- 'depth 2' => ['section/group', "Your configuration depth is 2 for path 'section/group'"],
- 'depth 1' => ['section', "Your configuration depth is 1 for path 'section'"],
- 'depth 4' => ['section/group/field/sub-field', "Your configuration depth is 4 for path"
- . " 'section/group/field/sub-field'", ],
+ 'depth 2' => ['section/group'],
+ 'depth 1' => ['section'],
];
}
}
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/ConfigurableImportExport/etc/module.xml b/app/code/Magento/ConfigurableImportExport/etc/module.xml
index 7ff81f8d63443..b59234ca0e7da 100644
--- a/app/code/Magento/ConfigurableImportExport/etc/module.xml
+++ b/app/code/Magento/ConfigurableImportExport/etc/module.xml
@@ -6,6 +6,9 @@
*/
-->
-
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php
index bee334596e990..f2bf3116af9e4 100644
--- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php
+++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable/Price.php
@@ -7,14 +7,15 @@
*/
namespace Magento\ConfigurableProduct\Model\Product\Type\Configurable;
+use Magento\Catalog\Model\Product;
+
+/**
+ * Class Price for configurable product
+ */
class Price extends \Magento\Catalog\Model\Product\Type\Price
{
/**
- * Get product final price
- *
- * @param float $qty
- * @param \Magento\Catalog\Model\Product $product
- * @return float
+ * @inheritdoc
*/
public function getFinalPrice($qty, $product)
{
@@ -22,7 +23,10 @@ public function getFinalPrice($qty, $product)
return $product->getCalculatedFinalPrice();
}
if ($product->getCustomOption('simple_product') && $product->getCustomOption('simple_product')->getProduct()) {
- $finalPrice = parent::getFinalPrice($qty, $product->getCustomOption('simple_product')->getProduct());
+ /** @var Product $simpleProduct */
+ $simpleProduct = $product->getCustomOption('simple_product')->getProduct();
+ $simpleProduct->setCustomerGroupId($product->getCustomerGroupId());
+ $finalPrice = parent::getFinalPrice($qty, $simpleProduct);
} else {
$priceInfo = $product->getPriceInfo();
$finalPrice = $priceInfo->getPrice('final_price')->getAmount()->getValue();
@@ -35,7 +39,7 @@ public function getFinalPrice($qty, $product)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getPrice($product)
{
@@ -48,6 +52,7 @@ public function getPrice($product)
}
}
}
+
return 0;
}
}
diff --git a/app/code/Magento/ConfigurableProduct/Plugin/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php b/app/code/Magento/ConfigurableProduct/Plugin/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php
new file mode 100644
index 0000000000000..8bdde2aeb0cff
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Plugin/Tax/Model/Sales/Total/Quote/CommonTaxCollector.php
@@ -0,0 +1,44 @@
+getProduct()->getTypeId() === Configurable::TYPE_CODE && $item->getHasChildren()) {
+ $childItem = $item->getChildren()[0];
+ $result->getTaxClassKey()->setValue($childItem->getProduct()->getTaxClassId());
+ }
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml
index d2abfc7977519..43dae2d70d416 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminConfigurableProductActionGroup.xml
@@ -145,9 +145,135 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml
index 033e6757c3bf9..c0a9f03906030 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/AdminCreateApiConfigurableProductActionGroup.xml
@@ -62,4 +62,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
index 39c206e365a2d..e2759fc0fd23d 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontCategoryActionGroup.xml
@@ -21,4 +21,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
index 0a8d8e56426ba..51bb041b66089 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductActionGroup.xml
@@ -24,4 +24,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductAttributeActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductAttributeActionGroup.xml
new file mode 100644
index 0000000000000..7780827381533
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/StorefrontProductAttributeActionGroup.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
new file mode 100644
index 0000000000000..f3b0786236062
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml
index 9342172f7d4df..4c5f83ecebecf 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Data/ProductConfigurableAttributeData.xml
@@ -42,4 +42,22 @@
Black
5.00
+
+ Green
+ sku-green
+ simple
+ 1
+ 1
+ 1
+ 1
+
+
+ Red
+ sku-red
+ simple
+ 2
+ 1
+ 10
+ 1
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml
index 6e8303e6baead..78e4c7bced8e2 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminChooseAffectedAttributeSetSection.xml
@@ -10,6 +10,8 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
index 9b4798c95ec72..eccff2830d2a5 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminCreateProductConfigurationsPanelSection.xml
@@ -35,9 +35,18 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductDropdownOrderSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
new file mode 100644
index 0000000000000..6056e7f3cbd09
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml
index c5d6abd89edbf..d1b16cb8f5ce3 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/AdminProductFormConfigurationsSection.xml
@@ -10,6 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
+
@@ -20,13 +21,21 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index b195c19f7bedd..09de3cc302903 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
@@ -11,11 +11,13 @@
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAssertNoticeThatExistingSkuAutomaticallyChangedWhenSavingProductWithSameSkuTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAssertNoticeThatExistingSkuAutomaticallyChangedWhenSavingProductWithSameSkuTest.xml
new file mode 100644
index 0000000000000..68bf703ecdab4
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminAssertNoticeThatExistingSkuAutomaticallyChangedWhenSavingProductWithSameSkuTest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml
index 24af7d44e8261..2af85e1bac048 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminConfigurableProductCreateTest.xml
@@ -36,6 +36,7 @@
+
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/AdminCreateAndSwitchProductType.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
new file mode 100755
index 0000000000000..e79f7f75cce3f
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductBasedOnParentSkuTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductBasedOnParentSkuTest.xml
new file mode 100644
index 0000000000000..f4f607e9119b6
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductBasedOnParentSkuTest.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithCreatingCategoryAndAttributeTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithCreatingCategoryAndAttributeTest.xml
new file mode 100644
index 0000000000000..a7242b43c2b5f
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithCreatingCategoryAndAttributeTest.xml
@@ -0,0 +1,124 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithDisabledChildrenProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithDisabledChildrenProductsTest.xml
new file mode 100644
index 0000000000000..49f3f8b5ea931
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithDisabledChildrenProductsTest.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithImagesTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithImagesTest.xml
new file mode 100644
index 0000000000000..925e7a890cead
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithImagesTest.xml
@@ -0,0 +1,159 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDisplayOutOfStockProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDisplayOutOfStockProductsTest.xml
new file mode 100644
index 0000000000000..0b83fdc1788d3
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDisplayOutOfStockProductsTest.xml
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml
new file mode 100644
index 0000000000000..e24ac07c30d1e
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithThreeProductDontDisplayOutOfStockProductsTest.xml
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml
new file mode 100644
index 0000000000000..51f4bf0279942
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTierPriceForOneItemTest.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Buy {{tierProductPrice.quantity}} for ${{tierProductPrice.price}} each and save 27%
+ tierPriceText
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml
new file mode 100644
index 0000000000000..1db9b3e5b79b2
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsAssignedToCategoryTest.xml
@@ -0,0 +1,154 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml
new file mode 100644
index 0000000000000..934a410d58a8a
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateConfigurableProductWithTwoOptionsWithoutAssignedToCategoryTest.xml
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml
index 03e1d1b260ffd..456be43f80b8d 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/ProductsQtyReturnAfterOrderCancelTest.xml
@@ -76,7 +76,6 @@
-
diff --git a/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml
new file mode 100644
index 0000000000000..bb69122dc0be9
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/StorefrontVerifyConfigurableProductLayeredNavigationTest.xml
@@ -0,0 +1,149 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php
index 64b9b3776442a..ab52d4eb86021 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Model/Product/Type/Configurable/PriceTest.php
@@ -6,22 +6,47 @@
namespace Magento\ConfigurableProduct\Test\Unit\Model\Product\Type\Configurable;
+use Magento\Catalog\Model\Product;
+use Magento\Catalog\Model\Product\Configuration\Item\Option;
+use Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price as ConfigurablePrice;
+use Magento\Framework\Event\ManagerInterface;
+use Magento\Framework\Pricing\Amount\AmountInterface;
+use Magento\Framework\Pricing\Price\PriceInterface;
+use Magento\Framework\Pricing\PriceInfo\Base as PriceInfoBase;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use PHPUnit\Framework\MockObject\MockObject;
class PriceTest extends \PHPUnit\Framework\TestCase
{
- /** @var \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price */
+ /**
+ * @var ObjectManagerHelper
+ */
+ protected $objectManagerHelper;
+
+ /**
+ * @var ConfigurablePrice
+ */
protected $model;
- /** @var ObjectManagerHelper */
- protected $objectManagerHelper;
+ /**
+ * @var ManagerInterface|MockObject
+ */
+ private $eventManagerMock;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->objectManagerHelper = new ObjectManagerHelper($this);
+ $this->eventManagerMock = $this->createPartialMock(
+ ManagerInterface::class,
+ ['dispatch']
+ );
$this->model = $this->objectManagerHelper->getObject(
- \Magento\ConfigurableProduct\Model\Product\Type\Configurable\Price::class
+ ConfigurablePrice::class,
+ ['eventManager' => $this->eventManagerMock]
);
}
@@ -29,29 +54,29 @@ public function testGetFinalPrice()
{
$finalPrice = 10;
$qty = 1;
- $configurableProduct = $this->getMockBuilder(\Magento\Catalog\Model\Product::class)
- ->disableOriginalConstructor()
- ->setMethods(['getCustomOption', 'getPriceInfo', 'setFinalPrice', '__wakeUp'])
- ->getMock();
- $customOption = $this->getMockBuilder(\Magento\Catalog\Model\Product\Configuration\Item\Option::class)
+
+ /** @var Product|MockObject $configurableProduct */
+ $configurableProduct = $this->getMockBuilder(Product::class)
->disableOriginalConstructor()
- ->setMethods(['getProduct'])
+ ->setMethods(['getCustomOption', 'getPriceInfo', 'setFinalPrice'])
->getMock();
- $priceInfo = $this->getMockBuilder(\Magento\Framework\Pricing\PriceInfo\Base::class)
+ /** @var PriceInfoBase|MockObject $priceInfo */
+ $priceInfo = $this->getMockBuilder(PriceInfoBase::class)
->disableOriginalConstructor()
->setMethods(['getPrice'])
->getMock();
- $price = $this->getMockBuilder(\Magento\Framework\Pricing\Price\PriceInterface::class)
+ /** @var PriceInterface|MockObject $price */
+ $price = $this->getMockBuilder(PriceInterface::class)
->disableOriginalConstructor()
->getMock();
- $amount = $this->getMockBuilder(\Magento\Framework\Pricing\Amount\AmountInterface::class)
+ /** @var AmountInterface|MockObject $amount */
+ $amount = $this->getMockBuilder(AmountInterface::class)
->disableOriginalConstructor()
->getMock();
$configurableProduct->expects($this->any())
->method('getCustomOption')
->willReturnMap([['simple_product', false], ['option_ids', false]]);
- $customOption->expects($this->never())->method('getProduct');
$configurableProduct->expects($this->once())->method('getPriceInfo')->willReturn($priceInfo);
$priceInfo->expects($this->once())->method('getPrice')->with('final_price')->willReturn($price);
$price->expects($this->once())->method('getAmount')->willReturn($amount);
@@ -60,4 +85,60 @@ public function testGetFinalPrice()
$this->assertEquals($finalPrice, $this->model->getFinalPrice($qty, $configurableProduct));
}
+
+ public function testGetFinalPriceWithSimpleProduct()
+ {
+ $finalPrice = 10;
+ $qty = 1;
+ $customerGroupId = 1;
+
+ /** @var Product|MockObject $configurableProduct */
+ $configurableProduct = $this->createPartialMock(
+ Product::class,
+ ['getCustomOption', 'setFinalPrice', 'getCustomerGroupId']
+ );
+ /** @var Option|MockObject $customOption */
+ $customOption = $this->createPartialMock(
+ Option::class,
+ ['getProduct']
+ );
+ /** @var Product|MockObject $simpleProduct */
+ $simpleProduct = $this->createPartialMock(
+ Product::class,
+ ['setCustomerGroupId', 'setFinalPrice', 'getPrice', 'getTierPrice', 'getData', 'getCustomOption']
+ );
+
+ $configurableProduct->method('getCustomOption')
+ ->willReturnMap([
+ ['simple_product', $customOption],
+ ['option_ids', false]
+ ]);
+ $configurableProduct->method('getCustomerGroupId')->willReturn($customerGroupId);
+ $configurableProduct->expects($this->atLeastOnce())
+ ->method('setFinalPrice')
+ ->with($finalPrice)
+ ->willReturnSelf();
+ $customOption->method('getProduct')->willReturn($simpleProduct);
+ $simpleProduct->expects($this->atLeastOnce())
+ ->method('setCustomerGroupId')
+ ->with($customerGroupId)
+ ->willReturnSelf();
+ $simpleProduct->method('getPrice')->willReturn($finalPrice);
+ $simpleProduct->method('getTierPrice')->with($qty)->willReturn($finalPrice);
+ $simpleProduct->expects($this->atLeastOnce())
+ ->method('setFinalPrice')
+ ->with($finalPrice)
+ ->willReturnSelf();
+ $simpleProduct->method('getData')->with('final_price')->willReturn($finalPrice);
+ $simpleProduct->method('getCustomOption')->with('option_ids')->willReturn(false);
+ $this->eventManagerMock->expects($this->once())
+ ->method('dispatch')
+ ->with('catalog_product_get_final_price', ['product' => $simpleProduct, 'qty' => $qty]);
+
+ $this->assertEquals(
+ $finalPrice,
+ $this->model->getFinalPrice($qty, $configurableProduct),
+ 'The final price calculation is wrong'
+ );
+ }
}
diff --git a/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Tax/Model/Sales/Total/Quote/CommonTaxCollectorTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Tax/Model/Sales/Total/Quote/CommonTaxCollectorTest.php
new file mode 100644
index 0000000000000..1a5c6c0003bfa
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Plugin/Tax/Model/Sales/Total/Quote/CommonTaxCollectorTest.php
@@ -0,0 +1,94 @@
+objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+ $this->commonTaxCollectorPlugin = $this->objectManager->getObject(CommonTaxCollectorPlugin::class);
+ }
+
+ /**
+ * Test to apply Tax Class Id from child item for configurable product
+ */
+ public function testAfterMapItem()
+ {
+ $childTaxClassId = 10;
+
+ /** @var Product|MockObject $childProductMock */
+ $childProductMock = $this->createPartialMock(
+ Product::class,
+ ['getTaxClassId']
+ );
+ $childProductMock->method('getTaxClassId')->willReturn($childTaxClassId);
+ /* @var AbstractItem|MockObject $quoteItemMock */
+ $childQuoteItemMock = $this->createMock(
+ AbstractItem::class
+ );
+ $childQuoteItemMock->method('getProduct')->willReturn($childProductMock);
+
+ /** @var Product|MockObject $productMock */
+ $productMock = $this->createPartialMock(
+ Product::class,
+ ['getTypeId']
+ );
+ $productMock->method('getTypeId')->willReturn(Configurable::TYPE_CODE);
+ /* @var AbstractItem|MockObject $quoteItemMock */
+ $quoteItemMock = $this->createPartialMock(
+ AbstractItem::class,
+ ['getProduct', 'getHasChildren', 'getChildren', 'getQuote', 'getAddress', 'getOptionByCode']
+ );
+ $quoteItemMock->method('getProduct')->willReturn($productMock);
+ $quoteItemMock->method('getHasChildren')->willReturn(true);
+ $quoteItemMock->method('getChildren')->willReturn([$childQuoteItemMock]);
+
+ /* @var TaxClassKeyInterface|MockObject $taxClassObjectMock */
+ $taxClassObjectMock = $this->createMock(TaxClassKeyInterface::class);
+ $taxClassObjectMock->expects($this->once())->method('setValue')->with($childTaxClassId);
+
+ /* @var QuoteDetailsItemInterface|MockObject $quoteDetailsItemMock */
+ $quoteDetailsItemMock = $this->createMock(QuoteDetailsItemInterface::class);
+ $quoteDetailsItemMock->method('getTaxClassKey')->willReturn($taxClassObjectMock);
+
+ $this->commonTaxCollectorPlugin->afterMapItem(
+ $this->createMock(CommonTaxCollector::class),
+ $quoteDetailsItemMock,
+ $this->createMock(QuoteDetailsItemInterfaceFactory::class),
+ $quoteItemMock
+ );
+ }
+}
diff --git a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php
index fbab25ff1bea6..e0cc83922e03e 100644
--- a/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php
+++ b/app/code/Magento/ConfigurableProduct/Ui/DataProvider/Product/Form/Modifier/ConfigurablePanel.php
@@ -5,14 +5,14 @@
*/
namespace Magento\ConfigurableProduct\Ui\DataProvider\Product\Form\Modifier;
+use Magento\Catalog\Model\Locator\LocatorInterface;
use Magento\Catalog\Model\Product\Attribute\Backend\Sku;
use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
+use Magento\Framework\UrlInterface;
use Magento\Ui\Component\Container;
-use Magento\Ui\Component\Form;
use Magento\Ui\Component\DynamicRows;
+use Magento\Ui\Component\Form;
use Magento\Ui\Component\Modal;
-use Magento\Framework\UrlInterface;
-use Magento\Catalog\Model\Locator\LocatorInterface;
/**
* Data provider for Configurable panel
@@ -90,7 +90,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyData(array $data)
{
@@ -98,7 +98,7 @@ public function modifyData(array $data)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function modifyMeta(array $meta)
@@ -197,7 +197,7 @@ public function modifyMeta(array $meta)
'autoRender' => false,
'componentType' => 'insertListing',
'component' => 'Magento_ConfigurableProduct/js'
- .'/components/associated-product-insert-listing',
+ . '/components/associated-product-insert-listing',
'dataScope' => $this->associatedListingPrefix
. static::ASSOCIATED_PRODUCT_LISTING,
'externalProvider' => $this->associatedListingPrefix
@@ -328,14 +328,12 @@ protected function getButtonSet()
'component' => 'Magento_Ui/js/form/components/button',
'actions' => [
[
- 'targetName' =>
- $this->dataScopeName . '.configurableModal',
+ 'targetName' => $this->dataScopeName . '.configurableModal',
'actionName' => 'trigger',
'params' => ['active', true],
],
[
- 'targetName' =>
- $this->dataScopeName . '.configurableModal',
+ 'targetName' => $this->dataScopeName . '.configurableModal',
'actionName' => 'openModal',
],
],
@@ -471,8 +469,7 @@ protected function getRows()
'sku',
__('SKU'),
[
- 'validation' =>
- [
+ 'validation' => [
'required-entry' => true,
'max_text_length' => Sku::SKU_MAX_LENGTH,
],
@@ -577,6 +574,7 @@ protected function getColumn(
'dataType' => Form\Element\DataType\Text::NAME,
'dataScope' => $name,
'visibleIfCanEdit' => false,
+ 'labelVisible' => false,
'imports' => [
'visible' => '!${$.provider}:${$.parentScope}.canEdit'
],
@@ -595,6 +593,7 @@ protected function getColumn(
'component' => 'Magento_Ui/js/form/components/group',
'label' => $label,
'dataScope' => '',
+ 'showLabel' => false
];
$container['children'] = [
$name . '_edit' => $fieldEdit,
diff --git a/app/code/Magento/ConfigurableProduct/composer.json b/app/code/Magento/ConfigurableProduct/composer.json
index e795ea7cd3618..76096fd6bdf67 100644
--- a/app/code/Magento/ConfigurableProduct/composer.json
+++ b/app/code/Magento/ConfigurableProduct/composer.json
@@ -25,7 +25,8 @@
"magento/module-sales-rule": "*",
"magento/module-product-video": "*",
"magento/module-configurable-sample-data": "*",
- "magento/module-product-links-sample-data": "*"
+ "magento/module-product-links-sample-data": "*",
+ "magento/module-tax": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/ConfigurableProduct/etc/di.xml b/app/code/Magento/ConfigurableProduct/etc/di.xml
index 0ae9ffde66f43..c3ffe988b00d7 100644
--- a/app/code/Magento/ConfigurableProduct/etc/di.xml
+++ b/app/code/Magento/ConfigurableProduct/etc/di.xml
@@ -245,4 +245,14 @@
+
+
+
+
+
+
+ - configurable
+
+
+
diff --git a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js
index 6bbab77a3a0ab..b2ef35546eea8 100644
--- a/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js
+++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/web/js/components/price-configurable.js
@@ -11,9 +11,6 @@ define([
return Abstract.extend({
defaults: {
- listens: {
- isConfigurable: 'handlePriceValue'
- },
imports: {
isConfigurable: '!ns = ${ $.ns }, index = configurable-matrix:isEmpty'
},
@@ -22,12 +19,15 @@ define([
}
},
- /**
- * Invokes initialize method of parent class,
- * contains initialization logic
- */
+ /** @inheritdoc */
initialize: function () {
this._super();
+ // resolve initial disable state
+ this.handlePriceValue(this.isConfigurable);
+ // add listener to track "configurable" type
+ this.setListeners({
+ isConfigurable: 'handlePriceValue'
+ });
return this;
},
@@ -50,11 +50,10 @@ define([
* @param {String} isConfigurable
*/
handlePriceValue: function (isConfigurable) {
+ this.disabled(!!this.isUseDefault() || isConfigurable);
+
if (isConfigurable) {
- this.disable();
this.clear();
- } else {
- this.enable();
}
}
});
diff --git a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
index 9fda4ec0173ec..12571602878d1 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
+++ b/app/code/Magento/ConfigurableProductGraphQl/Model/Variant/Collection.php
@@ -90,14 +90,17 @@ public function __construct(
*/
public function addParentProduct(Product $product) : void
{
- if (isset($this->parentProducts[$product->getId()])) {
+ $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
+ $productId = $product->getData($linkField);
+
+ if (isset($this->parentProducts[$productId])) {
return;
}
if (!empty($this->childrenMap)) {
$this->childrenMap = [];
}
- $this->parentProducts[$product->getId()] = $product;
+ $this->parentProducts[$productId] = $product;
}
/**
@@ -140,16 +143,12 @@ private function fetch() : array
return $this->childrenMap;
}
- $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
foreach ($this->parentProducts as $product) {
$attributeData = $this->getAttributesCodes($product);
/** @var ChildCollection $childCollection */
$childCollection = $this->childCollectionFactory->create();
- $childCollection->addAttributeToSelect($attributeData);
-
- /** @var Product $product */
- $product->setData($linkField, $product->getId());
$childCollection->setProductFilter($product);
+ $childCollection->addAttributeToSelect($attributeData);
/** @var Product $childProduct */
foreach ($childCollection->getItems() as $childProduct) {
diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls
index df95632c4b606..d4780c5c0867a 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls
@@ -49,7 +49,7 @@ type AddConfigurableProductsToCartOutput {
}
input ConfigurableProductCartItemInput {
- data: CartItemDetailsInput!
+ data: CartItemInput!
variant_sku: String!
customizable_options:[CustomizableOptionInput!]
}
diff --git a/app/code/Magento/Cron/Model/Schedule.php b/app/code/Magento/Cron/Model/Schedule.php
index 200b0fd690882..582c7c811b71f 100644
--- a/app/code/Magento/Cron/Model/Schedule.php
+++ b/app/code/Magento/Cron/Model/Schedule.php
@@ -9,6 +9,7 @@
use Magento\Framework\Exception\CronException;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Framework\Intl\DateTimeFactory;
/**
* Crontab schedule model
@@ -50,13 +51,19 @@ class Schedule extends \Magento\Framework\Model\AbstractModel
*/
private $timezoneConverter;
+ /**
+ * @var DateTimeFactory
+ */
+ private $dateTimeFactory;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
- * @param TimezoneInterface $timezoneConverter
+ * @param TimezoneInterface|null $timezoneConverter
+ * @param DateTimeFactory|null $dateTimeFactory
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -64,10 +71,12 @@ public function __construct(
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
array $data = [],
- TimezoneInterface $timezoneConverter = null
+ TimezoneInterface $timezoneConverter = null,
+ DateTimeFactory $dateTimeFactory = null
) {
parent::__construct($context, $registry, $resource, $resourceCollection, $data);
$this->timezoneConverter = $timezoneConverter ?: ObjectManager::getInstance()->get(TimezoneInterface::class);
+ $this->dateTimeFactory = $dateTimeFactory ?: ObjectManager::getInstance()->get(DateTimeFactory::class);
}
/**
@@ -111,17 +120,20 @@ public function trySchedule()
if (!$e || !$time) {
return false;
}
+ $configTimeZone = $this->timezoneConverter->getConfigTimezone();
+ $storeDateTime = $this->dateTimeFactory->create(null, new \DateTimeZone($configTimeZone));
if (!is_numeric($time)) {
//convert time from UTC to admin store timezone
//we assume that all schedules in configuration (crontab.xml and DB tables) are in admin store timezone
- $time = $this->timezoneConverter->date($time)->format('Y-m-d H:i');
- $time = strtotime($time);
+ $dateTimeUtc = $this->dateTimeFactory->create($time);
+ $time = $dateTimeUtc->getTimestamp();
}
- $match = $this->matchCronExpression($e[0], strftime('%M', $time))
- && $this->matchCronExpression($e[1], strftime('%H', $time))
- && $this->matchCronExpression($e[2], strftime('%d', $time))
- && $this->matchCronExpression($e[3], strftime('%m', $time))
- && $this->matchCronExpression($e[4], strftime('%w', $time));
+ $time = $storeDateTime->setTimestamp($time);
+ $match = $this->matchCronExpression($e[0], $time->format('i'))
+ && $this->matchCronExpression($e[1], $time->format('H'))
+ && $this->matchCronExpression($e[2], $time->format('d'))
+ && $this->matchCronExpression($e[3], $time->format('m'))
+ && $this->matchCronExpression($e[4], $time->format('w'));
return $match;
}
diff --git a/app/code/Magento/Cron/Test/Unit/Model/ScheduleTest.php b/app/code/Magento/Cron/Test/Unit/Model/ScheduleTest.php
index e9f4c61c7f551..da5539859a4b5 100644
--- a/app/code/Magento/Cron/Test/Unit/Model/ScheduleTest.php
+++ b/app/code/Magento/Cron/Test/Unit/Model/ScheduleTest.php
@@ -6,6 +6,9 @@
namespace Magento\Cron\Test\Unit\Model;
use Magento\Cron\Model\Schedule;
+use Magento\Framework\Intl\DateTimeFactory;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
/**
* Class \Magento\Cron\Test\Unit\Model\ObserverTest
@@ -18,11 +21,27 @@ class ScheduleTest extends \PHPUnit\Framework\TestCase
*/
protected $helper;
+ /**
+ * @var \Magento\Cron\Model\ResourceModel\Schedule
+ */
protected $resourceJobMock;
+ /**
+ * @var TimezoneInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $timezoneConverter;
+
+ /**
+ * @var DateTimeFactory|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $dateTimeFactory;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
- $this->helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
+ $this->helper = new ObjectManager($this);
$this->resourceJobMock = $this->getMockBuilder(\Magento\Cron\Model\ResourceModel\Schedule::class)
->disableOriginalConstructor()
@@ -32,18 +51,30 @@ protected function setUp()
$this->resourceJobMock->expects($this->any())
->method('getIdFieldName')
->will($this->returnValue('id'));
+
+ $this->timezoneConverter = $this->getMockBuilder(TimezoneInterface::class)
+ ->setMethods(['date'])
+ ->getMockForAbstractClass();
+
+ $this->dateTimeFactory = $this->getMockBuilder(DateTimeFactory::class)
+ ->setMethods(['create'])
+ ->getMock();
}
/**
+ * Test for SetCronExpr
+ *
* @param string $cronExpression
* @param array $expected
+ *
+ * @return void
* @dataProvider setCronExprDataProvider
*/
- public function testSetCronExpr($cronExpression, $expected)
+ public function testSetCronExpr($cronExpression, $expected): void
{
// 1. Create mocks
- /** @var \Magento\Cron\Model\Schedule $model */
- $model = $this->helper->getObject(\Magento\Cron\Model\Schedule::class);
+ /** @var Schedule $model */
+ $model = $this->helper->getObject(Schedule::class);
// 2. Run tested method
$model->setCronExpr($cronExpression);
@@ -61,7 +92,7 @@ public function testSetCronExpr($cronExpression, $expected)
*
* @return array
*/
- public function setCronExprDataProvider()
+ public function setCronExprDataProvider(): array
{
return [
['1 2 3 4 5', [1, 2, 3, 4, 5]],
@@ -121,27 +152,33 @@ public function setCronExprDataProvider()
}
/**
+ * Test for SetCronExprException
+ *
* @param string $cronExpression
+ *
+ * @return void
* @expectedException \Magento\Framework\Exception\CronException
* @dataProvider setCronExprExceptionDataProvider
*/
- public function testSetCronExprException($cronExpression)
+ public function testSetCronExprException($cronExpression): void
{
// 1. Create mocks
- /** @var \Magento\Cron\Model\Schedule $model */
- $model = $this->helper->getObject(\Magento\Cron\Model\Schedule::class);
+ /** @var Schedule $model */
+ $model = $this->helper->getObject(Schedule::class);
// 2. Run tested method
$model->setCronExpr($cronExpression);
}
/**
+ * Data provider
+ *
* Here is a list of allowed characters and values for Cron expression
* http://docs.oracle.com/cd/E12058_01/doc/doc.1014/e12030/cron_expressions.htm
*
* @return array
*/
- public function setCronExprExceptionDataProvider()
+ public function setCronExprExceptionDataProvider(): array
{
return [
[''],
@@ -153,17 +190,31 @@ public function setCronExprExceptionDataProvider()
}
/**
+ * Test for trySchedule
+ *
* @param int $scheduledAt
* @param array $cronExprArr
* @param $expected
+ *
+ * @return void
* @dataProvider tryScheduleDataProvider
*/
- public function testTrySchedule($scheduledAt, $cronExprArr, $expected)
+ public function testTrySchedule($scheduledAt, $cronExprArr, $expected): void
{
// 1. Create mocks
+ $this->timezoneConverter->method('getConfigTimezone')
+ ->willReturn('UTC');
+
+ $this->dateTimeFactory->method('create')
+ ->willReturn(new \DateTime());
+
/** @var \Magento\Cron\Model\Schedule $model */
$model = $this->helper->getObject(
- \Magento\Cron\Model\Schedule::class
+ \Magento\Cron\Model\Schedule::class,
+ [
+ 'timezoneConverter' => $this->timezoneConverter,
+ 'dateTimeFactory' => $this->dateTimeFactory,
+ ]
);
// 2. Set fixtures
@@ -177,22 +228,29 @@ public function testTrySchedule($scheduledAt, $cronExprArr, $expected)
$this->assertEquals($expected, $result);
}
- public function testTryScheduleWithConversionToAdminStoreTime()
+ /**
+ * Test for tryScheduleWithConversionToAdminStoreTime
+ *
+ * @return void
+ */
+ public function testTryScheduleWithConversionToAdminStoreTime(): void
{
$scheduledAt = '2011-12-13 14:15:16';
$cronExprArr = ['*', '*', '*', '*', '*'];
- // 1. Create mocks
- $timezoneConverter = $this->createMock(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class);
- $timezoneConverter->expects($this->once())
- ->method('date')
- ->with($scheduledAt)
- ->willReturn(new \DateTime($scheduledAt));
+ $this->timezoneConverter->method('getConfigTimezone')
+ ->willReturn('UTC');
+
+ $this->dateTimeFactory->method('create')
+ ->willReturn(new \DateTime());
/** @var \Magento\Cron\Model\Schedule $model */
$model = $this->helper->getObject(
\Magento\Cron\Model\Schedule::class,
- ['timezoneConverter' => $timezoneConverter]
+ [
+ 'timezoneConverter' => $this->timezoneConverter,
+ 'dateTimeFactory' => $this->dateTimeFactory,
+ ]
);
// 2. Set fixtures
@@ -207,11 +265,15 @@ public function testTryScheduleWithConversionToAdminStoreTime()
}
/**
+ * Data provider
+ *
* @return array
*/
- public function tryScheduleDataProvider()
+ public function tryScheduleDataProvider(): array
{
$date = '2011-12-13 14:15:16';
+ $timestamp = (new \DateTime($date))->getTimestamp();
+ $day = 'Monday';
return [
[$date, [], false],
[$date, null, false],
@@ -219,22 +281,26 @@ public function tryScheduleDataProvider()
[$date, [], false],
[$date, null, false],
[$date, false, false],
- [strtotime($date), ['*', '*', '*', '*', '*'], true],
- [strtotime($date), ['15', '*', '*', '*', '*'], true],
- [strtotime($date), ['*', '14', '*', '*', '*'], true],
- [strtotime($date), ['*', '*', '13', '*', '*'], true],
- [strtotime($date), ['*', '*', '*', '12', '*'], true],
- [strtotime('Monday'), ['*', '*', '*', '*', '1'], true],
+ [$timestamp, ['*', '*', '*', '*', '*'], true],
+ [$timestamp, ['15', '*', '*', '*', '*'], true],
+ [$timestamp, ['*', '14', '*', '*', '*'], true],
+ [$timestamp, ['*', '*', '13', '*', '*'], true],
+ [$timestamp, ['*', '*', '*', '12', '*'], true],
+ [(new \DateTime($day))->getTimestamp(), ['*', '*', '*', '*', '1'], true],
];
}
/**
+ * Test for matchCronExpression
+ *
* @param string $cronExpressionPart
* @param int $dateTimePart
* @param bool $expectedResult
+ *
+ * @return void
* @dataProvider matchCronExpressionDataProvider
*/
- public function testMatchCronExpression($cronExpressionPart, $dateTimePart, $expectedResult)
+ public function testMatchCronExpression($cronExpressionPart, $dateTimePart, $expectedResult): void
{
// 1. Create mocks
/** @var \Magento\Cron\Model\Schedule $model */
@@ -248,9 +314,11 @@ public function testMatchCronExpression($cronExpressionPart, $dateTimePart, $exp
}
/**
+ * Data provider
+ *
* @return array
*/
- public function matchCronExpressionDataProvider()
+ public function matchCronExpressionDataProvider(): array
{
return [
['*', 0, true],
@@ -287,11 +355,15 @@ public function matchCronExpressionDataProvider()
}
/**
+ * Test for matchCronExpressionException
+ *
* @param string $cronExpressionPart
+ *
+ * @return void
* @expectedException \Magento\Framework\Exception\CronException
* @dataProvider matchCronExpressionExceptionDataProvider
*/
- public function testMatchCronExpressionException($cronExpressionPart)
+ public function testMatchCronExpressionException($cronExpressionPart): void
{
$dateTimePart = 10;
@@ -304,9 +376,11 @@ public function testMatchCronExpressionException($cronExpressionPart)
}
/**
+ * Data provider
+ *
* @return array
*/
- public function matchCronExpressionExceptionDataProvider()
+ public function matchCronExpressionExceptionDataProvider(): array
{
return [
['1/2/3'], //Invalid cron expression, expecting 'match/modulus': 1/2/3
@@ -317,11 +391,15 @@ public function matchCronExpressionExceptionDataProvider()
}
/**
+ * Test for GetNumeric
+ *
* @param mixed $param
* @param int $expectedResult
+ *
+ * @return void
* @dataProvider getNumericDataProvider
*/
- public function testGetNumeric($param, $expectedResult)
+ public function testGetNumeric($param, $expectedResult): void
{
// 1. Create mocks
/** @var \Magento\Cron\Model\Schedule $model */
@@ -335,9 +413,11 @@ public function testGetNumeric($param, $expectedResult)
}
/**
+ * Data provider
+ *
* @return array
*/
- public function getNumericDataProvider()
+ public function getNumericDataProvider(): array
{
return [
[null, false],
@@ -362,7 +442,12 @@ public function getNumericDataProvider()
];
}
- public function testTryLockJobSuccess()
+ /**
+ * Test for tryLockJobSuccess
+ *
+ * @return void
+ */
+ public function testTryLockJobSuccess(): void
{
$scheduleId = 1;
@@ -386,7 +471,12 @@ public function testTryLockJobSuccess()
$this->assertEquals(Schedule::STATUS_RUNNING, $model->getStatus());
}
- public function testTryLockJobFailure()
+ /**
+ * Test for tryLockJobFailure
+ *
+ * @return void
+ */
+ public function testTryLockJobFailure(): void
{
$scheduleId = 1;
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/Block/Adminhtml/Edit/Tab/Orders.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
index f2b8133e352ad..2fb59ec767e8a 100644
--- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
+++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/Orders.php
@@ -63,7 +63,8 @@ protected function _construct()
{
parent::_construct();
$this->setId('customer_orders_grid');
- $this->setDefaultSort('created_at', 'desc');
+ $this->setDefaultSort('created_at');
+ $this->setDefaultDir('desc');
$this->setUseAjax(true);
}
diff --git a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
index 1bc6bb1da3680..e63c00ba18d29 100644
--- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
+++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Tab/View/Cart.php
@@ -77,7 +77,8 @@ protected function _construct()
{
parent::_construct();
$this->setId('customer_view_cart_grid');
- $this->setDefaultSort('added_at', 'desc');
+ $this->setDefaultSort('added_at');
+ $this->setDefaultDir('desc');
$this->setSortable(false);
$this->setPagerVisibility(false);
$this->setFilterVisibility(false);
diff --git a/app/code/Magento/Customer/Block/Form/Login.php b/app/code/Magento/Customer/Block/Form/Login.php
index 7b265ae1f0f32..d3d3306a49b44 100644
--- a/app/code/Magento/Customer/Block/Form/Login.php
+++ b/app/code/Magento/Customer/Block/Form/Login.php
@@ -47,15 +47,6 @@ public function __construct(
$this->_customerSession = $customerSession;
}
- /**
- * @return $this
- */
- protected function _prepareLayout()
- {
- $this->pageConfig->getTitle()->set(__('Customer Login'));
- return parent::_prepareLayout();
- }
-
/**
* Retrieve form posting url
*
diff --git a/app/code/Magento/Customer/Block/Form/Register.php b/app/code/Magento/Customer/Block/Form/Register.php
index 322dd2cbfe915..59966768a2eda 100644
--- a/app/code/Magento/Customer/Block/Form/Register.php
+++ b/app/code/Magento/Customer/Block/Form/Register.php
@@ -86,17 +86,6 @@ public function getConfig($path)
return $this->_scopeConfig->getValue($path, \Magento\Store\Model\ScopeInterface::SCOPE_STORE);
}
- /**
- * Prepare layout
- *
- * @return $this
- */
- protected function _prepareLayout()
- {
- $this->pageConfig->getTitle()->set(__('Create New Customer Account'));
- return parent::_prepareLayout();
- }
-
/**
* Retrieve form posting url
*
diff --git a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
index cb0343f4ec43b..38ed688a835bc 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Save.php
@@ -289,11 +289,9 @@ protected function _extractCustomerAddressData(array & $extractedCustomerData)
public function execute()
{
$returnToEdit = false;
- $originalRequestData = $this->getRequest()->getPostValue();
-
$customerId = $this->getCurrentCustomerId();
- if ($originalRequestData) {
+ if ($this->getRequest()->getPostValue()) {
try {
// optional fields might be set in request for future processing by observers in other modules
$customerData = $this->_extractCustomerData();
@@ -364,7 +362,7 @@ public function execute()
$messages = $exception->getMessage();
}
$this->_addSessionErrorMessages($messages);
- $this->_getSession()->setCustomerFormData($originalRequestData);
+ $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData());
$returnToEdit = true;
} catch (\Magento\Framework\Exception\AbstractAggregateException $exception) {
$errors = $exception->getErrors();
@@ -373,18 +371,19 @@ public function execute()
$messages[] = $error->getMessage();
}
$this->_addSessionErrorMessages($messages);
- $this->_getSession()->setCustomerFormData($originalRequestData);
+ $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData());
$returnToEdit = true;
} catch (LocalizedException $exception) {
$this->_addSessionErrorMessages($exception->getMessage());
- $this->_getSession()->setCustomerFormData($originalRequestData);
+ $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData());
$returnToEdit = true;
} catch (\Exception $exception) {
$this->messageManager->addException($exception, __('Something went wrong while saving the customer.'));
- $this->_getSession()->setCustomerFormData($originalRequestData);
+ $this->_getSession()->setCustomerFormData($this->retrieveFormattedFormData());
$returnToEdit = true;
}
}
+
$resultRedirect = $this->resultRedirectFactory->create();
if ($returnToEdit) {
if ($customerId) {
@@ -489,4 +488,29 @@ private function disableAddressValidation($customer)
$addressModel->setShouldIgnoreValidation(true);
}
}
+
+ /**
+ * Retrieve formatted form data
+ *
+ * @return array
+ */
+ private function retrieveFormattedFormData(): array
+ {
+ $originalRequestData = $this->getRequest()->getPostValue();
+
+ /* Customer data filtration */
+ if (isset($originalRequestData['customer'])) {
+ $customerData = $this->_extractData(
+ 'adminhtml_customer',
+ CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
+ [],
+ 'customer'
+ );
+
+ $customerData = array_intersect_key($customerData, $originalRequestData['customer']);
+ $originalRequestData['customer'] = array_merge($originalRequestData['customer'], $customerData);
+ }
+
+ return $originalRequestData;
+ }
}
diff --git a/app/code/Magento/Customer/CustomerData/Plugin/SessionChecker.php b/app/code/Magento/Customer/CustomerData/Plugin/SessionChecker.php
index aa73e275ee0ca..f82a4d15ae8bf 100644
--- a/app/code/Magento/Customer/CustomerData/Plugin/SessionChecker.php
+++ b/app/code/Magento/Customer/CustomerData/Plugin/SessionChecker.php
@@ -5,10 +5,13 @@
*/
namespace Magento\Customer\CustomerData\Plugin;
-use Magento\Framework\Session\SessionManager;
+use Magento\Framework\Session\SessionManagerInterface;
use Magento\Framework\Stdlib\Cookie\CookieMetadataFactory;
use Magento\Framework\Stdlib\Cookie\PhpCookieManager;
+/**
+ * Class SessionChecker
+ */
class SessionChecker
{
/**
@@ -36,10 +39,12 @@ public function __construct(
/**
* Delete frontend session cookie if customer session is expired
*
- * @param SessionManager $sessionManager
+ * @param SessionManagerInterface $sessionManager
* @return void
+ * @throws \Magento\Framework\Exception\InputException
+ * @throws \Magento\Framework\Stdlib\Cookie\FailureToSendException
*/
- public function beforeStart(SessionManager $sessionManager)
+ public function beforeStart(SessionManagerInterface $sessionManager)
{
if (!$this->cookieManager->getCookie($sessionManager->getName())
&& $this->cookieManager->getCookie('mage-cache-sessid')
diff --git a/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php b/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php
index 380b8a4d3446f..b1602e8ca1939 100644
--- a/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php
+++ b/app/code/Magento/Customer/Model/Attribute/Data/Postcode.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Customer\Model\Attribute\Data;
use Magento\Directory\Helper\Data as DirectoryHelper;
@@ -13,7 +14,8 @@
use Magento\Framework\Stdlib\DateTime\TimezoneInterface as MagentoTimezone;
/**
- * Customer Address Postal/Zip Code Attribute Data Model
+ * Customer Address Postal/Zip Code Attribute Data Model.
+ *
* This Data Model Has to Be Set Up in additional EAV attribute table
*/
class Postcode extends \Magento\Eav\Model\Attribute\Data\AbstractData
@@ -40,7 +42,8 @@ public function __construct(
}
/**
- * Validate postal/zip code
+ * Validate postal/zip code.
+ *
* Return true and skip validation if country zip code is optional
*
* @param array|string $value
@@ -104,7 +107,7 @@ public function restoreValue($value)
}
/**
- * Return formated attribute value from entity model
+ * Return formatted attribute value from entity model
*
* @param string $format
* @return string|array
diff --git a/app/code/Magento/Customer/Model/Options.php b/app/code/Magento/Customer/Model/Options.php
index 7747e309d82a6..71e70f8e14208 100644
--- a/app/code/Magento/Customer/Model/Options.php
+++ b/app/code/Magento/Customer/Model/Options.php
@@ -8,7 +8,11 @@
use Magento\Config\Model\Config\Source\Nooptreq as NooptreqSource;
use Magento\Customer\Helper\Address as AddressHelper;
use Magento\Framework\Escaper;
+use Magento\Store\Api\Data\StoreInterface;
+/**
+ * Customer Options.
+ */
class Options
{
/**
@@ -38,7 +42,7 @@ public function __construct(
/**
* Retrieve name prefix dropdown options
*
- * @param null $store
+ * @param null|string|bool|int|StoreInterface $store
* @return array|bool
*/
public function getNamePrefixOptions($store = null)
@@ -52,7 +56,7 @@ public function getNamePrefixOptions($store = null)
/**
* Retrieve name suffix dropdown options
*
- * @param null $store
+ * @param null|string|bool|int|StoreInterface $store
* @return array|bool
*/
public function getNameSuffixOptions($store = null)
@@ -64,7 +68,9 @@ public function getNameSuffixOptions($store = null)
}
/**
- * @param $options
+ * Unserialize and clear name prefix or suffix options.
+ *
+ * @param string $options
* @param bool $isOptional
* @return array|bool
*
@@ -78,6 +84,7 @@ protected function _prepareNamePrefixSuffixOptions($options, $isOptional = false
/**
* Unserialize and clear name prefix or suffix options
+ *
* If field is optional, add an empty first option.
*
* @param string $options
@@ -91,7 +98,7 @@ private function prepareNamePrefixSuffixOptions($options, $isOptional = false)
return false;
}
$result = [];
- $options = explode(';', $options);
+ $options = array_filter(explode(';', $options));
foreach ($options as $value) {
$value = $this->escaper->escapeHtml(trim($value));
$result[$value] = $value;
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Customer/Collection.php b/app/code/Magento/Customer/Model/ResourceModel/Customer/Collection.php
index af8980a129d3e..394a0d3ed556d 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Customer/Collection.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Customer/Collection.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Customer\Model\ResourceModel\Customer;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
* Customers collection
*
@@ -43,6 +45,7 @@ class Collection extends \Magento\Eav\Model\Entity\Collection\VersionControl\Abs
* @param \Magento\Framework\DB\Adapter\AdapterInterface $connection
* @param string $modelName
*
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -58,7 +61,8 @@ public function __construct(
\Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot,
\Magento\Framework\DataObject\Copy\Config $fieldsetConfig,
\Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
- $modelName = self::CUSTOMER_MODEL_NAME
+ $modelName = self::CUSTOMER_MODEL_NAME,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->_fieldsetConfig = $fieldsetConfig;
$this->_modelName = $modelName;
@@ -73,7 +77,8 @@ public function __construct(
$resourceHelper,
$universalFactory,
$entitySnapshot,
- $connection
+ $connection,
+ $resourceModelPool
);
}
diff --git a/app/code/Magento/Customer/Model/Visitor.php b/app/code/Magento/Customer/Model/Visitor.php
index 9caa2988c5a94..4f129f05aa82c 100644
--- a/app/code/Magento/Customer/Model/Visitor.php
+++ b/app/code/Magento/Customer/Model/Visitor.php
@@ -14,6 +14,7 @@
*
* @package Magento\Customer\Model
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class Visitor extends \Magento\Framework\Model\AbstractModel
{
@@ -168,10 +169,6 @@ public function initByRequest($observer)
$this->setLastVisitAt((new \DateTime())->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT));
- // prevent saving Visitor for safe methods, e.g. GET request
- if ($this->requestSafety->isSafeMethod()) {
- return $this;
- }
if (!$this->getId()) {
$this->setSessionId($this->session->getSessionId());
$this->save();
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertAddressInCustomersAddressGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertAddressInCustomersAddressGridActionGroup.xml
new file mode 100644
index 0000000000000..53dba774d6c43
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertAddressInCustomersAddressGridActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerAccountInformationActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerAccountInformationActionGroup.xml
new file mode 100644
index 0000000000000..a908d042fcc59
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerAccountInformationActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultBillingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultBillingAddressActionGroup.xml
new file mode 100644
index 0000000000000..32b624706102f
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultBillingAddressActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultShippingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultShippingAddressActionGroup.xml
new file mode 100644
index 0000000000000..9d7c209121fd6
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerDefaultShippingAddressActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerInCustomersGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerInCustomersGridActionGroup.xml
new file mode 100644
index 0000000000000..d7529b3bdd58e
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerInCustomersGridActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultBillingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultBillingAddressActionGroup.xml
new file mode 100644
index 0000000000000..5557025c4b1de
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultBillingAddressActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultShippingAddressActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultShippingAddressActionGroup.xml
new file mode 100644
index 0000000000000..e33ebbb96ee19
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertCustomerNoDefaultShippingAddressActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertNumberOfRecordsInCustomersAddressGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertNumberOfRecordsInCustomersAddressGridActionGroup.xml
new file mode 100644
index 0000000000000..390f723d91f17
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminAssertNumberOfRecordsInCustomersAddressGridActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml
new file mode 100644
index 0000000000000..86039056999b0
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerGridActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerSaveAndContinueActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerSaveAndContinueActionGroup.xml
new file mode 100644
index 0000000000000..03b950a6dbe6f
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminCustomerSaveAndContinueActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteAddressInCustomersAddressGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteAddressInCustomersAddressGridActionGroup.xml
new file mode 100644
index 0000000000000..b61b353714e19
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteAddressInCustomersAddressGridActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml
index 2609f0ab5c0d6..788e5f8967f43 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminDeleteCustomerGroupActionGroup.xml
@@ -18,8 +18,8 @@
-
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressNoZipNoStateActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressNoZipNoStateActionGroup.xml
new file mode 100644
index 0000000000000..954b83bead1d3
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressNoZipNoStateActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressSetDefaultShippingAndBillingActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressSetDefaultShippingAndBillingActionGroup.xml
new file mode 100644
index 0000000000000..0c1af1cb5b67c
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressSetDefaultShippingAndBillingActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml
new file mode 100644
index 0000000000000..594337c1a6922
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerAddressesFromActionGroup.xml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml
new file mode 100644
index 0000000000000..ddeefeb3c3742
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminEditCustomerInformationFromActionGroup.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerAddressGridByPhoneNumberActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerAddressGridByPhoneNumberActionGroup.xml
new file mode 100644
index 0000000000000..2d0d44a4cc529
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerAddressGridByPhoneNumberActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerByNameActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerByNameActionGroup.xml
new file mode 100644
index 0000000000000..c49a0dbe20ae7
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerByNameActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGridByEmailActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGridByEmailActionGroup.xml
new file mode 100644
index 0000000000000..9cab8a790ff58
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminFilterCustomerGridByEmailActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerAddressGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerAddressGridActionGroup.xml
new file mode 100644
index 0000000000000..135f010784199
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerAddressGridActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerGridActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerGridActionGroup.xml
new file mode 100644
index 0000000000000..5c6ff347d565a
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminResetFilterInCustomerGridActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAndAssertSuccessMessageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAndAssertSuccessMessageActionGroup.xml
new file mode 100644
index 0000000000000..d3907e96b0d77
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSaveCustomerAndAssertSuccessMessageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectAllCustomersActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectAllCustomersActionGroup.xml
new file mode 100644
index 0000000000000..1a8b4da67e74a
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectAllCustomersActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectCustomerByEmailActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectCustomerByEmailActionGroup.xml
new file mode 100644
index 0000000000000..bb84d578fd9ed
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AdminSelectCustomerByEmailActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAuthorizationPopUpPasswordAutoCompleteOffActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAuthorizationPopUpPasswordAutoCompleteOffActionGroup.xml
new file mode 100644
index 0000000000000..186d0244e8c71
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertAuthorizationPopUpPasswordAutoCompleteOffActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
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/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml
new file mode 100644
index 0000000000000..23a067cd94eea
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStoreFrontPasswordAutocompleteOffActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontPasswordAutocompleteOffActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontPasswordAutocompleteOffActionGroup.xml
new file mode 100644
index 0000000000000..23a067cd94eea
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertStorefrontPasswordAutocompleteOffActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
index 4d531214db150..06659dae156a4 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/DeleteCustomerActionGroup.xml
@@ -24,4 +24,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
index 7be36ffbd9bc4..703b9f542f81a 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontActionGroup.xml
@@ -11,9 +11,12 @@
-
-
-
-
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontWithEmailAndPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontWithEmailAndPasswordActionGroup.xml
new file mode 100644
index 0000000000000..071450001051e
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/LoginToStorefrontWithEmailAndPasswordActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml
new file mode 100644
index 0000000000000..be639d245f022
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml
new file mode 100644
index 0000000000000..076797f349107
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/NavigateCustomerGroupActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
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/OpenEditCustomerFromAdminActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
index af918e8208566..208f4f51e38e6 100755
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/OpenEditCustomerFromAdminActionGroup.xml
@@ -12,13 +12,14 @@
-
+
+
-
+
-
+
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/ActionGroup/SetGroupCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SetGroupCustomerActionGroup.xml
new file mode 100644
index 0000000000000..ca5e16c4ddb40
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SetGroupCustomerActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
index 76acf6e865963..ef956293d367b 100644
--- a/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/SignUpNewUserFromStorefrontActionGroup.xml
@@ -24,7 +24,35 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -39,15 +67,91 @@
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml
new file mode 100644
index 0000000000000..b12858fc1037e
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StoreFrontClickSignInButtonActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertSuccessLoginToStorefrontActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertSuccessLoginToStorefrontActionGroup.xml
new file mode 100644
index 0000000000000..475702ad69221
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontAssertSuccessLoginToStorefrontActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignInButtonActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignInButtonActionGroup.xml
new file mode 100644
index 0000000000000..b12858fc1037e
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignInButtonActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookContainsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookContainsActionGroup.xml
new file mode 100644
index 0000000000000..8385dc17ecf98
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookContainsActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNotContainsActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNotContainsActionGroup.xml
new file mode 100644
index 0000000000000..afef2d9a04e34
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNotContainsActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNumberOfAddressesActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNumberOfAddressesActionGroup.xml
new file mode 100644
index 0000000000000..febc482d62e8b
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerAddressBookNumberOfAddressesActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerGoToSidebarMenuActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerGoToSidebarMenuActionGroup.xml
new file mode 100644
index 0000000000000..84d2f353b51d2
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerGoToSidebarMenuActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/VerifyGroupCustomerActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/VerifyGroupCustomerActionGroup.xml
new file mode 100644
index 0000000000000..712d3a59a2144
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/VerifyGroupCustomerActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
index da36cf722325e..5d1a900167144 100755
--- a/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/AddressData.xml
@@ -191,4 +191,84 @@
true
false
+
+ United Kingdom
+
+ - 3962 Horner Street
+
+ Magento
+ 334-200-4061
+ London
+ Fn
+ Ln
+ Mn
+ Mr
+ Sr
+ U1234567891
+ true
+ true
+
+
+ John
+ Doe
+ Magento
+ 0123456789-02134567
+
+ - 172, Westminster Bridge Rd
+ - 7700 xyz street
+
+ GB
+ United Kingdom
+ London
+
+
+ 12345
+
+
+ Jaen
+ Reno
+ Magento
+ 555-888-111-999
+
+ - 18-20 Rue Maréchal Lecler
+ - 18-20 Rue Maréchal Lecler
+
+ FR
+ France
+ Quintin
+ Côtes-d'Armor
+ 12345
+
+
+ Jany
+ Doe
+ Magento
+ 555-888-111-999
+
+ - 7700 West Parmer Lane
+ - 7700 West Parmer Lane
+
+ US
+ United States
+ Denver
+ Colorado
+ 12345
+
+
+ Mag
+ Ento
+ Magento
+
+ - Piwowarska 6
+
+ Bielsko-Biała
+ Bielsko
+ PL
+ Poland
+ 43-310
+ 799885616
+ Yes
+ Yes
+ RegionUT
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
index b3c0d8d9e0047..06c23a2864984 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerData.xml
@@ -21,6 +21,7 @@
John
Doe
S
+ John Doe
pwdTest123!
Mr
Sr
@@ -45,6 +46,19 @@
0
US_Address_TX
+
+ 1
+ true
+ true
+ John.Doe@example.com
+ LoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsumLoremIpsum
+ Doe
+ John Doe
+ pwdTest123!
+ 0
+ 0
+ US_Address_TX
+
1
John.Doe@example.com
@@ -178,4 +192,15 @@
US_Default_Billing_Address_TX
US_Default_Shipping_Address_CA
+
+ 1
+ true
+ true
+ Patric.Patric@example.com
+ Patrick</title></head><svg/onload=alert('XSS')>
+ <script>alert('Last name')</script>
+ 123123^q
+ 0
+ 0
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml
index c1f11c9e9c390..28305d37cf77b 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerGroupData.xml
@@ -8,14 +8,30 @@
+
+ 0
+ NOT LOGGED IN
+ 3
+ Retail Customer
+
General
3
Retail Customer
+
+ Group_
+ 3
+ Retail Customer
+
- General
+
+ Group
+ 3
+ Retail Customer
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_group-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_group-meta.xml
new file mode 100644
index 0000000000000..3139ea278a0dd
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_group-meta.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+ application/json
+
+ string
+ integer
+ string
+
+
+
+ application/json
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAccountChangePasswordPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAccountChangePasswordPage.xml
new file mode 100644
index 0000000000000..43198297b1731
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerAccountChangePasswordPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignInPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignInPage.xml
index 0d4fef8f6e967..b4814a3e4bedd 100644
--- a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignInPage.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerSignInPage.xml
@@ -10,5 +10,6 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/PageObject.xsd">
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Page/StorefrontForgotPasswordPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontForgotPasswordPage.xml
new file mode 100644
index 0000000000000..2633a0c760cec
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontForgotPasswordPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml
index 6a3687bb77c8f..71e3e673477d2 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAccountInformationSection.xml
@@ -9,7 +9,7 @@
-
+
@@ -18,10 +18,18 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml
index b9a3839ff9894..f3df6cc5e8c00 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressFiltersSection.xml
@@ -20,5 +20,7 @@
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridSection.xml
index fb153a7c102a5..e639fca834b2b 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressGridSection.xml
@@ -12,6 +12,5 @@
-
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml
index d8d93814333ca..e743c4af66d9f 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridActionsSection.xml
@@ -12,9 +12,10 @@
-
+
-
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridSection.xml
index 85c086d01848b..5393d6c1ab9b9 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesGridSection.xml
@@ -19,5 +19,8 @@
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesSection.xml
index 8068f94032730..26df107708c47 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerAddressesSection.xml
@@ -30,5 +30,10 @@
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml
index 02d9bc2eb5f12..25617ca05dd44 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml
@@ -16,5 +16,7 @@
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml
index d9d3bfe7f737c..91363c614c1f8 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGridSection.xml
@@ -11,5 +11,10 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupMainSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupMainSection.xml
index 1fdb15f189ace..4cb7f5e3f628e 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupMainSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerGroupMainSection.xml
@@ -15,5 +15,6 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml
index 0a56763b66704..304068d89b729 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerMainActionsSection.xml
@@ -10,6 +10,7 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml
index 04d6c4dc2a09d..ffddc6292ef5d 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerAddressesSection.xml
@@ -13,6 +13,8 @@
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml
index f5bbb84eaa593..1c5bbc76e4d6e 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerInformationSection.xml
@@ -11,5 +11,8 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerNewsletterSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerNewsletterSection.xml
new file mode 100644
index 0000000000000..51b4b54c5c8b6
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminEditCustomerNewsletterSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/CustomersPageSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/CustomersPageSection.xml
index 60c635387199a..93a988caf3d1c 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/CustomersPageSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/CustomersPageSection.xml
@@ -11,9 +11,11 @@
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/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
index 59da4e9279a03..b819a78002c62 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
@@ -14,6 +14,10 @@
-
+
+
+
+
+
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/StorefrontCustomerAddressesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
index 29a2f549274a7..aad9d02842271 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAddressesSection.xml
@@ -17,5 +17,6 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
index ee14ee5c165c5..8881a2a012ce8 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
@@ -12,6 +12,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml
index 70d1bb6675db5..93e7bf71b0894 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerDashboardAccountInformationSection.xml
@@ -10,6 +10,8 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
@@ -20,6 +22,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml
new file mode 100644
index 0000000000000..078021db062cc
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerMessagesSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerMessagesSection.xml
new file mode 100644
index 0000000000000..07d044921c8e5
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerMessagesSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
index 0e31f0e0c7782..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 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
index 25c07ca9cb3c9..f52b379379ad1 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
@@ -12,6 +12,7 @@
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontForgotPasswordSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontForgotPasswordSection.xml
new file mode 100644
index 0000000000000..bdae69c425db1
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontForgotPasswordSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml
index 1955c6a417ba9..4d7572aedc59b 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml
@@ -10,10 +10,11 @@
xsi:noNamespaceSchemaLocation="urn:magento:mftf:Page/etc/SectionObject.xsd">
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml
new file mode 100644
index 0000000000000..36592ab38e91d
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerRetailerWithoutAddressTest.xml
@@ -0,0 +1,63 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryPolandTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryPolandTest.xml
new file mode 100644
index 0000000000000..cbc8b89d3f242
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryPolandTest.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml
new file mode 100644
index 0000000000000..43f2aa7f8de95
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCountryUSATest.xml
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml
new file mode 100644
index 0000000000000..872da149ed0b2
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithCustomGroupTest.xml
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml
new file mode 100644
index 0000000000000..1b901a7b3e1cd
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithPrefixTest.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml
new file mode 100644
index 0000000000000..fe4bb3ee59e6e
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateCustomerWithoutAddressTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml
new file mode 100644
index 0000000000000..22ad60ff5de34
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontSignupNewsletterTest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontTest.xml
new file mode 100644
index 0000000000000..fc65a271a8196
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerOnStorefrontTest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml
new file mode 100644
index 0000000000000..de4ab9ffaa121
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminCreateNewCustomerTest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml
new file mode 100644
index 0000000000000..7fef916fc458a
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminDeleteCustomerTest.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest.xml
new file mode 100644
index 0000000000000..f58f23dee4235
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminUpdateCustomerTest.xml
@@ -0,0 +1,308 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCreateCustomerRequiredFieldsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCreateCustomerRequiredFieldsTest.xml
new file mode 100644
index 0000000000000..7dab6eefde8ec
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCreateCustomerRequiredFieldsTest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressRequiredFieldsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressRequiredFieldsTest.xml
new file mode 100644
index 0000000000000..bfb47dc9e1911
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AdminVerifyCustomerAddressRequiredFieldsTest.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml
new file mode 100644
index 0000000000000..fb083f39ad387
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/ChangeCustomerGroupTest.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/PasswordAutocompleteOffTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/PasswordAutocompleteOffTest.xml
new file mode 100644
index 0000000000000..f364d24806b9c
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/PasswordAutocompleteOffTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml
index 229e81e877292..ab805193854b0 100644
--- a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontCheckTaxAddingValidVATIdTest.xml
@@ -72,7 +72,7 @@
-
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml
new file mode 100644
index 0000000000000..12e603bd3748c
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginWithIncorrectCredentialsTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginWithIncorrectCredentialsTest.xml
new file mode 100644
index 0000000000000..104b5d56314ba
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontLoginWithIncorrectCredentialsTest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml
new file mode 100644
index 0000000000000..dae456c96a679
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressFranceTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml
new file mode 100644
index 0000000000000..7b6e695aa8dc4
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerAddressUKTest.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerPasswordTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerPasswordTest.xml
new file mode 100644
index 0000000000000..9bc253c91af92
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontUpdateCustomerPasswordTest.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifyNoXssInjectionOnUpdateCustomerInformationAddAddressTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifyNoXssInjectionOnUpdateCustomerInformationAddAddressTest.xml
new file mode 100644
index 0000000000000..e11404db9a9a9
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontVerifyNoXssInjectionOnUpdateCustomerInformationAddAddressTest.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml
new file mode 100644
index 0000000000000..648c30b1ca0bb
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/VerifyDisabledCustomerGroupFieldTest.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php b/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php
index 31bcc37612302..47f96b132b3db 100644
--- a/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Block/Address/GridTest.php
@@ -81,7 +81,7 @@ protected function setUp()
public function testGetChildHtml()
{
$customerId = 1;
-
+ $outputString = 'OutputString';
/** @var \Magento\Framework\View\Element\BlockInterface|\PHPUnit_Framework_MockObject_MockObject $block */
$block = $this->getMockBuilder(\Magento\Framework\View\Element\BlockInterface::class)
->setMethods(['setCollection'])
@@ -93,7 +93,7 @@ public function testGetChildHtml()
/** @var \PHPUnit_Framework_MockObject_MockObject */
$addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class)
->disableOriginalConstructor()
- ->setMethods(['setOrder', 'setCustomerFilter', 'load'])
+ ->setMethods(['setOrder', 'setCustomerFilter', 'load','addFieldToFilter'])
->getMock();
$layout->expects($this->atLeastOnce())->method('getChildName')->with('NameInLayout', 'pager')
@@ -108,12 +108,13 @@ public function testGetChildHtml()
->willReturnSelf();
$addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->with([$customerId])
->willReturnSelf();
+ $addressCollection->expects(static::any())->method('addFieldToFilter')->willReturnSelf();
$this->addressCollectionFactory->expects($this->atLeastOnce())->method('create')
->willReturn($addressCollection);
$block->expects($this->atLeastOnce())->method('setCollection')->with($addressCollection)->willReturnSelf();
$this->gridBlock->setNameInLayout('NameInLayout');
$this->gridBlock->setLayout($layout);
- $this->assertEquals('OutputString', $this->gridBlock->getChildHtml('pager'));
+ $this->assertEquals($outputString, $this->gridBlock->getChildHtml('pager'));
}
/**
@@ -137,7 +138,7 @@ public function testGetAdditionalAddresses()
/** @var \PHPUnit_Framework_MockObject_MockObject */
$addressCollection = $this->getMockBuilder(\Magento\Customer\Model\ResourceModel\Address\Collection::class)
->disableOriginalConstructor()
- ->setMethods(['setOrder', 'setCustomerFilter', 'load', 'getIterator'])
+ ->setMethods(['setOrder', 'setCustomerFilter', 'load', 'getIterator','addFieldToFilter'])
->getMock();
$addressDataModel = $this->getMockForAbstractClass(\Magento\Customer\Api\Data\AddressInterface::class);
$address = $this->getMockBuilder(\Magento\Customer\Model\Address::class)
@@ -157,6 +158,7 @@ public function testGetAdditionalAddresses()
->willReturnSelf();
$addressCollection->expects($this->atLeastOnce())->method('setCustomerFilter')->with([$customerId])
->willReturnSelf();
+ $addressCollection->expects(static::any())->method('addFieldToFilter')->willReturnSelf();
$addressCollection->expects($this->atLeastOnce())->method('getIterator')
->willReturn(new \ArrayIterator($collection));
$this->addressCollectionFactory->expects($this->atLeastOnce())->method('create')
diff --git a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
index 8d802e907a810..57f384d32d980 100644
--- a/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Controller/Adminhtml/Index/SaveTest.php
@@ -699,22 +699,24 @@ public function testExecuteWithNewCustomerAndValidationException()
'customer' => [
'coolness' => false,
'disable_auto_group_change' => 'false',
+ 'dob' => '3/12/1996',
],
'subscription' => $subscription,
];
$extractedData = [
'coolness' => false,
'disable_auto_group_change' => 'false',
+ 'dob' => '1996-03-12',
];
/** @var AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject $customerFormMock */
$attributeMock = $this->getMockBuilder(
\Magento\Customer\Api\Data\AttributeMetadataInterface::class
)->disableOriginalConstructor()->getMock();
- $attributeMock->expects($this->once())
+ $attributeMock->expects($this->exactly(2))
->method('getAttributeCode')
->willReturn('coolness');
- $attributeMock->expects($this->once())
+ $attributeMock->expects($this->exactly(2))
->method('getFrontendInput')
->willReturn('int');
$attributes = [$attributeMock];
@@ -737,12 +739,12 @@ public function testExecuteWithNewCustomerAndValidationException()
$objectMock = $this->getMockBuilder(\Magento\Framework\DataObject::class)
->disableOriginalConstructor()
->getMock();
- $objectMock->expects($this->once())
+ $objectMock->expects($this->exactly(2))
->method('getData')
->with('customer')
->willReturn($postValue['customer']);
- $this->objectFactoryMock->expects($this->once())
+ $this->objectFactoryMock->expects($this->exactly(2))
->method('create')
->with(['data' => $postValue])
->willReturn($objectMock);
@@ -750,19 +752,19 @@ public function testExecuteWithNewCustomerAndValidationException()
$customerFormMock = $this->getMockBuilder(
\Magento\Customer\Model\Metadata\Form::class
)->disableOriginalConstructor()->getMock();
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('extractData')
->with($this->requestMock, 'customer')
->willReturn($extractedData);
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('compactData')
->with($extractedData)
->willReturn($extractedData);
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('getAttributes')
->willReturn($attributes);
- $this->formFactoryMock->expects($this->once())
+ $this->formFactoryMock->expects($this->exactly(2))
->method('create')
->with(
CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
@@ -810,7 +812,10 @@ public function testExecuteWithNewCustomerAndValidationException()
$this->sessionMock->expects($this->once())
->method('setCustomerFormData')
- ->with($postValue);
+ ->with([
+ 'customer' => $extractedData,
+ 'subscription' => $subscription,
+ ]);
/** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */
$redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class)
@@ -841,22 +846,24 @@ public function testExecuteWithNewCustomerAndLocalizedException()
'customer' => [
'coolness' => false,
'disable_auto_group_change' => 'false',
+ 'dob' => '3/12/1996',
],
'subscription' => $subscription,
];
$extractedData = [
'coolness' => false,
'disable_auto_group_change' => 'false',
+ 'dob' => '1996-03-12',
];
/** @var AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject $customerFormMock */
$attributeMock = $this->getMockBuilder(
\Magento\Customer\Api\Data\AttributeMetadataInterface::class
)->disableOriginalConstructor()->getMock();
- $attributeMock->expects($this->once())
+ $attributeMock->expects($this->exactly(2))
->method('getAttributeCode')
->willReturn('coolness');
- $attributeMock->expects($this->once())
+ $attributeMock->expects($this->exactly(2))
->method('getFrontendInput')
->willReturn('int');
$attributes = [$attributeMock];
@@ -879,12 +886,12 @@ public function testExecuteWithNewCustomerAndLocalizedException()
$objectMock = $this->getMockBuilder(\Magento\Framework\DataObject::class)
->disableOriginalConstructor()
->getMock();
- $objectMock->expects($this->once())
+ $objectMock->expects($this->exactly(2))
->method('getData')
->with('customer')
->willReturn($postValue['customer']);
- $this->objectFactoryMock->expects($this->once())
+ $this->objectFactoryMock->expects($this->exactly(2))
->method('create')
->with(['data' => $postValue])
->willReturn($objectMock);
@@ -893,19 +900,19 @@ public function testExecuteWithNewCustomerAndLocalizedException()
$customerFormMock = $this->getMockBuilder(
\Magento\Customer\Model\Metadata\Form::class
)->disableOriginalConstructor()->getMock();
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('extractData')
->with($this->requestMock, 'customer')
->willReturn($extractedData);
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('compactData')
->with($extractedData)
->willReturn($extractedData);
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('getAttributes')
->willReturn($attributes);
- $this->formFactoryMock->expects($this->once())
+ $this->formFactoryMock->expects($this->exactly(2))
->method('create')
->with(
CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
@@ -952,7 +959,10 @@ public function testExecuteWithNewCustomerAndLocalizedException()
$this->sessionMock->expects($this->once())
->method('setCustomerFormData')
- ->with($postValue);
+ ->with([
+ 'customer' => $extractedData,
+ 'subscription' => $subscription,
+ ]);
/** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */
$redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class)
@@ -983,22 +993,24 @@ public function testExecuteWithNewCustomerAndException()
'customer' => [
'coolness' => false,
'disable_auto_group_change' => 'false',
+ 'dob' => '3/12/1996',
],
'subscription' => $subscription,
];
$extractedData = [
'coolness' => false,
'disable_auto_group_change' => 'false',
+ 'dob' => '1996-03-12',
];
/** @var AttributeMetadataInterface|\PHPUnit_Framework_MockObject_MockObject $customerFormMock */
$attributeMock = $this->getMockBuilder(
\Magento\Customer\Api\Data\AttributeMetadataInterface::class
)->disableOriginalConstructor()->getMock();
- $attributeMock->expects($this->once())
+ $attributeMock->expects($this->exactly(2))
->method('getAttributeCode')
->willReturn('coolness');
- $attributeMock->expects($this->once())
+ $attributeMock->expects($this->exactly(2))
->method('getFrontendInput')
->willReturn('int');
$attributes = [$attributeMock];
@@ -1021,12 +1033,12 @@ public function testExecuteWithNewCustomerAndException()
$objectMock = $this->getMockBuilder(\Magento\Framework\DataObject::class)
->disableOriginalConstructor()
->getMock();
- $objectMock->expects($this->once())
+ $objectMock->expects($this->exactly(2))
->method('getData')
->with('customer')
->willReturn($postValue['customer']);
- $this->objectFactoryMock->expects($this->once())
+ $this->objectFactoryMock->expects($this->exactly(2))
->method('create')
->with(['data' => $postValue])
->willReturn($objectMock);
@@ -1034,19 +1046,19 @@ public function testExecuteWithNewCustomerAndException()
$customerFormMock = $this->getMockBuilder(
\Magento\Customer\Model\Metadata\Form::class
)->disableOriginalConstructor()->getMock();
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('extractData')
->with($this->requestMock, 'customer')
->willReturn($extractedData);
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('compactData')
->with($extractedData)
->willReturn($extractedData);
- $customerFormMock->expects($this->once())
+ $customerFormMock->expects($this->exactly(2))
->method('getAttributes')
->willReturn($attributes);
- $this->formFactoryMock->expects($this->once())
+ $this->formFactoryMock->expects($this->exactly(2))
->method('create')
->with(
CustomerMetadataInterface::ENTITY_TYPE_CUSTOMER,
@@ -1095,7 +1107,10 @@ public function testExecuteWithNewCustomerAndException()
$this->sessionMock->expects($this->once())
->method('setCustomerFormData')
- ->with($postValue);
+ ->with([
+ 'customer' => $extractedData,
+ 'subscription' => $subscription,
+ ]);
/** @var Redirect|\PHPUnit_Framework_MockObject_MockObject $redirectMock */
$redirectMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Redirect::class)
diff --git a/app/code/Magento/Customer/etc/frontend/di.xml b/app/code/Magento/Customer/etc/frontend/di.xml
index 4a45c4ad48d19..c31742519e581 100644
--- a/app/code/Magento/Customer/etc/frontend/di.xml
+++ b/app/code/Magento/Customer/etc/frontend/di.xml
@@ -57,7 +57,7 @@
-
+
@@ -77,4 +77,4 @@
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_account_create.xml b/app/code/Magento/Customer/view/frontend/layout/customer_account_create.xml
index fd5ecbfa7f277..0c5af453f2373 100644
--- a/app/code/Magento/Customer/view/frontend/layout/customer_account_create.xml
+++ b/app/code/Magento/Customer/view/frontend/layout/customer_account_create.xml
@@ -6,6 +6,9 @@
*/
-->
+
+ Create New Customer Account
+
diff --git a/app/code/Magento/Customer/view/frontend/layout/customer_account_login.xml b/app/code/Magento/Customer/view/frontend/layout/customer_account_login.xml
index d49dae6dee58f..3518df736c4ac 100644
--- a/app/code/Magento/Customer/view/frontend/layout/customer_account_login.xml
+++ b/app/code/Magento/Customer/view/frontend/layout/customer_account_login.xml
@@ -6,6 +6,9 @@
*/
-->
+
+ Customer Login
+
diff --git a/app/code/Magento/CustomerAnalytics/composer.json b/app/code/Magento/CustomerAnalytics/composer.json
index 7dec4279ee280..3840c534b1964 100644
--- a/app/code/Magento/CustomerAnalytics/composer.json
+++ b/app/code/Magento/CustomerAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-customer": "*"
+ "magento/module-customer": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CreateCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CreateCustomerAddress.php
new file mode 100644
index 0000000000000..388b6dc2ea943
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CreateCustomerAddress.php
@@ -0,0 +1,115 @@
+getAllowedAddressAttributes = $getAllowedAddressAttributes;
+ $this->addressFactory = $addressFactory;
+ $this->addressRepository = $addressRepository;
+ $this->dataObjectHelper = $dataObjectHelper;
+ }
+
+ /**
+ * Create customer address
+ *
+ * @param int $customerId
+ * @param array $data
+ * @return AddressInterface
+ * @throws GraphQlInputException
+ */
+ public function execute(int $customerId, array $data): AddressInterface
+ {
+ $this->validateData($data);
+
+ /** @var AddressInterface $address */
+ $address = $this->addressFactory->create();
+ $this->dataObjectHelper->populateWithArray($address, $data, AddressInterface::class);
+
+ if (isset($data['region']['region_id'])) {
+ $address->setRegionId($address->getRegion()->getRegionId());
+ }
+ $address->setCustomerId($customerId);
+
+ try {
+ $this->addressRepository->save($address);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ return $address;
+ }
+
+ /**
+ * Validate customer address create data
+ *
+ * @param array $addressData
+ * @return void
+ * @throws GraphQlInputException
+ */
+ public function validateData(array $addressData): void
+ {
+ $attributes = $this->getAllowedAddressAttributes->execute();
+ $errorInput = [];
+
+ foreach ($attributes as $attributeName => $attributeInfo) {
+ if ($attributeInfo->getIsRequired()
+ && (!isset($addressData[$attributeName]) || empty($addressData[$attributeName]))
+ ) {
+ $errorInput[] = $attributeName;
+ }
+ }
+
+ if ($errorInput) {
+ throw new GraphQlInputException(
+ __('Required parameters are missing: %1', [implode(', ', $errorInput)])
+ );
+ }
+ }
+}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressCreateDataValidator.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressCreateDataValidator.php
deleted file mode 100644
index 65672bcd3503b..0000000000000
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressCreateDataValidator.php
+++ /dev/null
@@ -1,56 +0,0 @@
-getAllowedAddressAttributes = $getAllowedAddressAttributes;
- }
-
- /**
- * Validate customer address create data
- *
- * @param array $addressData
- * @return void
- * @throws GraphQlInputException
- */
- public function validate(array $addressData): void
- {
- $attributes = $this->getAllowedAddressAttributes->execute();
- $errorInput = [];
-
- foreach ($attributes as $attributeName => $attributeInfo) {
- if ($attributeInfo->getIsRequired()
- && (!isset($addressData[$attributeName]) || empty($addressData[$attributeName]))
- ) {
- $errorInput[] = $attributeName;
- }
- }
-
- if ($errorInput) {
- throw new GraphQlInputException(
- __('Required parameters are missing: %1', [implode(', ', $errorInput)])
- );
- }
- }
-}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressUpdateDataValidator.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressUpdateDataValidator.php
deleted file mode 100644
index 13716b491fddf..0000000000000
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressUpdateDataValidator.php
+++ /dev/null
@@ -1,56 +0,0 @@
-getAllowedAddressAttributes = $getAllowedAddressAttributes;
- }
-
- /**
- * Validate customer address update data
- *
- * @param array $addressData
- * @return void
- * @throws GraphQlInputException
- */
- public function validate(array $addressData): void
- {
- $attributes = $this->getAllowedAddressAttributes->execute();
- $errorInput = [];
-
- foreach ($attributes as $attributeName => $attributeInfo) {
- if ($attributeInfo->getIsRequired()
- && (isset($addressData[$attributeName]) && empty($addressData[$attributeName]))
- ) {
- $errorInput[] = $attributeName;
- }
- }
-
- if ($errorInput) {
- throw new GraphQlInputException(
- __('Required parameters are missing: %1', [implode(', ', $errorInput)])
- );
- }
- }
-}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/DeleteCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/DeleteCustomerAddress.php
new file mode 100644
index 0000000000000..586fbebde703f
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/DeleteCustomerAddress.php
@@ -0,0 +1,60 @@
+addressRepository = $addressRepository;
+ }
+
+ /**
+ * Delete customer address
+ *
+ * @param AddressInterface $address
+ * @return void
+ * @throws GraphQlInputException
+ */
+ public function execute(AddressInterface $address): void
+ {
+ if ($address->isDefaultBilling()) {
+ throw new GraphQlInputException(
+ __('Customer Address %1 is set as default billing address and can not be deleted', [$address->getId()])
+ );
+ }
+ if ($address->isDefaultShipping()) {
+ throw new GraphQlInputException(
+ __('Customer Address %1 is set as default shipping address and can not be deleted', [$address->getId()])
+ );
+ }
+
+ try {
+ $this->addressRepository->delete($address);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ }
+}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressDataProvider.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/ExtractCustomerAddressData.php
similarity index 80%
rename from app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressDataProvider.php
rename to app/code/Magento/CustomerGraphQl/Model/Customer/Address/ExtractCustomerAddressData.php
index 9640953032ac6..a4649bccc02e8 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/CustomerAddressDataProvider.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/ExtractCustomerAddressData.php
@@ -17,9 +17,9 @@
use Magento\Framework\Serialize\SerializerInterface;
/**
- * Customer Address field data provider, used for GraphQL request processing.
+ * Transform single customer address data from object to in array format
*/
-class CustomerAddressDataProvider
+class ExtractCustomerAddressData
{
/**
* @var ServiceOutputProcessor
@@ -82,24 +82,27 @@ private function curateAddressDefaultValues(array $address, AddressInterface $ad
/**
* Transform single customer address data from object to in array format
*
- * @param AddressInterface $addressObject
+ * @param AddressInterface $address
* @return array
*/
- public function getAddressData(AddressInterface $addressObject): array
+ public function execute(AddressInterface $address): array
{
- $address = $this->serviceOutputProcessor->process(
- $addressObject,
+ $addressData = $this->serviceOutputProcessor->process(
+ $address,
AddressRepositoryInterface::class,
'getById'
);
- $address = $this->curateAddressDefaultValues($address, $addressObject);
+ $addressData = $this->curateAddressDefaultValues($addressData, $address);
- if (isset($address[CustomAttributesDataInterface::EXTENSION_ATTRIBUTES_KEY])) {
- $address = array_merge($address, $address[CustomAttributesDataInterface::EXTENSION_ATTRIBUTES_KEY]);
+ if (isset($addressData[CustomAttributesDataInterface::EXTENSION_ATTRIBUTES_KEY])) {
+ $addressData = array_merge(
+ $addressData,
+ $addressData[CustomAttributesDataInterface::EXTENSION_ATTRIBUTES_KEY]
+ );
}
$customAttributes = [];
- if (isset($address[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES])) {
- foreach ($address[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES] as $attribute) {
+ if (isset($addressData[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES])) {
+ foreach ($addressData[CustomAttributesDataInterface::CUSTOM_ATTRIBUTES] as $attribute) {
$isArray = false;
if (is_array($attribute['value'])) {
$isArray = true;
@@ -120,8 +123,8 @@ public function getAddressData(AddressInterface $addressObject): array
$customAttributes[$attribute['attribute_code']] = $attribute['value'];
}
}
- $address = array_merge($address, $customAttributes);
+ $addressData = array_merge($addressData, $customAttributes);
- return $address;
+ return $addressData;
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddressForUser.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddress.php
similarity index 56%
rename from app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddressForUser.php
rename to app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddress.php
index f7323402a6c62..7258f2e726fd7 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddressForUser.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/GetCustomerAddress.php
@@ -9,14 +9,16 @@
use Magento\Customer\Api\AddressRepositoryInterface;
use Magento\Customer\Api\Data\AddressInterface;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
/**
- * Get customer address for user
+ * Get customer address
*/
-class GetCustomerAddressForUser
+class GetCustomerAddress
{
/**
* @var AddressRepositoryInterface
@@ -32,30 +34,35 @@ public function __construct(AddressRepositoryInterface $addressRepository)
}
/**
- * Get customer address for user
+ * Get customer address
*
* @param int $addressId
- * @param int $userId
+ * @param int $customerId
* @return AddressInterface
- * @throws GraphQlAuthorizationException
+ * @throws GraphQlInputException
* @throws GraphQlNoSuchEntityException
+ * @throws GraphQlAuthorizationException
*/
- public function execute(int $addressId, int $userId): AddressInterface
+ public function execute(int $addressId, int $customerId): AddressInterface
{
try {
- /** @var AddressInterface $address */
- $address = $this->addressRepository->getById($addressId);
+ $customerAddress = $this->addressRepository->getById($addressId);
} catch (NoSuchEntityException $e) {
throw new GraphQlNoSuchEntityException(
- __('Address id %1 does not exist.', [$addressId])
+ __('Could not find a address with ID "%address_id"', ['address_id' => $addressId])
);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
}
- if ($address->getCustomerId() != $userId) {
+ if ((int)$customerAddress->getCustomerId() !== $customerId) {
throw new GraphQlAuthorizationException(
- __('Current customer does not have permission to address id %1', [$addressId])
+ __(
+ 'Current customer does not have permission to address with ID "%address_id"',
+ ['address_id' => $addressId]
+ )
);
}
- return $address;
+ return $customerAddress;
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/Address/UpdateCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/UpdateCustomerAddress.php
new file mode 100644
index 0000000000000..65745a20bc8eb
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/Address/UpdateCustomerAddress.php
@@ -0,0 +1,111 @@
+getAllowedAddressAttributes = $getAllowedAddressAttributes;
+ $this->addressRepository = $addressRepository;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->restrictedKeys = $restrictedKeys;
+ }
+
+ /**
+ * Update customer address
+ *
+ * @param AddressInterface $address
+ * @param array $data
+ * @return void
+ * @throws GraphQlInputException
+ */
+ public function execute(AddressInterface $address, array $data): void
+ {
+ $this->validateData($data);
+
+ $filteredData = array_diff_key($data, array_flip($this->restrictedKeys));
+ $this->dataObjectHelper->populateWithArray($address, $filteredData, AddressInterface::class);
+
+ if (isset($data['region']['region_id'])) {
+ $address->setRegionId($address->getRegion()->getRegionId());
+ }
+
+ try {
+ $this->addressRepository->save($address);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ }
+
+ /**
+ * Validate customer address update data
+ *
+ * @param array $addressData
+ * @return void
+ * @throws GraphQlInputException
+ */
+ public function validateData(array $addressData): void
+ {
+ $attributes = $this->getAllowedAddressAttributes->execute();
+ $errorInput = [];
+
+ foreach ($attributes as $attributeName => $attributeInfo) {
+ if ($attributeInfo->getIsRequired()
+ && (isset($addressData[$attributeName]) && empty($addressData[$attributeName]))
+ ) {
+ $errorInput[] = $attributeName;
+ }
+ }
+
+ if ($errorInput) {
+ throw new GraphQlInputException(
+ __('Required parameters are missing: %1', [implode(', ', $errorInput)])
+ );
+ }
+ }
+}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php
index f3c03e5fc18aa..3cc831e1ca40e 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerPassword.php
@@ -9,7 +9,12 @@
use Magento\Customer\Model\AuthenticationInterface;
use Magento\Framework\Exception\InvalidEmailOrPasswordException;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Exception\State\UserLockedException;
use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
/**
* Check customer password
@@ -36,15 +41,21 @@ public function __construct(
* @param string $password
* @param int $customerId
* @throws GraphQlAuthenticationException
+ * @throws GraphQlInputException
+ * @throws GraphQlNoSuchEntityException
*/
public function execute(string $password, int $customerId)
{
try {
$this->authentication->authenticate($customerId, $password);
} catch (InvalidEmailOrPasswordException $e) {
- throw new GraphQlAuthenticationException(
- __('The password doesn\'t match this account. Verify the password and try again.')
- );
+ throw new GraphQlAuthenticationException(__($e->getMessage()), $e);
+ } catch (UserLockedException $e) {
+ throw new GraphQlAuthenticationException(__($e->getMessage()), $e);
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
}
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CreateAccount.php b/app/code/Magento/CustomerGraphQl/Model/Customer/CreateCustomerAccount.php
similarity index 62%
rename from app/code/Magento/CustomerGraphQl/Model/Customer/CreateAccount.php
rename to app/code/Magento/CustomerGraphQl/Model/Customer/CreateCustomerAccount.php
index 4a4b5c863528b..b7b66df042467 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/CreateAccount.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/CreateCustomerAccount.php
@@ -12,13 +12,13 @@
use Magento\Customer\Api\Data\CustomerInterfaceFactory;
use Magento\Framework\Api\DataObjectHelper;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Store\Model\StoreManagerInterface;
/**
- * Class CreateAccount creates new customer account
+ * Create new customer account
*/
-class CreateAccount
+class CreateCustomerAccount
{
/**
* @var DataObjectHelper
@@ -40,46 +40,73 @@ class CreateAccount
*/
private $storeManager;
+ /**
+ * @var ChangeSubscriptionStatus
+ */
+ private $changeSubscriptionStatus;
+
/**
* @param DataObjectHelper $dataObjectHelper
* @param CustomerInterfaceFactory $customerFactory
* @param StoreManagerInterface $storeManager
* @param AccountManagementInterface $accountManagement
+ * @param ChangeSubscriptionStatus $changeSubscriptionStatus
*/
public function __construct(
DataObjectHelper $dataObjectHelper,
CustomerInterfaceFactory $customerFactory,
StoreManagerInterface $storeManager,
- AccountManagementInterface $accountManagement
+ AccountManagementInterface $accountManagement,
+ ChangeSubscriptionStatus $changeSubscriptionStatus
) {
$this->dataObjectHelper = $dataObjectHelper;
$this->customerFactory = $customerFactory;
$this->accountManagement = $accountManagement;
$this->storeManager = $storeManager;
+ $this->changeSubscriptionStatus = $changeSubscriptionStatus;
}
/**
* Creates new customer account
*
- * @param array $args
+ * @param array $data
+ * @return CustomerInterface
+ * @throws GraphQlInputException
+ */
+ public function execute(array $data): CustomerInterface
+ {
+ try {
+ $customer = $this->createAccount($data);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()));
+ }
+
+ if (isset($data['is_subscribed'])) {
+ $this->changeSubscriptionStatus->execute((int)$customer->getId(), (bool)$data['is_subscribed']);
+ }
+ return $customer;
+ }
+
+ /**
+ * Create account
+ *
+ * @param array $data
* @return CustomerInterface
* @throws LocalizedException
- * @throws NoSuchEntityException
*/
- public function execute(array $args): CustomerInterface
+ private function createAccount(array $data): CustomerInterface
{
$customerDataObject = $this->customerFactory->create();
$this->dataObjectHelper->populateWithArray(
$customerDataObject,
- $args['input'],
+ $data,
CustomerInterface::class
);
$store = $this->storeManager->getStore();
$customerDataObject->setWebsiteId($store->getWebsiteId());
$customerDataObject->setStoreId($store->getId());
- $password = array_key_exists('password', $args['input']) ? $args['input']['password'] : null;
-
+ $password = array_key_exists('password', $data) ? $data['password'] : null;
return $this->accountManagement->createAccount($customerDataObject, $password);
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php b/app/code/Magento/CustomerGraphQl/Model/Customer/ExtractCustomerData.php
similarity index 72%
rename from app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php
rename to app/code/Magento/CustomerGraphQl/Model/Customer/ExtractCustomerData.php
index c8382593eab23..de37482aca056 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/CustomerDataProvider.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/ExtractCustomerData.php
@@ -9,22 +9,15 @@
use Magento\Customer\Api\CustomerRepositoryInterface;
use Magento\Framework\Exception\LocalizedException;
-use Magento\Framework\Exception\NoSuchEntityException;
-use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Framework\Webapi\ServiceOutputProcessor;
use Magento\Customer\Api\Data\CustomerInterface;
/**
- * Customer field data provider, used for GraphQL request processing.
+ * Transform single customer data from object to in array format
*/
-class CustomerDataProvider
+class ExtractCustomerData
{
- /**
- * @var CustomerRepositoryInterface
- */
- private $customerRepository;
-
/**
* @var ServiceOutputProcessor
*/
@@ -36,47 +29,24 @@ class CustomerDataProvider
private $serializer;
/**
- * @param CustomerRepositoryInterface $customerRepository
* @param ServiceOutputProcessor $serviceOutputProcessor
* @param SerializerInterface $serializer
*/
public function __construct(
- CustomerRepositoryInterface $customerRepository,
ServiceOutputProcessor $serviceOutputProcessor,
SerializerInterface $serializer
) {
- $this->customerRepository = $customerRepository;
$this->serviceOutputProcessor = $serviceOutputProcessor;
$this->serializer = $serializer;
}
- /**
- * Get customer data by Id or empty array
- *
- * @param int $customerId
- * @return array
- * @throws NoSuchEntityException|LocalizedException
- */
- public function getCustomerById(int $customerId): array
- {
- try {
- $customer = $this->customerRepository->getById($customerId);
- } catch (NoSuchEntityException $e) {
- throw new GraphQlNoSuchEntityException(
- __('Customer id "%customer_id" does not exist.', ['customer_id' => $customerId]),
- $e
- );
- }
- return $this->processCustomer($customer);
- }
-
/**
* Curate default shipping and default billing keys
*
* @param array $arrayAddress
* @return array
*/
- private function curateAddressData(array $arrayAddress) : array
+ private function curateAddressData(array $arrayAddress): array
{
foreach ($arrayAddress as $key => $address) {
if (!isset($address['default_shipping'])) {
@@ -94,8 +64,9 @@ private function curateAddressData(array $arrayAddress) : array
*
* @param CustomerInterface $customer
* @return array
+ * @throws LocalizedException
*/
- private function processCustomer(CustomerInterface $customer): array
+ public function execute(CustomerInterface $customer): array
{
$customerData = $this->serviceOutputProcessor->process(
$customer,
@@ -131,6 +102,7 @@ private function processCustomer(CustomerInterface $customer): array
}
$customerData = array_merge($customerData, $customAttributes);
+ $customerData['model'] = $customer;
return $customerData;
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php b/app/code/Magento/CustomerGraphQl/Model/Customer/GetCustomer.php
similarity index 67%
rename from app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php
rename to app/code/Magento/CustomerGraphQl/Model/Customer/GetCustomer.php
index 030fc47d19e81..8bd5c9157493c 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/CheckCustomerAccount.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/GetCustomer.php
@@ -10,16 +10,20 @@
use Magento\Authorization\Model\UserContextInterface;
use Magento\Customer\Api\AccountManagementInterface;
use Magento\Customer\Api\CustomerRepositoryInterface;
+use Magento\Customer\Api\Data\CustomerInterface;
use Magento\Customer\Model\AuthenticationInterface;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
+use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
/**
- * Check customer account
+ * Get customer
*/
-class CheckCustomerAccount
+class GetCustomer
{
/**
* @var AuthenticationInterface
@@ -52,38 +56,49 @@ public function __construct(
}
/**
- * Check customer account
+ * Get customer
*
- * @param int|null $customerId
- * @param int|null $customerType
- * @return void
+ * @param ContextInterface $context
+ * @return CustomerInterface
+ * @throws GraphQlAuthenticationException
* @throws GraphQlAuthorizationException
+ * @throws GraphQlInputException
* @throws GraphQlNoSuchEntityException
- * @throws GraphQlAuthenticationException
*/
- public function execute(?int $customerId, ?int $customerType): void
+ public function execute(ContextInterface $context): CustomerInterface
{
- if (true === $this->isCustomerGuest($customerId, $customerType)) {
+ $currentUserId = $context->getUserId();
+ $currentUserType = $context->getUserType();
+
+ if (true === $this->isUserGuest($currentUserId, $currentUserType)) {
throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.'));
}
try {
- $this->customerRepository->getById($customerId);
+ $customer = $this->customerRepository->getById($currentUserId);
} catch (NoSuchEntityException $e) {
throw new GraphQlNoSuchEntityException(
- __('Customer with id "%customer_id" does not exist.', ['customer_id' => $customerId]),
+ __('Customer with id "%customer_id" does not exist.', ['customer_id' => $currentUserId]),
$e
);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()));
}
- if (true === $this->authentication->isLocked($customerId)) {
+ if (true === $this->authentication->isLocked($currentUserId)) {
throw new GraphQlAuthenticationException(__('The account is locked.'));
}
- $confirmationStatus = $this->accountManagement->getConfirmationStatus($customerId);
+ try {
+ $confirmationStatus = $this->accountManagement->getConfirmationStatus($currentUserId);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()));
+ }
+
if ($confirmationStatus === AccountManagementInterface::ACCOUNT_CONFIRMATION_REQUIRED) {
throw new GraphQlAuthenticationException(__("This account isn't confirmed. Verify and try again."));
}
+ return $customer;
}
/**
@@ -93,7 +108,7 @@ public function execute(?int $customerId, ?int $customerType): void
* @param int|null $customerType
* @return bool
*/
- private function isCustomerGuest(?int $customerId, ?int $customerType): bool
+ private function isUserGuest(?int $customerId, ?int $customerType): bool
{
if (null === $customerId || null === $customerType) {
return true;
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/SaveCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Customer/SaveCustomer.php
new file mode 100644
index 0000000000000..1605c63b62f4c
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/SaveCustomer.php
@@ -0,0 +1,58 @@
+customerRepository = $customerRepository;
+ }
+
+ /**
+ * Save customer
+ *
+ * @param CustomerInterface $customer
+ * @throws GraphQlAlreadyExistsException
+ * @throws GraphQlAuthenticationException
+ * @throws GraphQlInputException
+ */
+ public function execute(CustomerInterface $customer): void
+ {
+ try {
+ $this->customerRepository->save($customer);
+ } catch (AlreadyExistsException $e) {
+ throw new GraphQlAlreadyExistsException(
+ __('A customer with the same email address already exists in an associated website.'),
+ $e
+ );
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ }
+}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/SetUpUserContext.php b/app/code/Magento/CustomerGraphQl/Model/Customer/SetUpUserContext.php
deleted file mode 100644
index 1fcf1c0d7c1c3..0000000000000
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/SetUpUserContext.php
+++ /dev/null
@@ -1,30 +0,0 @@
-setUserId((int)$customer->getId());
- $context->setUserType(UserContextInterface::USER_TYPE_CUSTOMER);
- }
-}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerAccount.php b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerAccount.php
new file mode 100644
index 0000000000000..8601d586b3c95
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerAccount.php
@@ -0,0 +1,108 @@
+saveCustomer = $saveCustomer;
+ $this->storeManager = $storeManager;
+ $this->checkCustomerPassword = $checkCustomerPassword;
+ $this->dataObjectHelper = $dataObjectHelper;
+ $this->restrictedKeys = $restrictedKeys;
+ $this->changeSubscriptionStatus = $changeSubscriptionStatus;
+ }
+
+ /**
+ * Update customer account data
+ *
+ * @param CustomerInterface $customer
+ * @param array $data
+ * @return void
+ * @throws GraphQlAlreadyExistsException
+ * @throws GraphQlAuthenticationException
+ * @throws GraphQlInputException
+ */
+ public function execute(CustomerInterface $customer, array $data): void
+ {
+ if (isset($data['email']) && $customer->getEmail() !== $data['email']) {
+ if (!isset($data['password']) || empty($data['password'])) {
+ throw new GraphQlInputException(__('Provide the current "password" to change "email".'));
+ }
+
+ $this->checkCustomerPassword->execute($data['password'], (int)$customer->getId());
+ $customer->setEmail($data['email']);
+ }
+
+ $filteredData = array_diff_key($data, array_flip($this->restrictedKeys));
+ $this->dataObjectHelper->populateWithArray($customer, $filteredData, CustomerInterface::class);
+
+ $customer->setStoreId($this->storeManager->getStore()->getId());
+
+ $this->saveCustomer->execute($customer);
+
+ if (isset($data['is_subscribed'])) {
+ $this->changeSubscriptionStatus->execute((int)$customer->getId(), (bool)$data['is_subscribed']);
+ }
+ }
+}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerData.php b/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerData.php
deleted file mode 100644
index 18510b872e64a..0000000000000
--- a/app/code/Magento/CustomerGraphQl/Model/Customer/UpdateCustomerData.php
+++ /dev/null
@@ -1,94 +0,0 @@
-customerRepository = $customerRepository;
- $this->storeManager = $storeManager;
- $this->checkCustomerPassword = $checkCustomerPassword;
- }
-
- /**
- * Update account information
- *
- * @param int $customerId
- * @param array $data
- * @return void
- * @throws GraphQlNoSuchEntityException
- * @throws GraphQlInputException
- * @throws GraphQlAlreadyExistsException
- */
- public function execute(int $customerId, array $data): void
- {
- $customer = $this->customerRepository->getById($customerId);
-
- if (isset($data['firstname'])) {
- $customer->setFirstname($data['firstname']);
- }
-
- if (isset($data['lastname'])) {
- $customer->setLastname($data['lastname']);
- }
-
- if (isset($data['email']) && $customer->getEmail() !== $data['email']) {
- if (!isset($data['password']) || empty($data['password'])) {
- throw new GraphQlInputException(__('Provide the current "password" to change "email".'));
- }
-
- $this->checkCustomerPassword->execute($data['password'], $customerId);
- $customer->setEmail($data['email']);
- }
-
- $customer->setStoreId($this->storeManager->getStore()->getId());
-
- try {
- $this->customerRepository->save($customer);
- } catch (AlreadyExistsException $e) {
- throw new GraphQlAlreadyExistsException(
- __('A customer with the same email address already exists in an associated website.'),
- $e
- );
- }
- }
-}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php
index 78fa852a7ac59..317b7725b0265 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/ChangePassword.php
@@ -9,8 +9,9 @@
use Magento\Customer\Api\AccountManagementInterface;
use Magento\CustomerGraphQl\Model\Customer\CheckCustomerPassword;
-use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
+use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -22,9 +23,9 @@
class ChangePassword implements ResolverInterface
{
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
* @var CheckCustomerPassword
@@ -37,26 +38,26 @@ class ChangePassword implements ResolverInterface
private $accountManagement;
/**
- * @var CustomerDataProvider
+ * @var ExtractCustomerData
*/
- private $customerDataProvider;
+ private $extractCustomerData;
/**
- * @param CheckCustomerAccount $checkCustomerAccount
+ * @param GetCustomer $getCustomer
* @param CheckCustomerPassword $checkCustomerPassword
* @param AccountManagementInterface $accountManagement
- * @param CustomerDataProvider $customerDataProvider
+ * @param ExtractCustomerData $extractCustomerData
*/
public function __construct(
- CheckCustomerAccount $checkCustomerAccount,
+ GetCustomer $getCustomer,
CheckCustomerPassword $checkCustomerPassword,
AccountManagementInterface $accountManagement,
- CustomerDataProvider $customerDataProvider
+ ExtractCustomerData $extractCustomerData
) {
- $this->checkCustomerAccount = $checkCustomerAccount;
+ $this->getCustomer = $getCustomer;
$this->checkCustomerPassword = $checkCustomerPassword;
$this->accountManagement = $accountManagement;
- $this->customerDataProvider = $customerDataProvider;
+ $this->extractCustomerData = $extractCustomerData;
}
/**
@@ -69,24 +70,24 @@ public function resolve(
array $value = null,
array $args = null
) {
- if (!isset($args['currentPassword'])) {
+ if (!isset($args['currentPassword']) || '' == trim($args['currentPassword'])) {
throw new GraphQlInputException(__('Specify the "currentPassword" value.'));
}
- if (!isset($args['newPassword'])) {
+ if (!isset($args['newPassword']) || '' == trim($args['newPassword'])) {
throw new GraphQlInputException(__('Specify the "newPassword" value.'));
}
- $currentUserId = $context->getUserId();
- $currentUserType = $context->getUserType();
- $this->checkCustomerAccount->execute($currentUserId, $currentUserType);
+ $customer = $this->getCustomer->execute($context);
+ $customerId = (int)$customer->getId();
- $currentUserId = (int)$currentUserId;
- $this->checkCustomerPassword->execute($args['currentPassword'], $currentUserId);
+ $this->checkCustomerPassword->execute($args['currentPassword'], $customerId);
- $this->accountManagement->changePasswordById($currentUserId, $args['currentPassword'], $args['newPassword']);
-
- $data = $this->customerDataProvider->getCustomerById($currentUserId);
- return $data;
+ try {
+ $this->accountManagement->changePasswordById($customerId, $args['currentPassword'], $args['newPassword']);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ return $this->extractCustomerData->execute($customer);
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php
index 299045c6b62b0..1ae22bcc12792 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomer.php
@@ -7,16 +7,13 @@
namespace Magento\CustomerGraphQl\Model\Resolver;
-use Magento\CustomerGraphQl\Model\Customer\ChangeSubscriptionStatus;
-use Magento\CustomerGraphQl\Model\Customer\CreateAccount;
-use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider;
-use Magento\CustomerGraphQl\Model\Customer\SetUpUserContext;
-use Magento\Framework\Exception\State\InputMismatchException;
+use Magento\Authorization\Model\UserContextInterface;
+use Magento\CustomerGraphQl\Model\Customer\CreateCustomerAccount;
+use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\Framework\Validator\Exception as ValidatorException;
/**
* Create customer account resolver
@@ -24,41 +21,25 @@
class CreateCustomer implements ResolverInterface
{
/**
- * @var CustomerDataProvider
+ * @var ExtractCustomerData
*/
- private $customerDataProvider;
+ private $extractCustomerData;
/**
- * @var ChangeSubscriptionStatus
+ * @var CreateCustomerAccount
*/
- private $changeSubscriptionStatus;
+ private $createCustomerAccount;
/**
- * @var CreateAccount
- */
- private $createAccount;
-
- /**
- * @var SetUpUserContext
- */
- private $setUpUserContext;
-
- /**
- * @param CustomerDataProvider $customerDataProvider
- * @param ChangeSubscriptionStatus $changeSubscriptionStatus
- * @param SetUpUserContext $setUpUserContext
- * @param CreateAccount $createAccount
+ * @param ExtractCustomerData $extractCustomerData
+ * @param CreateCustomerAccount $createCustomerAccount
*/
public function __construct(
- CustomerDataProvider $customerDataProvider,
- ChangeSubscriptionStatus $changeSubscriptionStatus,
- SetUpUserContext $setUpUserContext,
- CreateAccount $createAccount
+ ExtractCustomerData $extractCustomerData,
+ CreateCustomerAccount $createCustomerAccount
) {
- $this->customerDataProvider = $customerDataProvider;
- $this->changeSubscriptionStatus = $changeSubscriptionStatus;
- $this->createAccount = $createAccount;
- $this->setUpUserContext = $setUpUserContext;
+ $this->extractCustomerData = $extractCustomerData;
+ $this->createCustomerAccount = $createCustomerAccount;
}
/**
@@ -74,22 +55,13 @@ public function resolve(
if (!isset($args['input']) || !is_array($args['input']) || empty($args['input'])) {
throw new GraphQlInputException(__('"input" value should be specified'));
}
- try {
- $customer = $this->createAccount->execute($args);
- $customerId = (int)$customer->getId();
- $this->setUpUserContext->execute($context, $customer);
- if (array_key_exists('is_subscribed', $args['input'])) {
- if ($args['input']['is_subscribed']) {
- $this->changeSubscriptionStatus->execute($customerId, true);
- }
- }
- $data = $this->customerDataProvider->getCustomerById($customerId);
- } catch (ValidatorException $e) {
- throw new GraphQlInputException(__($e->getMessage()));
- } catch (InputMismatchException $e) {
- throw new GraphQlInputException(__($e->getMessage()));
- }
+ $customer = $this->createCustomerAccount->execute($args['input']);
+
+ $context->setUserId((int)$customer->getId());
+ $context->setUserType(UserContextInterface::USER_TYPE_CUSTOMER);
+
+ $data = $this->extractCustomerData->execute($customer);
return ['customer' => $data];
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerAddress.php
index 823444e5a2d7d..fd8122de961ee 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerAddress.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CreateCustomerAddress.php
@@ -7,14 +7,9 @@
namespace Magento\CustomerGraphQl\Model\Resolver;
-use Magento\Customer\Api\AddressRepositoryInterface;
-use Magento\Customer\Api\Data\AddressInterfaceFactory;
-use Magento\Customer\Api\Data\AddressInterface;
-use Magento\CustomerGraphQl\Model\Customer\Address\CustomerAddressCreateDataValidator;
-use Magento\CustomerGraphQl\Model\Customer\Address\CustomerAddressDataProvider;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
-use Magento\Framework\Api\DataObjectHelper;
-use Magento\Framework\Exception\InputException;
+use Magento\CustomerGraphQl\Model\Customer\Address\CreateCustomerAddress as CreateCustomerAddressModel;
+use Magento\CustomerGraphQl\Model\Customer\Address\ExtractCustomerAddressData;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Config\Element\Field;
@@ -26,57 +21,33 @@
class CreateCustomerAddress implements ResolverInterface
{
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
- * @var AddressRepositoryInterface
+ * @var CreateCustomerAddressModel
*/
- private $addressRepository;
+ private $createCustomerAddress;
/**
- * @var AddressInterfaceFactory
+ * @var ExtractCustomerAddressData
*/
- private $addressInterfaceFactory;
+ private $extractCustomerAddressData;
/**
- * @var CustomerAddressDataProvider
- */
- private $customerAddressDataProvider;
-
- /**
- * @var DataObjectHelper
- */
- private $dataObjectHelper;
-
- /**
- * @var CustomerAddressCreateDataValidator
- */
- private $customerAddressCreateDataValidator;
-
- /**
- * @param CheckCustomerAccount $checkCustomerAccount
- * @param AddressRepositoryInterface $addressRepository
- * @param AddressInterfaceFactory $addressInterfaceFactory
- * @param CustomerAddressDataProvider $customerAddressDataProvider
- * @param DataObjectHelper $dataObjectHelper
- * @param CustomerAddressCreateDataValidator $customerAddressCreateDataValidator
+ * @param GetCustomer $getCustomer
+ * @param CreateCustomerAddressModel $createCustomerAddress
+ * @param ExtractCustomerAddressData $extractCustomerAddressData
*/
public function __construct(
- CheckCustomerAccount $checkCustomerAccount,
- AddressRepositoryInterface $addressRepository,
- AddressInterfaceFactory $addressInterfaceFactory,
- CustomerAddressDataProvider $customerAddressDataProvider,
- DataObjectHelper $dataObjectHelper,
- CustomerAddressCreateDataValidator $customerAddressCreateDataValidator
+ GetCustomer $getCustomer,
+ CreateCustomerAddressModel $createCustomerAddress,
+ ExtractCustomerAddressData $extractCustomerAddressData
) {
- $this->checkCustomerAccount = $checkCustomerAccount;
- $this->addressRepository = $addressRepository;
- $this->addressInterfaceFactory = $addressInterfaceFactory;
- $this->customerAddressDataProvider = $customerAddressDataProvider;
- $this->dataObjectHelper = $dataObjectHelper;
- $this->customerAddressCreateDataValidator = $customerAddressCreateDataValidator;
+ $this->getCustomer = $getCustomer;
+ $this->createCustomerAddress = $createCustomerAddress;
+ $this->extractCustomerAddressData = $extractCustomerAddressData;
}
/**
@@ -89,36 +60,13 @@ public function resolve(
array $value = null,
array $args = null
) {
- $currentUserId = $context->getUserId();
- $currentUserType = $context->getUserType();
-
- $this->checkCustomerAccount->execute($currentUserId, $currentUserType);
- $this->customerAddressCreateDataValidator->validate($args['input']);
-
- $address = $this->createCustomerAddress((int)$currentUserId, $args['input']);
- return $this->customerAddressDataProvider->getAddressData($address);
- }
+ if (!isset($args['input']) || !is_array($args['input']) || empty($args['input'])) {
+ throw new GraphQlInputException(__('"input" value should be specified'));
+ }
- /**
- * Create customer address
- *
- * @param int $customerId
- * @param array $addressData
- * @return AddressInterface
- * @throws GraphQlInputException
- */
- private function createCustomerAddress(int $customerId, array $addressData) : AddressInterface
- {
- /** @var AddressInterface $address */
- $address = $this->addressInterfaceFactory->create();
- $this->dataObjectHelper->populateWithArray($address, $addressData, AddressInterface::class);
- $address->setCustomerId($customerId);
+ $customer = $this->getCustomer->execute($context);
- try {
- $address = $this->addressRepository->save($address);
- } catch (InputException $e) {
- throw new GraphQlInputException(__($e->getMessage()), $e);
- }
- return $address;
+ $address = $this->createCustomerAddress->execute((int)$customer->getId(), $args['input']);
+ return $this->extractCustomerAddressData->execute($address);
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php
index c3c78a1004da6..91048d4836c80 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/Customer.php
@@ -7,9 +7,9 @@
namespace Magento\CustomerGraphQl\Model\Resolver;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider;
+use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -19,25 +19,25 @@
class Customer implements ResolverInterface
{
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
- * @var CustomerDataProvider
+ * @var ExtractCustomerData
*/
- private $customerDataProvider;
+ private $extractCustomerData;
/**
- * @param CheckCustomerAccount $checkCustomerAccount
- * @param CustomerDataProvider $customerDataProvider
+ * @param GetCustomer $getCustomer
+ * @param ExtractCustomerData $extractCustomerData
*/
public function __construct(
- CheckCustomerAccount $checkCustomerAccount,
- CustomerDataProvider $customerDataProvider
+ GetCustomer $getCustomer,
+ ExtractCustomerData $extractCustomerData
) {
- $this->checkCustomerAccount = $checkCustomerAccount;
- $this->customerDataProvider = $customerDataProvider;
+ $this->getCustomer = $getCustomer;
+ $this->extractCustomerData = $extractCustomerData;
}
/**
@@ -50,13 +50,8 @@ public function resolve(
array $value = null,
array $args = null
) {
- $currentUserId = $context->getUserId();
- $currentUserType = $context->getUserType();
+ $customer = $this->getCustomer->execute($context);
- $this->checkCustomerAccount->execute($currentUserId, $currentUserType);
-
- $currentUserId = (int)$currentUserId;
- $data = $this->customerDataProvider->getCustomerById($currentUserId);
- return $data;
+ return $this->extractCustomerData->execute($customer);
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/CustomerAddresses.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/CustomerAddresses.php
new file mode 100644
index 0000000000000..e6e3887de423c
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/CustomerAddresses.php
@@ -0,0 +1,71 @@
+getCustomer = $getCustomer;
+ $this->extractCustomerAddressData = $extractCustomerAddressData;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+ /** @var Customer $customer */
+ $customer = $value['model'];
+
+ $addressesData = [];
+ $addresses = $customer->getAddresses();
+
+ if (count($addresses)) {
+ foreach ($addresses as $address) {
+ $addressesData[] = $this->extractCustomerAddressData->execute($address);
+ }
+ }
+ return $addressesData;
+ }
+}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomerAddress.php
index 084857c84d5a4..08e82d930f268 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomerAddress.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/DeleteCustomerAddress.php
@@ -7,14 +7,13 @@
namespace Magento\CustomerGraphQl\Model\Resolver;
-use Magento\Customer\Api\AddressRepositoryInterface;
-use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddressForUser;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
+use Magento\CustomerGraphQl\Model\Customer\Address\DeleteCustomerAddress as DeleteCustomerAddressModel;
+use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
-use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
-use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
/**
* Customers address delete, used for GraphQL request processing.
@@ -22,33 +21,33 @@
class DeleteCustomerAddress implements ResolverInterface
{
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
- * @var AddressRepositoryInterface
+ * @var GetCustomerAddress
*/
- private $addressRepository;
+ private $getCustomerAddress;
/**
- * @var GetCustomerAddressForUser
+ * @var DeleteCustomerAddressModel
*/
- private $getCustomerAddressForUser;
+ private $deleteCustomerAddress;
/**
- * @param CheckCustomerAccount $checkCustomerAccount
- * @param AddressRepositoryInterface $addressRepository
- * @param GetCustomerAddressForUser $getCustomerAddressForUser
+ * @param GetCustomer $getCustomer
+ * @param GetCustomerAddress $getCustomerAddress
+ * @param DeleteCustomerAddressModel $deleteCustomerAddress
*/
public function __construct(
- CheckCustomerAccount $checkCustomerAccount,
- AddressRepositoryInterface $addressRepository,
- GetCustomerAddressForUser $getCustomerAddressForUser
+ GetCustomer $getCustomer,
+ GetCustomerAddress $getCustomerAddress,
+ DeleteCustomerAddressModel $deleteCustomerAddress
) {
- $this->checkCustomerAccount = $checkCustomerAccount;
- $this->addressRepository = $addressRepository;
- $this->getCustomerAddressForUser = $getCustomerAddressForUser;
+ $this->getCustomer = $getCustomer;
+ $this->getCustomerAddress = $getCustomerAddress;
+ $this->deleteCustomerAddress = $deleteCustomerAddress;
}
/**
@@ -61,36 +60,14 @@ public function resolve(
array $value = null,
array $args = null
) {
- $currentUserId = $context->getUserId();
- $currentUserType = $context->getUserType();
-
- $this->checkCustomerAccount->execute($currentUserId, $currentUserType);
+ if (!isset($args['id']) || empty($args['id'])) {
+ throw new GraphQlInputException(__('Address "id" value should be specified'));
+ }
- return $this->deleteCustomerAddress((int)$currentUserId, (int)$args['id']);
- }
+ $customer = $this->getCustomer->execute($context);
+ $address = $this->getCustomerAddress->execute((int)$args['id'], (int)$customer->getId());
- /**
- * Delete customer address
- *
- * @param int $customerId
- * @param int $addressId
- * @return bool
- * @throws GraphQlAuthorizationException
- * @throws GraphQlNoSuchEntityException
- */
- private function deleteCustomerAddress($customerId, $addressId)
- {
- $address = $this->getCustomerAddressForUser->execute($addressId, $customerId);
- if ($address->isDefaultBilling()) {
- throw new GraphQlAuthorizationException(
- __('Customer Address %1 is set as default billing address and can not be deleted', [$addressId])
- );
- }
- if ($address->isDefaultShipping()) {
- throw new GraphQlAuthorizationException(
- __('Customer Address %1 is set as default shipping address and can not be deleted', [$addressId])
- );
- }
- return $this->addressRepository->delete($address);
+ $this->deleteCustomerAddress->execute($address);
+ return true;
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/IsEmailAvailable.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/IsEmailAvailable.php
new file mode 100644
index 0000000000000..ddf1aec275ece
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/IsEmailAvailable.php
@@ -0,0 +1,60 @@
+accountManagement = $accountManagement;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($args['email']) || empty($args['email'])) {
+ throw new GraphQlInputException(__('"Email should be specified'));
+ }
+
+ try {
+ $isEmailAvailable = $this->accountManagement->isEmailAvailable($args['email']);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+
+ return [
+ 'is_email_available' => $isEmailAvailable
+ ];
+ }
+}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php
index c0bd864b3ee09..fc5691d97cbfe 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/IsSubscribed.php
@@ -7,7 +7,7 @@
namespace Magento\CustomerGraphQl\Model\Resolver;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -19,9 +19,9 @@
class IsSubscribed implements ResolverInterface
{
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
* @var SubscriberFactory
@@ -29,14 +29,14 @@ class IsSubscribed implements ResolverInterface
private $subscriberFactory;
/**
- * @param CheckCustomerAccount $checkCustomerAccount
+ * @param GetCustomer $getCustomer
* @param SubscriberFactory $subscriberFactory
*/
public function __construct(
- CheckCustomerAccount $checkCustomerAccount,
+ GetCustomer $getCustomer,
SubscriberFactory $subscriberFactory
) {
- $this->checkCustomerAccount = $checkCustomerAccount;
+ $this->getCustomer = $getCustomer;
$this->subscriberFactory = $subscriberFactory;
}
@@ -50,12 +50,9 @@ public function resolve(
array $value = null,
array $args = null
) {
- $currentUserId = $context->getUserId();
- $currentUserType = $context->getUserType();
+ $customer = $this->getCustomer->execute($context);
- $this->checkCustomerAccount->execute($currentUserId, $currentUserType);
-
- $status = $this->subscriberFactory->create()->loadByCustomerId((int)$currentUserId)->isSubscribed();
+ $status = $this->subscriberFactory->create()->loadByCustomerId((int)$customer->getId())->isSubscribed();
return (bool)$status;
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php
index 3301162dc0088..92779597e5afa 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/RevokeCustomerToken.php
@@ -7,7 +7,7 @@
namespace Magento\CustomerGraphQl\Model\Resolver;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
@@ -19,9 +19,9 @@
class RevokeCustomerToken implements ResolverInterface
{
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
* @var CustomerTokenServiceInterface
@@ -29,14 +29,14 @@ class RevokeCustomerToken implements ResolverInterface
private $customerTokenService;
/**
- * @param CheckCustomerAccount $checkCustomerAccount
+ * @param GetCustomer $getCustomer
* @param CustomerTokenServiceInterface $customerTokenService
*/
public function __construct(
- CheckCustomerAccount $checkCustomerAccount,
+ GetCustomer $getCustomer,
CustomerTokenServiceInterface $customerTokenService
) {
- $this->checkCustomerAccount = $checkCustomerAccount;
+ $this->getCustomer = $getCustomer;
$this->customerTokenService = $customerTokenService;
}
@@ -50,11 +50,8 @@ public function resolve(
array $value = null,
array $args = null
) {
- $currentUserId = $context->getUserId();
- $currentUserType = $context->getUserType();
+ $customer = $this->getCustomer->execute($context);
- $this->checkCustomerAccount->execute($currentUserId, $currentUserType);
-
- return ['result' => $this->customerTokenService->revokeCustomerAccessToken((int)$currentUserId)];
+ return ['result' => $this->customerTokenService->revokeCustomerAccessToken((int)$customer->getId())];
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php
index 50760d2e2e31c..7e06a2a063b4b 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomer.php
@@ -7,12 +7,11 @@
namespace Magento\CustomerGraphQl\Model\Resolver;
-use Magento\CustomerGraphQl\Model\Customer\ChangeSubscriptionStatus;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
-use Magento\CustomerGraphQl\Model\Customer\UpdateCustomerData;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
+use Magento\CustomerGraphQl\Model\Customer\UpdateCustomerAccount;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\CustomerGraphQl\Model\Customer\CustomerDataProvider;
+use Magento\CustomerGraphQl\Model\Customer\ExtractCustomerData;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -22,41 +21,33 @@
class UpdateCustomer implements ResolverInterface
{
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
- * @var UpdateCustomerData
+ * @var UpdateCustomerAccount
*/
- private $updateCustomerData;
+ private $updateCustomerAccount;
/**
- * @var ChangeSubscriptionStatus
+ * @var ExtractCustomerData
*/
- private $changeSubscriptionStatus;
+ private $extractCustomerData;
/**
- * @var CustomerDataProvider
- */
- private $customerDataProvider;
-
- /**
- * @param CheckCustomerAccount $checkCustomerAccount
- * @param UpdateCustomerData $updateCustomerData
- * @param ChangeSubscriptionStatus $changeSubscriptionStatus
- * @param CustomerDataProvider $customerDataProvider
+ * @param GetCustomer $getCustomer
+ * @param UpdateCustomerAccount $updateCustomerAccount
+ * @param ExtractCustomerData $extractCustomerData
*/
public function __construct(
- CheckCustomerAccount $checkCustomerAccount,
- UpdateCustomerData $updateCustomerData,
- ChangeSubscriptionStatus $changeSubscriptionStatus,
- CustomerDataProvider $customerDataProvider
+ GetCustomer $getCustomer,
+ UpdateCustomerAccount $updateCustomerAccount,
+ ExtractCustomerData $extractCustomerData
) {
- $this->checkCustomerAccount = $checkCustomerAccount;
- $this->updateCustomerData = $updateCustomerData;
- $this->changeSubscriptionStatus = $changeSubscriptionStatus;
- $this->customerDataProvider = $customerDataProvider;
+ $this->getCustomer = $getCustomer;
+ $this->updateCustomerAccount = $updateCustomerAccount;
+ $this->extractCustomerData = $extractCustomerData;
}
/**
@@ -73,19 +64,10 @@ public function resolve(
throw new GraphQlInputException(__('"input" value should be specified'));
}
- $currentUserId = $context->getUserId();
- $currentUserType = $context->getUserType();
-
- $this->checkCustomerAccount->execute($currentUserId, $currentUserType);
-
- $currentUserId = (int)$currentUserId;
- $this->updateCustomerData->execute($currentUserId, $args['input']);
-
- if (isset($args['input']['is_subscribed'])) {
- $this->changeSubscriptionStatus->execute($currentUserId, (bool)$args['input']['is_subscribed']);
- }
+ $customer = $this->getCustomer->execute($context);
+ $this->updateCustomerAccount->execute($customer, $args['input']);
- $data = $this->customerDataProvider->getCustomerById($currentUserId);
+ $data = $this->extractCustomerData->execute($customer);
return ['customer' => $data];
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php
index 833ab2e450280..bf41b7ddd10c9 100644
--- a/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php
+++ b/app/code/Magento/CustomerGraphQl/Model/Resolver/UpdateCustomerAddress.php
@@ -7,13 +7,11 @@
namespace Magento\CustomerGraphQl\Model\Resolver;
-use Magento\Customer\Api\AddressRepositoryInterface;
-use Magento\Customer\Api\Data\AddressInterface;
-use Magento\CustomerGraphQl\Model\Customer\Address\CustomerAddressDataProvider;
-use Magento\CustomerGraphQl\Model\Customer\Address\CustomerAddressUpdateDataValidator;
-use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddressForUser;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
-use Magento\Framework\Api\DataObjectHelper;
+use Magento\CustomerGraphQl\Model\Customer\Address\ExtractCustomerAddressData;
+use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress;
+use Magento\CustomerGraphQl\Model\Customer\Address\UpdateCustomerAddress as UpdateCustomerAddressModel;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
@@ -24,57 +22,41 @@
class UpdateCustomerAddress implements ResolverInterface
{
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
- * @var AddressRepositoryInterface
+ * @var GetCustomerAddress
*/
- private $addressRepository;
+ private $getCustomerAddress;
/**
- * @var CustomerAddressDataProvider
+ * @var UpdateCustomerAddressModel
*/
- private $customerAddressDataProvider;
+ private $updateCustomerAddress;
/**
- * @var DataObjectHelper
+ * @var ExtractCustomerAddressData
*/
- private $dataObjectHelper;
+ private $extractCustomerAddressData;
/**
- * @var CustomerAddressUpdateDataValidator
- */
- private $customerAddressUpdateDataValidator;
-
- /**
- * @var GetCustomerAddressForUser
- */
- private $getCustomerAddressForUser;
-
- /**
- * @param CheckCustomerAccount $checkCustomerAccount
- * @param AddressRepositoryInterface $addressRepository
- * @param CustomerAddressDataProvider $customerAddressDataProvider
- * @param DataObjectHelper $dataObjectHelper
- * @param CustomerAddressUpdateDataValidator $customerAddressUpdateDataValidator
- * @param GetCustomerAddressForUser $getCustomerAddressForUser
+ * @param GetCustomer $getCustomer
+ * @param GetCustomerAddress $getCustomerAddress
+ * @param UpdateCustomerAddressModel $updateCustomerAddress
+ * @param ExtractCustomerAddressData $extractCustomerAddressData
*/
public function __construct(
- CheckCustomerAccount $checkCustomerAccount,
- AddressRepositoryInterface $addressRepository,
- CustomerAddressDataProvider $customerAddressDataProvider,
- DataObjectHelper $dataObjectHelper,
- CustomerAddressUpdateDataValidator $customerAddressUpdateDataValidator,
- GetCustomerAddressForUser $getCustomerAddressForUser
+ GetCustomer $getCustomer,
+ GetCustomerAddress $getCustomerAddress,
+ UpdateCustomerAddressModel $updateCustomerAddress,
+ ExtractCustomerAddressData $extractCustomerAddressData
) {
- $this->checkCustomerAccount = $checkCustomerAccount;
- $this->addressRepository = $addressRepository;
- $this->customerAddressDataProvider = $customerAddressDataProvider;
- $this->dataObjectHelper = $dataObjectHelper;
- $this->customerAddressUpdateDataValidator = $customerAddressUpdateDataValidator;
- $this->getCustomerAddressForUser = $getCustomerAddressForUser;
+ $this->getCustomer = $getCustomer;
+ $this->getCustomerAddress = $getCustomerAddress;
+ $this->updateCustomerAddress = $updateCustomerAddress;
+ $this->extractCustomerAddressData = $extractCustomerAddressData;
}
/**
@@ -87,32 +69,18 @@ public function resolve(
array $value = null,
array $args = null
) {
- $currentUserId = $context->getUserId();
- $currentUserType = $context->getUserType();
-
- $this->checkCustomerAccount->execute($currentUserId, $currentUserType);
- $this->customerAddressUpdateDataValidator->validate($args['input']);
-
- $address = $this->updateCustomerAddress((int)$currentUserId, (int)$args['id'], $args['input']);
- return $this->customerAddressDataProvider->getAddressData($address);
- }
+ if (!isset($args['id']) || empty($args['id'])) {
+ throw new GraphQlInputException(__('Address "id" value should be specified'));
+ }
- /**
- * Update customer address
- *
- * @param int $customerId
- * @param int $addressId
- * @param array $addressData
- * @return AddressInterface
- */
- private function updateCustomerAddress(int $customerId, int $addressId, array $addressData)
- {
- $address = $this->getCustomerAddressForUser->execute($addressId, $customerId);
- $this->dataObjectHelper->populateWithArray($address, $addressData, AddressInterface::class);
- if (isset($addressData['region']['region_id'])) {
- $address->setRegionId($address->getRegion()->getRegionId());
+ if (!isset($args['input']) || !is_array($args['input']) || empty($args['input'])) {
+ throw new GraphQlInputException(__('"input" value should be specified'));
}
- return $this->addressRepository->save($address);
+ $customer = $this->getCustomer->execute($context);
+ $address = $this->getCustomerAddress->execute((int)$args['id'], (int)$customer->getId());
+
+ $this->updateCustomerAddress->execute($address, $args['input']);
+ return $this->extractCustomerAddressData->execute($address);
}
}
diff --git a/app/code/Magento/CustomerGraphQl/Test/Mftf/README.md b/app/code/Magento/CustomerGraphQl/Test/Mftf/README.md
deleted file mode 100644
index ae023224f4d9b..0000000000000
--- a/app/code/Magento/CustomerGraphQl/Test/Mftf/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Customer Graph Ql Functional Tests
-
-The Functional Test Module for **Magento Customer Graph Ql** module.
diff --git a/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml
new file mode 100644
index 0000000000000..f1bd3563fda3d
--- /dev/null
+++ b/app/code/Magento/CustomerGraphQl/etc/graphql/di.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ - Magento\Customer\Api\Data\CustomerInterface::EMAIL
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
index f4a417fe2f017..4e4fd1d0fa8ad 100644
--- a/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/CustomerGraphQl/etc/schema.graphqls
@@ -3,6 +3,9 @@
type Query {
customer: Customer @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\Customer") @doc(description: "The customer query returns information about a customer account")
+ isEmailAvailable (
+ email: String! @doc(description: "The new customer email")
+ ): IsEmailAvailableOutput @resolver(class: "Magento\\CustomerGraphQl\\Model\\Resolver\\IsEmailAvailable")
}
type Mutation {
@@ -88,7 +91,8 @@ type Customer @doc(description: "Customer defines the customer name and address
taxvat: String @doc(description: "The customer's Tax/VAT number (for corporate customers)")
id: Int @doc(description: "The ID assigned to the customer")
is_subscribed: Boolean @doc(description: "Indicates whether the customer is subscribed to the company's newsletter") @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\IsSubscribed")
- addresses: [CustomerAddress] @doc(description: "An array containing the customer's shipping and billing addresses")
+ addresses: [CustomerAddress] @doc(description: "An array containing the customer's shipping and billing addresses") @resolver(class: "\\Magento\\CustomerGraphQl\\Model\\Resolver\\CustomerAddresses")
+ gender: Int @doc(description: "The customer's gender(Male - 1, Female - 2)")
}
type CustomerAddress @doc(description: "CustomerAddress contains detailed information about a customer's billing and shipping addresses"){
@@ -126,6 +130,10 @@ type CustomerAddressAttribute {
value: String @doc(description: "Attribute value")
}
+type IsEmailAvailableOutput {
+ is_email_available: Boolean @doc(description: "Is email availabel value")
+}
+
enum CountryCodeEnum @doc(description: "The list of countries codes") {
AF @doc(description: "Afghanistan")
AX @doc(description: "Åland Islands")
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/Dhl/Model/Carrier.php b/app/code/Magento/Dhl/Model/Carrier.php
index 42716d73373a2..1ad8b79ad12f3 100644
--- a/app/code/Magento/Dhl/Model/Carrier.php
+++ b/app/code/Magento/Dhl/Model/Carrier.php
@@ -1964,9 +1964,7 @@ protected function isDutiable($origCountryId, $destCountryId) : bool
{
$this->_checkDomesticStatus($origCountryId, $destCountryId);
- return
- self::DHL_CONTENT_TYPE_NON_DOC == $this->getConfigData('content_type')
- || !$this->_isDomestic;
+ return !$this->_isDomestic;
}
/**
diff --git a/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php b/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php
index ac458024fb65c..c3d82ef34a448 100644
--- a/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php
+++ b/app/code/Magento/Dhl/Test/Unit/Model/CarrierTest.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Dhl\Test\Unit\Model;
use Magento\Dhl\Model\Carrier;
@@ -34,7 +35,6 @@
use Magento\Store\Model\Website;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
use Psr\Log\LoggerInterface;
-use Magento\Store\Model\ScopeInterface;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -81,11 +81,6 @@ class CarrierTest extends \PHPUnit\Framework\TestCase
*/
private $xmlValidator;
- /**
- * @var Request|MockObject
- */
- private $request;
-
/**
* @var LoggerInterface|MockObject
*/
@@ -108,25 +103,6 @@ protected function setUp()
{
$this->objectManager = new ObjectManager($this);
- $this->request = $this->getMockBuilder(Request::class)
- ->disableOriginalConstructor()
- ->setMethods(
- [
- 'getPackages',
- 'getOrigCountryId',
- 'setPackages',
- 'setPackageWeight',
- 'setPackageValue',
- 'setValueWithDiscount',
- 'setPackageCustomsValue',
- 'setFreeMethodWeight',
- 'getPackageWeight',
- 'getFreeMethodWeight',
- 'getOrderShipment',
- ]
- )
- ->getMock();
-
$this->scope = $this->getMockForAbstractClass(ScopeConfigInterface::class);
$this->error = $this->getMockBuilder(Error::class)
@@ -194,7 +170,7 @@ public function scopeConfigGetValue($path)
'carriers/dhl/shipment_days' => 'Mon,Tue,Wed,Thu,Fri,Sat',
'carriers/dhl/intl_shipment_days' => 'Mon,Tue,Wed,Thu,Fri,Sat',
'carriers/dhl/allowed_methods' => 'IE',
- 'carriers/dhl/international_searvice' => 'IE',
+ 'carriers/dhl/international_service' => 'IE',
'carriers/dhl/gateway_url' => 'https://xmlpi-ea.dhl.com/XMLShippingServlet',
'carriers/dhl/id' => 'some ID',
'carriers/dhl/password' => 'some password',
@@ -214,6 +190,11 @@ public function scopeConfigGetValue($path)
return isset($pathMap[$path]) ? $pathMap[$path] : null;
}
+ /**
+ * Prepare shipping label content test
+ *
+ * @throws \ReflectionException
+ */
public function testPrepareShippingLabelContent()
{
$xml = simplexml_load_file(
@@ -225,6 +206,8 @@ public function testPrepareShippingLabelContent()
}
/**
+ * Prepare shipping label content exception test
+ *
* @dataProvider prepareShippingLabelContentExceptionDataProvider
* @expectedException \Magento\Framework\Exception\LocalizedException
* @expectedExceptionMessage Unable to retrieve shipping label
@@ -235,6 +218,8 @@ public function testPrepareShippingLabelContentException(\SimpleXMLElement $xml)
}
/**
+ * Prepare shipping label content exception data provider
+ *
* @return array
*/
public function prepareShippingLabelContentExceptionDataProvider()
@@ -254,8 +239,11 @@ public function prepareShippingLabelContentExceptionDataProvider()
}
/**
+ * Invoke prepare shipping label content
+ *
* @param \SimpleXMLElement $xml
* @return \Magento\Framework\DataObject
+ * @throws \ReflectionException
*/
protected function _invokePrepareShippingLabelContent(\SimpleXMLElement $xml)
{
@@ -283,9 +271,9 @@ public function testCollectRates()
->willReturn($responseXml);
$this->coreDateMock->method('date')
- ->willReturnCallback(function () {
- return date(\DATE_RFC3339);
- });
+ ->willReturnCallback(function () {
+ return date(\DATE_RFC3339);
+ });
$request = $this->objectManager->getObject(RateRequest::class, $requestData);
@@ -338,13 +326,15 @@ public function testCollectRatesErrorMessage()
/**
* Test request to shipment sends valid xml values.
*
+ * @dataProvider requestToShipmentDataProvider
* @param string $origCountryId
* @param string $expectedRegionCode
- * @dataProvider requestToShipmentDataProvider
+ * @param string $destCountryId
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \ReflectionException
*/
- public function testRequestToShipment(string $origCountryId, string $expectedRegionCode)
+ public function testRequestToShipment(string $origCountryId, string $expectedRegionCode, string $destCountryId)
{
- $expectedRequestXml = file_get_contents(__DIR__ . '/_files/shipment_request.xml');
$scopeConfigValueMap = [
['carriers/dhl/account', 'store', null, '1234567890'],
['carriers/dhl/gateway_url', 'store', null, 'https://xmlpi-ea.dhl.com/XMLShippingServlet'],
@@ -361,6 +351,54 @@ public function testRequestToShipment(string $origCountryId, string $expectedReg
$this->httpResponse->method('getBody')
->willReturn(utf8_encode(file_get_contents(__DIR__ . '/_files/response_shipping_label.xml')));
+ $request = $this->getRequest($origCountryId, $destCountryId);
+
+ $this->logger->method('debug')
+ ->with($this->stringContains('**** **** '));
+
+ $result = $this->model->requestToShipment($request);
+
+ $reflectionClass = new \ReflectionObject($this->httpClient);
+ $rawPostData = $reflectionClass->getProperty('raw_post_data');
+ $rawPostData->setAccessible(true);
+
+ $this->assertNotNull($result);
+ $requestXml = $rawPostData->getValue($this->httpClient);
+ $requestElement = new Element($requestXml);
+
+ $messageReference = $requestElement->Request->ServiceHeader->MessageReference->__toString();
+ $this->assertStringStartsWith('MAGE_SHIP_', $messageReference);
+ $this->assertGreaterThanOrEqual(28, strlen($messageReference));
+ $this->assertLessThanOrEqual(32, strlen($messageReference));
+ $requestElement->Request->ServiceHeader->MessageReference = 'MAGE_SHIP_28TO32_Char_CHECKED';
+
+ $this->assertXmlStringEqualsXmlString(
+ $this->getExpectedRequestXml($origCountryId, $destCountryId, $expectedRegionCode)->asXML(),
+ $requestElement->asXML()
+ );
+ }
+
+ /**
+ * Prepare and retrieve request object
+ *
+ * @param string $origCountryId
+ * @param string $destCountryId
+ * @return Request|MockObject
+ */
+ private function getRequest(string $origCountryId, string $destCountryId)
+ {
+ $order = $this->getMockBuilder(Order::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $order->method('getSubtotal')
+ ->willReturn('10.00');
+
+ $shipment = $this->getMockBuilder(Order\Shipment::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $shipment->method('getOrder')
+ ->willReturn($order);
+
$packages = [
'package' => [
'params' => [
@@ -381,62 +419,77 @@ public function testRequestToShipment(string $origCountryId, string $expectedReg
],
];
- $order = $this->getMockBuilder(Order::class)
- ->disableOriginalConstructor()
- ->getMock();
- $order->method('getSubtotal')
- ->willReturn('10.00');
+ $methods = [
+ 'getPackages' => $packages,
+ 'getOrigCountryId' => $origCountryId,
+ 'getDestCountryId' => $destCountryId,
+ 'getShipperAddressCountryCode' => $origCountryId,
+ 'getRecipientAddressCountryCode' => $destCountryId,
+ 'setPackages' => null,
+ 'setPackageWeight' => null,
+ 'setPackageValue' => null,
+ 'setValueWithDiscount' => null,
+ 'setPackageCustomsValue' => null,
+ 'setFreeMethodWeight' => null,
+ 'getPackageWeight' => '0.454000000001',
+ 'getFreeMethodWeight' => '0.454000000001',
+ 'getOrderShipment' => $shipment,
+ ];
- $shipment = $this->getMockBuilder(Order\Shipment::class)
+ /** @var Request|MockObject $request */
+ $request = $this->getMockBuilder(Request::class)
->disableOriginalConstructor()
+ ->setMethods(array_keys($methods))
->getMock();
- $shipment->method('getOrder')
- ->willReturn($order);
- $this->request->method('getPackages')
- ->willReturn($packages);
- $this->request->method('getOrigCountryId')
- ->willReturn($origCountryId);
- $this->request->method('setPackages')
- ->willReturnSelf();
- $this->request->method('setPackageWeight')
- ->willReturnSelf();
- $this->request->method('setPackageValue')
- ->willReturnSelf();
- $this->request->method('setValueWithDiscount')
- ->willReturnSelf();
- $this->request->method('setPackageCustomsValue')
- ->willReturnSelf();
- $this->request->method('setFreeMethodWeight')
- ->willReturnSelf();
- $this->request->method('getPackageWeight')
- ->willReturn('0.454000000001');
- $this->request->method('getFreeMethodWeight')
- ->willReturn('0.454000000001');
- $this->request->method('getOrderShipment')
- ->willReturn($shipment);
+ foreach ($methods as $method => $return) {
+ $return ? $request->method($method)->willReturn($return) : $request->method($method)->willReturnSelf();
+ }
- $this->logger->method('debug')
- ->with($this->stringContains('**** **** '));
+ return $request;
+ }
- $result = $this->model->requestToShipment($this->request);
+ /**
+ * Prepare and retrieve expected request xml element
+ *
+ * @param string $origCountryId
+ * @param string $destCountryId
+ * @return Element
+ */
+ private function getExpectedRequestXml(string $origCountryId, string $destCountryId, string $regionCode)
+ {
+ $requestXmlPath = $origCountryId == $destCountryId
+ ? '/_files/domestic_shipment_request.xml'
+ : '/_files/shipment_request.xml';
- $reflectionClass = new \ReflectionObject($this->httpClient);
- $rawPostData = $reflectionClass->getProperty('raw_post_data');
- $rawPostData->setAccessible(true);
+ $expectedRequestElement = new Element(file_get_contents(__DIR__ . $requestXmlPath));
- $this->assertNotNull($result);
- $requestXml = $rawPostData->getValue($this->httpClient);
- $requestElement = new Element($requestXml);
- $this->assertEquals($expectedRegionCode, $requestElement->RegionCode->__toString());
- $requestElement->RegionCode = 'Checked';
- $messageReference = $requestElement->Request->ServiceHeader->MessageReference->__toString();
- $this->assertStringStartsWith('MAGE_SHIP_', $messageReference);
- $this->assertGreaterThanOrEqual(28, strlen($messageReference));
- $this->assertLessThanOrEqual(32, strlen($messageReference));
- $requestElement->Request->ServiceHeader->MessageReference = 'MAGE_SHIP_28TO32_Char_CHECKED';
- $expectedRequestElement = new Element($expectedRequestXml);
- $this->assertXmlStringEqualsXmlString($expectedRequestElement->asXML(), $requestElement->asXML());
+ $expectedRequestElement->Consignee->CountryCode = $destCountryId;
+ $expectedRequestElement->Consignee->CountryName = $this->getCountryName($destCountryId);
+
+ $expectedRequestElement->Shipper->CountryCode = $origCountryId;
+ $expectedRequestElement->Shipper->CountryName = $this->getCountryName($origCountryId);
+
+ $expectedRequestElement->RegionCode = $regionCode;
+
+ return $expectedRequestElement;
+ }
+
+ /**
+ * Get Country Name by Country Code
+ *
+ * @param string $countryCode
+ * @return string
+ */
+ private function getCountryName($countryCode)
+ {
+ $countryNames = [
+ 'US' => 'United States of America',
+ 'SG' => 'Singapore',
+ 'GB' => 'United Kingdom',
+ 'DE' => 'Germany',
+ ];
+ return $countryNames[$countryCode];
}
/**
@@ -448,17 +501,21 @@ public function requestToShipmentDataProvider()
{
return [
[
- 'GB', 'EU'
+ 'GB', 'EU', 'US'
],
[
- 'SG', 'AP'
+ 'SG', 'AP', 'US'
+ ],
+ [
+ 'DE', 'EU', 'DE'
]
];
}
/**
- * @dataProvider dhlProductsDataProvider
+ * Get DHL products test
*
+ * @dataProvider dhlProductsDataProvider
* @param string $docType
* @param array $products
*/
@@ -468,9 +525,11 @@ public function testGetDhlProducts(string $docType, array $products)
}
/**
+ * DHL products data provider
+ *
* @return array
*/
- public function dhlProductsDataProvider() : array
+ public function dhlProductsDataProvider(): array
{
return [
'doc' => [
@@ -537,6 +596,8 @@ public function testBuildMessageReference($servicePrefix)
}
/**
+ * Build message reference data provider
+ *
* @return array
*/
public function buildMessageReferenceDataProvider()
@@ -581,6 +642,8 @@ public function testBuildSoftwareName($productName)
}
/**
+ * Data provider for testBuildSoftwareName
+ *
* @return array
*/
public function buildSoftwareNameDataProvider()
@@ -610,6 +673,8 @@ public function testBuildSoftwareVersion($productVersion)
}
/**
+ * Data provider for testBuildSoftwareVersion
+ *
* @return array
*/
public function buildSoftwareVersionProvider()
@@ -695,6 +760,8 @@ private function getRateMethodFactory(): MockObject
}
/**
+ * Get config reader
+ *
* @return MockObject
*/
private function getConfigReader(): MockObject
@@ -709,6 +776,8 @@ private function getConfigReader(): MockObject
}
/**
+ * Get read factory
+ *
* @return MockObject
*/
private function getReadFactory(): MockObject
@@ -727,6 +796,8 @@ private function getReadFactory(): MockObject
}
/**
+ * Get store manager
+ *
* @return MockObject
*/
private function getStoreManager(): MockObject
@@ -748,6 +819,8 @@ private function getStoreManager(): MockObject
}
/**
+ * Get carrier helper
+ *
* @return CarrierHelper
*/
private function getCarrierHelper(): CarrierHelper
@@ -766,6 +839,8 @@ private function getCarrierHelper(): CarrierHelper
}
/**
+ * Get HTTP client factory
+ *
* @return MockObject
*/
private function getHttpClientFactory(): MockObject
diff --git a/app/code/Magento/Dhl/Test/Unit/Model/_files/domestic_shipment_request.xml b/app/code/Magento/Dhl/Test/Unit/Model/_files/domestic_shipment_request.xml
new file mode 100644
index 0000000000000..b71c2fa4a7dde
--- /dev/null
+++ b/app/code/Magento/Dhl/Test/Unit/Model/_files/domestic_shipment_request.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+ currentTime
+ MAGE_SHIP_28TO32_Char_CHECKED
+ some ID
+ some password
+
+
+ CHECKED
+ N
+ N
+ EN
+ Y
+
+ 1234567890
+ S
+ 1234567890
+ S
+ 1234567890
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1
+
+
+ shipment reference
+ St
+
+
+ 1
+
+
+ 1
+ CP
+ 0.454
+ 3
+ 3
+ 3
+ item_name
+
+
+ 0.454
+ K
+
+
+ currentTime
+ DHL Parcel
+ DD
+ C
+ CP
+ USD
+
+
+ 1234567890
+
+ 1234567890
+
+
+
+
+
+
+
+
+
+
+ PDF
+
\ No newline at end of file
diff --git a/app/code/Magento/Dhl/etc/adminhtml/system.xml b/app/code/Magento/Dhl/etc/adminhtml/system.xml
index 91ed6c6568a70..37b653225c7b9 100644
--- a/app/code/Magento/Dhl/etc/adminhtml/system.xml
+++ b/app/code/Magento/Dhl/etc/adminhtml/system.xml
@@ -32,7 +32,8 @@
Account Number
- Content Type
+ Content Type (Non Domestic)
+ Whether to use Documents or NonDocuments service for non domestic shipments. (Shipments within the EU are classed as domestic)
Magento\Dhl\Model\Source\Contenttype
@@ -81,18 +82,12 @@
- Allowed Methods
+ Documents Allowed Methods
Magento\Dhl\Model\Source\Method\Doc
-
- D
-
- Allowed Methods
+ Non Documents Allowed Methods
Magento\Dhl\Model\Source\Method\Nondoc
-
- N
-
Ready time
diff --git a/app/code/Magento/Directory/Model/ResourceModel/Currency.php b/app/code/Magento/Directory/Model/ResourceModel/Currency.php
index ffbcce11cb4f6..5339b0c9eb5bd 100644
--- a/app/code/Magento/Directory/Model/ResourceModel/Currency.php
+++ b/app/code/Magento/Directory/Model/ResourceModel/Currency.php
@@ -216,7 +216,7 @@ protected function _getRatesByCode($code, $toCurrencies = null)
$connection = $this->getConnection();
$bind = [':currency_from' => $code];
$select = $connection->select()->from(
- $this->getTable('directory_currency_rate'),
+ $this->_currencyRateTable,
['currency_to', 'rate']
)->where(
'currency_from = :currency_from'
diff --git a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForIndia.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForIndia.php
index 69d500960d3f0..47f4fb0a6c7f3 100644
--- a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForIndia.php
+++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForIndia.php
@@ -13,8 +13,7 @@
use Magento\Framework\Setup\Patch\PatchVersionInterface;
/**
- * Class AddDataForIndia
- * @package Magento\Directory\Setup\Patch\Data
+ * Add Regions for India.
*/
class AddDataForIndia implements DataPatchInterface, PatchVersionInterface
{
@@ -29,7 +28,7 @@ class AddDataForIndia implements DataPatchInterface, PatchVersionInterface
private $dataInstallerFactory;
/**
- * AddDataForCroatia constructor.
+ * AddDataForIndia constructor.
*
* @param ModuleDataSetupInterface $moduleDataSetup
* @param \Magento\Directory\Setup\DataInstallerFactory $dataInstallerFactory
@@ -43,7 +42,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function apply()
{
@@ -103,7 +102,7 @@ private function getDataForIndia()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getDependencies()
{
@@ -113,7 +112,7 @@ public static function getDependencies()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getVersion()
{
@@ -121,7 +120,7 @@ public static function getVersion()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAliases()
{
diff --git a/app/code/Magento/Directory/Setup/Patch/Data/AddDataForMexico.php b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForMexico.php
new file mode 100644
index 0000000000000..32bdf90800d6b
--- /dev/null
+++ b/app/code/Magento/Directory/Setup/Patch/Data/AddDataForMexico.php
@@ -0,0 +1,127 @@
+moduleDataSetup = $moduleDataSetup;
+ $this->dataInstallerFactory = $dataInstallerFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ /** @var DataInstaller $dataInstaller */
+ $dataInstaller = $this->dataInstallerFactory->create();
+ $dataInstaller->addCountryRegions(
+ $this->moduleDataSetup->getConnection(),
+ $this->getDataForMexico()
+ );
+ }
+
+ /**
+ * Mexican states data.
+ *
+ * @return array
+ */
+ private function getDataForMexico()
+ {
+ return [
+ ['MX', 'AGU', 'Aguascalientes'],
+ ['MX', 'BCN', 'Baja California'],
+ ['MX', 'BCS', 'Baja California Sur'],
+ ['MX', 'CAM', 'Campeche'],
+ ['MX', 'CHP', 'Chiapas'],
+ ['MX', 'CHH', 'Chihuahua'],
+ ['MX', 'CMX', 'Ciudad de México'],
+ ['MX', 'COA', 'Coahuila'],
+ ['MX', 'COL', 'Colima'],
+ ['MX', 'DUR', 'Durango'],
+ ['MX', 'MEX', 'Estado de México'],
+ ['MX', 'GUA', 'Guanajuato'],
+ ['MX', 'GRO', 'Guerrero'],
+ ['MX', 'HID', 'Hidalgo'],
+ ['MX', 'JAL', 'Jalisco'],
+ ['MX', 'MIC', 'Michoacán'],
+ ['MX', 'MOR', 'Morelos'],
+ ['MX', 'NAY', 'Nayarit'],
+ ['MX', 'NLE', 'Nuevo León'],
+ ['MX', 'OAX', 'Oaxaca'],
+ ['MX', 'PUE', 'Puebla'],
+ ['MX', 'QUE', 'Querétaro'],
+ ['MX', 'ROO', 'Quintana Roo'],
+ ['MX', 'SLP', 'San Luis Potosí'],
+ ['MX', 'SIN', 'Sinaloa'],
+ ['MX', 'SON', 'Sonora'],
+ ['MX', 'TAB', 'Tabasco'],
+ ['MX', 'TAM', 'Tamaulipas'],
+ ['MX', 'TLA', 'Tlaxcala'],
+ ['MX', 'VER', 'Veracruz'],
+ ['MX', 'YUC', 'Yucatán'],
+ ['MX', 'ZAC', 'Zacatecas']
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [
+ InitializeDirectoryData::class,
+ AddDataForAustralia::class,
+ AddDataForCroatia::class,
+ AddDataForIndia::class,
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getVersion()
+ {
+ return '2.0.4';
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
index 40ef6975fad8b..8da1920f9a444 100644
--- a/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/DirectoryGraphQl/etc/schema.graphqls
@@ -10,8 +10,10 @@ type Query {
type Currency {
base_currency_code: String
base_currency_symbol: String
- default_display_currecy_code: String
- default_display_currecy_symbol: String
+ default_display_currecy_code: String @deprecated(reason: "Symbol was missed. Use `default_display_currency_code`.")
+ default_display_currency_code: String
+ default_display_currecy_symbol: String @deprecated(reason: "Symbol was missed. Use `default_display_currency_symbol`.")
+ default_display_currency_symbol: String
available_currency_codes: [String]
exchange_rates: [ExchangeRate]
}
diff --git a/app/code/Magento/Downloadable/Helper/Download.php b/app/code/Magento/Downloadable/Helper/Download.php
index 150a5ec474f36..6b7db3af51195 100644
--- a/app/code/Magento/Downloadable/Helper/Download.php
+++ b/app/code/Magento/Downloadable/Helper/Download.php
@@ -13,6 +13,7 @@
/**
* Downloadable Products Download Helper
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class Download extends \Magento\Framework\App\Helper\AbstractHelper
{
@@ -186,19 +187,20 @@ public function getFileSize()
public function getContentType()
{
$this->_getHandle();
- if ($this->_linkType == self::LINK_TYPE_FILE) {
- if (function_exists(
- 'mime_content_type'
- ) && ($contentType = mime_content_type(
- $this->_workingDirectory->getAbsolutePath($this->_resourceFile)
- ))
+ if ($this->_linkType === self::LINK_TYPE_FILE) {
+ if (function_exists('mime_content_type')
+ && ($contentType = mime_content_type(
+ $this->_workingDirectory->getAbsolutePath($this->_resourceFile)
+ ))
) {
return $contentType;
- } else {
- return $this->_downloadableFile->getFileType($this->_resourceFile);
}
- } elseif ($this->_linkType == self::LINK_TYPE_URL) {
- return $this->_handle->stat($this->_resourceFile)['type'];
+ return $this->_downloadableFile->getFileType($this->_resourceFile);
+ }
+ if ($this->_linkType === self::LINK_TYPE_URL) {
+ return (is_array($this->_handle->stat($this->_resourceFile)['type'])
+ ? end($this->_handle->stat($this->_resourceFile)['type'])
+ : $this->_handle->stat($this->_resourceFile)['type']);
}
return $this->_contentType;
}
@@ -252,10 +254,21 @@ public function setResource($resourceFile, $linkType = self::LINK_TYPE_FILE)
);
}
}
-
+
$this->_resourceFile = $resourceFile;
+
+ /**
+ * check header for urls
+ */
+ if ($linkType === self::LINK_TYPE_URL) {
+ $headers = array_change_key_case(get_headers($this->_resourceFile, 1), CASE_LOWER);
+ if (isset($headers['location'])) {
+ $this->_resourceFile = is_array($headers['location']) ? current($headers['location'])
+ : $headers['location'];
+ }
+ }
+
$this->_linkType = $linkType;
-
return $this;
}
diff --git a/app/code/Magento/Downloadable/Model/ResourceModel/Link.php b/app/code/Magento/Downloadable/Model/ResourceModel/Link.php
index 24d1d7831c9e3..df8427bdde652 100644
--- a/app/code/Magento/Downloadable/Model/ResourceModel/Link.php
+++ b/app/code/Magento/Downloadable/Model/ResourceModel/Link.php
@@ -5,10 +5,6 @@
*/
namespace Magento\Downloadable\Model\ResourceModel;
-use Magento\Catalog\Api\Data\ProductInterface;
-use Magento\Framework\App\ObjectManager;
-use Magento\Framework\EntityManager\MetadataPool;
-
/**
* Downloadable Product Samples resource model
*
@@ -17,11 +13,6 @@
*/
class Link extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
- /**
- * @var MetadataPool
- */
- private $metadataPool;
-
/**
* Catalog data
*
@@ -210,10 +201,7 @@ public function getSearchableData($productId, $storeId)
[]
)->join(
['cpe' => $this->getTable('catalog_product_entity')],
- sprintf(
- 'cpe.entity_id = m.product_id',
- $this->getMetadataPool()->getMetadata(ProductInterface::class)->getLinkField()
- ),
+ 'cpe.entity_id = m.product_id',
[]
)->joinLeft(
['st' => $this->getTable('downloadable_link_title')],
@@ -228,22 +216,12 @@ public function getSearchableData($productId, $storeId)
}
/**
+ * Get Currency model.
+ *
* @return \Magento\Directory\Model\Currency
*/
protected function _createCurrency()
{
return $this->_currencyFactory->create();
}
-
- /**
- * Get MetadataPool instance
- * @return MetadataPool
- */
- private function getMetadataPool()
- {
- if (!$this->metadataPool) {
- $this->metadataPool = ObjectManager::getInstance()->get(MetadataPool::class);
- }
- return $this->metadataPool;
- }
}
diff --git a/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
new file mode 100644
index 0000000000000..b84dbff1154cf
--- /dev/null
+++ b/app/code/Magento/Downloadable/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDropdownOrderSection.xml b/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
new file mode 100644
index 0000000000000..39b4e303d5165
--- /dev/null
+++ b/app/code/Magento/Downloadable/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
new file mode 100755
index 0000000000000..55740af4d834f
--- /dev/null
+++ b/app/code/Magento/Downloadable/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php
index a352c4bdf7bc3..c4824f913daf8 100644
--- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php
+++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Links.php
@@ -3,22 +3,24 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier;
-use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Catalog\Model\Locator\LocatorInterface;
+use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Downloadable\Model\Product\Type;
-use Magento\Downloadable\Model\Source\TypeUpload;
use Magento\Downloadable\Model\Source\Shareable;
-use Magento\Store\Model\StoreManagerInterface;
+use Magento\Downloadable\Model\Source\TypeUpload;
use Magento\Framework\Stdlib\ArrayManager;
-use Magento\Ui\Component\DynamicRows;
use Magento\Framework\UrlInterface;
+use Magento\Store\Model\StoreManagerInterface;
use Magento\Ui\Component\Container;
+use Magento\Ui\Component\DynamicRows;
use Magento\Ui\Component\Form;
/**
- * Class adds a grid with links
+ * Class adds a grid with links.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Links extends AbstractModifier
@@ -86,7 +88,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyData(array $data)
{
@@ -101,7 +103,7 @@ public function modifyData(array $data)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function modifyMeta(array $meta)
@@ -160,6 +162,8 @@ public function modifyMeta(array $meta)
}
/**
+ * Returns configuration for dynamic rows
+ *
* @return array
*/
protected function getDynamicRows()
@@ -180,6 +184,8 @@ protected function getDynamicRows()
}
/**
+ * Returns Record column configuration
+ *
* @return array
*/
protected function getRecord()
@@ -221,6 +227,8 @@ protected function getRecord()
}
/**
+ * Returns Title column configuration
+ *
* @return array
*/
protected function getTitleColumn()
@@ -232,12 +240,14 @@ protected function getTitleColumn()
'label' => __('Title'),
'showLabel' => false,
'dataScope' => '',
+ 'sortOrder' => 10,
];
$titleField['arguments']['data']['config'] = [
'formElement' => Form\Element\Input::NAME,
'componentType' => Form\Field::NAME,
'dataType' => Form\Element\DataType\Text::NAME,
'dataScope' => 'title',
+ 'labelVisible' => false,
'validation' => [
'required-entry' => true,
],
@@ -247,6 +257,8 @@ protected function getTitleColumn()
}
/**
+ * Returns Price column configuration
+ *
* @return array
*/
protected function getPriceColumn()
@@ -258,6 +270,7 @@ protected function getPriceColumn()
'label' => __('Price'),
'showLabel' => false,
'dataScope' => '',
+ 'sortOrder' => 20,
];
$priceField['arguments']['data']['config'] = [
'formElement' => Form\Element\Input::NAME,
@@ -265,6 +278,7 @@ protected function getPriceColumn()
'dataType' => Form\Element\DataType\Number::NAME,
'component' => 'Magento_Downloadable/js/components/price-handler',
'dataScope' => 'price',
+ 'labelVisible' => false,
'addbefore' => $this->locator->getStore()->getBaseCurrency()
->getCurrencySymbol(),
'validation' => [
@@ -281,6 +295,8 @@ protected function getPriceColumn()
}
/**
+ * Returns File column configuration
+ *
* @return array
*/
protected function getFileColumn()
@@ -292,6 +308,7 @@ protected function getFileColumn()
'label' => __('File'),
'showLabel' => false,
'dataScope' => '',
+ 'sortOrder' => 30,
];
$fileTypeField['arguments']['data']['config'] = [
'formElement' => Form\Element\Select::NAME,
@@ -302,6 +319,7 @@ protected function getFileColumn()
'options' => $this->typeUpload->toOptionArray(),
'typeFile' => 'links_file',
'typeUrl' => 'link_url',
+ 'labelVisible' => false,
];
$fileLinkUrl['arguments']['data']['config'] = [
'formElement' => Form\Element\Input::NAME,
@@ -344,6 +362,8 @@ protected function getFileColumn()
}
/**
+ * Returns Sample column configuration
+ *
* @return array
*/
protected function getSampleColumn()
@@ -355,6 +375,7 @@ protected function getSampleColumn()
'label' => __('Sample'),
'showLabel' => false,
'dataScope' => '',
+ 'sortOrder' => 40,
];
$sampleTypeField['arguments']['data']['config'] = [
'formElement' => Form\Element\Select::NAME,
@@ -363,6 +384,7 @@ protected function getSampleColumn()
'dataType' => Form\Element\DataType\Text::NAME,
'dataScope' => 'sample.type',
'options' => $this->typeUpload->toOptionArray(),
+ 'labelVisible' => false,
'typeFile' => 'sample_file',
'typeUrl' => 'sample_url',
];
@@ -382,6 +404,7 @@ protected function getSampleColumn()
'component' => 'Magento_Downloadable/js/components/file-uploader',
'elementTmpl' => 'Magento_Downloadable/components/file-uploader',
'fileInputName' => 'link_samples',
+ 'labelVisible' => false,
'uploaderConfig' => [
'url' => $this->urlBuilder->addSessionParam()->getUrl(
'adminhtml/downloadable_file/upload',
@@ -403,6 +426,8 @@ protected function getSampleColumn()
}
/**
+ * Returns Sharable columns configuration
+ *
* @return array
*/
protected function getShareableColumn()
@@ -413,6 +438,7 @@ protected function getShareableColumn()
'componentType' => Form\Field::NAME,
'dataType' => Form\Element\DataType\Number::NAME,
'dataScope' => 'is_shareable',
+ 'sortOrder' => 50,
'options' => $this->shareable->toOptionArray(),
];
@@ -420,6 +446,8 @@ protected function getShareableColumn()
}
/**
+ * Returns max downloads column configuration
+ *
* @return array
*/
protected function getMaxDownloadsColumn()
@@ -431,12 +459,14 @@ protected function getMaxDownloadsColumn()
'label' => __('Max. Downloads'),
'showLabel' => false,
'dataScope' => '',
+ 'sortOrder' => 60,
];
$numberOfDownloadsField['arguments']['data']['config'] = [
'formElement' => Form\Element\Input::NAME,
'componentType' => Form\Field::NAME,
'dataType' => Form\Element\DataType\Number::NAME,
'dataScope' => 'number_of_downloads',
+ 'labelVisible' => false,
'value' => 0,
'validation' => [
'validate-zero-or-greater' => true,
diff --git a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Samples.php b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Samples.php
index 1587163ba8121..81c5918eb24ae 100644
--- a/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Samples.php
+++ b/app/code/Magento/Downloadable/Ui/DataProvider/Product/Form/Modifier/Samples.php
@@ -3,21 +3,23 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Downloadable\Ui\DataProvider\Product\Form\Modifier;
-use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Catalog\Model\Locator\LocatorInterface;
+use Magento\Catalog\Ui\DataProvider\Product\Form\Modifier\AbstractModifier;
use Magento\Downloadable\Model\Product\Type;
use Magento\Downloadable\Model\Source\TypeUpload;
-use Magento\Store\Model\StoreManagerInterface;
use Magento\Framework\Stdlib\ArrayManager;
-use Magento\Ui\Component\DynamicRows;
use Magento\Framework\UrlInterface;
+use Magento\Store\Model\StoreManagerInterface;
use Magento\Ui\Component\Container;
+use Magento\Ui\Component\DynamicRows;
use Magento\Ui\Component\Form;
/**
- * Class adds a grid with samples
+ * Class adds a grid with samples.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Samples extends AbstractModifier
@@ -77,7 +79,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function modifyData(array $data)
{
@@ -90,7 +92,7 @@ public function modifyData(array $data)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function modifyMeta(array $meta)
@@ -135,6 +137,8 @@ public function modifyMeta(array $meta)
}
/**
+ * Returns configuration for dynamic rows
+ *
* @return array
*/
protected function getDynamicRows()
@@ -155,6 +159,8 @@ protected function getDynamicRows()
}
/**
+ * Returns Record column configuration
+ *
* @return array
*/
protected function getRecord()
@@ -192,6 +198,8 @@ protected function getRecord()
}
/**
+ * Returns Title column configuration
+ *
* @return array
*/
protected function getTitleColumn()
@@ -203,12 +211,14 @@ protected function getTitleColumn()
'showLabel' => false,
'label' => __('Title'),
'dataScope' => '',
+ 'sortOrder' => 10,
];
$titleField['arguments']['data']['config'] = [
'formElement' => Form\Element\Input::NAME,
'componentType' => Form\Field::NAME,
'dataType' => Form\Element\DataType\Text::NAME,
'dataScope' => 'title',
+ 'labelVisible' => false,
'validation' => [
'required-entry' => true,
],
@@ -218,6 +228,8 @@ protected function getTitleColumn()
}
/**
+ * Returns Sample column configuration
+ *
* @return array
*/
protected function getSampleColumn()
@@ -229,6 +241,7 @@ protected function getSampleColumn()
'label' => __('File'),
'showLabel' => false,
'dataScope' => '',
+ 'sortOrder' => 20,
];
$sampleType['arguments']['data']['config'] = [
'formElement' => Form\Element\Select::NAME,
@@ -236,6 +249,7 @@ protected function getSampleColumn()
'component' => 'Magento_Downloadable/js/components/upload-type-handler',
'dataType' => Form\Element\DataType\Text::NAME,
'dataScope' => 'type',
+ 'labelVisible' => false,
'options' => $this->typeUpload->toOptionArray(),
'typeFile' => 'sample_file',
'typeUrl' => 'sample_url',
@@ -246,6 +260,7 @@ protected function getSampleColumn()
'dataType' => Form\Element\DataType\Text::NAME,
'dataScope' => 'sample_url',
'placeholder' => 'URL',
+ 'labelVisible' => false,
'validation' => [
'required-entry' => true,
'validate-url' => true,
diff --git a/app/code/Magento/Downloadable/view/frontend/layout/catalog_product_view_type_downloadable.xml b/app/code/Magento/Downloadable/view/frontend/layout/catalog_product_view_type_downloadable.xml
index 45e5f0b8da72d..f851558c1a563 100644
--- a/app/code/Magento/Downloadable/view/frontend/layout/catalog_product_view_type_downloadable.xml
+++ b/app/code/Magento/Downloadable/view/frontend/layout/catalog_product_view_type_downloadable.xml
@@ -26,17 +26,6 @@
-
-
-
- product.price.render.default
- final_price
- 1
- item_view
- copy-
-
-
-
diff --git a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Main/AbstractMain.php b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Main/AbstractMain.php
index c5a18a3de99c6..be9d2700664c7 100644
--- a/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Main/AbstractMain.php
+++ b/app/code/Magento/Eav/Block/Adminhtml/Attribute/Edit/Main/AbstractMain.php
@@ -4,15 +4,13 @@
* See COPYING.txt for license details.
*/
-/**
- * Product attribute add/edit form main tab
- *
- * @author Magento Core Team
- */
namespace Magento\Eav\Block\Adminhtml\Attribute\Edit\Main;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
+/**
+ * Product attribute add/edit form main tab
+ */
abstract class AbstractMain extends \Magento\Backend\Block\Widget\Form\Generic
{
/**
@@ -110,7 +108,6 @@ protected function _prepareForm()
/** @var \Magento\Framework\Data\Form $form */
$form = $this->_formFactory->create(
-
['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
);
@@ -280,10 +277,11 @@ protected function _initFormValues()
}
/**
- * Processing block html after rendering
+ * Processing block html after rendering.
+ *
* Adding js block to the end of this block
*
- * @param string $html
+ * @param string $html
* @return string
*/
protected function _afterToHtml($html)
diff --git a/app/code/Magento/Eav/Model/Attribute/Data/File.php b/app/code/Magento/Eav/Model/Attribute/Data/File.php
index f14e01accef07..a52c88261166e 100644
--- a/app/code/Magento/Eav/Model/Attribute/Data/File.php
+++ b/app/code/Magento/Eav/Model/Attribute/Data/File.php
@@ -146,7 +146,7 @@ protected function _validateByRules($value)
return $this->_fileValidator->getMessages();
}
- if (empty($value['tmp_name'])) {
+ if (!empty($value['tmp_name']) && !file_exists($value['tmp_name'])) {
return [__('"%1" is not a valid file.', $label)];
}
diff --git a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
index d0a5e8de53ae9..1fd71e446e6bb 100644
--- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
+++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
@@ -1683,14 +1683,16 @@ public function saveAttribute(DataObject $object, $attributeCode)
$connection->beginTransaction();
try {
- $select = $connection->select()->from($table, 'value_id')->where($where);
- $origValueId = $connection->fetchOne($select);
+ $select = $connection->select()->from($table, ['value_id', 'value'])->where($where);
+ $origRow = $connection->fetchRow($select);
+ $origValueId = $origRow['value_id'] ?? false;
+ $origValue = $origRow['value'] ?? null;
if ($origValueId === false && $newValue !== null) {
$this->_insertAttribute($object, $attribute, $newValue);
} elseif ($origValueId !== false && $newValue !== null) {
$this->_updateAttribute($object, $attribute, $origValueId, $newValue);
- } elseif ($origValueId !== false && $newValue === null) {
+ } elseif ($origValueId !== false && $newValue === null && $origValue !== null) {
$connection->delete($table, $where);
}
$this->_processAttributeValues();
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
index 0b6ac2b998de7..2e55964560588 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/Group.php
@@ -3,11 +3,16 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Eav\Model\Entity\Attribute;
+use Magento\Eav\Api\Data\AttributeGroupExtensionInterface;
use Magento\Framework\Api\AttributeValueFactory;
+use Magento\Framework\Exception\LocalizedException;
/**
+ * Entity attribute group model
+ *
* @api
* @method int getSortOrder()
* @method \Magento\Eav\Model\Entity\Attribute\Group setSortOrder(int $value)
@@ -27,6 +32,11 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements
*/
private $translitFilter;
+ /**
+ * @var array
+ */
+ private $reservedSystemNames = [];
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -35,7 +45,8 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements
* @param \Magento\Framework\Filter\Translit $translitFilter
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
- * @param array $data
+ * @param array $data (optional)
+ * @param array $reservedSystemNames (optional)
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -45,7 +56,8 @@ public function __construct(
\Magento\Framework\Filter\Translit $translitFilter,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ array $reservedSystemNames = []
) {
parent::__construct(
$context,
@@ -56,6 +68,7 @@ public function __construct(
$resourceCollection,
$data
);
+ $this->reservedSystemNames = $reservedSystemNames;
$this->translitFilter = $translitFilter;
}
@@ -74,6 +87,7 @@ protected function _construct()
* Checks if current attribute group exists
*
* @return bool
+ * @throws LocalizedException
* @codeCoverageIgnore
*/
public function itemExists()
@@ -85,6 +99,7 @@ public function itemExists()
* Delete groups
*
* @return $this
+ * @throws LocalizedException
* @codeCoverageIgnore
*/
public function deleteGroups()
@@ -110,9 +125,10 @@ public function beforeSave()
),
'-'
);
- if (empty($attributeGroupCode)) {
+ $isReservedSystemName = in_array(strtolower($attributeGroupCode), $this->reservedSystemNames);
+ if (empty($attributeGroupCode) || $isReservedSystemName) {
// in the following code md5 is not used for security purposes
- $attributeGroupCode = md5($groupName);
+ $attributeGroupCode = md5(strtolower($groupName));
}
$this->setAttributeGroupCode($attributeGroupCode);
}
@@ -121,7 +137,8 @@ public function beforeSave()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ *
* @codeCoverageIgnoreStart
*/
public function getAttributeGroupId()
@@ -130,7 +147,7 @@ public function getAttributeGroupId()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAttributeGroupName()
{
@@ -138,7 +155,7 @@ public function getAttributeGroupName()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAttributeSetId()
{
@@ -146,7 +163,7 @@ public function getAttributeSetId()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeGroupId($attributeGroupId)
{
@@ -154,7 +171,7 @@ public function setAttributeGroupId($attributeGroupId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeGroupName($attributeGroupName)
{
@@ -162,7 +179,7 @@ public function setAttributeGroupName($attributeGroupName)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setAttributeSetId($attributeSetId)
{
@@ -170,9 +187,9 @@ public function setAttributeSetId($attributeSetId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
- * @return \Magento\Eav\Api\Data\AttributeGroupExtensionInterface|null
+ * @return AttributeGroupExtensionInterface|null
*/
public function getExtensionAttributes()
{
@@ -180,14 +197,13 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*
- * @param \Magento\Eav\Api\Data\AttributeGroupExtensionInterface $extensionAttributes
+ * @param AttributeGroupExtensionInterface $extensionAttributes
* @return $this
*/
- public function setExtensionAttributes(
- \Magento\Eav\Api\Data\AttributeGroupExtensionInterface $extensionAttributes
- ) {
+ public function setExtensionAttributes(AttributeGroupExtensionInterface $extensionAttributes)
+ {
return $this->_setExtensionAttributes($extensionAttributes);
}
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php b/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
index 56188ab997b76..36ad026029056 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/Source/AbstractSource.php
@@ -73,7 +73,7 @@ public function getOptionText($value)
}
}
// End
- if (isset($options[$value])) {
+ if (is_scalar($value) && isset($options[$value])) {
return $options[$value];
}
return false;
diff --git a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php
index 0eb87374f3ba3..dad420ea0b375 100644
--- a/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php
+++ b/app/code/Magento/Eav/Model/Entity/Collection/AbstractCollection.php
@@ -6,10 +6,12 @@
namespace Magento\Eav\Model\Entity\Collection;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection\SourceProviderInterface;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\DB\Select;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
/**
* Entity/Attribute/Model - collection abstract
@@ -125,9 +127,15 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn
protected $_resourceHelper;
/**
+ * @deprecated To instantiate resource models, use $resourceModelPool instead
+ *
* @var \Magento\Framework\Validator\UniversalFactory
*/
protected $_universalFactory;
+ /**
+ * @var ResourceModelPoolInterface
+ */
+ private $resourceModelPool;
/**
* @param \Magento\Framework\Data\Collection\EntityFactory $entityFactory
@@ -140,6 +148,7 @@ abstract class AbstractCollection extends AbstractDb implements SourceProviderIn
* @param \Magento\Eav\Model\ResourceModel\Helper $resourceHelper
* @param \Magento\Framework\Validator\UniversalFactory $universalFactory
* @param mixed $connection
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @codeCoverageIgnore
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -152,8 +161,9 @@ public function __construct(
\Magento\Framework\App\ResourceConnection $resource,
\Magento\Eav\Model\EntityFactory $eavEntityFactory,
\Magento\Eav\Model\ResourceModel\Helper $resourceHelper,
- \Magento\Framework\Validator\UniversalFactory $universalFactory,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\Validator\UniversalFactory $universalFactory = null,
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->_eventManager = $eventManager;
$this->_eavConfig = $eavConfig;
@@ -161,6 +171,12 @@ public function __construct(
$this->_eavEntityFactory = $eavEntityFactory;
$this->_resourceHelper = $resourceHelper;
$this->_universalFactory = $universalFactory;
+ if ($resourceModelPool === null) {
+ $resourceModelPool = ObjectManager::getInstance()->get(
+ ResourceModelPoolInterface::class
+ );
+ }
+ $this->resourceModelPool = $resourceModelPool;
parent::__construct($entityFactory, $logger, $fetchStrategy, $connection);
$this->_construct();
$this->setConnection($this->getEntity()->getConnection());
@@ -227,7 +243,7 @@ protected function _initSelect()
protected function _init($model, $entityModel)
{
$this->setItemObjectClass($model);
- $entity = $this->_universalFactory->create($entityModel);
+ $entity = $this->resourceModelPool->get($entityModel);
$this->setEntity($entity);
return $this;
@@ -399,7 +415,7 @@ public function addAttributeToFilter($attribute, $condition = null, $joinType =
*/
public function addFieldToFilter($attribute, $condition = null)
{
- return $this->addAttributeToFilter($attribute, $condition);
+ return $this->addAttributeToFilter($attribute, $condition, 'left');
}
/**
diff --git a/app/code/Magento/Eav/Model/Entity/Collection/VersionControl/AbstractCollection.php b/app/code/Magento/Eav/Model/Entity/Collection/VersionControl/AbstractCollection.php
index e626ed35eb1e9..2181c6bc1be05 100644
--- a/app/code/Magento/Eav/Model/Entity/Collection/VersionControl/AbstractCollection.php
+++ b/app/code/Magento/Eav/Model/Entity/Collection/VersionControl/AbstractCollection.php
@@ -5,10 +5,13 @@
*/
namespace Magento\Eav\Model\Entity\Collection\VersionControl;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
* Class Abstract Collection
* @api
* @since 100.0.2
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
abstract class AbstractCollection extends \Magento\Eav\Model\Entity\Collection\AbstractCollection
{
@@ -27,8 +30,9 @@ abstract class AbstractCollection extends \Magento\Eav\Model\Entity\Collection\A
* @param \Magento\Eav\Model\EntityFactory $eavEntityFactory
* @param \Magento\Eav\Model\ResourceModel\Helper $resourceHelper
* @param \Magento\Framework\Validator\UniversalFactory $universalFactory
- * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot,
+ * @param \Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot ,
* @param mixed $connection
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @codeCoverageIgnore
*/
@@ -43,7 +47,8 @@ public function __construct(
\Magento\Eav\Model\ResourceModel\Helper $resourceHelper,
\Magento\Framework\Validator\UniversalFactory $universalFactory,
\Magento\Framework\Model\ResourceModel\Db\VersionControl\Snapshot $entitySnapshot,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->entitySnapshot = $entitySnapshot;
@@ -57,7 +62,8 @@ public function __construct(
$eavEntityFactory,
$resourceHelper,
$universalFactory,
- $connection
+ $connection,
+ $resourceModelPool
);
}
diff --git a/app/code/Magento/Eav/Model/Entity/Type.php b/app/code/Magento/Eav/Model/Entity/Type.php
index 444d58bf546d4..b24f86c73e8df 100644
--- a/app/code/Magento/Eav/Model/Entity/Type.php
+++ b/app/code/Magento/Eav/Model/Entity/Type.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Eav\Model\Entity;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
* Entity type model
*
@@ -75,10 +78,16 @@ class Type extends \Magento\Framework\Model\AbstractModel
protected $_storeFactory;
/**
+ * @deprecated To instantiate resource models, use $resourceModelPool instead
* @var \Magento\Framework\Validator\UniversalFactory
*/
protected $_universalFactory;
+ /**
+ * @var ResourceModelPoolInterface
+ */
+ private $resourceModelPool;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -89,7 +98,9 @@ class Type extends \Magento\Framework\Model\AbstractModel
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @codeCoverageIgnore
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -100,13 +111,20 @@ public function __construct(
\Magento\Framework\Validator\UniversalFactory $universalFactory,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ ResourceModelPoolInterface $resourceModelPool = null
) {
parent::__construct($context, $registry, $resource, $resourceCollection, $data);
$this->_attributeFactory = $attributeFactory;
$this->_attSetFactory = $attSetFactory;
$this->_storeFactory = $storeFactory;
$this->_universalFactory = $universalFactory;
+ if ($resourceModelPool === null) {
+ $resourceModelPool = ObjectManager::getInstance()->get(
+ ResourceModelPoolInterface::class
+ );
+ }
+ $this->resourceModelPool = $resourceModelPool;
}
/**
@@ -363,7 +381,7 @@ public function getAttributeModel()
*/
public function getEntity()
{
- return $this->_universalFactory->create($this->_data['entity_model']);
+ return $this->resourceModelPool->get($this->_data['entity_model']);
}
/**
diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php
index 221c1a9a7de77..6a8f6ef36bfdb 100644
--- a/app/code/Magento/Eav/Setup/EavSetup.php
+++ b/app/code/Magento/Eav/Setup/EavSetup.php
@@ -11,12 +11,12 @@
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Group\CollectionFactory;
use Magento\Eav\Model\Validator\Attribute\Code;
use Magento\Framework\App\CacheInterface;
-use Magento\Framework\App\ObjectManager;
-use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Setup\ModuleDataSetupInterface;
/**
+ * Base eav setup class.
+ *
* @api
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -112,7 +112,8 @@ public function __construct(
}
/**
- * Gets setup model
+ * Gets setup model.
+ *
* @deprecated
* @return ModuleDataSetupInterface
*/
@@ -579,6 +580,8 @@ public function addAttributeGroup($entityTypeId, $setId, $name, $sortOrder = nul
}
/**
+ * Convert group name to attribute group code.
+ *
* @param string $groupName
* @return string
* @since 100.1.0
@@ -1048,7 +1051,7 @@ private function _updateAttributeAdditionalData($entityTypeId, $id, $field, $val
return $this;
}
}
-
+
$attributeId = $this->getAttributeId($entityTypeId, $id);
if (false === $attributeId) {
throw new LocalizedException(__('Attribute with ID: "%1" does not exist', $id));
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
index d4c91e98d9608..1584b922abaa9 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/GroupTest.php
@@ -40,6 +40,7 @@ protected function setUp()
'resource' => $this->resourceMock,
'translitFilter' => $translitFilter,
'context' => $contextMock,
+ 'reservedSystemNames' => ['configurable'],
];
$objectManager = new ObjectManager($this);
$this->model = $objectManager->getObject(
@@ -67,6 +68,8 @@ public function attributeGroupCodeDataProvider()
{
return [
['General Group', 'general-group'],
+ ['configurable', md5('configurable')],
+ ['configurAble', md5('configurable')],
['///', md5('///')],
];
}
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/BooleanTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/BooleanTest.php
index ee972c27aa8a2..8cf5df877a6eb 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/BooleanTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Attribute/Source/BooleanTest.php
@@ -101,13 +101,13 @@ public function addValueSortToCollectionDataProvider()
'expectedJoinCondition' => [
0 => [
'requisites' => ['code_t1' => "table"],
- 'condition' =>
- "e.entity_id=code_t1.entity_id AND code_t1.attribute_id='123' AND code_t1.store_id='0'",
+ 'condition' => "e.entity_id=code_t1.entity_id AND code_t1.attribute_id='123'"
+ . " AND code_t1.store_id='0'",
],
1 => [
'requisites' => ['code_t2' => "table"],
- 'condition' =>
- "e.entity_id=code_t2.entity_id AND code_t2.attribute_id='123' AND code_t2.store_id='12'",
+ 'condition' => "e.entity_id=code_t2.entity_id AND code_t2.attribute_id='123'"
+ . " AND code_t2.store_id='12'",
],
],
'expectedOrder' => 'IF(code_t2.value_id > 0, code_t2.value, code_t1.value) ASC',
@@ -118,13 +118,13 @@ public function addValueSortToCollectionDataProvider()
'expectedJoinCondition' => [
0 => [
'requisites' => ['code_t1' => "table"],
- 'condition' =>
- "e.entity_id=code_t1.entity_id AND code_t1.attribute_id='123' AND code_t1.store_id='0'",
+ 'condition' => "e.entity_id=code_t1.entity_id AND code_t1.attribute_id='123'"
+ . " AND code_t1.store_id='0'",
],
1 => [
'requisites' => ['code_t2' => "table"],
- 'condition' =>
- "e.entity_id=code_t2.entity_id AND code_t2.attribute_id='123' AND code_t2.store_id='12'",
+ 'condition' => "e.entity_id=code_t2.entity_id AND code_t2.attribute_id='123'"
+ . " AND code_t2.store_id='12'",
],
],
'expectedOrder' => 'IF(code_t2.value_id > 0, code_t2.value, code_t1.value) DESC',
@@ -135,8 +135,8 @@ public function addValueSortToCollectionDataProvider()
'expectedJoinCondition' => [
0 => [
'requisites' => ['code_t' => "table"],
- 'condition' =>
- "e.entity_id=code_t.entity_id AND code_t.attribute_id='123' AND code_t.store_id='0'",
+ 'condition' => "e.entity_id=code_t.entity_id AND code_t.attribute_id='123'"
+ . " AND code_t.store_id='0'",
],
],
'expectedOrder' => 'code_t.value DESC',
@@ -147,8 +147,8 @@ public function addValueSortToCollectionDataProvider()
'expectedJoinCondition' => [
0 => [
'requisites' => ['code_t' => "table"],
- 'condition' =>
- "e.entity_id=code_t.entity_id AND code_t.attribute_id='123' AND code_t.store_id='0'",
+ 'condition' => "e.entity_id=code_t.entity_id AND code_t.attribute_id='123'"
+ . " AND code_t.store_id='0'",
],
],
'expectedOrder' => 'code_t.value ASC',
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/AbstractCollectionTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/AbstractCollectionTest.php
index bc4ed7d4bd9e4..c7af666604b39 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/AbstractCollectionTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/AbstractCollectionTest.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Eav\Test\Unit\Model\Entity\Collection;
+use Magento\Framework\Data\Collection\Db\FetchStrategyInterface;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
* AbstractCollection test
*
@@ -28,7 +31,7 @@ class AbstractCollectionTest extends \PHPUnit\Framework\TestCase
protected $loggerMock;
/**
- * @var \Magento\Framework\Data\Collection\Db\FetchStrategyInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var FetchStrategyInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $fetchStrategyMock;
@@ -58,9 +61,9 @@ class AbstractCollectionTest extends \PHPUnit\Framework\TestCase
protected $resourceHelperMock;
/**
- * @var \Magento\Framework\Validator\UniversalFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var ResourceModelPoolInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $validatorFactoryMock;
+ protected $resourceModelPoolMock;
/**
* @var \Magento\Framework\DB\Statement\Pdo\Mysql|\PHPUnit_Framework_MockObject_MockObject
@@ -71,17 +74,11 @@ protected function setUp()
{
$this->coreEntityFactoryMock = $this->createMock(\Magento\Framework\Data\Collection\EntityFactory::class);
$this->loggerMock = $this->createMock(\Psr\Log\LoggerInterface::class);
- $this->fetchStrategyMock = $this->createMock(
- \Magento\Framework\Data\Collection\Db\FetchStrategyInterface::class
- );
+ $this->fetchStrategyMock = $this->createMock(FetchStrategyInterface::class);
$this->eventManagerMock = $this->createMock(\Magento\Framework\Event\ManagerInterface::class);
$this->configMock = $this->createMock(\Magento\Eav\Model\Config::class);
- $this->coreResourceMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class);
$this->resourceHelperMock = $this->createMock(\Magento\Eav\Model\ResourceModel\Helper::class);
- $this->validatorFactoryMock = $this->createMock(\Magento\Framework\Validator\UniversalFactory::class);
$this->entityFactoryMock = $this->createMock(\Magento\Eav\Model\EntityFactory::class);
- /** @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject */
- $connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class);
$this->statementMock = $this->createPartialMock(\Magento\Framework\DB\Statement\Pdo\Mysql::class, ['fetch']);
/** @var $selectMock \Magento\Framework\DB\Select|\PHPUnit_Framework_MockObject_MockObject */
$selectMock = $this->createMock(\Magento\Framework\DB\Select::class);
@@ -92,9 +89,12 @@ protected function setUp()
)->will(
$this->returnCallback([$this, 'getMagentoObject'])
);
+ /** @var \Magento\Framework\DB\Adapter\AdapterInterface|\PHPUnit_Framework_MockObject_MockObject */
+ $connectionMock = $this->createMock(\Magento\Framework\DB\Adapter\Pdo\Mysql::class);
$connectionMock->expects($this->any())->method('select')->will($this->returnValue($selectMock));
$connectionMock->expects($this->any())->method('query')->willReturn($this->statementMock);
+ $this->coreResourceMock = $this->createMock(\Magento\Framework\App\ResourceConnection::class);
$this->coreResourceMock->expects(
$this->any()
)->method(
@@ -106,10 +106,11 @@ protected function setUp()
$entityMock->expects($this->any())->method('getConnection')->will($this->returnValue($connectionMock));
$entityMock->expects($this->any())->method('getDefaultAttributes')->will($this->returnValue([]));
- $this->validatorFactoryMock->expects(
+ $this->resourceModelPoolMock = $this->createMock(ResourceModelPoolInterface::class);
+ $this->resourceModelPoolMock->expects(
$this->any()
)->method(
- 'create'
+ 'get'
)->with(
'test_entity_model' // see \Magento\Eav\Test\Unit\Model\Entity\Collection\AbstractCollectionStub
)->will(
@@ -125,8 +126,9 @@ protected function setUp()
$this->coreResourceMock,
$this->entityFactoryMock,
$this->resourceHelperMock,
- $this->validatorFactoryMock,
- null
+ null,
+ null,
+ $this->resourceModelPoolMock
);
}
diff --git a/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/VersionControl/AbstractCollectionTest.php b/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/VersionControl/AbstractCollectionTest.php
index cce7b43786a76..5b41b9b71f4b5 100644
--- a/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/VersionControl/AbstractCollectionTest.php
+++ b/app/code/Magento/Eav/Test/Unit/Model/Entity/Collection/VersionControl/AbstractCollectionTest.php
@@ -39,7 +39,7 @@ protected function setUp()
\Magento\Eav\Test\Unit\Model\Entity\Collection\VersionControl\AbstractCollectionStub::class,
[
'entityFactory' => $this->coreEntityFactoryMock,
- 'universalFactory' => $this->validatorFactoryMock,
+ 'resourceModelPool' => $this->resourceModelPoolMock,
'entitySnapshot' => $this->entitySnapshot
]
);
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/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php
index b7f21696162dd..26173fcf29b0c 100644
--- a/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php
+++ b/app/code/Magento/Elasticsearch/Elasticsearch5/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php
@@ -19,13 +19,19 @@ class Converter implements ConverterInterface
*/
private const ES_NO_INDEX = false;
+ /**
+ * Text flags for Elasticsearch no analyze index value
+ */
+ private const ES_NO_ANALYZE = false;
+
/**
* Mapping between internal data types and elastic service.
*
* @var array
*/
private $mapping = [
- 'no_index' => self::ES_NO_INDEX,
+ ConverterInterface::INTERNAL_NO_INDEX_VALUE => self::ES_NO_INDEX,
+ ConverterInterface::INTERNAL_NO_ANALYZE_VALUE => self::ES_NO_ANALYZE,
];
/**
diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php
index a6838d831b4bc..0ae347d5791ad 100644
--- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php
+++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Adapter.php
@@ -125,6 +125,7 @@ public function query(RequestInterface $request)
[
'documents' => $rawDocuments,
'aggregations' => $aggregationBuilder->build($request, $rawResponse),
+ 'total' => isset($rawResponse['hits']['total']) ? $rawResponse['hits']['total'] : 0
]
);
return $queryResponse;
diff --git a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php
index db961d86962e9..09968db00aa25 100644
--- a/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php
+++ b/app/code/Magento/Elasticsearch/Elasticsearch5/SearchAdapter/Query/Builder.php
@@ -3,8 +3,11 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Query;
+use Magento\Elasticsearch\SearchAdapter\Query\Builder\Sort;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Search\RequestInterface;
use Magento\Elasticsearch\Model\Config;
use Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver;
@@ -12,6 +15,8 @@
use Magento\Framework\App\ScopeResolverInterface;
/**
+ * Query builder for search adapter.
+ *
* @api
* @since 100.1.0
*/
@@ -41,22 +46,30 @@ class Builder
*/
protected $scopeResolver;
+ /**
+ * @var Sort
+ */
+ protected $sortBuilder;
+
/**
* @param Config $clientConfig
* @param SearchIndexNameResolver $searchIndexNameResolver
* @param AggregationBuilder $aggregationBuilder
* @param ScopeResolverInterface $scopeResolver
+ * @param Sort|null $sortBuilder
*/
public function __construct(
Config $clientConfig,
SearchIndexNameResolver $searchIndexNameResolver,
AggregationBuilder $aggregationBuilder,
- ScopeResolverInterface $scopeResolver
+ ScopeResolverInterface $scopeResolver,
+ Sort $sortBuilder = null
) {
$this->clientConfig = $clientConfig;
$this->searchIndexNameResolver = $searchIndexNameResolver;
$this->aggregationBuilder = $aggregationBuilder;
$this->scopeResolver = $scopeResolver;
+ $this->sortBuilder = $sortBuilder ?: ObjectManager::getInstance()->get(Sort::class);
}
/**
@@ -70,6 +83,7 @@ public function initQuery(RequestInterface $request)
{
$dimension = current($request->getDimensions());
$storeId = $this->scopeResolver->getScope($dimension->getValue())->getId();
+
$searchQuery = [
'index' => $this->searchIndexNameResolver->getIndexName($storeId, $request->getIndex()),
'type' => $this->clientConfig->getEntityType(),
@@ -77,6 +91,7 @@ public function initQuery(RequestInterface $request)
'from' => $request->getFrom(),
'size' => $request->getSize(),
'stored_fields' => ['_id', '_score'],
+ 'sort' => $this->sortBuilder->getSort($request),
'query' => [],
],
];
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php
index 17ebf9c83903e..54586fa357ff0 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter.php
@@ -146,6 +146,16 @@ public function getAttributeCode(): string
return $this->attributeCode;
}
+ /**
+ * Check if attribute is sortable.
+ *
+ * @return bool
+ */
+ public function isSortable(): bool
+ {
+ return (int)$this->getAttribute()->getUsedForSortBy() === 1;
+ }
+
/**
* Check if attribute is defined by user.
*
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter/DummyAttribute.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter/DummyAttribute.php
index b8c0da53c53e0..19b9f85c44b03 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter/DummyAttribute.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/AttributeAdapter/DummyAttribute.php
@@ -59,4 +59,24 @@ public function setCustomAttributes(array $attributes)
{
return $this;
}
+
+ /**
+ * Get property value that guarantee of using an attribute in sort purposes on the storefront.
+ *
+ * @return bool
+ */
+ public function getUsedForSortBy()
+ {
+ return false;
+ }
+
+ /**
+ * Dummy attribute doesn't have backend type.
+ *
+ * @return null
+ */
+ public function getBackendType()
+ {
+ return null;
+ }
}
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php
index 9e2659a757924..268fe00e4c41e 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicField.php
@@ -18,6 +18,8 @@
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface
as FieldNameResolver;
use Magento\Framework\Api\SearchCriteriaBuilder;
+use Magento\Catalog\Model\ResourceModel\Category\Collection;
+use Magento\Framework\App\ObjectManager;
/**
* Provide dynamic fields for product.
@@ -27,10 +29,18 @@ class DynamicField implements FieldProviderInterface
/**
* Category list.
*
+ * @deprecated
* @var CategoryListInterface
*/
private $categoryList;
+ /**
+ * Category collection.
+ *
+ * @var Collection
+ */
+ private $categoryCollection;
+
/**
* Customer group repository.
*
@@ -73,6 +83,7 @@ class DynamicField implements FieldProviderInterface
* @param CategoryListInterface $categoryList
* @param FieldNameResolver $fieldNameResolver
* @param AttributeProvider $attributeAdapterProvider
+ * @param Collection|null $categoryCollection
*/
public function __construct(
FieldTypeConverterInterface $fieldTypeConverter,
@@ -81,7 +92,8 @@ public function __construct(
SearchCriteriaBuilder $searchCriteriaBuilder,
CategoryListInterface $categoryList,
FieldNameResolver $fieldNameResolver,
- AttributeProvider $attributeAdapterProvider
+ AttributeProvider $attributeAdapterProvider,
+ Collection $categoryCollection = null
) {
$this->groupRepository = $groupRepository;
$this->searchCriteriaBuilder = $searchCriteriaBuilder;
@@ -90,6 +102,8 @@ public function __construct(
$this->categoryList = $categoryList;
$this->fieldNameResolver = $fieldNameResolver;
$this->attributeAdapterProvider = $attributeAdapterProvider;
+ $this->categoryCollection = $categoryCollection ?:
+ ObjectManager::getInstance()->get(Collection::class);
}
/**
@@ -98,18 +112,17 @@ public function __construct(
public function getFields(array $context = []): array
{
$allAttributes = [];
- $searchCriteria = $this->searchCriteriaBuilder->create();
- $categories = $this->categoryList->getList($searchCriteria)->getItems();
+ $categoryIds = $this->categoryCollection->getAllIds();
$positionAttribute = $this->attributeAdapterProvider->getByAttributeCode('position');
$categoryNameAttribute = $this->attributeAdapterProvider->getByAttributeCode('category_name');
- foreach ($categories as $category) {
+ foreach ($categoryIds as $categoryId) {
$categoryPositionKey = $this->fieldNameResolver->getFieldName(
$positionAttribute,
- ['categoryId' => $category->getId()]
+ ['categoryId' => $categoryId]
);
$categoryNameKey = $this->fieldNameResolver->getFieldName(
$categoryNameAttribute,
- ['categoryId' => $category->getId()]
+ ['categoryId' => $categoryId]
);
$allAttributes[$categoryPositionKey] = [
'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING),
@@ -121,12 +134,15 @@ public function getFields(array $context = []): array
];
}
+ $searchCriteria = $this->searchCriteriaBuilder->create();
$groups = $this->groupRepository->getList($searchCriteria)->getItems();
$priceAttribute = $this->attributeAdapterProvider->getByAttributeCode('price');
+ $ctx = isset($context['websiteId']) ? ['websiteId' => $context['websiteId']] : [];
foreach ($groups as $group) {
+ $ctx['customerGroupId'] = $group->getId();
$groupPriceKey = $this->fieldNameResolver->getFieldName(
$priceAttribute,
- array_merge($context, ['customerGroupId' => $group->getId()])
+ $ctx
);
$allAttributes[$groupPriceKey] = [
'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_FLOAT),
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php
index 5abe4972e34d0..535ab62dd5991 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/Converter.php
@@ -17,13 +17,19 @@ class Converter implements ConverterInterface
*/
private const ES_NO_INDEX = 'no';
+ /**
+ * Text flags for Elasticsearch no analyze index value
+ */
+ private const ES_NO_ANALYZE = 'not_analyzed';
+
/**
* Mapping between internal data types and elastic service.
*
* @var array
*/
private $mapping = [
- 'no_index' => self::ES_NO_INDEX,
+ ConverterInterface::INTERNAL_NO_INDEX_VALUE => self::ES_NO_INDEX,
+ ConverterInterface::INTERNAL_NO_ANALYZE_VALUE => self::ES_NO_ANALYZE,
];
/**
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/ConverterInterface.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/ConverterInterface.php
index 02c4d29558dad..5ecfd62430032 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/ConverterInterface.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldIndex/ConverterInterface.php
@@ -17,6 +17,7 @@ interface ConverterInterface
*/
public const INTERNAL_NO_INDEX_VALUE = 'no_index';
public const INTERNAL_INDEX_VALUE = 'index';
+ public const INTERNAL_NO_ANALYZE_VALUE = 'no_analyze';
/**
* Get service field index type.
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
index 3208ca7fc6171..5f319daeb3b39 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
@@ -51,22 +51,22 @@ public function __construct(
*/
public function getFieldName(AttributeAdapter $attribute, $context = []): ?string
{
- $fieldType = $this->fieldTypeResolver->getFieldType($attribute);
$attributeCode = $attribute->getAttributeCode();
- $frontendInput = $attribute->getFrontendInput();
if (empty($context['type'])) {
- $fieldName = $attributeCode;
- } elseif ($context['type'] === FieldMapperInterface::TYPE_FILTER) {
+ return $attributeCode;
+ }
+
+ $fieldType = $this->fieldTypeResolver->getFieldType($attribute);
+ $frontendInput = $attribute->getFrontendInput();
+ $fieldName = $attributeCode;
+ if ($context['type'] === FieldMapperInterface::TYPE_FILTER) {
if ($this->isStringServiceFieldType($fieldType)) {
- return $this->getFieldName(
- $attribute,
- array_merge($context, ['type' => FieldMapperInterface::TYPE_QUERY])
- );
+ return $this->getQueryTypeFieldName($frontendInput, $fieldType, $attributeCode);
}
$fieldName = $attributeCode;
} elseif ($context['type'] === FieldMapperInterface::TYPE_QUERY) {
$fieldName = $this->getQueryTypeFieldName($frontendInput, $fieldType, $attributeCode);
- } else {
+ } elseif ($context['type'] == FieldMapperInterface::TYPE_SORT && $attribute->isSortable()) {
$fieldName = 'sort_' . $attributeCode;
}
@@ -115,10 +115,11 @@ private function getQueryTypeFieldName($frontendInput, $fieldType, $attributeCod
private function getRefinedFieldName($frontendInput, $fieldType, $attributeCode)
{
$stringTypeKey = $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING);
+ $keywordTypeKey = $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD);
switch ($frontendInput) {
case 'select':
case 'multiselect':
- return in_array($fieldType, [$stringTypeKey, 'integer'], true)
+ return in_array($fieldType, [$stringTypeKey, $keywordTypeKey, 'integer'], true)
? $attributeCode . '_value'
: $attributeCode;
case 'boolean':
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/SpecialAttribute.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/SpecialAttribute.php
index 4fa16db98639e..652fc853adbcc 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/SpecialAttribute.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/SpecialAttribute.php
@@ -24,7 +24,9 @@ class SpecialAttribute implements ResolverInterface
*/
public function getFieldName(AttributeAdapter $attribute, $context = []): ?string
{
- if (in_array($attribute->getAttributeCode(), ['id', 'sku', 'store_id', 'visibility'], true)) {
+ if (in_array($attribute->getAttributeCode(), ['id', 'sku', 'store_id', 'visibility'], true)
+ && empty($context['type'])
+ ) {
return $attribute->getAttributeCode();
}
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php
index b5c78cbc28f45..6876b23bbb156 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/FieldMapper/Product/FieldProvider/StaticField.php
@@ -7,6 +7,7 @@
namespace Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider;
+use Magento\Framework\App\ObjectManager;
use Magento\Eav\Model\Config;
use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
@@ -19,6 +20,7 @@
as FieldTypeResolver;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ResolverInterface
as FieldIndexResolver;
+use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
/**
* Provide static fields for mapping of product.
@@ -55,6 +57,11 @@ class StaticField implements FieldProviderInterface
*/
private $fieldIndexResolver;
+ /**
+ * @var FieldName\ResolverInterface
+ */
+ private $fieldNameResolver;
+
/**
* @param Config $eavConfig
* @param FieldTypeConverterInterface $fieldTypeConverter
@@ -62,6 +69,7 @@ class StaticField implements FieldProviderInterface
* @param FieldTypeResolver $fieldTypeResolver
* @param FieldIndexResolver $fieldIndexResolver
* @param AttributeProvider $attributeAdapterProvider
+ * @param FieldName\ResolverInterface|null $fieldNameResolver
*/
public function __construct(
Config $eavConfig,
@@ -69,7 +77,8 @@ public function __construct(
IndexTypeConverterInterface $indexTypeConverter,
FieldTypeResolver $fieldTypeResolver,
FieldIndexResolver $fieldIndexResolver,
- AttributeProvider $attributeAdapterProvider
+ AttributeProvider $attributeAdapterProvider,
+ FieldName\ResolverInterface $fieldNameResolver = null
) {
$this->eavConfig = $eavConfig;
$this->fieldTypeConverter = $fieldTypeConverter;
@@ -77,6 +86,8 @@ public function __construct(
$this->fieldTypeResolver = $fieldTypeResolver;
$this->fieldIndexResolver = $fieldIndexResolver;
$this->attributeAdapterProvider = $attributeAdapterProvider;
+ $this->fieldNameResolver = $fieldNameResolver ?: ObjectManager::getInstance()
+ ->get(FieldName\ResolverInterface::class);
}
/**
@@ -84,6 +95,7 @@ public function __construct(
*
* @param array $context
* @return array
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getFields(array $context = []): array
{
@@ -92,19 +104,38 @@ public function getFields(array $context = []): array
foreach ($attributes as $attribute) {
$attributeAdapter = $this->attributeAdapterProvider->getByAttributeCode($attribute->getAttributeCode());
- $code = $attributeAdapter->getAttributeCode();
+ $fieldName = $this->fieldNameResolver->getFieldName($attributeAdapter);
- $allAttributes[$code] = [
+ $allAttributes[$fieldName] = [
'type' => $this->fieldTypeResolver->getFieldType($attributeAdapter),
];
$index = $this->fieldIndexResolver->getFieldIndex($attributeAdapter);
if (null !== $index) {
- $allAttributes[$code]['index'] = $index;
+ $allAttributes[$fieldName]['index'] = $index;
+ }
+
+ if ($attributeAdapter->isSortable()) {
+ $sortFieldName = $this->fieldNameResolver->getFieldName(
+ $attributeAdapter,
+ ['type' => FieldMapperInterface::TYPE_SORT]
+ );
+ $allAttributes[$fieldName]['fields'][$sortFieldName] = [
+ 'type' => $this->fieldTypeConverter->convert(
+ FieldTypeConverterInterface::INTERNAL_DATA_TYPE_KEYWORD
+ ),
+ 'index' => $this->indexTypeConverter->convert(
+ IndexTypeConverterInterface::INTERNAL_NO_ANALYZE_VALUE
+ )
+ ];
}
if ($attributeAdapter->isComplexType()) {
- $allAttributes[$code . '_value'] = [
+ $childFieldName = $this->fieldNameResolver->getFieldName(
+ $attributeAdapter,
+ ['type' => FieldMapperInterface::TYPE_QUERY]
+ );
+ $allAttributes[$childFieldName] = [
'type' => $this->fieldTypeConverter->convert(FieldTypeConverterInterface::INTERNAL_DATA_TYPE_STRING)
];
}
diff --git a/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php b/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php
new file mode 100644
index 0000000000000..b3f8a56110f8d
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Model/Advanced/ProductCollectionPrepareStrategy.php
@@ -0,0 +1,41 @@
+catalogConfig = $catalogConfig;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function prepare(Collection $collection)
+ {
+ $collection
+ ->addAttributeToSelect($this->catalogConfig->getProductAttributes())
+ ->addMinimalPrice()
+ ->addTaxPercents();
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Model/Config.php b/app/code/Magento/Elasticsearch/Model/Config.php
index dc08a72a9feb3..387db07c62f90 100644
--- a/app/code/Magento/Elasticsearch/Model/Config.php
+++ b/app/code/Magento/Elasticsearch/Model/Config.php
@@ -25,8 +25,6 @@ class Config implements ClientOptionsInterface
*/
const ENGINE_NAME = 'elasticsearch';
- private const ENGINE_NAME_5 = 'elasticsearch5';
-
/**
* Elasticsearch Entity type
*/
@@ -64,23 +62,31 @@ class Config implements ClientOptionsInterface
private $engineResolver;
/**
- * Constructor
+ * Available Elasticsearch engines.
*
+ * @var array
+ */
+ private $engineList;
+
+ /**
* @param ScopeConfigInterface $scopeConfig
* @param ClientResolver|null $clientResolver
* @param EngineResolverInterface|null $engineResolver
* @param string|null $prefix
+ * @param array $engineList
*/
public function __construct(
ScopeConfigInterface $scopeConfig,
ClientResolver $clientResolver = null,
EngineResolverInterface $engineResolver = null,
- $prefix = null
+ $prefix = null,
+ $engineList = []
) {
$this->scopeConfig = $scopeConfig;
$this->clientResolver = $clientResolver ?: ObjectManager::getInstance()->get(ClientResolver::class);
$this->engineResolver = $engineResolver ?: ObjectManager::getInstance()->get(EngineResolverInterface::class);
$this->prefix = $prefix ?: $this->clientResolver->getCurrentEngine();
+ $this->engineList = $engineList;
}
/**
@@ -138,7 +144,7 @@ public function getSearchConfigData($field, $storeId = null)
*/
public function isElasticsearchEnabled()
{
- return in_array($this->engineResolver->getCurrentSearchEngine(), [self::ENGINE_NAME, self::ENGINE_NAME_5]);
+ return in_array($this->engineResolver->getCurrentSearchEngine(), $this->engineList);
}
/**
diff --git a/app/code/Magento/Elasticsearch/Model/Layer/Category/ItemCollectionProvider.php b/app/code/Magento/Elasticsearch/Model/Layer/Category/ItemCollectionProvider.php
new file mode 100644
index 0000000000000..ef2992e1fff9f
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Model/Layer/Category/ItemCollectionProvider.php
@@ -0,0 +1,53 @@
+engineResolver = $engineResolver;
+ $this->factories = $factories;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCollection(\Magento\Catalog\Model\Category $category)
+ {
+ if (!isset($this->factories[$this->engineResolver->getCurrentSearchEngine()])) {
+ throw new \DomainException('Undefined factory ' . $this->engineResolver->getCurrentSearchEngine());
+ }
+ $collection = $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create();
+ $collection->addCategoryFilter($category);
+
+ return $collection;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php b/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php
new file mode 100644
index 0000000000000..7d2a30b493d30
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Model/Layer/Search/ItemCollectionProvider.php
@@ -0,0 +1,52 @@
+engineResolver = $engineResolver;
+ $this->factories = $factories;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getCollection(\Magento\Catalog\Model\Category $category)
+ {
+ if (!isset($this->factories[$this->engineResolver->getCurrentSearchEngine()])) {
+ throw new \DomainException('Undefined factory ' . $this->engineResolver->getCurrentSearchEngine());
+ }
+ $collection = $this->factories[$this->engineResolver->getCurrentSearchEngine()]->create();
+
+ return $collection;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php
new file mode 100644
index 0000000000000..255c7885e84b9
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchCriteriaResolver.php
@@ -0,0 +1,87 @@
+builder = $builder;
+ $this->collection = $collection;
+ $this->searchRequestName = $searchRequestName;
+ $this->currentPage = $currentPage;
+ $this->size = $size;
+ $this->orders = $orders;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(): SearchCriteria
+ {
+ $this->builder->setPageSize($this->size);
+ $searchCriteria = $this->builder->create();
+ $searchCriteria->setRequestName($this->searchRequestName);
+ $searchCriteria->setSortOrders($this->orders);
+ $searchCriteria->setCurrentPage($this->currentPage - 1);
+
+ return $searchCriteria;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php
new file mode 100644
index 0000000000000..3ae2d384782c3
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/SearchResultApplier.php
@@ -0,0 +1,59 @@
+collection = $collection;
+ $this->searchResult = $searchResult;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ if (empty($this->searchResult->getItems())) {
+ $this->collection->getSelect()->where('NULL');
+ return;
+ }
+ $ids = [];
+ foreach ($this->searchResult->getItems() as $item) {
+ $ids[] = (int)$item->getId();
+ }
+ $this->collection->setPageSize(null);
+ $this->collection->getSelect()->where('e.entity_id IN (?)', $ids);
+ $orderList = join(',', $ids);
+ $this->collection->getSelect()->reset(\Magento\Framework\DB\Select::ORDER);
+ $this->collection->getSelect()->order("FIELD(e.entity_id,$orderList)");
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php
new file mode 100644
index 0000000000000..109721fcc71a9
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Model/ResourceModel/Fulltext/Collection/TotalRecordsResolver.php
@@ -0,0 +1,38 @@
+searchResult = $searchResult;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(): ?int
+ {
+ return $this->searchResult->getTotalCount();
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php b/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php
index 43b2bfe553a98..6f9ef552351fd 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Adapter.php
@@ -68,8 +68,7 @@ public function __construct(
}
/**
- * @param RequestInterface $request
- * @return QueryResponse
+ * @inheritdoc
*/
public function query(RequestInterface $request)
{
@@ -86,6 +85,7 @@ public function query(RequestInterface $request)
[
'documents' => $rawDocuments,
'aggregations' => $aggregationBuilder->build($request, $rawResponse),
+ 'total' => isset($rawResponse['hits']['total']) ? $rawResponse['hits']['total'] : 0
]
);
return $queryResponse;
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php
index cf75366b9b25e..d0aaa4b3dd572 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.php
@@ -3,19 +3,20 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Elasticsearch\SearchAdapter\Query;
use Magento\Framework\Search\RequestInterface;
-use Magento\Framework\App\ScopeResolverInterface;
use Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Query\Builder as Elasticsearch5Builder;
/**
+ * Query builder for search adapter.
+ *
* @api
* @since 100.1.0
*/
class Builder extends Elasticsearch5Builder
{
-
/**
* Set initial settings for query
*
@@ -34,6 +35,7 @@ public function initQuery(RequestInterface $request)
'from' => $request->getFrom(),
'size' => $request->getSize(),
'fields' => ['_id', '_score'],
+ 'sort' => $this->sortBuilder->getSort($request),
'query' => [],
],
];
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
index e83c49941bcc2..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,8 @@
*/
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;
@@ -34,6 +36,16 @@ class Match implements QueryInterface
*/
protected $preprocessorContainer;
+ /**
+ * @var AttributeProvider
+ */
+ private $attributeProvider;
+
+ /**
+ * @var TypeResolver
+ */
+ private $fieldTypeResolver;
+
/**
* @var ValueTransformerPool
*/
@@ -42,15 +54,23 @@ class Match implements QueryInterface
/**
* @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,
+ 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);
}
@@ -116,14 +136,11 @@ protected function buildQueries(array $matches, array $queryValue)
$value = preg_replace('#^"(.*)"$#m', '$1', $queryValue['value'], -1, $count);
$condition = ($count) ? 'match_phrase' : 'match';
- $attributesTypes = $this->fieldMapper->getAllAttributesTypes();
$transformedTypes = [];
foreach ($matches as $match) {
- $resolvedField = $this->fieldMapper->getFieldName(
- $match['field'],
- ['type' => FieldMapperInterface::TYPE_QUERY]
- );
- $valueTransformer = $this->valueTransformerPool->get($attributesTypes[$resolvedField]['type'] ?? 'text');
+ $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);
@@ -134,6 +151,10 @@ protected function buildQueries(array $matches, array $queryValue)
continue;
}
+ $resolvedField = $this->fieldMapper->getFieldName(
+ $match['field'],
+ ['type' => FieldMapperInterface::TYPE_QUERY]
+ );
$conditions[] = [
'condition' => $queryValue['condition'],
'body' => [
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php
new file mode 100644
index 0000000000000..5ccf202e3812b
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Sort.php
@@ -0,0 +1,106 @@
+ '_score',
+ ];
+
+ /**
+ * @var AttributeProvider
+ */
+ private $attributeAdapterProvider;
+
+ /**
+ * @var FieldNameResolver
+ */
+ private $fieldNameResolver;
+
+ /**
+ * @var array
+ */
+ private $skippedFields;
+
+ /**
+ * @var array
+ */
+ private $map;
+
+ /**
+ * @param AttributeProvider $attributeAdapterProvider
+ * @param FieldNameResolver $fieldNameResolver
+ * @param array $skippedFields
+ * @param array $map
+ */
+ public function __construct(
+ AttributeProvider $attributeAdapterProvider,
+ FieldNameResolver $fieldNameResolver,
+ array $skippedFields = [],
+ array $map = []
+ ) {
+ $this->attributeAdapterProvider = $attributeAdapterProvider;
+ $this->fieldNameResolver = $fieldNameResolver;
+ $this->skippedFields = array_merge(self::DEFAULT_SKIPPED_FIELDS, $skippedFields);
+ $this->map = array_merge(self::DEFAULT_MAP, $map);
+ }
+
+ /**
+ * Prepare sort.
+ *
+ * @param RequestInterface $request
+ * @return array
+ */
+ public function getSort(RequestInterface $request)
+ {
+ $sorts = [];
+ foreach ($request->getSort() as $item) {
+ if (in_array($item['field'], $this->skippedFields)) {
+ continue;
+ }
+ $attribute = $this->attributeAdapterProvider->getByAttributeCode($item['field']);
+ $fieldName = $this->fieldNameResolver->getFieldName($attribute);
+ if (isset($this->map[$fieldName])) {
+ $fieldName = $this->map[$fieldName];
+ }
+ if ($attribute->isSortable() && !($attribute->isFloatType() || $attribute->isIntegerType())) {
+ $suffix = $this->fieldNameResolver->getFieldName(
+ $attribute,
+ ['type' => FieldMapperInterface::TYPE_SORT]
+ );
+ $fieldName .= '.' . $suffix;
+ }
+ $sorts[] = [
+ $fieldName => [
+ 'order' => strtolower($item['direction'])
+ ]
+ ];
+ }
+
+ return $sorts;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/ResponseFactory.php b/app/code/Magento/Elasticsearch/SearchAdapter/ResponseFactory.php
index 33fda48f4af57..0813975ac9a4b 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/ResponseFactory.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/ResponseFactory.php
@@ -76,6 +76,7 @@ public function create($response)
[
'documents' => $documents,
'aggregations' => $aggregations,
+ 'total' => $response['total']
]
);
}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php
index ba5e97aa14b54..7c2a33c05aa08 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/DynamicFieldTest.php
@@ -24,6 +24,7 @@
use Magento\Customer\Api\Data\GroupInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface
as FieldNameResolver;
+use Magento\Catalog\Model\ResourceModel\Category\Collection;
/**
* @SuppressWarnings(PHPMD)
@@ -65,6 +66,11 @@ class DynamicFieldTest extends \PHPUnit\Framework\TestCase
*/
private $categoryList;
+ /**
+ * @var Collection
+ */
+ private $categoryCollection;
+
/**
* @var FieldNameResolver
*/
@@ -100,6 +106,10 @@ protected function setUp()
$this->categoryList = $this->getMockBuilder(CategoryListInterface::class)
->disableOriginalConstructor()
->getMock();
+ $this->categoryCollection = $this->getMockBuilder(Collection::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getAllIds'])
+ ->getMock();
$objectManager = new ObjectManagerHelper($this);
@@ -113,6 +123,7 @@ protected function setUp()
'attributeAdapterProvider' => $this->attributeAdapterProvider,
'categoryList' => $this->categoryList,
'fieldNameResolver' => $this->fieldNameResolver,
+ 'categoryCollection' => $this->categoryCollection,
]
);
}
@@ -124,7 +135,6 @@ protected function setUp()
* @param $groupId
* @param array $expected
* @return void
- * @throws \Magento\Framework\Exception\LocalizedException
*/
public function testGetAllAttributesTypes(
$complexType,
@@ -138,10 +148,6 @@ public function testGetAllAttributesTypes(
$this->searchCriteriaBuilder->expects($this->any())
->method('create')
->willReturn($searchCriteria);
- $categorySearchResults = $this->getMockBuilder(CategorySearchResultsInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['getItems'])
- ->getMockForAbstractClass();
$groupSearchResults = $this->getMockBuilder(GroupSearchResultsInterface::class)
->disableOriginalConstructor()
->setMethods(['getItems'])
@@ -156,19 +162,10 @@ public function testGetAllAttributesTypes(
$groupSearchResults->expects($this->any())
->method('getItems')
->willReturn([$group]);
- $category = $this->getMockBuilder(CategoryInterface::class)
- ->disableOriginalConstructor()
- ->setMethods(['getId'])
- ->getMockForAbstractClass();
- $category->expects($this->any())
- ->method('getId')
- ->willReturn($categoryId);
- $categorySearchResults->expects($this->any())
- ->method('getItems')
- ->willReturn([$category]);
- $this->categoryList->expects($this->any())
- ->method('getList')
- ->willReturn($categorySearchResults);
+
+ $this->categoryCollection->expects($this->any())
+ ->method('getAllIds')
+ ->willReturn([$categoryId]);
$categoryAttributeMock = $this->getMockBuilder(AttributeAdapter::class)
->disableOriginalConstructor()
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
index 4fa99f3bf834d..fd5c87bc9518b 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
@@ -66,6 +66,7 @@ protected function setUp()
* @param $fieldType
* @param $attributeCode
* @param $frontendInput
+ * @param $isSortable
* @param $context
* @param $expected
* @return void
@@ -74,6 +75,7 @@ public function testGetFieldName(
$fieldType,
$attributeCode,
$frontendInput,
+ $isSortable,
$context,
$expected
) {
@@ -82,7 +84,7 @@ public function testGetFieldName(
->willReturn('string');
$attributeMock = $this->getMockBuilder(AttributeAdapter::class)
->disableOriginalConstructor()
- ->setMethods(['getAttributeCode', 'getFrontendInput'])
+ ->setMethods(['getAttributeCode', 'getFrontendInput', 'isSortable'])
->getMock();
$attributeMock->expects($this->any())
->method('getAttributeCode')
@@ -90,6 +92,9 @@ public function testGetFieldName(
$attributeMock->expects($this->any())
->method('getFrontendInput')
->willReturn($frontendInput);
+ $attributeMock->expects($this->any())
+ ->method('isSortable')
+ ->willReturn($isSortable);
$this->fieldTypeResolver->expects($this->any())
->method('getFieldType')
->willReturn($fieldType);
@@ -106,13 +111,13 @@ public function testGetFieldName(
public function getFieldNameProvider()
{
return [
- ['', 'code', '', [], 'code'],
- ['', 'code', '', ['type' => 'default'], 'code'],
- ['string', '*', '', ['type' => 'default'], '_all'],
- ['', 'code', '', ['type' => 'default'], 'code'],
- ['', 'code', 'select', ['type' => 'default'], 'code'],
- ['', 'code', 'boolean', ['type' => 'default'], 'code'],
- ['', 'code', '', ['type' => 'type'], 'sort_code'],
+ ['', 'code', '', false, [], 'code'],
+ ['', 'code', '', false, ['type' => 'default'], 'code'],
+ ['string', '*', '', false, ['type' => 'default'], '_all'],
+ ['', 'code', '', false, ['type' => 'default'], 'code'],
+ ['', 'code', 'select', false, ['type' => 'default'], 'code'],
+ ['', 'code', 'boolean', false, ['type' => 'default'], 'code'],
+ ['', 'code', '', true, ['type' => 'sort'], 'sort_code'],
];
}
}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php
index bf8b601ed43ab..de85b8b6602b8 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/StaticFieldTest.php
@@ -20,6 +20,8 @@
as FieldTypeResolver;
use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\ResolverInterface
as FieldIndexResolver;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface
+ as FieldNameResolver;
/**
* @SuppressWarnings(PHPMD)
@@ -61,6 +63,11 @@ class StaticFieldTest extends \PHPUnit\Framework\TestCase
*/
private $fieldTypeResolver;
+ /**
+ * @var FieldNameResolver
+ */
+ private $fieldNameResolver;
+
/**
* Set up test environment
*
@@ -90,6 +97,10 @@ protected function setUp()
->disableOriginalConstructor()
->setMethods(['getFieldIndex'])
->getMock();
+ $this->fieldNameResolver = $this->getMockBuilder(FieldNameResolver::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getFieldName'])
+ ->getMock();
$objectManager = new ObjectManagerHelper($this);
@@ -102,6 +113,7 @@ protected function setUp()
'attributeAdapterProvider' => $this->attributeAdapterProvider,
'fieldIndexResolver' => $this->fieldIndexResolver,
'fieldTypeResolver' => $this->fieldTypeResolver,
+ 'fieldNameResolver' => $this->fieldNameResolver,
]
);
}
@@ -113,6 +125,10 @@ protected function setUp()
* @param $indexType
* @param $isComplexType
* @param $complexType
+ * @param $isSortable
+ * @param $fieldName
+ * @param $compositeFieldName
+ * @param $sortFieldName
* @param array $expected
* @return void
*/
@@ -122,6 +138,10 @@ public function testGetAllAttributesTypes(
$indexType,
$isComplexType,
$complexType,
+ $isSortable,
+ $fieldName,
+ $compositeFieldName,
+ $sortFieldName,
$expected
) {
$this->fieldTypeResolver->expects($this->any())
@@ -132,7 +152,30 @@ public function testGetAllAttributesTypes(
->willReturn($indexType);
$this->indexTypeConverter->expects($this->any())
->method('convert')
- ->willReturn('no');
+ ->with($this->anything())
+ ->will($this->returnCallback(
+ function ($type) {
+ if ($type === 'no_index') {
+ return 'no';
+ } elseif ($type === 'no_analyze') {
+ return 'not_analyzed';
+ }
+ }
+ ));
+ $this->fieldNameResolver->expects($this->any())
+ ->method('getFieldName')
+ ->with($this->anything())
+ ->will($this->returnCallback(
+ function ($attributeMock, $context) use ($fieldName, $compositeFieldName, $sortFieldName) {
+ if (empty($context)) {
+ return $fieldName;
+ } elseif ($context['type'] === 'sort') {
+ return $sortFieldName;
+ } elseif ($context['type'] === 'text') {
+ return $compositeFieldName;
+ }
+ }
+ ));
$productAttributeMock = $this->getMockBuilder(AbstractAttribute::class)
->setMethods(['getAttributeCode'])
@@ -146,11 +189,14 @@ public function testGetAllAttributesTypes(
$attributeMock = $this->getMockBuilder(AttributeAdapter::class)
->disableOriginalConstructor()
- ->setMethods(['isComplexType', 'getAttributeCode'])
+ ->setMethods(['isComplexType', 'getAttributeCode', 'isSortable'])
->getMock();
$attributeMock->expects($this->any())
->method('isComplexType')
->willReturn($isComplexType);
+ $attributeMock->expects($this->any())
+ ->method('isSortable')
+ ->willReturn($isSortable);
$attributeMock->expects($this->any())
->method('getAttributeCode')
->willReturn($attributeCode);
@@ -166,13 +212,12 @@ function ($type) use ($complexType) {
static $callCount = [];
$callCount[$type] = !isset($callCount[$type]) ? 1 : ++$callCount[$type];
- if ($type === 'string') {
- return 'string';
- }
if ($type === 'string') {
return 'string';
} elseif ($type === 'float') {
return 'float';
+ } elseif ($type === 'keyword') {
+ return 'string';
} else {
return $complexType;
}
@@ -197,6 +242,10 @@ public function attributeProvider()
true,
true,
'text',
+ false,
+ 'category_ids',
+ 'category_ids_value',
+ '',
[
'category_ids' => [
'type' => 'select',
@@ -217,6 +266,10 @@ public function attributeProvider()
'no',
false,
null,
+ false,
+ 'attr_code',
+ '',
+ '',
[
'attr_code' => [
'type' => 'text',
@@ -234,6 +287,10 @@ public function attributeProvider()
null,
false,
null,
+ false,
+ 'attr_code',
+ '',
+ '',
[
'attr_code' => [
'type' => 'text'
@@ -243,6 +300,32 @@ public function attributeProvider()
'index' => 'no'
]
]
+ ],
+ [
+ 'attr_code',
+ 'text',
+ null,
+ false,
+ null,
+ true,
+ 'attr_code',
+ '',
+ 'sort_attr_code',
+ [
+ 'attr_code' => [
+ 'type' => 'text',
+ 'fields' => [
+ 'sort_attr_code' => [
+ 'type' => 'string',
+ 'index' => 'not_analyzed'
+ ]
+ ]
+ ],
+ 'store_id' => [
+ 'type' => 'string',
+ 'index' => 'no'
+ ]
+ ]
]
];
}
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 c8aa3db39bd5e..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,16 +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
*/
@@ -25,6 +38,9 @@ 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')
@@ -37,6 +53,8 @@ protected function setUp()
[
'fieldMapper' => $this->getFieldMapper(),
'preprocessorContainer' => [],
+ 'attributeProvider' => $this->attributeProvider,
+ 'fieldTypeResolver' => $this->fieldTypeResolver,
'valueTransformerPool' => $valueTransformerPoolMock,
]
);
@@ -49,6 +67,16 @@ protected function setUp()
*/
public function testBuild()
{
+ $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');
@@ -80,6 +108,16 @@ public function testBuild()
*/
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 = [
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php
new file mode 100644
index 0000000000000..efd9073694129
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/SortTest.php
@@ -0,0 +1,237 @@
+attributeAdapterProvider = $this->getMockBuilder(AttributeProvider::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getByAttributeCode'])
+ ->getMock();
+ $this->fieldNameResolver = $this->getMockBuilder(FieldNameResolver::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getFieldName'])
+ ->getMock();
+
+ $this->sortBuilder = (new ObjectManager($this))->getObject(
+ Sort::class,
+ [
+ 'attributeAdapterProvider' => $this->attributeAdapterProvider,
+ 'fieldNameResolver' => $this->fieldNameResolver,
+ ]
+ );
+ }
+
+ /**
+ * @SuppressWarnings(PHPMD.UnusedLocalVariable)
+ * @dataProvider getSortProvider
+ * @param array $sortItems
+ * @param $isSortable
+ * @param $isFloatType
+ * @param $isIntegerType
+ * @param $fieldName
+ * @param array $expected
+ */
+ public function testGetSort(
+ array $sortItems,
+ $isSortable,
+ $isFloatType,
+ $isIntegerType,
+ $fieldName,
+ array $expected
+ ) {
+ /** @var MockObject|RequestInterface $request */
+ $request = $this->getMockBuilder(RequestInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getSort'])
+ ->getMockForAbstractClass();
+ $request->expects($this->any())
+ ->method('getSort')
+ ->willReturn($sortItems);
+ $attributeMock = $this->getMockBuilder(AttributeAdapter::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['isSortable', 'isFloatType', 'isIntegerType'])
+ ->getMock();
+ $attributeMock->expects($this->any())
+ ->method('isSortable')
+ ->willReturn($isSortable);
+ $attributeMock->expects($this->any())
+ ->method('isFloatType')
+ ->willReturn($isFloatType);
+ $attributeMock->expects($this->any())
+ ->method('isIntegerType')
+ ->willReturn($isIntegerType);
+ $this->attributeAdapterProvider->expects($this->any())
+ ->method('getByAttributeCode')
+ ->with($this->anything())
+ ->willReturn($attributeMock);
+ $this->fieldNameResolver->expects($this->any())
+ ->method('getFieldName')
+ ->with($this->anything())
+ ->will($this->returnCallback(
+ function ($attribute, $context) use ($fieldName) {
+ if (empty($context)) {
+ return $fieldName;
+ } elseif ($context['type'] === 'sort') {
+ return 'sort_' . $fieldName;
+ }
+ }
+ ));
+
+ $this->assertEquals(
+ $expected,
+ $this->sortBuilder->getSort($request)
+ );
+ }
+
+ /**
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ * @return array
+ */
+ public function getSortProvider()
+ {
+ return [
+ [
+ [
+ [
+ 'field' => 'entity_id',
+ 'direction' => 'DESC'
+ ]
+ ],
+ null,
+ null,
+ null,
+ null,
+ []
+ ],
+ [
+ [
+ [
+ 'field' => 'entity_id',
+ 'direction' => 'DESC'
+ ],
+ [
+ 'field' => 'price',
+ 'direction' => 'DESC'
+ ],
+ ],
+ false,
+ false,
+ false,
+ 'price',
+ [
+ [
+ 'price' => [
+ 'order' => 'desc'
+ ]
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ 'field' => 'entity_id',
+ 'direction' => 'DESC'
+ ],
+ [
+ 'field' => 'price',
+ 'direction' => 'DESC'
+ ],
+ ],
+ true,
+ true,
+ true,
+ 'price',
+ [
+ [
+ 'price' => [
+ 'order' => 'desc'
+ ]
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ 'field' => 'entity_id',
+ 'direction' => 'DESC'
+ ],
+ [
+ 'field' => 'name',
+ 'direction' => 'DESC'
+ ],
+ ],
+ true,
+ false,
+ false,
+ 'name',
+ [
+ [
+ 'name.sort_name' => [
+ 'order' => 'desc'
+ ]
+ ]
+ ]
+ ],
+ [
+ [
+ [
+ 'field' => 'entity_id',
+ 'direction' => 'DESC'
+ ],
+ [
+ 'field' => 'not_eav_attribute',
+ 'direction' => 'DESC'
+ ],
+ ],
+ false,
+ false,
+ false,
+ 'not_eav_attribute',
+ [
+ [
+ 'not_eav_attribute' => [
+ 'order' => 'desc'
+ ]
+ ]
+ ]
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ResponseFactoryTest.php b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ResponseFactoryTest.php
index 9ea241b2fbf5c..d89e420457206 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ResponseFactoryTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/ResponseFactoryTest.php
@@ -79,7 +79,7 @@ public function testCreate()
'itemTwo' => 45,
]
];
- $rawResponse = ['documents' => $documents, 'aggregations' => $aggregations];
+ $rawResponse = ['documents' => $documents, 'aggregations' => $aggregations, 'total' => 2];
$exceptedResponse = [
'documents' => [
@@ -102,6 +102,7 @@ public function testCreate()
'itemTwo' => 45
],
],
+ 'total' => 2,
];
$this->documentFactory->expects($this->at(0))->method('create')
@@ -118,7 +119,11 @@ public function testCreate()
$this->objectManager->expects($this->once())->method('create')
->with(
$this->equalTo(\Magento\Framework\Search\Response\QueryResponse::class),
- $this->equalTo(['documents' => ['document1', 'document2'], 'aggregations' => 'aggregationsData'])
+ $this->equalTo([
+ 'documents' => ['document1', 'document2'],
+ 'aggregations' => 'aggregationsData',
+ 'total' => 2
+ ])
)
->will($this->returnValue('QueryResponseObject'));
diff --git a/app/code/Magento/Elasticsearch/composer.json b/app/code/Magento/Elasticsearch/composer.json
index a821506f5ef6e..c6ac38c1e4005 100644
--- a/app/code/Magento/Elasticsearch/composer.json
+++ b/app/code/Magento/Elasticsearch/composer.json
@@ -12,7 +12,7 @@
"magento/module-store": "*",
"magento/module-catalog-inventory": "*",
"magento/framework": "*",
- "elasticsearch/elasticsearch": "~2.0|~5.1"
+ "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1"
},
"suggest": {
"magento/module-config": "*"
diff --git a/app/code/Magento/Elasticsearch/etc/di.xml b/app/code/Magento/Elasticsearch/etc/di.xml
index 05a67605ba0e6..9732ae8226431 100644
--- a/app/code/Magento/Elasticsearch/etc/di.xml
+++ b/app/code/Magento/Elasticsearch/etc/di.xml
@@ -13,6 +13,124 @@
+
+
+
+ - elasticsearch
+ - elasticsearch5
+
+
+
+
+
+
+ elasticsearchLayerSearchItemCollectionProvider
+ Magento\CatalogSearch\Model\Layer\Search\StateKey
+
+
+
+
+ Magento\Elasticsearch\Model\Layer\Search\Context
+
+
+
+
+ elasticsearchLayerCategoryItemCollectionProvider
+
+
+
+
+ Magento\Elasticsearch\Model\Layer\Category\Context
+
+
+
+
+ quick_search_container
+ elasticsearchSearchCriteriaResolverFactory
+ elasticsearchSearchResultApplier\Factory
+ elasticsearchTotalRecordsResolver\Factory
+
+
+
+
+ elasticsearchFulltextSearchCollection
+
+
+
+
+
+ - Magento\CatalogSearch\Model\ResourceModel\Fulltext\SearchCollectionFactory
+ - elasticsearchFulltextSearchCollectionFactory
+ - elasticsearchFulltextSearchCollectionFactory
+
+
+
+
+
+ catalog_view_container
+ elasticsearchSearchCriteriaResolverFactory
+ elasticsearchSearchResultApplier\Factory
+ elasticsearchTotalRecordsResolver\Factory
+
+
+
+
+ elasticsearchCategoryCollection
+
+
+
+
+
+ - Magento\CatalogSearch\Model\ResourceModel\Fulltext\CollectionFactory
+ - elasticsearchCategoryCollectionFactory
+ - elasticsearchCategoryCollectionFactory
+
+
+
+
+
+ advanced_search_container
+ elasticsearchSearchCriteriaResolverFactory
+ elasticsearchSearchResultApplier\Factory
+ elasticsearchTotalRecordsResolver\Factory
+
+
+
+
+ elasticsearchAdvancedCollection
+
+
+
+
+
+ - elasticsearchAdvancedCollectionFactory
+ - elasticsearchAdvancedCollectionFactory
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy
+ - Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy
+
+
+
+
+
+ Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection\SearchCriteriaResolver
+
+
+
+
+ Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection\SearchResultApplier
+
+
+
+
+ Magento\Elasticsearch\Model\ResourceModel\Fulltext\Collection\TotalRecordsResolver
+
+
@@ -21,7 +139,7 @@
-
+
- Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProviderProxy
@@ -31,7 +149,7 @@
- AdditionalFieldsForElasticsearchDataMapper
+ additionalFieldsProviderForElasticsearch
@@ -360,6 +478,7 @@
Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\Converter
Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldIndex\IndexResolver
\Magento\Elasticsearch\Elasticsearch5\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\Resolver\CompositeResolver
+ \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\ResolverInterface
@@ -417,7 +536,7 @@
- 10000
- - 2147483647
+ - 10000
diff --git a/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php
new file mode 100644
index 0000000000000..1b17db1a00f6e
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/Block/Adminhtml/System/Config/TestConnection.php
@@ -0,0 +1,31 @@
+ 'catalog_search_engine',
+ 'hostname' => 'catalog_search_elasticsearch6_server_hostname',
+ 'port' => 'catalog_search_elasticsearch6_server_port',
+ 'index' => 'catalog_search_elasticsearch6_index_prefix',
+ 'enableAuth' => 'catalog_search_elasticsearch6_enable_auth',
+ 'username' => 'catalog_search_elasticsearch6_username',
+ 'password' => 'catalog_search_elasticsearch6_password',
+ 'timeout' => 'catalog_search_elasticsearch6_server_timeout',
+ ];
+
+ return array_merge(parent::_getFieldMapping(), $fields);
+ }
+}
diff --git a/app/code/Magento/Elasticsearch6/LICENSE.txt b/app/code/Magento/Elasticsearch6/LICENSE.txt
new file mode 100644
index 0000000000000..49525fd99da9c
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/LICENSE.txt
@@ -0,0 +1,48 @@
+
+Open Software License ("OSL") v. 3.0
+
+This Open Software License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
+
+Licensed under the Open Software License version 3.0
+
+ 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
+
+ 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
+
+ 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
+
+ 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, with the proviso that copies of Original Work or Derivative Works that You distribute or communicate shall be licensed under this Open Software License;
+
+ 4. to perform the Original Work publicly; and
+
+ 5. to display the Original Work publicly.
+
+ 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
+
+ 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
+
+ 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
+
+ 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
+
+ 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
+
+ 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
+
+ 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
+
+ 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including 'fair use' or 'fair dealing'). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
+
+ 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
+
+ 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
+
+ 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
+
+ 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
+
+ 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+ 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
+
+ 16. Modification of This License. This License is Copyright (C) 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Open Software License" or "OSL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
\ No newline at end of file
diff --git a/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt b/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt
new file mode 100644
index 0000000000000..f39d641b18a19
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/LICENSE_AFL.txt
@@ -0,0 +1,48 @@
+
+Academic Free License ("AFL") v. 3.0
+
+This Academic Free License (the "License") applies to any original work of authorship (the "Original Work") whose owner (the "Licensor") has placed the following licensing notice adjacent to the copyright notice for the Original Work:
+
+Licensed under the Academic Free License version 3.0
+
+ 1. Grant of Copyright License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, for the duration of the copyright, to do the following:
+
+ 1. to reproduce the Original Work in copies, either alone or as part of a collective work;
+
+ 2. to translate, adapt, alter, transform, modify, or arrange the Original Work, thereby creating derivative works ("Derivative Works") based upon the Original Work;
+
+ 3. to distribute or communicate copies of the Original Work and Derivative Works to the public, under any license of your choice that does not contradict the terms and conditions, including Licensor's reserved rights and remedies, in this Academic Free License;
+
+ 4. to perform the Original Work publicly; and
+
+ 5. to display the Original Work publicly.
+
+ 2. Grant of Patent License. Licensor grants You a worldwide, royalty-free, non-exclusive, sublicensable license, under patent claims owned or controlled by the Licensor that are embodied in the Original Work as furnished by the Licensor, for the duration of the patents, to make, use, sell, offer for sale, have made, and import the Original Work and Derivative Works.
+
+ 3. Grant of Source Code License. The term "Source Code" means the preferred form of the Original Work for making modifications to it and all available documentation describing how to modify the Original Work. Licensor agrees to provide a machine-readable copy of the Source Code of the Original Work along with each copy of the Original Work that Licensor distributes. Licensor reserves the right to satisfy this obligation by placing a machine-readable copy of the Source Code in an information repository reasonably calculated to permit inexpensive and convenient access by You for as long as Licensor continues to distribute the Original Work.
+
+ 4. Exclusions From License Grant. Neither the names of Licensor, nor the names of any contributors to the Original Work, nor any of their trademarks or service marks, may be used to endorse or promote products derived from this Original Work without express prior permission of the Licensor. Except as expressly stated herein, nothing in this License grants any license to Licensor's trademarks, copyrights, patents, trade secrets or any other intellectual property. No patent license is granted to make, use, sell, offer for sale, have made, or import embodiments of any patent claims other than the licensed claims defined in Section 2. No license is granted to the trademarks of Licensor even if such marks are included in the Original Work. Nothing in this License shall be interpreted to prohibit Licensor from licensing under terms different from this License any Original Work that Licensor otherwise would have a right to license.
+
+ 5. External Deployment. The term "External Deployment" means the use, distribution, or communication of the Original Work or Derivative Works in any way such that the Original Work or Derivative Works may be used by anyone other than You, whether those works are distributed or communicated to those persons or made available as an application intended for use over a network. As an express condition for the grants of license hereunder, You must treat any External Deployment by You of the Original Work or a Derivative Work as a distribution under section 1(c).
+
+ 6. Attribution Rights. You must retain, in the Source Code of any Derivative Works that You create, all copyright, patent, or trademark notices from the Source Code of the Original Work, as well as any notices of licensing and any descriptive text identified therein as an "Attribution Notice." You must cause the Source Code for any Derivative Works that You create to carry a prominent Attribution Notice reasonably calculated to inform recipients that You have modified the Original Work.
+
+ 7. Warranty of Provenance and Disclaimer of Warranty. Licensor warrants that the copyright in and to the Original Work and the patent rights granted herein by Licensor are owned by the Licensor or are sublicensed to You under the terms of this License with the permission of the contributor(s) of those copyrights and patent rights. Except as expressly stated in the immediately preceding sentence, the Original Work is provided under this License on an "AS IS" BASIS and WITHOUT WARRANTY, either express or implied, including, without limitation, the warranties of non-infringement, merchantability or fitness for a particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential part of this License. No license to the Original Work is granted by this License except under this disclaimer.
+
+ 8. Limitation of Liability. Under no circumstances and under no legal theory, whether in tort (including negligence), contract, or otherwise, shall the Licensor be liable to anyone for any indirect, special, incidental, or consequential damages of any character arising as a result of this License or the use of the Original Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses. This limitation of liability shall not apply to the extent applicable law prohibits such limitation.
+
+ 9. Acceptance and Termination. If, at any time, You expressly assented to this License, that assent indicates your clear and irrevocable acceptance of this License and all of its terms and conditions. If You distribute or communicate copies of the Original Work or a Derivative Work, You must make a reasonable effort under the circumstances to obtain the express assent of recipients to the terms of this License. This License conditions your rights to undertake the activities listed in Section 1, including your right to create Derivative Works based upon the Original Work, and doing so without honoring these terms and conditions is prohibited by copyright law and international treaty. Nothing in this License is intended to affect copyright exceptions and limitations (including "fair use" or "fair dealing"). This License shall terminate immediately and You may no longer exercise any of the rights granted to You by this License upon your failure to honor the conditions in Section 1(c).
+
+ 10. Termination for Patent Action. This License shall terminate automatically and You may no longer exercise any of the rights granted to You by this License as of the date You commence an action, including a cross-claim or counterclaim, against Licensor or any licensee alleging that the Original Work infringes a patent. This termination provision shall not apply for an action alleging patent infringement by combinations of the Original Work with other software or hardware.
+
+ 11. Jurisdiction, Venue and Governing Law. Any action or suit relating to this License may be brought only in the courts of a jurisdiction wherein the Licensor resides or in which Licensor conducts its primary business, and under the laws of that jurisdiction excluding its conflict-of-law provisions. The application of the United Nations Convention on Contracts for the International Sale of Goods is expressly excluded. Any use of the Original Work outside the scope of this License or after its termination shall be subject to the requirements and penalties of copyright or patent law in the appropriate jurisdiction. This section shall survive the termination of this License.
+
+ 12. Attorneys' Fees. In any action to enforce the terms of this License or seeking damages relating thereto, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. This section shall survive the termination of this License.
+
+ 13. Miscellaneous. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable.
+
+ 14. Definition of "You" in This License. "You" throughout this License, whether in upper or lower case, means an individual or a legal entity exercising rights under, and complying with all of the terms of, this License. For legal entities, "You" includes any entity that controls, is controlled by, or is under common control with you. For purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+
+ 15. Right to Use. You may use the Original Work in all ways not otherwise restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You.
+
+ 16. Modification of This License. This License is Copyright © 2005 Lawrence Rosen. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate your modified version (the "Modified License") and apply it to other original works of authorship subject to the following conditions: (i) You may not indicate in any way that your Modified License is the "Academic Free License" or "AFL" and you may not use those names in the name of your Modified License; (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this License; and (iii) You may not claim that your original works are open source software unless your Modified License has been approved by Open Source Initiative (OSI) and You comply with its license review and certification process.
diff --git a/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php b/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
new file mode 100644
index 0000000000000..7532927f1dc85
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolver.php
@@ -0,0 +1,35 @@
+buildConfig($options);
+ $elasticsearchClient = \Elasticsearch\ClientBuilder::fromConfig($config, true);
+ }
+ $this->client[getmypid()] = $elasticsearchClient;
+ $this->clientOptions = $options;
+ }
+
+ /**
+ * Get Elasticsearch Client
+ *
+ * @return \Elasticsearch\Client
+ */
+ private function getClient()
+ {
+ $pid = getmypid();
+ if (!isset($this->client[$pid])) {
+ $config = $this->buildConfig($this->clientOptions);
+ $this->client[$pid] = \Elasticsearch\ClientBuilder::fromConfig($config, true);
+ }
+ return $this->client[$pid];
+ }
+
+ /**
+ * Ping the Elasticsearch client
+ *
+ * @return bool
+ */
+ public function ping()
+ {
+ if ($this->pingResult === null) {
+ $this->pingResult = $this->getClient()->ping(['client' => ['timeout' => $this->clientOptions['timeout']]]);
+ }
+
+ return $this->pingResult;
+ }
+
+ /**
+ * Validate connection params
+ *
+ * @return bool
+ */
+ public function testConnection()
+ {
+ return $this->ping();
+ }
+
+ /**
+ * Build config.
+ *
+ * @param array $options
+ * @return array
+ */
+ private function buildConfig($options = [])
+ {
+ $host = preg_replace('/http[s]?:\/\//i', '', $options['hostname']);
+ $protocol = parse_url($options['hostname'], PHP_URL_SCHEME);
+ if (!$protocol) {
+ $protocol = 'http';
+ }
+ if (!empty($options['port'])) {
+ $host .= ':' . $options['port'];
+ }
+ if (!empty($options['enableAuth']) && ($options['enableAuth'] == 1)) {
+ $host = sprintf('%s://%s:%s@%s', $protocol, $options['username'], $options['password'], $host);
+ }
+
+ $options['hosts'] = [$host];
+ return $options;
+ }
+
+ /**
+ * Performs bulk query over Elasticsearch index
+ *
+ * @param array $query
+ * @return void
+ */
+ public function bulkQuery($query)
+ {
+ $this->getClient()->bulk($query);
+ }
+
+ /**
+ * Creates an Elasticsearch index.
+ *
+ * @param string $index
+ * @param array $settings
+ * @return void
+ */
+ public function createIndex($index, $settings)
+ {
+ $this->getClient()->indices()->create([
+ 'index' => $index,
+ 'body' => $settings,
+ ]);
+ }
+
+ /**
+ * Delete an Elasticsearch index.
+ *
+ * @param string $index
+ * @return void
+ */
+ public function deleteIndex($index)
+ {
+ $this->getClient()->indices()->delete(['index' => $index]);
+ }
+
+ /**
+ * Check if index is empty.
+ *
+ * @param string $index
+ * @return bool
+ */
+ public function isEmptyIndex($index)
+ {
+ $stats = $this->getClient()->indices()->stats(['index' => $index, 'metric' => 'docs']);
+ if ($stats['indices'][$index]['primaries']['docs']['count'] == 0) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Updates alias.
+ *
+ * @param string $alias
+ * @param string $newIndex
+ * @param string $oldIndex
+ * @return void
+ */
+ public function updateAlias($alias, $newIndex, $oldIndex = '')
+ {
+ $params['body'] = ['actions' => []];
+ if ($oldIndex) {
+ $params['body']['actions'][] = ['remove' => ['alias' => $alias, 'index' => $oldIndex]];
+ }
+ if ($newIndex) {
+ $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]];
+ }
+
+ $this->getClient()->indices()->updateAliases($params);
+ }
+
+ /**
+ * Checks whether Elasticsearch index exists
+ *
+ * @param string $index
+ * @return bool
+ */
+ public function indexExists($index)
+ {
+ return $this->getClient()->indices()->exists(['index' => $index]);
+ }
+
+ /**
+ * Exists alias.
+ *
+ * @param string $alias
+ * @param string $index
+ * @return bool
+ */
+ public function existsAlias($alias, $index = '')
+ {
+ $params = ['name' => $alias];
+ if ($index) {
+ $params['index'] = $index;
+ }
+ return $this->getClient()->indices()->existsAlias($params);
+ }
+
+ /**
+ * Get alias.
+ *
+ * @param string $alias
+ * @return array
+ */
+ public function getAlias($alias)
+ {
+ return $this->getClient()->indices()->getAlias(['name' => $alias]);
+ }
+
+ /**
+ * Add mapping to Elasticsearch index
+ *
+ * @param array $fields
+ * @param string $index
+ * @param string $entityType
+ * @return void
+ */
+ public function addFieldsMapping(array $fields, $index, $entityType)
+ {
+ $params = [
+ 'index' => $index,
+ 'type' => $entityType,
+ 'body' => [
+ $entityType => [
+ 'properties' => [
+ '_search' => [
+ 'type' => 'text'
+ ],
+ ],
+ 'dynamic_templates' => [
+ [
+ 'price_mapping' => [
+ 'match' => 'price_*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'float',
+ 'store' => true,
+ ],
+ ],
+ ],
+ [
+ 'string_mapping' => [
+ 'match' => '*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'text',
+ 'index' => false,
+ 'copy_to' => '_search'
+ ],
+ ],
+ ],
+ [
+ 'position_mapping' => [
+ 'match' => 'position_*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'int',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ];
+
+ foreach ($fields as $field => $fieldInfo) {
+ $params['body'][$entityType]['properties'][$field] = $fieldInfo;
+ }
+
+ $this->getClient()->indices()->putMapping($params);
+ }
+
+ /**
+ * Delete mapping in Elasticsearch index
+ *
+ * @param string $index
+ * @param string $entityType
+ * @return void
+ */
+ public function deleteMapping($index, $entityType)
+ {
+ $this->getClient()->indices()->deleteMapping([
+ 'index' => $index,
+ 'type' => $entityType,
+ ]);
+ }
+
+ /**
+ * Execute search by $query
+ *
+ * @param array $query
+ * @return array
+ */
+ public function query($query)
+ {
+ return $this->getClient()->search($query);
+ }
+
+ /**
+ * Execute suggest query
+ *
+ * @param array $query
+ * @return array
+ */
+ public function suggest($query)
+ {
+ return $this->getClient()->suggest($query);
+ }
+}
diff --git a/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php b/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php
new file mode 100644
index 0000000000000..d05471734bb8f
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/Model/DataProvider/Suggestions.php
@@ -0,0 +1,275 @@
+queryResultFactory = $queryResultFactory;
+ $this->connectionManager = $connectionManager;
+ $this->scopeConfig = $scopeConfig;
+ $this->config = $config;
+ $this->searchIndexNameResolver = $searchIndexNameResolver;
+ $this->storeManager = $storeManager;
+ $this->fieldProvider = $fieldProvider;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getItems(QueryInterface $query)
+ {
+ $result = [];
+ if ($this->isSuggestionsAllowed()) {
+ $isResultsCountEnabled = $this->isResultsCountEnabled();
+
+ foreach ($this->getSuggestions($query) as $suggestion) {
+ $count = null;
+ if ($isResultsCountEnabled) {
+ $count = isset($suggestion['freq']) ? $suggestion['freq'] : null;
+ }
+ $result[] = $this->queryResultFactory->create(
+ [
+ 'queryText' => $suggestion['text'],
+ 'resultsCount' => $count,
+ ]
+ );
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function isResultsCountEnabled()
+ {
+ return $this->scopeConfig->isSetFlag(
+ SuggestedQueriesInterface::SEARCH_SUGGESTION_COUNT_RESULTS_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+ }
+
+ /**
+ * Get Suggestions
+ *
+ * @param QueryInterface $query
+ *
+ * @return array
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ private function getSuggestions(QueryInterface $query)
+ {
+ $suggestions = [];
+ $searchSuggestionsCount = $this->getSearchSuggestionsCount();
+
+ $searchQuery = $this->initQuery($query);
+ $searchQuery = $this->addSuggestFields($searchQuery, $searchSuggestionsCount);
+
+ $result = $this->fetchQuery($searchQuery);
+
+ if (is_array($result)) {
+ foreach ($result['suggest'] ?? [] as $suggest) {
+ foreach ($suggest as $token) {
+ foreach ($token['options'] ?? [] as $key => $suggestion) {
+ $suggestions[$suggestion['score'] . '_' . $key] = $suggestion;
+ }
+ }
+ }
+ ksort($suggestions);
+ $texts = array_unique(array_column($suggestions, 'text'));
+ $suggestions = array_slice(
+ array_intersect_key(array_values($suggestions), $texts),
+ 0,
+ $searchSuggestionsCount
+ );
+ }
+
+ return $suggestions;
+ }
+
+ /**
+ * Init Search Query
+ *
+ * @param string $query
+ *
+ * @return array
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ private function initQuery($query)
+ {
+ $searchQuery = [
+ 'index' => $this->searchIndexNameResolver->getIndexName(
+ $this->storeManager->getStore()->getId(),
+ Config::ELASTICSEARCH_TYPE_DEFAULT
+ ),
+ 'type' => Config::ELASTICSEARCH_TYPE_DEFAULT,
+ 'body' => [
+ 'suggest' => [
+ 'text' => $query->getQueryText()
+ ]
+ ],
+ ];
+
+ return $searchQuery;
+ }
+
+ /**
+ * Build Suggest on searchable fields.
+ *
+ * @param array $searchQuery
+ * @param int $searchSuggestionsCount
+ *
+ * @return array
+ */
+ private function addSuggestFields($searchQuery, $searchSuggestionsCount)
+ {
+ $fields = $this->getSuggestFields();
+ foreach ($fields as $field) {
+ $searchQuery['body']['suggest']['phrase_' . $field] = [
+ 'phrase' => [
+ 'field' => $field,
+ 'analyzer' => 'standard',
+ 'size' => $searchSuggestionsCount,
+ 'max_errors' => 1,
+ 'direct_generator' => [
+ [
+ 'field' => $field,
+ 'min_word_length' => 3,
+ 'min_doc_freq' => 1,
+ ]
+ ],
+ ],
+ ];
+ }
+
+ return $searchQuery;
+ }
+
+ /**
+ * Get fields to build suggest query on.
+ *
+ * @return array
+ */
+ private function getSuggestFields()
+ {
+ $fields = array_filter($this->fieldProvider->getFields(), function ($field) {
+ return (($field['type'] ?? null) === 'text') && (($field['index'] ?? null) !== false);
+ });
+
+ return array_keys($fields);
+ }
+
+ /**
+ * Fetch Query
+ *
+ * @param array $query
+ * @return array
+ */
+ private function fetchQuery(array $query)
+ {
+ return $this->connectionManager->getConnection()->query($query);
+ }
+
+ /**
+ * Get search suggestions Max Count from config
+ *
+ * @return int
+ */
+ private function getSearchSuggestionsCount()
+ {
+ return (int) $this->scopeConfig->getValue(
+ SuggestedQueriesInterface::SEARCH_SUGGESTION_COUNT,
+ ScopeInterface::SCOPE_STORE
+ );
+ }
+
+ /**
+ * Is Search Suggestions Allowed
+ *
+ * @return bool
+ */
+ private function isSuggestionsAllowed()
+ {
+ $isSuggestionsEnabled = $this->scopeConfig->isSetFlag(
+ SuggestedQueriesInterface::SEARCH_SUGGESTION_ENABLED,
+ ScopeInterface::SCOPE_STORE
+ );
+ $isEnabled = $this->config->isElasticsearchEnabled();
+ $isSuggestionsAllowed = ($isEnabled && $isSuggestionsEnabled);
+
+ return $isSuggestionsAllowed;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch6/README.md b/app/code/Magento/Elasticsearch6/README.md
new file mode 100644
index 0000000000000..8bf95ad95d147
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/README.md
@@ -0,0 +1,2 @@
+Magento\Elasticsearch module allows to use Elastic search engine (v6) for product searching capabilities.
+The module implements Magento\Search library interfaces.
diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
new file mode 100644
index 0000000000000..a3c6e7e148f3d
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Adapter/FieldMapper/Product/FieldProvider/FieldName/Resolver/DefaultResolverTest.php
@@ -0,0 +1,123 @@
+fieldTypeResolver = $this->getMockBuilder(FieldTypeResolver::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getFieldType'])
+ ->getMockForAbstractClass();
+ $this->fieldTypeConverter = $this->getMockBuilder(FieldTypeConverterInterface::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['convert'])
+ ->getMockForAbstractClass();
+
+ $this->resolver = $objectManager->getObject(
+ DefaultResolver::class,
+ [
+ 'fieldTypeResolver' => $this->fieldTypeResolver,
+ 'fieldTypeConverter' => $this->fieldTypeConverter
+ ]
+ );
+ }
+
+ /**
+ * @dataProvider getFieldNameProvider
+ * @param $fieldType
+ * @param $attributeCode
+ * @param $frontendInput
+ * @param $isSortable
+ * @param $context
+ * @param $expected
+ * @return void
+ */
+ public function testGetFieldName(
+ $fieldType,
+ $attributeCode,
+ $frontendInput,
+ $isSortable,
+ $context,
+ $expected
+ ) {
+ $this->fieldTypeConverter->expects($this->any())
+ ->method('convert')
+ ->willReturn('string');
+ $attributeMock = $this->getMockBuilder(AttributeAdapter::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getAttributeCode', 'getFrontendInput', 'isSortable'])
+ ->getMock();
+ $attributeMock->expects($this->any())
+ ->method('getAttributeCode')
+ ->willReturn($attributeCode);
+ $attributeMock->expects($this->any())
+ ->method('getFrontendInput')
+ ->willReturn($frontendInput);
+ $attributeMock->expects($this->any())
+ ->method('isSortable')
+ ->willReturn($isSortable);
+ $this->fieldTypeResolver->expects($this->any())
+ ->method('getFieldType')
+ ->willReturn($fieldType);
+
+ $this->assertEquals(
+ $expected,
+ $this->resolver->getFieldName($attributeMock, $context)
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function getFieldNameProvider()
+ {
+ return [
+ ['', 'code', '', false, [], 'code'],
+ ['', 'code', '', false, ['type' => 'default'], 'code'],
+ ['string', '*', '', false, ['type' => 'default'], '_search'],
+ ['', 'code', '', false, ['type' => 'default'], 'code'],
+ ['', 'code', 'select', false, ['type' => 'default'], 'code'],
+ ['', 'code', 'boolean', false, ['type' => 'default'], 'code'],
+ ['', 'code', '', true, ['type' => 'sort'], 'sort_code'],
+ ];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php
new file mode 100644
index 0000000000000..8276d0dd8dbe8
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/Client/ElasticsearchTest.php
@@ -0,0 +1,562 @@
+elasticsearchClientMock = $this->getMockBuilder(\Elasticsearch\Client::class)
+ ->setMethods([
+ 'indices',
+ 'ping',
+ 'bulk',
+ 'search',
+ 'scroll',
+ 'suggest',
+ 'info',
+ ])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->indicesMock = $this->getMockBuilder(\Elasticsearch\Namespaces\IndicesNamespace::class)
+ ->setMethods([
+ 'exists',
+ 'getSettings',
+ 'create',
+ 'delete',
+ 'putMapping',
+ 'deleteMapping',
+ 'stats',
+ 'updateAliases',
+ 'existsAlias',
+ 'getAlias',
+ ])
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->elasticsearchClientMock->expects($this->any())
+ ->method('indices')
+ ->willReturn($this->indicesMock);
+ $this->elasticsearchClientMock->expects($this->any())
+ ->method('ping')
+ ->willReturn(true);
+ $this->elasticsearchClientMock->expects($this->any())
+ ->method('info')
+ ->willReturn(['version' => ['number' => '6.0.0']]);
+
+ $this->objectManager = new ObjectManagerHelper($this);
+ $this->model = $this->objectManager->getObject(
+ \Magento\Elasticsearch6\Model\Client\Elasticsearch::class,
+ [
+ 'options' => $this->getOptions(),
+ 'elasticsearchClient' => $this->elasticsearchClientMock
+ ]
+ );
+ }
+
+ /**
+ * @expectedException \Magento\Framework\Exception\LocalizedException
+ */
+ public function testConstructorOptionsException()
+ {
+ $result = $this->objectManager->getObject(
+ \Magento\Elasticsearch6\Model\Client\Elasticsearch::class,
+ [
+ 'options' => []
+ ]
+ );
+ $this->assertNotNull($result);
+ }
+
+ /**
+ * Test client creation from the list of options
+ */
+ public function testConstructorWithOptions()
+ {
+ $result = $this->objectManager->getObject(
+ \Magento\Elasticsearch6\Model\Client\Elasticsearch::class,
+ [
+ 'options' => $this->getOptions()
+ ]
+ );
+ $this->assertNotNull($result);
+ }
+
+ /**
+ * Test ping functionality
+ */
+ public function testPing()
+ {
+ $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(true);
+ $this->assertEquals(true, $this->model->ping());
+ }
+
+ /**
+ * Test validation of connection parameters
+ */
+ public function testTestConnection()
+ {
+ $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(true);
+ $this->assertEquals(true, $this->model->testConnection());
+ }
+
+ /**
+ * Test validation of connection parameters returns false
+ */
+ public function testTestConnectionFalse()
+ {
+ $this->elasticsearchClientMock->expects($this->once())->method('ping')->willReturn(false);
+ $this->assertEquals(true, $this->model->testConnection());
+ }
+
+ /**
+ * Test validation of connection parameters
+ */
+ public function testTestConnectionPing()
+ {
+ $this->model = $this->objectManager->getObject(
+ \Magento\Elasticsearch6\Model\Client\Elasticsearch::class,
+ [
+ 'options' => $this->getEmptyIndexOption(),
+ 'elasticsearchClient' => $this->elasticsearchClientMock
+ ]
+ );
+
+ $this->model->ping();
+ $this->assertEquals(true, $this->model->testConnection());
+ }
+
+ /**
+ * Test bulkQuery() method
+ */
+ public function testBulkQuery()
+ {
+ $this->elasticsearchClientMock->expects($this->once())
+ ->method('bulk')
+ ->with([]);
+ $this->model->bulkQuery([]);
+ }
+
+ /**
+ * Test createIndex() method, case when such index exists
+ */
+ public function testCreateIndexExists()
+ {
+ $this->indicesMock->expects($this->once())
+ ->method('create')
+ ->with([
+ 'index' => 'indexName',
+ 'body' => [],
+ ]);
+ $this->model->createIndex('indexName', []);
+ }
+
+ /**
+ * Test deleteIndex() method.
+ */
+ public function testDeleteIndex()
+ {
+ $this->indicesMock->expects($this->once())
+ ->method('delete')
+ ->with(['index' => 'indexName']);
+ $this->model->deleteIndex('indexName');
+ }
+
+ /**
+ * Test isEmptyIndex() method.
+ */
+ public function testIsEmptyIndex()
+ {
+ $indexName = 'magento2_index';
+ $stats['indices'][$indexName]['primaries']['docs']['count'] = 0;
+
+ $this->indicesMock->expects($this->once())
+ ->method('stats')
+ ->with(['index' => $indexName, 'metric' => 'docs'])
+ ->willReturn($stats);
+ $this->assertTrue($this->model->isEmptyIndex($indexName));
+ }
+
+ /**
+ * Test isEmptyIndex() method returns false.
+ */
+ public function testIsEmptyIndexFalse()
+ {
+ $indexName = 'magento2_index';
+ $stats['indices'][$indexName]['primaries']['docs']['count'] = 1;
+
+ $this->indicesMock->expects($this->once())
+ ->method('stats')
+ ->with(['index' => $indexName, 'metric' => 'docs'])
+ ->willReturn($stats);
+ $this->assertFalse($this->model->isEmptyIndex($indexName));
+ }
+
+ /**
+ * Test updateAlias() method with new index.
+ */
+ public function testUpdateAlias()
+ {
+ $alias = 'alias1';
+ $index = 'index1';
+
+ $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $index]];
+
+ $this->indicesMock->expects($this->once())
+ ->method('updateAliases')
+ ->with($params);
+ $this->model->updateAlias($alias, $index);
+ }
+
+ /**
+ * Test updateAlias() method with new and old index.
+ */
+ public function testUpdateAliasRemoveOldIndex()
+ {
+ $alias = 'alias1';
+ $newIndex = 'index1';
+ $oldIndex = 'indexOld';
+
+ $params['body']['actions'][] = ['remove' => ['alias' => $alias, 'index' => $oldIndex]];
+ $params['body']['actions'][] = ['add' => ['alias' => $alias, 'index' => $newIndex]];
+
+ $this->indicesMock->expects($this->once())
+ ->method('updateAliases')
+ ->with($params);
+ $this->model->updateAlias($alias, $newIndex, $oldIndex);
+ }
+
+ /**
+ * Test indexExists() method, case when no such index exists
+ */
+ public function testIndexExists()
+ {
+ $this->indicesMock->expects($this->once())
+ ->method('exists')
+ ->with([
+ 'index' => 'indexName',
+ ])
+ ->willReturn(true);
+ $this->model->indexExists('indexName');
+ }
+
+ /**
+ * Tests existsAlias() method checking for alias.
+ */
+ public function testExistsAlias()
+ {
+ $alias = 'alias1';
+ $params = ['name' => $alias];
+ $this->indicesMock->expects($this->once())
+ ->method('existsAlias')
+ ->with($params)
+ ->willReturn(true);
+ $this->assertTrue($this->model->existsAlias($alias));
+ }
+
+ /**
+ * Tests existsAlias() method checking for alias and index.
+ */
+ public function testExistsAliasWithIndex()
+ {
+ $alias = 'alias1';
+ $index = 'index1';
+ $params = ['name' => $alias, 'index' => $index];
+ $this->indicesMock->expects($this->once())
+ ->method('existsAlias')
+ ->with($params)
+ ->willReturn(true);
+ $this->assertTrue($this->model->existsAlias($alias, $index));
+ }
+
+ /**
+ * Test getAlias() method.
+ */
+ public function testGetAlias()
+ {
+ $alias = 'alias1';
+ $params = ['name' => $alias];
+ $this->indicesMock->expects($this->once())
+ ->method('getAlias')
+ ->with($params)
+ ->willReturn([]);
+ $this->assertEquals([], $this->model->getAlias($alias));
+ }
+
+ /**
+ * Test createIndexIfNotExists() method, case when operation fails
+ * @expectedException \Exception
+ */
+ public function testCreateIndexFailure()
+ {
+ $this->indicesMock->expects($this->once())
+ ->method('create')
+ ->with([
+ 'index' => 'indexName',
+ 'body' => [],
+ ])
+ ->willThrowException(new \Exception('Something went wrong'));
+ $this->model->createIndex('indexName', []);
+ }
+
+ /**
+ * Test testAddFieldsMapping() method
+ */
+ public function testAddFieldsMapping()
+ {
+ $this->indicesMock->expects($this->once())
+ ->method('putMapping')
+ ->with([
+ 'index' => 'indexName',
+ 'type' => 'product',
+ 'body' => [
+ 'product' => [
+ 'properties' => [
+ '_search' => [
+ 'type' => 'text',
+ ],
+ 'name' => [
+ 'type' => 'text',
+ ],
+ ],
+ 'dynamic_templates' => [
+ [
+ 'price_mapping' => [
+ 'match' => 'price_*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'float',
+ 'store' => true,
+ ],
+ ],
+ ],
+ [
+ 'string_mapping' => [
+ 'match' => '*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'text',
+ 'index' => false,
+ 'copy_to' => '_search'
+ ],
+ ],
+ ],
+ [
+ 'position_mapping' => [
+ 'match' => 'position_*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'int',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ]);
+ $this->model->addFieldsMapping(
+ [
+ 'name' => [
+ 'type' => 'text',
+ ],
+ ],
+ 'indexName',
+ 'product'
+ );
+ }
+
+ /**
+ * Test testAddFieldsMapping() method
+ * @expectedException \Exception
+ */
+ public function testAddFieldsMappingFailure()
+ {
+ $this->indicesMock->expects($this->once())
+ ->method('putMapping')
+ ->with([
+ 'index' => 'indexName',
+ 'type' => 'product',
+ 'body' => [
+ 'product' => [
+ 'properties' => [
+ '_search' => [
+ 'type' => 'text',
+ ],
+ 'name' => [
+ 'type' => 'text',
+ ],
+ ],
+ 'dynamic_templates' => [
+ [
+ 'price_mapping' => [
+ 'match' => 'price_*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'float',
+ 'store' => true,
+ ],
+ ],
+ ],
+ [
+ 'string_mapping' => [
+ 'match' => '*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'text',
+ 'index' => false,
+ 'copy_to' => '_search'
+ ],
+ ],
+ ],
+ [
+ 'position_mapping' => [
+ 'match' => 'position_*',
+ 'match_mapping_type' => 'string',
+ 'mapping' => [
+ 'type' => 'int',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ ])
+ ->willThrowException(new \Exception('Something went wrong'));
+ $this->model->addFieldsMapping(
+ [
+ 'name' => [
+ 'type' => 'text',
+ ],
+ ],
+ 'indexName',
+ 'product'
+ );
+ }
+
+ /**
+ * Test deleteMapping() method
+ */
+ public function testDeleteMapping()
+ {
+ $this->indicesMock->expects($this->once())
+ ->method('deleteMapping')
+ ->with([
+ 'index' => 'indexName',
+ 'type' => 'product',
+ ]);
+ $this->model->deleteMapping(
+ 'indexName',
+ 'product'
+ );
+ }
+
+ /**
+ * Test deleteMapping() method
+ * @expectedException \Exception
+ */
+ public function testDeleteMappingFailure()
+ {
+ $this->indicesMock->expects($this->once())
+ ->method('deleteMapping')
+ ->with([
+ 'index' => 'indexName',
+ 'type' => 'product',
+ ])
+ ->willThrowException(new \Exception('Something went wrong'));
+ $this->model->deleteMapping(
+ 'indexName',
+ 'product'
+ );
+ }
+
+ /**
+ * Test query() method
+ * @return void
+ */
+ public function testQuery()
+ {
+ $query = 'test phrase query';
+ $this->elasticsearchClientMock->expects($this->once())
+ ->method('search')
+ ->with($query)
+ ->willReturn([]);
+ $this->assertEquals([], $this->model->query($query));
+ }
+
+ /**
+ * Test suggest() method
+ * @return void
+ */
+ public function testSuggest()
+ {
+ $query = 'query';
+ $this->elasticsearchClientMock->expects($this->once())
+ ->method('suggest')
+ ->willReturn([]);
+ $this->assertEquals([], $this->model->suggest($query));
+ }
+
+ /**
+ * Get elasticsearch client options
+ *
+ * @return array
+ */
+ protected function getOptions()
+ {
+ return [
+ 'hostname' => 'localhost',
+ 'port' => '9200',
+ 'timeout' => 15,
+ 'index' => 'magento2',
+ 'enableAuth' => 1,
+ 'username' => 'user',
+ 'password' => 'passwd',
+ ];
+ }
+
+ /**
+ * @return array
+ */
+ protected function getEmptyIndexOption()
+ {
+ return [
+ 'hostname' => 'localhost',
+ 'port' => '9200',
+ 'index' => '',
+ 'timeout' => 15,
+ 'enableAuth' => 1,
+ 'username' => 'user',
+ 'password' => 'passwd',
+ ];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php b/app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php
new file mode 100644
index 0000000000000..b3c60b70ffa8e
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/Test/Unit/Model/DataProvider/SuggestionsTest.php
@@ -0,0 +1,183 @@
+config = $this->getMockBuilder(\Magento\Elasticsearch\Model\Config::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['isElasticsearchEnabled'])
+ ->getMock();
+
+ $this->queryResultFactory = $this->getMockBuilder(\Magento\Search\Model\QueryResultFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+
+ $this->connectionManager = $this->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\ConnectionManager::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getConnection'])
+ ->getMock();
+
+ $this->scopeConfig = $this->getMockBuilder(\Magento\Framework\App\Config\ScopeConfigInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->searchIndexNameResolver = $this
+ ->getMockBuilder(\Magento\Elasticsearch\SearchAdapter\SearchIndexNameResolver::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getIndexName'])
+ ->getMock();
+
+ $this->storeManager = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->query = $this->getMockBuilder(\Magento\Search\Model\QueryInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $objectManager = new ObjectManagerHelper($this);
+
+ $this->model = $objectManager->getObject(
+ \Magento\Elasticsearch6\Model\DataProvider\Suggestions::class,
+ [
+ 'queryResultFactory' => $this->queryResultFactory,
+ 'connectionManager' => $this->connectionManager,
+ 'scopeConfig' => $this->scopeConfig,
+ 'config' => $this->config,
+ 'searchIndexNameResolver' => $this->searchIndexNameResolver,
+ 'storeManager' => $this->storeManager
+ ]
+ );
+ }
+
+ /**
+ * Test getItems() method
+ */
+ public function testGetItems()
+ {
+ $this->scopeConfig->expects($this->any())
+ ->method('getValue')
+ ->willReturn(1);
+
+ $this->config->expects($this->any())
+ ->method('isElasticsearchEnabled')
+ ->willReturn(1);
+
+ $store = $this->getMockBuilder(\Magento\Store\Api\Data\StoreInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->storeManager->expects($this->any())
+ ->method('getStore')
+ ->willReturn($store);
+
+ $store->expects($this->any())
+ ->method('getId')
+ ->willReturn(1);
+
+ $this->searchIndexNameResolver->expects($this->any())
+ ->method('getIndexName')
+ ->willReturn('magento2_product_1');
+
+ $this->query->expects($this->any())
+ ->method('getQueryText')
+ ->willReturn('query');
+
+ $client = $this->getMockBuilder(\Magento\Elasticsearch6\Model\Client\Elasticsearch::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->connectionManager->expects($this->any())
+ ->method('getConnection')
+ ->willReturn($client);
+
+ $client->expects($this->any())
+ ->method('query')
+ ->willReturn([
+ 'suggest' => [
+ 'phrase_field' => [
+ 'options' => [
+ 'text' => 'query',
+ 'score' => 1,
+ 'freq' => 1,
+ ]
+ ],
+ ],
+ ]);
+
+ $query = $this->getMockBuilder(\Magento\Search\Model\QueryResult::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $this->queryResultFactory->expects($this->any())
+ ->method('create')
+ ->willReturn($query);
+
+ $this->assertInternalType('array', $this->model->getItems($this->query));
+ }
+}
diff --git a/app/code/Magento/Elasticsearch6/composer.json b/app/code/Magento/Elasticsearch6/composer.json
new file mode 100644
index 0000000000000..26b6c8c678ade
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "magento/module-elasticsearch-6",
+ "description": "N/A",
+ "require": {
+ "php": "~7.1.3||~7.2.0",
+ "magento/framework": "*",
+ "magento/module-advanced-search": "*",
+ "magento/module-catalog-search": "*",
+ "magento/module-search": "*",
+ "magento/module-store": "*",
+ "magento/module-elasticsearch": "*",
+ "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1"
+ },
+ "suggest": {
+ "magento/module-config": "*"
+ },
+ "type": "magento2-module",
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\Elasticsearch6\\": ""
+ }
+ }
+}
diff --git a/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml b/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml
new file mode 100644
index 0000000000000..067a0acb8c908
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/etc/adminhtml/system.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+ Elasticsearch Server Hostname
+
+ elasticsearch6
+
+
+
+
+ Elasticsearch Server Port
+
+ elasticsearch6
+
+
+
+
+ Elasticsearch Index Prefix
+
+ elasticsearch6
+
+
+
+
+ Enable Elasticsearch HTTP Auth
+ Magento\Config\Model\Config\Source\Yesno
+
+ elasticsearch6
+
+
+
+
+ Elasticsearch HTTP Username
+
+ elasticsearch6
+ 1
+
+
+
+
+ Elasticsearch HTTP Password
+
+ elasticsearch6
+ 1
+
+
+
+
+ Elasticsearch Server Timeout
+
+ elasticsearch6
+
+
+
+
+
+ Test Connection
+ Magento\Elasticsearch6\Block\Adminhtml\System\Config\TestConnection
+
+ elasticsearch6
+
+
+
+
+
+
diff --git a/app/code/Magento/Elasticsearch6/etc/config.xml b/app/code/Magento/Elasticsearch6/etc/config.xml
new file mode 100644
index 0000000000000..047ae977fdef1
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/etc/config.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+ localhost
+ 9200
+ magento2
+ 0
+ 15
+
+
+
+
diff --git a/app/code/Magento/Elasticsearch6/etc/di.xml b/app/code/Magento/Elasticsearch6/etc/di.xml
new file mode 100644
index 0000000000000..011dfa1019738
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/etc/di.xml
@@ -0,0 +1,205 @@
+
+
+
+
+
+
+ - elasticsearch6
+
+
+
+
+
+
+
+ - Elasticsearch 6.0+
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Elasticsearch5\Model\Adapter\BatchDataMapper\CategoryFieldsProvider
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Elasticsearch5\Model\Adapter\DataMapper\ProductDataMapper
+
+
+
+
+
+
+
+ - Magento\Elasticsearch6\Model\Adapter\FieldMapper\ProductFieldMapper
+
+
+
+
+
+
+
+ - \Magento\Elasticsearch6\Model\Client\ElasticsearchFactory
+
+
+ - \Magento\Elasticsearch\Model\Config
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Indexer\IndexerHandler
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Indexer\IndexStructure
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\ResourceModel\Engine
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Adapter
+
+
+
+
+
+
+
+ - elasticsearch6
+
+
+
+
+
+
+ Magento\Elasticsearch6\Model\Client\Elasticsearch
+
+
+
+
+
+
+ - Magento\Elasticsearch6\Model\Client\ElasticsearchFactory
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Elasticsearch5\SearchAdapter\Aggregation\Interval
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Dynamic\DataProvider
+
+
+
+
+
+
+
+
+ - Magento\Elasticsearch6\Model\DataProvider\Suggestions
+
+
+
+
+
+
+ elasticsearch5FieldProvider
+
+
+
+
+
+
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\NotEavAttribute
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\SpecialAttribute
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Price
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\CategoryName
+ - \Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\Position
+ - \Magento\Elasticsearch6\Model\Adapter\FieldMapper\Product\FieldProvider\FieldName\Resolver\DefaultResolver
+
+
+
+
+
+
+ elasticsearch5FieldProvider
+ elasticsearch6FieldNameResolver
+
+
+
+
+
+
+ - 10000
+
+
+
+
+
+
+
+ - elasticsearchCategoryCollectionFactory
+
+
+
+
+
+
+
+ - elasticsearchAdvancedCollectionFactory
+
+
+
+
+
+
+
+ - Magento\Elasticsearch\Model\Advanced\ProductCollectionPrepareStrategy
+
+
+
+
+
+
+
+ - elasticsearchFulltextSearchCollectionFactory
+
+
+
+
diff --git a/app/code/Magento/Elasticsearch6/etc/module.xml b/app/code/Magento/Elasticsearch6/etc/module.xml
new file mode 100644
index 0000000000000..4fde2394dfbdd
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/etc/module.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Elasticsearch6/registration.php b/app/code/Magento/Elasticsearch6/registration.php
new file mode 100644
index 0000000000000..7ab10e996eb8c
--- /dev/null
+++ b/app/code/Magento/Elasticsearch6/registration.php
@@ -0,0 +1,11 @@
+getDestPostcode()) {
$r->setDestPostal($request->getDestPostcode());
- } else {
}
if ($request->getDestCity()) {
diff --git a/app/code/Magento/Fedex/Plugin/Block/DataProviders/Tracking/ChangeTitle.php b/app/code/Magento/Fedex/Plugin/Block/DataProviders/Tracking/ChangeTitle.php
new file mode 100644
index 0000000000000..86a576f2db650
--- /dev/null
+++ b/app/code/Magento/Fedex/Plugin/Block/DataProviders/Tracking/ChangeTitle.php
@@ -0,0 +1,34 @@
+getCarrier() === Carrier::CODE) {
+ $result = __('Expected Delivery:');
+ }
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Fedex/Plugin/Block/Tracking/PopupDeliveryDate.php b/app/code/Magento/Fedex/Plugin/Block/Tracking/PopupDeliveryDate.php
new file mode 100644
index 0000000000000..e1597707f9d02
--- /dev/null
+++ b/app/code/Magento/Fedex/Plugin/Block/Tracking/PopupDeliveryDate.php
@@ -0,0 +1,54 @@
+getCarrier($subject) === Carrier::CODE) {
+ $result = $subject->formatDeliveryDate($date);
+ }
+ return $result;
+ }
+
+ /**
+ * Retrieve carrier name from tracking info
+ *
+ * @param Popup $subject
+ * @return string
+ */
+ private function getCarrier(Popup $subject): string
+ {
+ foreach ($subject->getTrackingInfo() as $trackingData) {
+ foreach ($trackingData as $trackingInfo) {
+ if ($trackingInfo instanceof Status) {
+ $carrier = $trackingInfo->getCarrier();
+ return $carrier;
+ }
+ }
+ }
+ return '';
+ }
+}
diff --git a/app/code/Magento/Fedex/etc/di.xml b/app/code/Magento/Fedex/etc/di.xml
index f17f8f2afe663..c542b1f04d1eb 100644
--- a/app/code/Magento/Fedex/etc/di.xml
+++ b/app/code/Magento/Fedex/etc/di.xml
@@ -22,4 +22,10 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php b/app/code/Magento/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php
index c923ced8918d2..c15b76583187a 100644
--- a/app/code/Magento/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php
+++ b/app/code/Magento/GiftMessage/Block/Adminhtml/Sales/Order/View/Items.php
@@ -171,7 +171,7 @@ public function getMessage()
/**
* Retrieve save url
*
- * @return array
+ * @return string
* @codeCoverageIgnore
*/
public function getSaveUrl()
diff --git a/app/code/Magento/GiftMessage/Block/Cart/GiftOptions.php b/app/code/Magento/GiftMessage/Block/Cart/GiftOptions.php
index 5da8b3b55700e..28a6baa436ef6 100644
--- a/app/code/Magento/GiftMessage/Block/Cart/GiftOptions.php
+++ b/app/code/Magento/GiftMessage/Block/Cart/GiftOptions.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\GiftMessage\Block\Cart;
use Magento\Backend\Block\Template\Context;
@@ -10,6 +11,8 @@
use Magento\GiftMessage\Model\CompositeConfigProvider;
/**
+ * Gift options cart block.
+ *
* @api
* @since 100.0.2
*/
@@ -63,6 +66,8 @@ public function __construct(
}
/**
+ * Retrieve encoded js layout.
+ *
* @return string
*/
public function getJsLayout()
@@ -76,7 +81,7 @@ public function getJsLayout()
/**
* Retrieve gift message configuration
*
- * @return array
+ * @return string
*/
public function getGiftOptionsConfigJson()
{
diff --git a/app/code/Magento/GiftMessage/Model/CompositeConfigProvider.php b/app/code/Magento/GiftMessage/Model/CompositeConfigProvider.php
index 0fdce9e9090ac..cb370c27863ca 100644
--- a/app/code/Magento/GiftMessage/Model/CompositeConfigProvider.php
+++ b/app/code/Magento/GiftMessage/Model/CompositeConfigProvider.php
@@ -7,6 +7,9 @@
use Magento\Checkout\Model\ConfigProviderInterface;
+/**
+ * Class CompositeConfigProvider
+ */
class CompositeConfigProvider implements ConfigProviderInterface
{
/**
@@ -18,13 +21,13 @@ class CompositeConfigProvider implements ConfigProviderInterface
* @param ConfigProviderInterface[] $configProviders
*/
public function __construct(
- array $configProviders
+ array $configProviders = []
) {
$this->configProviders = $configProviders;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getConfig()
{
diff --git a/app/code/Magento/GiftMessage/Model/Type/Plugin/Onepage.php b/app/code/Magento/GiftMessage/Model/Type/Plugin/Onepage.php
index adb500a818517..e1c8c0b5bf5a5 100644
--- a/app/code/Magento/GiftMessage/Model/Type/Plugin/Onepage.php
+++ b/app/code/Magento/GiftMessage/Model/Type/Plugin/Onepage.php
@@ -3,8 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\GiftMessage\Model\Type\Plugin;
+/**
+ * Add gift message to quote plugin.
+ */
class Onepage
{
/**
@@ -30,9 +34,11 @@ public function __construct(
}
/**
+ * Add gift message ot quote.
+ *
* @param \Magento\Checkout\Model\Type\Onepage $subject
* @param array $result
- * @return $this
+ * @return array
*/
public function afterSaveShippingMethod(
\Magento\Checkout\Model\Type\Onepage $subject,
diff --git a/app/code/Magento/GoogleAnalytics/Block/Ga.php b/app/code/Magento/GoogleAnalytics/Block/Ga.php
index 7d065ea50b369..b5917407b60ae 100644
--- a/app/code/Magento/GoogleAnalytics/Block/Ga.php
+++ b/app/code/Magento/GoogleAnalytics/Block/Ga.php
@@ -75,7 +75,8 @@ public function getPageName()
}
/**
- * Render regular page tracking javascript code
+ * Render regular page tracking javascript code.
+ *
* The custom "page name" may be set from layout or somewhere else. It must start from slash.
*
* @param string $accountId
diff --git a/app/code/Magento/GoogleAnalytics/Helper/Data.php b/app/code/Magento/GoogleAnalytics/Helper/Data.php
index 2af03c71fb1b0..90a207921d51f 100644
--- a/app/code/Magento/GoogleAnalytics/Helper/Data.php
+++ b/app/code/Magento/GoogleAnalytics/Helper/Data.php
@@ -46,6 +46,6 @@ public function isGoogleAnalyticsAvailable($store = null)
*/
public function isAnonymizedIpActive($store = null)
{
- return $this->scopeConfig->getValue(self::XML_PATH_ANONYMIZE, ScopeInterface::SCOPE_STORE, $store);
+ return (bool)$this->scopeConfig->getValue(self::XML_PATH_ANONYMIZE, ScopeInterface::SCOPE_STORE, $store);
}
}
diff --git a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Type/Grouped/AssociatedProductsCollection.php b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Type/Grouped/AssociatedProductsCollection.php
index 8d1548036cd3e..251dca8ef1615 100644
--- a/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Type/Grouped/AssociatedProductsCollection.php
+++ b/app/code/Magento/GroupedProduct/Model/ResourceModel/Product/Type/Grouped/AssociatedProductsCollection.php
@@ -7,7 +7,16 @@
*/
namespace Magento\GroupedProduct\Model\ResourceModel\Product\Type\Grouped;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
+ * Associated products collection.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class AssociatedProductsCollection extends \Magento\Catalog\Model\ResourceModel\Product\Link\Product\Collection
@@ -52,6 +61,12 @@ class AssociatedProductsCollection extends \Magento\Catalog\Model\ResourceModel\
* @param \Magento\Catalog\Model\ProductTypes\ConfigInterface $config
* @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection
*
+ * @param ProductLimitationFactory|null $productLimitationFactory
+ * @param MetadataPool|null $metadataPool
+ * @param TableMaintainer|null $tableMaintainer
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -76,7 +91,13 @@ public function __construct(
\Magento\Customer\Api\GroupManagementInterface $groupManagement,
\Magento\Framework\Registry $coreRegistry,
\Magento\Catalog\Model\ProductTypes\ConfigInterface $config,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ ProductLimitationFactory $productLimitationFactory = null,
+ MetadataPool $metadataPool = null,
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->_coreRegistry = $coreRegistry;
$this->_config = $config;
@@ -100,7 +121,13 @@ public function __construct(
$customerSession,
$dateTime,
$groupManagement,
- $connection
+ $connection,
+ $productLimitationFactory,
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
}
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml b/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
new file mode 100644
index 0000000000000..5937267b4a61e
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/ActionGroup/VerifyProductTypeOrderActionGroup.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml
index cb268b51f08f9..ba3703e7b0edc 100644
--- a/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Data/GroupedProductData.xml
@@ -18,7 +18,7 @@
EavStockItem
- api-grouped-product
+ apiGroupedProduct
grouped
4
Api Grouped Product
@@ -29,7 +29,7 @@
ApiProductShortDescription
- api-grouped-product
+ apiGroupedProduct
grouped
4
Api Grouped Product
diff --git a/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductDropdownOrderSection.xml b/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
new file mode 100644
index 0000000000000..3efbabd34c1a4
--- /dev/null
+++ b/app/code/Magento/GroupedProduct/Test/Mftf/Section/AdminProductDropdownOrderSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php b/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php
index 57d9bc78aaf28..fff84d9221c8a 100644
--- a/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php
+++ b/app/code/Magento/GroupedProduct/Ui/DataProvider/Product/Form/Modifier/Grouped.php
@@ -414,8 +414,8 @@ protected function getButtonSet()
'component' => 'Magento_Ui/js/form/components/button',
'actions' => [
[
- 'targetName' =>
- $this->uiComponentsConfig['form'] . '.' . $this->uiComponentsConfig['form']
+ 'targetName' => $this->uiComponentsConfig['form'] .
+ '.' . $this->uiComponentsConfig['form']
. '.'
. static::GROUP_GROUPED
. '.'
@@ -423,8 +423,8 @@ protected function getButtonSet()
'actionName' => 'openModal',
],
[
- 'targetName' =>
- $this->uiComponentsConfig['form'] . '.' . $this->uiComponentsConfig['form']
+ 'targetName' => $this->uiComponentsConfig['form'] .
+ '.' . $this->uiComponentsConfig['form']
. '.'
. static::GROUP_GROUPED
. '.'
diff --git a/app/code/Magento/ImportExport/Api/Data/ExportInfoInterface.php b/app/code/Magento/ImportExport/Api/Data/ExportInfoInterface.php
new file mode 100644
index 0000000000000..01c41e35fc4eb
--- /dev/null
+++ b/app/code/Magento/ImportExport/Api/Data/ExportInfoInterface.php
@@ -0,0 +1,90 @@
+addFieldset('base_fieldset', ['legend' => __('Import Settings')]);
- $fieldsets['base']->addField(
+ $fieldsets['base'] = $form->addFieldset(
+ 'base_fieldset',
+ ['legend' => __('Import Settings')]
+ )->addField(
'entity',
'select',
[
@@ -95,12 +97,11 @@ protected function _prepareForm()
// add behaviour fieldsets
$uniqueBehaviors = $this->_importModel->getUniqueEntityBehaviors();
foreach ($uniqueBehaviors as $behaviorCode => $behaviorClass) {
- $fieldsets[$behaviorCode] = $form->addFieldset(
+ $fieldset = $form->addFieldset(
$behaviorCode . '_fieldset',
['legend' => __('Import Behavior'), 'class' => 'no-display']
);
- /** @var $behaviorSource \Magento\ImportExport\Model\Source\Import\AbstractBehavior */
- $fieldsets[$behaviorCode]->addField(
+ $fieldset->addField(
$behaviorCode,
'select',
[
@@ -116,13 +117,13 @@ protected function _prepareForm()
'after_element_html' => $this->getImportBehaviorTooltip(),
]
);
- $fieldsets[$behaviorCode]->addField(
- $behaviorCode . \Magento\ImportExport\Model\Import::FIELD_NAME_VALIDATION_STRATEGY,
+ $fieldset->addField(
+ $behaviorCode . Import::FIELD_NAME_VALIDATION_STRATEGY,
'select',
[
- 'name' => \Magento\ImportExport\Model\Import::FIELD_NAME_VALIDATION_STRATEGY,
- 'title' => __(' '),
- 'label' => __(' '),
+ 'name' => Import::FIELD_NAME_VALIDATION_STRATEGY,
+ 'title' => __('Validation Strategy'),
+ 'label' => __('Validation Strategy'),
'required' => true,
'class' => $behaviorCode,
'disabled' => true,
@@ -133,11 +134,11 @@ protected function _prepareForm()
'after_element_html' => $this->getDownloadSampleFileHtml(),
]
);
- $fieldsets[$behaviorCode]->addField(
- $behaviorCode . '_' . \Magento\ImportExport\Model\Import::FIELD_NAME_ALLOWED_ERROR_COUNT,
+ $fieldset->addField(
+ $behaviorCode . '_' . Import::FIELD_NAME_ALLOWED_ERROR_COUNT,
'text',
[
- 'name' => \Magento\ImportExport\Model\Import::FIELD_NAME_ALLOWED_ERROR_COUNT,
+ 'name' => Import::FIELD_NAME_ALLOWED_ERROR_COUNT,
'label' => __('Allowed Errors Count'),
'title' => __('Allowed Errors Count'),
'required' => true,
@@ -149,11 +150,11 @@ protected function _prepareForm()
),
]
);
- $fieldsets[$behaviorCode]->addField(
- $behaviorCode . '_' . \Magento\ImportExport\Model\Import::FIELD_FIELD_SEPARATOR,
+ $fieldset->addField(
+ $behaviorCode . '_' . Import::FIELD_FIELD_SEPARATOR,
'text',
[
- 'name' => \Magento\ImportExport\Model\Import::FIELD_FIELD_SEPARATOR,
+ 'name' => Import::FIELD_FIELD_SEPARATOR,
'label' => __('Field separator'),
'title' => __('Field separator'),
'required' => true,
@@ -162,11 +163,11 @@ protected function _prepareForm()
'value' => ',',
]
);
- $fieldsets[$behaviorCode]->addField(
- $behaviorCode . \Magento\ImportExport\Model\Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR,
+ $fieldset->addField(
+ $behaviorCode . Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR,
'text',
[
- 'name' => \Magento\ImportExport\Model\Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR,
+ 'name' => Import::FIELD_FIELD_MULTIPLE_VALUE_SEPARATOR,
'label' => __('Multiple value separator'),
'title' => __('Multiple value separator'),
'required' => true,
@@ -175,11 +176,11 @@ protected function _prepareForm()
'value' => Import::DEFAULT_GLOBAL_MULTI_VALUE_SEPARATOR,
]
);
- $fieldsets[$behaviorCode]->addField(
- $behaviorCode . \Magento\ImportExport\Model\Import::FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT,
+ $fieldset->addField(
+ $behaviorCode . Import::FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT,
'text',
[
- 'name' => \Magento\ImportExport\Model\Import::FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT,
+ 'name' => Import::FIELD_EMPTY_ATTRIBUTE_VALUE_CONSTANT,
'label' => __('Empty attribute value constant'),
'title' => __('Empty attribute value constant'),
'required' => true,
@@ -188,28 +189,29 @@ protected function _prepareForm()
'value' => Import::DEFAULT_EMPTY_ATTRIBUTE_VALUE_CONSTANT,
]
);
- $fieldsets[$behaviorCode]->addField(
- $behaviorCode . \Magento\ImportExport\Model\Import::FIELDS_ENCLOSURE,
+ $fieldset->addField(
+ $behaviorCode . Import::FIELDS_ENCLOSURE,
'checkbox',
[
- 'name' => \Magento\ImportExport\Model\Import::FIELDS_ENCLOSURE,
+ 'name' => Import::FIELDS_ENCLOSURE,
'label' => __('Fields enclosure'),
'title' => __('Fields enclosure'),
'value' => 1,
]
);
+ $fieldsets[$behaviorCode] = $fieldset;
}
// fieldset for file uploading
- $fieldsets['upload'] = $form->addFieldset(
+ $fieldset = $form->addFieldset(
'upload_file_fieldset',
['legend' => __('File to Import'), 'class' => 'no-display']
);
- $fieldsets['upload']->addField(
- \Magento\ImportExport\Model\Import::FIELD_NAME_SOURCE_FILE,
+ $fieldset->addField(
+ Import::FIELD_NAME_SOURCE_FILE,
'file',
[
- 'name' => \Magento\ImportExport\Model\Import::FIELD_NAME_SOURCE_FILE,
+ 'name' => Import::FIELD_NAME_SOURCE_FILE,
'label' => __('Select File to Import'),
'title' => __('Select File to Import'),
'required' => true,
@@ -219,11 +221,11 @@ protected function _prepareForm()
),
]
);
- $fieldsets['upload']->addField(
- \Magento\ImportExport\Model\Import::FIELD_NAME_IMG_FILE_DIR,
+ $fieldset->addField(
+ Import::FIELD_NAME_IMG_FILE_DIR,
'text',
[
- 'name' => \Magento\ImportExport\Model\Import::FIELD_NAME_IMG_FILE_DIR,
+ 'name' => Import::FIELD_NAME_IMG_FILE_DIR,
'label' => __('Images File Directory'),
'title' => __('Images File Directory'),
'required' => false,
@@ -234,6 +236,7 @@ protected function _prepareForm()
),
]
);
+ $fieldsets['upload'] = $fieldset;
$form->setUseContainer(true);
$this->setForm($form);
diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php
index 38bfbd88b0c12..13c22a976e798 100644
--- a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php
+++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/Export.php
@@ -11,9 +11,12 @@
use Magento\Backend\App\Action\Context;
use Magento\Framework\App\Response\Http\FileFactory;
use Magento\ImportExport\Model\Export as ExportModel;
-use Magento\Framework\App\Filesystem\DirectoryList;
-use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\MessageQueue\PublisherInterface;
+use Magento\ImportExport\Model\Export\Entity\ExportInfoFactory;
+/**
+ * Controller for export operation.
+ */
class Export extends ExportController implements HttpPostActionInterface
{
/**
@@ -27,18 +30,38 @@ class Export extends ExportController implements HttpPostActionInterface
private $sessionManager;
/**
- * @param \Magento\Backend\App\Action\Context $context
- * @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
- * @param \Magento\Framework\Session\SessionManagerInterface $sessionManager [optional]
+ * @var PublisherInterface
+ */
+ private $messagePublisher;
+
+ /**
+ * @var ExportInfoFactory
+ */
+ private $exportInfoFactory;
+
+ /**
+ * @param Context $context
+ * @param FileFactory $fileFactory
+ * @param \Magento\Framework\Session\SessionManagerInterface|null $sessionManager
+ * @param PublisherInterface|null $publisher
+ * @param ExportInfoFactory|null $exportInfoFactory
*/
public function __construct(
Context $context,
FileFactory $fileFactory,
- \Magento\Framework\Session\SessionManagerInterface $sessionManager = null
+ \Magento\Framework\Session\SessionManagerInterface $sessionManager = null,
+ PublisherInterface $publisher = null,
+ ExportInfoFactory $exportInfoFactory = null
) {
$this->fileFactory = $fileFactory;
$this->sessionManager = $sessionManager ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\Session\SessionManagerInterface::class);
+ $this->messagePublisher = $publisher ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(PublisherInterface::class);
+ $this->exportInfoFactory = $exportInfoFactory ?:
+ \Magento\Framework\App\ObjectManager::getInstance()->get(
+ ExportInfoFactory::class
+ );
parent::__construct($context);
}
@@ -51,19 +74,19 @@ public function execute()
{
if ($this->getRequest()->getPost(ExportModel::FILTER_ELEMENT_GROUP)) {
try {
- /** @var $model \Magento\ImportExport\Model\Export */
- $model = $this->_objectManager->create(\Magento\ImportExport\Model\Export::class);
- $model->setData($this->getRequest()->getParams());
+ $params = $this->getRequest()->getParams();
+
+ /** @var ExportInfoFactory $dataObject */
+ $dataObject = $this->exportInfoFactory->create(
+ $params['file_format'],
+ $params['entity'],
+ $params['export_filter']
+ );
- $this->sessionManager->writeClose();
- return $this->fileFactory->create(
- $model->getFileName(),
- $model->export(),
- DirectoryList::VAR_DIR,
- $model->getContentType()
+ $this->messagePublisher->publish('import_export.export', $dataObject);
+ $this->messageManager->addSuccessMessage(
+ __('Message is added to queue, wait to get your file soon')
);
- } catch (LocalizedException $e) {
- $this->messageManager->addError($e->getMessage());
} catch (\Exception $e) {
$this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
$this->messageManager->addError(__('Please correct the data sent value.'));
diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php
new file mode 100644
index 0000000000000..6996ba90c3e10
--- /dev/null
+++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Delete.php
@@ -0,0 +1,79 @@
+filesystem = $filesystem;
+ $this->file = $file;
+ parent::__construct($context);
+ }
+
+ /**
+ * Controller basic method implementation.
+ *
+ * @return \Magento\Framework\App\ResponseInterface|\Magento\Framework\Controller\ResultInterface
+ * @throws LocalizedException
+ */
+ public function execute()
+ {
+ try {
+ if (empty($fileName = $this->getRequest()->getParam('filename'))) {
+ throw new LocalizedException(__('Please provide export file name'));
+ }
+ $directory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR);
+ $path = $directory->getAbsolutePath() . 'export/' . $fileName;
+ $this->file->deleteFile($path);
+ /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
+ $resultRedirect = $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
+ $resultRedirect->setPath('adminhtml/export/index');
+ return $resultRedirect;
+ } catch (FileSystemException $exception) {
+ throw new LocalizedException(__('There are no export file with such name %1', $fileName));
+ }
+ }
+}
diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php
new file mode 100644
index 0000000000000..32385e62a5dce
--- /dev/null
+++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Export/File/Download.php
@@ -0,0 +1,79 @@
+fileFactory = $fileFactory;
+ $this->filesystem = $filesystem;
+ parent::__construct($context);
+ }
+
+ /**
+ * Controller basic method implementation.
+ *
+ * @return \Magento\Framework\App\ResponseInterface
+ * @throws LocalizedException
+ */
+ public function execute()
+ {
+ if (empty($fileName = $this->getRequest()->getParam('filename'))) {
+ throw new LocalizedException(__('Please provide export file name'));
+ }
+ try {
+ $path = 'export/' . $fileName;
+ $directory = $this->filesystem->getDirectoryRead(DirectoryList::VAR_DIR);
+ if ($directory->isFile($path)) {
+ return $this->fileFactory->create(
+ $path,
+ $directory->readFile($path),
+ DirectoryList::VAR_DIR
+ );
+ }
+ } catch (LocalizedException | \Exception $exception) {
+ throw new LocalizedException(__('There are no export file with such name %1', $fileName));
+ }
+ }
+}
diff --git a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php
index a0992e28bb2cd..c18e666260898 100644
--- a/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php
+++ b/app/code/Magento/ImportExport/Controller/Adminhtml/Import/Validate.php
@@ -86,7 +86,7 @@ private function processValidationResult($validationResult, $resultBlock)
$resultBlock->addError(
__('Data validation failed. Please fix the following errors and upload the file again.')
);
- $this->addErrorMessages($resultBlock, $errorAggregator);
+
if ($errorAggregator->getErrorsCount()) {
$this->addMessageToSkipErrors($resultBlock);
}
@@ -100,6 +100,8 @@ private function processValidationResult($validationResult, $resultBlock)
$errorAggregator->getErrorsCount()
)
);
+
+ $this->addErrorMessages($resultBlock, $errorAggregator);
} else {
if ($errorAggregator->getErrorsCount()) {
$this->collectErrors($resultBlock);
diff --git a/app/code/Magento/ImportExport/Model/Export.php b/app/code/Magento/ImportExport/Model/Export.php
index 695df18fd1030..850ded7c8f256 100644
--- a/app/code/Magento/ImportExport/Model/Export.php
+++ b/app/code/Magento/ImportExport/Model/Export.php
@@ -13,6 +13,7 @@
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @since 100.0.2
+ * @deprecated
*/
class Export extends \Magento\ImportExport\Model\AbstractModel
{
diff --git a/app/code/Magento/ImportExport/Model/Export/Consumer.php b/app/code/Magento/ImportExport/Model/Export/Consumer.php
new file mode 100644
index 0000000000000..27019780269c4
--- /dev/null
+++ b/app/code/Magento/ImportExport/Model/Export/Consumer.php
@@ -0,0 +1,88 @@
+logger = $logger;
+ $this->exportManager = $exportManager;
+ $this->filesystem = $filesystem;
+ $this->notifier = $notifier;
+ }
+
+ /**
+ * Consumer logic.
+ *
+ * @param ExportInfoInterface $exportInfo
+ * @return void
+ */
+ public function process(ExportInfoInterface $exportInfo)
+ {
+ try {
+ $data = $this->exportManager->export($exportInfo);
+ $fileName = $exportInfo->getFileName();
+ $directory = $this->filesystem->getDirectoryWrite(DirectoryList::VAR_DIR);
+ $directory->writeFile('export/' . $fileName, $data);
+
+ $this->notifier->addMajor(
+ __('Your export file is ready'),
+ __('You can pick up your file at export main page')
+ );
+ } catch (LocalizedException | FileSystemException $exception) {
+ $this->notifier->addCritical(
+ __('Error during export process occurred'),
+ __('Error during export process occurred. Please check logs for detail')
+ );
+ $this->logger->critical('Something went wrong while export process. ' . $exception->getMessage());
+ }
+ }
+}
diff --git a/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfo.php b/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfo.php
new file mode 100644
index 0000000000000..6dffc1827cfd0
--- /dev/null
+++ b/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfo.php
@@ -0,0 +1,121 @@
+fileFormat;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setFileFormat($fileFormat)
+ {
+ $this->fileFormat = $fileFormat;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getFileName()
+ {
+ return $this->fileName;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setFileName($fileName)
+ {
+ $this->fileName = $fileName;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getContentType()
+ {
+ return $this->contentType;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setContentType($contentType)
+ {
+ $this->contentType = $contentType;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getEntity()
+ {
+ return $this->entity;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setEntity($entity)
+ {
+ $this->entity = $entity;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getExportFilter()
+ {
+ return $this->exportFilter;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setExportFilter($exportFilter)
+ {
+ $this->exportFilter = $exportFilter;
+ }
+}
diff --git a/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfoFactory.php b/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfoFactory.php
new file mode 100644
index 0000000000000..e3cbd162aa5af
--- /dev/null
+++ b/app/code/Magento/ImportExport/Model/Export/Entity/ExportInfoFactory.php
@@ -0,0 +1,210 @@
+objectManager = $objectManager;
+ $this->exportConfig = $exportConfig;
+ $this->entityFactory = $entityFactory;
+ $this->exportAdapterFac = $exportAdapterFac;
+ $this->serializer = $serializer;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Create ExportInfo object.
+ *
+ * @param string $fileFormat
+ * @param string $entity
+ * @param string $exportFilter
+ * @return ExportInfoInterface
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function create($fileFormat, $entity, $exportFilter)
+ {
+ $writer = $this->getWriter($fileFormat);
+ $entityAdapter = $this->getEntityAdapter($entity, $fileFormat, $exportFilter, $writer->getContentType());
+ $fileName = $this->generateFileName($entity, $entityAdapter, $writer->getFileExtension());
+ /** @var ExportInfoInterface $exportInfo */
+ $exportInfo = $this->objectManager->create(ExportInfoInterface::class);
+ $exportInfo->setExportFilter($this->serializer->serialize($exportFilter));
+ $exportInfo->setFileName($fileName);
+ $exportInfo->setEntity($entity);
+ $exportInfo->setFileFormat($fileFormat);
+ $exportInfo->setContentType($writer->getContentType());
+
+ return $exportInfo;
+ }
+
+ /**
+ * Generate file name
+ *
+ * @param string $entity
+ * @param AbstractEntity $entityAdapter
+ * @param string $fileExtensions
+ * @return string
+ */
+ private function generateFileName($entity, $entityAdapter, $fileExtensions)
+ {
+ $fileName = null;
+ if ($entityAdapter instanceof AbstractEntity) {
+ $fileName = $entityAdapter->getFileName();
+ }
+ if (!$fileName) {
+ $fileName = $entity;
+ }
+
+ return $fileName . '_' . date('Ymd_His') . '.' . $fileExtensions;
+ }
+
+ /**
+ * Create instance of entity adapter and return it.
+ *
+ * @param string $entity
+ * @param string $fileFormat
+ * @param string $exportFilter
+ * @param string $contentType
+ * @return \Magento\ImportExport\Model\Export\AbstractEntity|AbstractEntity
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ private function getEntityAdapter($entity, $fileFormat, $exportFilter, $contentType)
+ {
+ $entities = $this->exportConfig->getEntities();
+ if (isset($entities[$entity])) {
+ try {
+ $entityAdapter = $this->entityFactory->create($entities[$entity]['model']);
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Please enter a correct entity model.')
+ );
+ }
+ if (!$entityAdapter instanceof \Magento\ImportExport\Model\Export\Entity\AbstractEntity &&
+ !$entityAdapter instanceof \Magento\ImportExport\Model\Export\AbstractEntity
+ ) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __(
+ 'The entity adapter object must be an instance of %1 or %2.',
+ \Magento\ImportExport\Model\Export\Entity\AbstractEntity::class,
+ \Magento\ImportExport\Model\Export\AbstractEntity::class
+ )
+ );
+ }
+ // check for entity codes integrity
+ if ($entity != $entityAdapter->getEntityTypeCode()) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('The input entity code is not equal to entity adapter code.')
+ );
+ }
+ } else {
+ throw new \Magento\Framework\Exception\LocalizedException(__('Please enter a correct entity.'));
+ }
+ $entityAdapter->setParameters([
+ 'fileFormat' => $fileFormat,
+ 'entity' => $entity,
+ 'exportFilter' => $exportFilter,
+ 'contentType' => $contentType,
+ ]);
+ return $entityAdapter;
+ }
+
+ /**
+ * Returns writer for a file format
+ *
+ * @param string $fileFormat
+ * @return \Magento\ImportExport\Model\Export\Adapter\AbstractAdapter
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ private function getWriter($fileFormat)
+ {
+ $fileFormats = $this->exportConfig->getFileFormats();
+ if (isset($fileFormats[$fileFormat])) {
+ try {
+ $writer = $this->exportAdapterFac->create($fileFormats[$fileFormat]['model']);
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __('Please enter a correct entity model.')
+ );
+ }
+ if (!$writer instanceof \Magento\ImportExport\Model\Export\Adapter\AbstractAdapter) {
+ throw new \Magento\Framework\Exception\LocalizedException(
+ __(
+ 'The adapter object must be an instance of %1.',
+ \Magento\ImportExport\Model\Export\Adapter\AbstractAdapter::class
+ )
+ );
+ }
+ } else {
+ throw new \Magento\Framework\Exception\LocalizedException(__('Please correct the file format.'));
+ }
+ return $writer;
+ }
+}
diff --git a/app/code/Magento/ImportExport/Model/Export/ExportManagement.php b/app/code/Magento/ImportExport/Model/Export/ExportManagement.php
new file mode 100644
index 0000000000000..b4adcdd62b66d
--- /dev/null
+++ b/app/code/Magento/ImportExport/Model/Export/ExportManagement.php
@@ -0,0 +1,68 @@
+exportModelFactory = $exportModelFactory;
+ $this->hydrator = $hydrator;
+ $this->serializer = $serializer;
+ }
+
+ /**
+ * Export logic implementation.
+ *
+ * @param ExportInfoInterface $exportInfo
+ * @return mixed|string
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
+ public function export(ExportInfoInterface $exportInfo)
+ {
+ $arrData = $this->hydrator->extract($exportInfo);
+ $arrData['export_filter'] = $this->serializer->unserialize($arrData['export_filter']);
+ /** @var \Magento\ImportExport\Model\Export $exportModel */
+ $exportModel = $this->exportModelFactory->create();
+ $exportModel->setData($arrData);
+ return $exportModel->export();
+ }
+}
diff --git a/app/code/Magento/ImportExport/Model/Import.php b/app/code/Magento/ImportExport/Model/Import.php
index 1cc8dc224acb6..d115aea7f2ff9 100644
--- a/app/code/Magento/ImportExport/Model/Import.php
+++ b/app/code/Magento/ImportExport/Model/Import.php
@@ -312,7 +312,7 @@ public function getOperationResultMessages(ProcessingErrorAggregatorInterface $v
{
$messages = [];
if ($this->getProcessedRowsCount()) {
- if ($validationResult->getErrorsCount()) {
+ if ($validationResult->isErrorLimitExceeded()) {
$messages[] = __('Data validation failed. Please fix the following errors and upload the file again.');
// errors info
@@ -630,16 +630,7 @@ public function validateSource(\Magento\ImportExport\Model\Import\AbstractSource
$messages = $this->getOperationResultMessages($errorAggregator);
$this->addLogComment($messages);
- $errorsCount = $errorAggregator->getErrorsCount();
- $result = !$errorsCount;
- $validationStrategy = $this->getData(self::FIELD_NAME_VALIDATION_STRATEGY);
- if ($errorsCount
- && $validationStrategy === ProcessingErrorAggregatorInterface::VALIDATION_STRATEGY_SKIP_ERRORS
- ) {
- $this->messageManager->addWarningMessage(__('Skipped errors: %1', $errorsCount));
- $result = true;
- }
-
+ $result = !$errorAggregator->isErrorLimitExceeded();
if ($result) {
$this->addLogComment(__('Import data validation is complete.'));
}
diff --git a/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php b/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php
index 7f49e2022c410..028bf2c464d4b 100644
--- a/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php
+++ b/app/code/Magento/ImportExport/Model/Import/ErrorProcessing/ProcessingErrorAggregator.php
@@ -61,6 +61,8 @@ public function __construct(
}
/**
+ * Add error via code and level
+ *
* @param string $errorCode
* @param string $errorLevel
* @param int|null $rowNumber
@@ -96,6 +98,8 @@ public function addError(
}
/**
+ * Add row to be skipped during import
+ *
* @param int $rowNumber
* @return $this
*/
@@ -110,6 +114,8 @@ public function addRowToSkip($rowNumber)
}
/**
+ * Add specific row to invalid list via row number
+ *
* @param int $rowNumber
* @return $this
*/
@@ -126,6 +132,8 @@ protected function processInvalidRow($rowNumber)
}
/**
+ * Add error message template
+ *
* @param string $code
* @param string $template
* @return $this
@@ -138,6 +146,8 @@ public function addErrorMessageTemplate($code, $template)
}
/**
+ * Check if row is invalid by row number
+ *
* @param int $rowNumber
* @return bool
*/
@@ -147,6 +157,8 @@ public function isRowInvalid($rowNumber)
}
/**
+ * Get number of invalid rows
+ *
* @return int
*/
public function getInvalidRowsCount()
@@ -155,6 +167,8 @@ public function getInvalidRowsCount()
}
/**
+ * Initialize validation strategy
+ *
* @param string $validationStrategy
* @param int $allowedErrorCount
* @return $this
@@ -178,6 +192,8 @@ public function initValidationStrategy($validationStrategy, $allowedErrorCount =
}
/**
+ * Check if import has to be terminated
+ *
* @return bool
*/
public function hasToBeTerminated()
@@ -186,15 +202,17 @@ public function hasToBeTerminated()
}
/**
+ * Check if error limit has been exceeded
+ *
* @return bool
*/
public function isErrorLimitExceeded()
{
$isExceeded = false;
- $errorsCount = $this->getErrorsCount([ProcessingError::ERROR_LEVEL_NOT_CRITICAL]);
+ $errorsCount = $this->getErrorsCount();
if ($errorsCount > 0
&& $this->validationStrategy == self::VALIDATION_STRATEGY_STOP_ON_ERROR
- && $errorsCount >= $this->allowedErrorsCount
+ && $errorsCount > $this->allowedErrorsCount
) {
$isExceeded = true;
}
@@ -203,6 +221,8 @@ public function isErrorLimitExceeded()
}
/**
+ * Check if import has a fatal error
+ *
* @return bool
*/
public function hasFatalExceptions()
@@ -211,6 +231,8 @@ public function hasFatalExceptions()
}
/**
+ * Get all errors from an import process
+ *
* @return ProcessingError[]
*/
public function getAllErrors()
@@ -228,6 +250,8 @@ public function getAllErrors()
}
/**
+ * Get a specific set of errors via codes
+ *
* @param string[] $codes
* @return ProcessingError[]
*/
@@ -244,6 +268,8 @@ public function getErrorsByCode(array $codes)
}
/**
+ * Get an error via row number
+ *
* @param int $rowNumber
* @return ProcessingError[]
*/
@@ -258,6 +284,8 @@ public function getErrorByRowNumber($rowNumber)
}
/**
+ * Get a set rows via a set of error codes
+ *
* @param array $errorCode
* @param array $excludedCodes
* @param bool $replaceCodeWithMessage
@@ -292,6 +320,8 @@ public function getRowsGroupedByErrorCode(
}
/**
+ * Get the max allowed error count
+ *
* @return int
*/
public function getAllowedErrorsCount()
@@ -300,6 +330,8 @@ public function getAllowedErrorsCount()
}
/**
+ * Get current error count
+ *
* @param string[] $errorLevels
* @return int
*/
@@ -318,6 +350,8 @@ public function getErrorsCount(
}
/**
+ * Clear the error aggregator
+ *
* @return $this
*/
public function clear()
@@ -331,6 +365,8 @@ public function clear()
}
/**
+ * Check if an error has already been added to the aggregator
+ *
* @param int $rowNum
* @param string $errorCode
* @param string $columnName
@@ -348,6 +384,8 @@ protected function isErrorAlreadyAdded($rowNum, $errorCode, $columnName = null)
}
/**
+ * Build an error message via code, message and column name
+ *
* @param string $errorCode
* @param string $errorMessage
* @param string $columnName
@@ -369,6 +407,8 @@ protected function getErrorMessage($errorCode, $errorMessage, $columnName)
}
/**
+ * Process the error statistics for a given error level
+ *
* @param string $errorLevel
* @return $this
*/
diff --git a/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml
new file mode 100644
index 0000000000000..a9100b4730b8c
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/ActionGroup/AdminImportProductsActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml
index ad9e7672ce11a..528ad23aaf2bf 100644
--- a/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml
+++ b/app/code/Magento/ImportExport/Test/Mftf/Section/AdminExportAttributeSection.xml
@@ -11,5 +11,11 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml
new file mode 100644
index 0000000000000..ceb4e93e4e9aa
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithAddUpdateBehaviorTest.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SimpleProductForTest1
+ SimpleProductForTest1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml
new file mode 100644
index 0000000000000..d63a5546716b1
--- /dev/null
+++ b/app/code/Magento/ImportExport/Test/Mftf/Test/AdminImportProductsWithReplaceBehaviorTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SimpleProductForTest2
+ SimpleProductForTest2
+
+
+
+
+
+ SimpleProductForTest3
+ SimpleProductForTest3
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/_files/invalidExportXmlArray.php b/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/_files/invalidExportXmlArray.php
index 288a99770974a..179f3f3cadab0 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/_files/invalidExportXmlArray.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Export/Config/_files/invalidExportXmlArray.php
@@ -22,15 +22,15 @@
'attributes_with_type_modelName_and_invalid_value' => [
' '
- . ' ',
+ . ' ',
[
"Element 'entityType', attribute 'model': [facet 'pattern'] The value '1' is not accepted by the " .
- "pattern '[A-Za-z_\\\\]+'.\nLine: 1\n",
+ "pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
"Element 'entityType', attribute 'model': '1' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n",
- "Element 'fileFormat', attribute 'model': [facet 'pattern'] The value 'model1' is not " .
- "accepted by the pattern '[A-Za-z_\\\\]+'.\nLine: 1\n",
- "Element 'fileFormat', attribute 'model': 'model1' is not a valid " .
+ "Element 'fileFormat', attribute 'model': [facet 'pattern'] The value '1model' is not " .
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
+ "Element 'fileFormat', attribute 'model': '1model' is not a valid " .
"value of the atomic type 'modelName'.\nLine: 1\n"
],
],
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportMergedXmlArray.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportMergedXmlArray.php
index 357b35e8a313c..409c1af9cb38a 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportMergedXmlArray.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportMergedXmlArray.php
@@ -26,12 +26,12 @@
["Element 'entity', attribute 'notallowed': The attribute 'notallowed' is not allowed.\nLine: 1\n"],
],
'entity_model_with_invalid_value' => [
- ' ',
[
- "Element 'entity', attribute 'model': [facet 'pattern'] The value 'afwer34' is not " .
- "accepted by the pattern '[A-Za-z_\\\\]+'.\nLine: 1\n",
- "Element 'entity', attribute 'model': 'afwer34' is not a valid value of the atomic type" .
+ "Element 'entity', attribute 'model': [facet 'pattern'] The value '34afwer' is not " .
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
+ "Element 'entity', attribute 'model': '34afwer' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n"
],
],
@@ -40,7 +40,7 @@
' ',
[
"Element 'entity', attribute 'behaviorModel': [facet 'pattern'] The value '666' is not accepted by " .
- "the pattern '[A-Za-z_\\\\]+'.\nLine: 1\n",
+ "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
"Element 'entity', attribute 'behaviorModel': '666' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n"
],
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportXmlArray.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportXmlArray.php
index c913b53e8b531..c7b06a8731f02 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportXmlArray.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/Config/_files/invalidImportXmlArray.php
@@ -19,7 +19,7 @@
' ',
[
"Element 'entity', attribute 'model': [facet 'pattern'] The value '12345' is not accepted by " .
- "the pattern '[A-Za-z_\\\\]+'.\nLine: 1\n",
+ "the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
"Element 'entity', attribute 'model': '12345' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n"
],
@@ -28,7 +28,7 @@
' ',
[
"Element 'entity', attribute 'behaviorModel': [facet 'pattern'] The value '=--09' is not " .
- "accepted by the pattern '[A-Za-z_\\\\]+'.\nLine: 1\n",
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
"Element 'entity', attribute 'behaviorModel': '=--09' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n"
],
@@ -46,11 +46,11 @@
["Element 'entityType': The attribute 'model' is required but missing.\nLine: 1\n"],
],
'entitytype_with_invalid_model_attribute_value' => [
- ' ',
+ ' ',
[
- "Element 'entityType', attribute 'model': [facet 'pattern'] The value 'test1' is not " .
- "accepted by the pattern '[A-Za-z_\\\\]+'.\nLine: 1\n",
- "Element 'entityType', attribute 'model': 'test1' is not a valid value of the atomic type" .
+ "Element 'entityType', attribute 'model': [facet 'pattern'] The value '1test' is not " .
+ "accepted by the pattern '([\\\\]?[a-zA-Z_][a-zA-Z0-9_]*)+'.\nLine: 1\n",
+ "Element 'entityType', attribute 'model': '1test' is not a valid value of the atomic type" .
" 'modelName'.\nLine: 1\n"
],
],
diff --git a/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php b/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php
index b81b9f9093d1f..722cca4af6d49 100644
--- a/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php
+++ b/app/code/Magento/ImportExport/Test/Unit/Model/Import/ErrorProcessing/ProcessingErrorAggregatorTest.php
@@ -216,6 +216,7 @@ public function testIsErrorLimitExceededTrue()
*/
public function testIsErrorLimitExceededFalse()
{
+ $this->model->initValidationStrategy('validation-stop-on-errors', 5);
$this->model->addError('systemException');
$this->model->addError('systemException', 'critical', 7, 'Some column name', 'Message', 'Description');
$this->model->addError('systemException', 'critical', 4, 'Some column name', 'Message', 'Description');
diff --git a/app/code/Magento/ImportExport/Ui/Component/Columns/ExportGridActions.php b/app/code/Magento/ImportExport/Ui/Component/Columns/ExportGridActions.php
new file mode 100644
index 0000000000000..a7b9b072f00f4
--- /dev/null
+++ b/app/code/Magento/ImportExport/Ui/Component/Columns/ExportGridActions.php
@@ -0,0 +1,75 @@
+urlBuilder = $urlBuilder;
+ parent::__construct($context, $uiComponentFactory, $components, $data);
+ }
+
+ /**
+ * Prepare Data Source
+ *
+ * @param array $dataSource
+ * @return array
+ */
+ public function prepareDataSource(array $dataSource)
+ {
+ if (isset($dataSource['data']['items'])) {
+ foreach ($dataSource['data']['items'] as & $item) {
+ $name = $this->getData('name');
+ if (isset($item['file_name'])) {
+ $item[$name]['view'] = [
+ 'href' => $this->urlBuilder->getUrl(Download::URL, ['filename' => $item['file_name']]),
+ 'label' => __('Download')
+ ];
+ $item[$name]['delete'] = [
+ 'href' => $this->urlBuilder->getUrl(Delete::URL, ['filename' => $item['file_name']]),
+ 'label' => __('Delete'),
+ 'confirm' => [
+ 'title' => __('Delete'),
+ 'message' => __('Are you sure you wan\'t to delete a file?')
+ ]
+ ];
+ }
+ }
+ }
+ return $dataSource;
+ }
+}
diff --git a/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php b/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php
new file mode 100644
index 0000000000000..e64a6df430ea1
--- /dev/null
+++ b/app/code/Magento/ImportExport/Ui/DataProvider/ExportFileDataProvider.php
@@ -0,0 +1,99 @@
+file = $file;
+ $this->fileSystem = $filesystem;
+ parent::__construct(
+ $name,
+ $primaryFieldName,
+ $requestFieldName,
+ $reporting,
+ $searchCriteriaBuilder,
+ $request,
+ $filterBuilder,
+ $meta,
+ $data
+ );
+ }
+
+ /**
+ * Returns data for grid.
+ *
+ * @return array
+ * @throws \Magento\Framework\Exception\FileSystemException
+ */
+ public function getData()
+ {
+ $directory = $this->fileSystem->getDirectoryRead(DirectoryList::VAR_DIR);
+ $emptyResponse = ['items' => [], 'totalRecords' => 0];
+ if (!$this->file->isExists($directory->getAbsolutePath() . 'export/')) {
+ return $emptyResponse;
+ }
+
+ $files = $this->file->readDirectoryRecursively($directory->getAbsolutePath() . 'export/');
+ if (empty($files)) {
+ return $emptyResponse;
+ }
+ $result = [];
+ foreach ($files as $file) {
+ $result['items'][]['file_name'] = basename($file);
+ }
+
+ $result['totalRecords'] = count($result['items']);
+
+ return $result;
+ }
+}
diff --git a/app/code/Magento/ImportExport/composer.json b/app/code/Magento/ImportExport/composer.json
index b0ba04f5aa0eb..6363722eba7c8 100644
--- a/app/code/Magento/ImportExport/composer.json
+++ b/app/code/Magento/ImportExport/composer.json
@@ -12,7 +12,8 @@
"magento/module-catalog": "*",
"magento/module-eav": "*",
"magento/module-media-storage": "*",
- "magento/module-store": "*"
+ "magento/module-store": "*",
+ "magento/module-ui": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/ImportExport/etc/adminhtml/di.xml b/app/code/Magento/ImportExport/etc/adminhtml/di.xml
index 8f7955e679cc2..04ee726349123 100644
--- a/app/code/Magento/ImportExport/etc/adminhtml/di.xml
+++ b/app/code/Magento/ImportExport/etc/adminhtml/di.xml
@@ -11,4 +11,19 @@
Magento\Framework\Message\ExceptionMessageLookupFactory
+
+
+ Magento\Framework\Serialize\Serializer\Json
+
+
+
+
+ Magento\Framework\Filesystem\Driver\File
+
+
+
+
+ Magento\Framework\Filesystem\Driver\File
+
+
diff --git a/app/code/Magento/ImportExport/etc/communication.xml b/app/code/Magento/ImportExport/etc/communication.xml
new file mode 100644
index 0000000000000..7794b3e5ab248
--- /dev/null
+++ b/app/code/Magento/ImportExport/etc/communication.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/etc/di.xml b/app/code/Magento/ImportExport/etc/di.xml
index 36c76022a41c7..909b526e4790c 100644
--- a/app/code/Magento/ImportExport/etc/di.xml
+++ b/app/code/Magento/ImportExport/etc/di.xml
@@ -10,6 +10,8 @@
+
+
diff --git a/app/code/Magento/ImportExport/etc/export.xsd b/app/code/Magento/ImportExport/etc/export.xsd
index 65728a9be5c62..f62dbc891ef0f 100644
--- a/app/code/Magento/ImportExport/etc/export.xsd
+++ b/app/code/Magento/ImportExport/etc/export.xsd
@@ -71,11 +71,11 @@
- Model name can contain only [A-Za-z_\\].
+ Model name can contain only ([\\]?[a-zA-Z_][a-zA-Z0-9_]*)+.
-
+
diff --git a/app/code/Magento/ImportExport/etc/import.xsd b/app/code/Magento/ImportExport/etc/import.xsd
index aefa6541d7e13..e73038ebc0710 100644
--- a/app/code/Magento/ImportExport/etc/import.xsd
+++ b/app/code/Magento/ImportExport/etc/import.xsd
@@ -61,11 +61,11 @@
- Model name can contain only [A-Za-z_\\].
+ Model name can contain only ([\\]?[a-zA-Z_][a-zA-Z0-9_]*)+.
-
+
diff --git a/app/code/Magento/ImportExport/etc/queue.xml b/app/code/Magento/ImportExport/etc/queue.xml
new file mode 100644
index 0000000000000..7eb96819faf10
--- /dev/null
+++ b/app/code/Magento/ImportExport/etc/queue.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/etc/queue_consumer.xml b/app/code/Magento/ImportExport/etc/queue_consumer.xml
new file mode 100644
index 0000000000000..2c6612ac0ef1c
--- /dev/null
+++ b/app/code/Magento/ImportExport/etc/queue_consumer.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/etc/queue_publisher.xml b/app/code/Magento/ImportExport/etc/queue_publisher.xml
new file mode 100644
index 0000000000000..097b60bee1534
--- /dev/null
+++ b/app/code/Magento/ImportExport/etc/queue_publisher.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/etc/queue_topology.xml b/app/code/Magento/ImportExport/etc/queue_topology.xml
new file mode 100644
index 0000000000000..f77c13e2ba05f
--- /dev/null
+++ b/app/code/Magento/ImportExport/etc/queue_topology.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/ImportExport/i18n/en_US.csv b/app/code/Magento/ImportExport/i18n/en_US.csv
index cae4d6e19868d..d7680a71ac5f7 100644
--- a/app/code/Magento/ImportExport/i18n/en_US.csv
+++ b/app/code/Magento/ImportExport/i18n/en_US.csv
@@ -18,6 +18,7 @@ Import,Import
"Import Settings","Import Settings"
"Import Behavior","Import Behavior"
" "," "
+"Validation Strategy","Validation Strategy"
"Stop on Error","Stop on Error"
"Skip error entries","Skip error entries"
"Allowed Errors Count","Allowed Errors Count"
diff --git a/app/code/Magento/ImportExport/view/adminhtml/layout/adminhtml_export_index.xml b/app/code/Magento/ImportExport/view/adminhtml/layout/adminhtml_export_index.xml
index 6848650979306..b60fb40bfbd83 100644
--- a/app/code/Magento/ImportExport/view/adminhtml/layout/adminhtml_export_index.xml
+++ b/app/code/Magento/ImportExport/view/adminhtml/layout/adminhtml_export_index.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/code/Magento/ImportExport/view/adminhtml/ui_component/export_grid.xml b/app/code/Magento/ImportExport/view/adminhtml/ui_component/export_grid.xml
new file mode 100644
index 0000000000000..2b160bc9f6f40
--- /dev/null
+++ b/app/code/Magento/ImportExport/view/adminhtml/ui_component/export_grid.xml
@@ -0,0 +1,50 @@
+
+
+
+
+ -
+
- export_grid.export_grid_data_source
+
+
+
+
+ export_grid.export_grid_data_source
+
+ export_grid_columns
+
+
+
+
+ file_name
+
+
+
+ Magento_ImportExport::export
+
+
+ file_name
+ file_name
+
+
+
+
+
+
+ false
+ File name
+
+
+
+
+ file_name
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Indexer/Model/Indexer.php b/app/code/Magento/Indexer/Model/Indexer.php
index 87a7cce58e1a5..2821a46f29416 100644
--- a/app/code/Magento/Indexer/Model/Indexer.php
+++ b/app/code/Magento/Indexer/Model/Indexer.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Indexer\Model;
use Magento\Framework\Indexer\ActionFactory;
@@ -14,6 +15,8 @@
use Magento\Framework\Indexer\StructureFactory;
/**
+ * Indexer model.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Indexer extends \Magento\Framework\DataObject implements IndexerInterface
@@ -361,7 +364,7 @@ public function getLatestUpdated()
return $this->getView()->getUpdated();
}
}
- return $this->getState()->getUpdated();
+ return $this->getState()->getUpdated() ?: '';
}
/**
diff --git a/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php b/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php
index 6b7cc12218990..ca2da9585f934 100644
--- a/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php
+++ b/app/code/Magento/Indexer/Test/Unit/Model/IndexerTest.php
@@ -164,7 +164,12 @@ public function testGetLatestUpdated($getViewIsEnabled, $getViewGetUpdated, $get
}
}
} else {
- $this->assertEquals($getStateGetUpdated, $this->model->getLatestUpdated());
+ $getLatestUpdated = $this->model->getLatestUpdated();
+ $this->assertEquals($getStateGetUpdated, $getLatestUpdated);
+
+ if ($getStateGetUpdated === null) {
+ $this->assertNotNull($getLatestUpdated);
+ }
}
}
@@ -182,7 +187,8 @@ public function getLatestUpdatedDataProvider()
[true, '', '06-Jan-1944'],
[true, '06-Jan-1944', ''],
[true, '', ''],
- [true, '06-Jan-1944', '05-Jan-1944']
+ [true, '06-Jan-1944', '05-Jan-1944'],
+ [false, null, null],
];
}
diff --git a/app/code/Magento/Marketplace/Block/Partners.php b/app/code/Magento/Marketplace/Block/Partners.php
index 4f8ca798f1756..30d6a2910f4de 100644
--- a/app/code/Magento/Marketplace/Block/Partners.php
+++ b/app/code/Magento/Marketplace/Block/Partners.php
@@ -7,6 +7,8 @@
namespace Magento\Marketplace\Block;
/**
+ * Partners section block.
+ *
* @api
* @since 100.0.2
*/
@@ -39,7 +41,7 @@ public function __construct(
/**
* Gets partners
*
- * @return bool|string
+ * @return array
*/
public function getPartners()
{
diff --git a/app/code/Magento/MediaStorage/Service/ImageResize.php b/app/code/Magento/MediaStorage/Service/ImageResize.php
index 6e3929296e252..aae90512b3d95 100644
--- a/app/code/Magento/MediaStorage/Service/ImageResize.php
+++ b/app/code/Magento/MediaStorage/Service/ImageResize.php
@@ -24,6 +24,8 @@
use Magento\Framework\App\Filesystem\DirectoryList;
/**
+ * Image resize service.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ImageResize
@@ -123,7 +125,8 @@ public function __construct(
}
/**
- * Create resized images of different sizes from an original image
+ * Create resized images of different sizes from an original image.
+ *
* @param string $originalImageName
* @throws NotFoundException
*/
@@ -141,7 +144,8 @@ public function resizeFromImageName(string $originalImageName)
}
/**
- * Create resized images of different sizes from themes
+ * Create resized images of different sizes from themes.
+ *
* @param array|null $themes
* @return \Generator
* @throws NotFoundException
@@ -169,7 +173,8 @@ public function resizeFromThemes(array $themes = null): \Generator
}
/**
- * Search the current theme
+ * Search the current theme.
+ *
* @return array
*/
private function getThemesInUse(): array
@@ -187,7 +192,8 @@ private function getThemesInUse(): array
}
/**
- * Get view images data from themes
+ * Get view images data from themes.
+ *
* @param array $themes
* @return array
*/
@@ -211,7 +217,8 @@ private function getViewImages(array $themes): array
}
/**
- * Get unique image index
+ * Get unique image index.
+ *
* @param array $imageData
* @return string
*/
@@ -223,7 +230,8 @@ private function getUniqueImageIndex(array $imageData): string
}
/**
- * Make image
+ * Make image.
+ *
* @param string $originalImagePath
* @param array $imageParams
* @return Image
@@ -241,7 +249,8 @@ private function makeImage(string $originalImagePath, array $imageParams): Image
}
/**
- * Resize image
+ * Resize image.
+ *
* @param array $viewImage
* @param string $originalImagePath
* @param string $originalImageName
@@ -257,9 +266,41 @@ private function resize(array $viewImage, string $originalImagePath, string $ori
]
);
+ if (isset($imageParams['watermark_file'])) {
+ if ($imageParams['watermark_height'] !== null) {
+ $image->setWatermarkHeight($imageParams['watermark_height']);
+ }
+
+ if ($imageParams['watermark_width'] !== null) {
+ $image->setWatermarkWidth($imageParams['watermark_width']);
+ }
+
+ if ($imageParams['watermark_position'] !== null) {
+ $image->setWatermarkPosition($imageParams['watermark_position']);
+ }
+
+ if ($imageParams['watermark_image_opacity'] !== null) {
+ $image->setWatermarkImageOpacity($imageParams['watermark_image_opacity']);
+ }
+
+ $image->watermark($this->getWatermarkFilePath($imageParams['watermark_file']));
+ }
+
if ($imageParams['image_width'] !== null && $imageParams['image_height'] !== null) {
$image->resize($imageParams['image_width'], $imageParams['image_height']);
}
$image->save($imageAsset->getPath());
}
+
+ /**
+ * Returns watermark file absolute path
+ *
+ * @param string $file
+ * @return string
+ */
+ private function getWatermarkFilePath($file)
+ {
+ $path = $this->imageConfig->getMediaPath('/watermark/' . $file);
+ return $this->mediaDirectory->getAbsolutePath($path);
+ }
}
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/Msrp/Helper/Data.php b/app/code/Magento/Msrp/Helper/Data.php
index 393383bb2e772..2f6dd2da9bbc4 100644
--- a/app/code/Magento/Msrp/Helper/Data.php
+++ b/app/code/Magento/Msrp/Helper/Data.php
@@ -7,14 +7,17 @@
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
+use Magento\Framework\App\ObjectManager;
use Magento\Msrp\Model\Product\Attribute\Source\Type;
+use Magento\Msrp\Pricing\MsrpPriceCalculatorInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Api\ProductRepositoryInterface;
-use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
/**
* Msrp data helper
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Data extends AbstractHelper
{
@@ -43,6 +46,11 @@ class Data extends AbstractHelper
*/
protected $productRepository;
+ /**
+ * @var MsrpPriceCalculatorInterface
+ */
+ private $msrpPriceCalculator;
+
/**
* @param Context $context
* @param StoreManagerInterface $storeManager
@@ -51,6 +59,7 @@ class Data extends AbstractHelper
* @param \Magento\Msrp\Model\Config $config
* @param \Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency
* @param ProductRepositoryInterface $productRepository
+ * @param MsrpPriceCalculatorInterface|null $msrpPriceCalculator
*/
public function __construct(
Context $context,
@@ -59,7 +68,8 @@ public function __construct(
\Magento\Msrp\Model\Msrp $msrp,
\Magento\Msrp\Model\Config $config,
\Magento\Framework\Pricing\PriceCurrencyInterface $priceCurrency,
- ProductRepositoryInterface $productRepository
+ ProductRepositoryInterface $productRepository,
+ MsrpPriceCalculatorInterface $msrpPriceCalculator = null
) {
parent::__construct($context);
$this->storeManager = $storeManager;
@@ -68,6 +78,8 @@ public function __construct(
$this->config = $config;
$this->priceCurrency = $priceCurrency;
$this->productRepository = $productRepository;
+ $this->msrpPriceCalculator = $msrpPriceCalculator
+ ?: ObjectManager::getInstance()->get(MsrpPriceCalculatorInterface::class);
}
/**
@@ -78,6 +90,7 @@ public function __construct(
* @return bool
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function canApplyMsrp($product, $visibility = null)
{
@@ -111,6 +124,7 @@ public function canApplyMsrp($product, $visibility = null)
*
* @param Product $product
* @return string
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function getMsrpPriceMessage($product)
{
@@ -128,6 +142,7 @@ public function getMsrpPriceMessage($product)
*
* @param int|Product $product
* @return bool
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function isShowPriceOnGesture($product)
{
@@ -139,6 +154,7 @@ public function isShowPriceOnGesture($product)
*
* @param int|Product $product
* @return bool
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function isShowBeforeOrderConfirm($product)
{
@@ -149,31 +165,16 @@ public function isShowBeforeOrderConfirm($product)
* Check if any MAP price is larger than as low as value.
*
* @param int|Product $product
- * @return bool|float
+ * @return bool
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function isMinimalPriceLessMsrp($product)
{
if (is_numeric($product)) {
$product = $this->productRepository->getById($product, false, $this->storeManager->getStore()->getId());
}
- $msrp = $product->getMsrp();
+ $msrp = $this->msrpPriceCalculator->getMsrpPriceValue($product);
$price = $product->getPriceInfo()->getPrice(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE);
- if ($msrp === null) {
- if ($product->getTypeId() === \Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE) {
- $msrp = $product->getTypeInstance()->getChildrenMsrp($product);
- } elseif ($product->getTypeId() === Configurable::TYPE_CODE) {
- $prices = [];
- foreach ($product->getTypeInstance()->getUsedProducts($product) as $item) {
- if ($item->getMsrp() !== null) {
- $prices[] = $item->getMsrp();
- }
- }
-
- $msrp = $prices ? max($prices) : 0;
- } else {
- return false;
- }
- }
if ($msrp) {
$msrp = $this->priceCurrency->convertAndRound($msrp);
}
diff --git a/app/code/Magento/Msrp/Pricing/MsrpPriceCalculator.php b/app/code/Magento/Msrp/Pricing/MsrpPriceCalculator.php
new file mode 100644
index 0000000000000..3d1e5ef0b8e6c
--- /dev/null
+++ b/app/code/Magento/Msrp/Pricing/MsrpPriceCalculator.php
@@ -0,0 +1,64 @@
+msrpPriceCalculators = $this->getMsrpPriceCalculators($msrpPriceCalculators);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getMsrpPriceValue(ProductInterface $product): float
+ {
+ $productType = $product->getTypeId();
+ if (isset($this->msrpPriceCalculators[$productType])) {
+ $priceCalculator = $this->msrpPriceCalculators[$productType];
+ $msrp = $priceCalculator->getMsrpPriceValue($product);
+ } else {
+ $msrp = (float)$product->getMsrp();
+ }
+
+ return $msrp;
+ }
+
+ /**
+ * Convert the configuration of MSRP price calculators.
+ *
+ * @param array $msrpPriceCalculatorsConfig
+ * @return array
+ */
+ private function getMsrpPriceCalculators(array $msrpPriceCalculatorsConfig): array
+ {
+ $msrpPriceCalculators = [];
+ foreach ($msrpPriceCalculatorsConfig as $msrpPriceCalculator) {
+ if (isset($msrpPriceCalculator['productType'], $msrpPriceCalculator['priceCalculator'])) {
+ $msrpPriceCalculators[$msrpPriceCalculator['productType']] =
+ $msrpPriceCalculator['priceCalculator'];
+ }
+ }
+ return $msrpPriceCalculators;
+ }
+}
diff --git a/app/code/Magento/Msrp/Pricing/MsrpPriceCalculatorInterface.php b/app/code/Magento/Msrp/Pricing/MsrpPriceCalculatorInterface.php
new file mode 100644
index 0000000000000..c50a381a2efa4
--- /dev/null
+++ b/app/code/Magento/Msrp/Pricing/MsrpPriceCalculatorInterface.php
@@ -0,0 +1,23 @@
+msrpPriceCalculator = $msrpPriceCalculator;
+ parent::__construct($context, $saleableItem, $price, $rendererPool, $jsonHelper, $mathRandom);
+ }
+
+ /**
+ * Return MSRP price calculator.
+ *
+ * @return MsrpPriceCalculatorInterface
+ */
+ public function getMsrpPriceCalculator(): MsrpPriceCalculatorInterface
+ {
+ return $this->msrpPriceCalculator;
+ }
+}
diff --git a/app/code/Magento/Msrp/Test/Unit/Helper/DataTest.php b/app/code/Magento/Msrp/Test/Unit/Helper/DataTest.php
index 19f46b06ac5af..e4cd411a2e0b8 100644
--- a/app/code/Magento/Msrp/Test/Unit/Helper/DataTest.php
+++ b/app/code/Magento/Msrp/Test/Unit/Helper/DataTest.php
@@ -6,6 +6,8 @@
namespace Magento\Msrp\Test\Unit\Helper;
+use Magento\Msrp\Pricing\MsrpPriceCalculatorInterface;
+
/**
* Class DataTest
*/
@@ -26,6 +28,14 @@ class DataTest extends \PHPUnit\Framework\TestCase
*/
protected $productMock;
+ /**
+ * @var MsrpPriceCalculatorInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $msrpPriceCalculator;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->priceCurrencyMock = $this->createMock(\Magento\Framework\Pricing\PriceCurrencyInterface::class);
@@ -33,6 +43,8 @@ protected function setUp()
->disableOriginalConstructor()
->setMethods(['getMsrp', 'getPriceInfo', '__wakeup'])
->getMock();
+ $this->msrpPriceCalculator = $this->getMockBuilder(MsrpPriceCalculatorInterface::class)
+ ->getMockForAbstractClass();
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
@@ -40,10 +52,14 @@ protected function setUp()
\Magento\Msrp\Helper\Data::class,
[
'priceCurrency' => $this->priceCurrencyMock,
+ 'msrpPriceCalculator' => $this->msrpPriceCalculator,
]
);
}
+ /**
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
public function testIsMinimalPriceLessMsrp()
{
$msrp = 120;
@@ -73,12 +89,13 @@ function ($arg) {
->with(\Magento\Catalog\Pricing\Price\FinalPrice::PRICE_CODE)
->will($this->returnValue($finalPriceMock));
- $this->productMock->expects($this->any())
- ->method('getMsrp')
- ->will($this->returnValue($msrp));
+ $this->msrpPriceCalculator
+ ->expects($this->any())
+ ->method('getMsrpPriceValue')
+ ->willReturn($msrp);
$this->productMock->expects($this->any())
->method('getPriceInfo')
- ->will($this->returnValue($priceInfoMock));
+ ->willReturn($priceInfoMock);
$result = $this->helper->isMinimalPriceLessMsrp($this->productMock);
$this->assertTrue($result, "isMinimalPriceLessMsrp returned incorrect value");
diff --git a/app/code/Magento/Msrp/composer.json b/app/code/Magento/Msrp/composer.json
index e3099aa2f14d6..a2e6da6de5387 100644
--- a/app/code/Magento/Msrp/composer.json
+++ b/app/code/Magento/Msrp/composer.json
@@ -10,8 +10,6 @@
"magento/module-catalog": "*",
"magento/module-downloadable": "*",
"magento/module-eav": "*",
- "magento/module-grouped-product": "*",
- "magento/module-configurable-product": "*",
"magento/module-store": "*",
"magento/module-tax": "*"
},
diff --git a/app/code/Magento/Msrp/etc/adminhtml/system.xml b/app/code/Magento/Msrp/etc/adminhtml/system.xml
index 8ce0ea67343f8..c20d753a2e794 100644
--- a/app/code/Magento/Msrp/etc/adminhtml/system.xml
+++ b/app/code/Magento/Msrp/etc/adminhtml/system.xml
@@ -10,7 +10,7 @@
Minimum Advertised Price
-
+
Enable MAP
Magento\Config\Model\Config\Source\Yesno
diff --git a/app/code/Magento/Msrp/etc/di.xml b/app/code/Magento/Msrp/etc/di.xml
index 6f197f769d412..b8392b0bb0fe4 100644
--- a/app/code/Magento/Msrp/etc/di.xml
+++ b/app/code/Magento/Msrp/etc/di.xml
@@ -7,6 +7,7 @@
-->
+
diff --git a/app/code/Magento/Msrp/i18n/en_US.csv b/app/code/Magento/Msrp/i18n/en_US.csv
index d647f8527ec15..d47d72b2bdc9a 100644
--- a/app/code/Magento/Msrp/i18n/en_US.csv
+++ b/app/code/Magento/Msrp/i18n/en_US.csv
@@ -13,6 +13,7 @@ Price,Price
"Add to Cart","Add to Cart"
"Minimum Advertised Price","Minimum Advertised Price"
"Enable MAP","Enable MAP"
+"Warning! Enabling MAP by default will hide all product prices on Storefront.","Warning! Enabling MAP by default will hide all product prices on Storefront."
"Display Actual Price","Display Actual Price"
"Default Popup Text Message","Default Popup Text Message"
"Default ""What's This"" Text Message","Default ""What's This"" Text Message"
diff --git a/app/code/Magento/Msrp/view/base/layout/catalog_product_prices.xml b/app/code/Magento/Msrp/view/base/layout/catalog_product_prices.xml
index 9e2dd20638646..b8a3910bf21bc 100644
--- a/app/code/Magento/Msrp/view/base/layout/catalog_product_prices.xml
+++ b/app/code/Magento/Msrp/view/base/layout/catalog_product_prices.xml
@@ -11,7 +11,7 @@
-
-
-
- Magento\Catalog\Pricing\Render\PriceBox
+ - Magento\Msrp\Pricing\Render\PriceBox
- Magento_Msrp::product/price/msrp.phtml
diff --git a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
index a951c14cf4c70..a428df57ab113 100644
--- a/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
+++ b/app/code/Magento/Msrp/view/base/templates/product/price/msrp.phtml
@@ -9,7 +9,7 @@
/**
* Template for displaying product price at product view page, gift registry and wish-list
*
- * @var $block \Magento\Catalog\Pricing\Render\PriceBox
+ * @var $block \Magento\Msrp\Pricing\Render\PriceBox
*/
?>
getPrice();
$product = $block->getSaleableItem();
$productId = $product->getId();
-$amount = 0;
-if ($product->getMsrp()) {
- $amount = $product->getMsrp();
-} elseif ($product->getTypeId() === \Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE) {
- $amount = $product->getTypeInstance()->getChildrenMsrp($product);
-} elseif ($product->getTypeId() === \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE) {
- foreach ($product->getTypeInstance()->getUsedProducts($product) as $item) {
- if ($item->getMsrp() !== null) {
- $prices[] = $item->getMsrp();
- }
- }
- $amount = $prices ? max($prices) : 0;
-}
+$amount = $block->getMsrpPriceCalculator()->getMsrpPriceValue($product);
$msrpPrice = $block->renderAmount(
$priceType->getCustomAmount($amount),
diff --git a/app/code/Magento/MsrpConfigurableProduct/Pricing/MsrpPriceCalculator.php b/app/code/Magento/MsrpConfigurableProduct/Pricing/MsrpPriceCalculator.php
new file mode 100644
index 0000000000000..b6f5194ab8cbe
--- /dev/null
+++ b/app/code/Magento/MsrpConfigurableProduct/Pricing/MsrpPriceCalculator.php
@@ -0,0 +1,46 @@
+getTypeId() !== Configurable::TYPE_CODE) {
+ return 0;
+ }
+
+ /** @var Configurable $configurableProduct */
+ $configurableProduct = $product->getTypeInstance();
+ $msrp = 0;
+ $prices = [];
+ foreach ($configurableProduct->getUsedProducts($product) as $item) {
+ if ($item->getMsrp() !== null) {
+ $prices[] = $item->getMsrp();
+ }
+ }
+ if ($prices) {
+ $msrp = (float)max($prices);
+ }
+
+ return $msrp;
+ }
+}
diff --git a/app/code/Magento/MsrpConfigurableProduct/README.md b/app/code/Magento/MsrpConfigurableProduct/README.md
new file mode 100644
index 0000000000000..8911b6e9e6667
--- /dev/null
+++ b/app/code/Magento/MsrpConfigurableProduct/README.md
@@ -0,0 +1,3 @@
+# MsrpConfigurableProduct
+
+**MsrpConfigurableProduct** provides type and resolver information for the Msrp module from the ConfigurableProduct module.
\ No newline at end of file
diff --git a/app/code/Magento/MsrpConfigurableProduct/composer.json b/app/code/Magento/MsrpConfigurableProduct/composer.json
new file mode 100644
index 0000000000000..00c3cf6b03078
--- /dev/null
+++ b/app/code/Magento/MsrpConfigurableProduct/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "magento/module-msrp-configurable-product",
+ "description": "N/A",
+ "config": {
+ "sort-packages": true
+ },
+ "require": {
+ "php": "~7.1.3||~7.2.0",
+ "magento/framework": "*",
+ "magento/module-catalog": "*",
+ "magento/module-msrp": "*",
+ "magento/module-configurable-product": "*"
+ },
+ "type": "magento2-module",
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\MsrpConfigurableProduct\\": ""
+ }
+ }
+}
diff --git a/app/code/Magento/MsrpConfigurableProduct/etc/di.xml b/app/code/Magento/MsrpConfigurableProduct/etc/di.xml
new file mode 100644
index 0000000000000..ea33c81ff7cf5
--- /dev/null
+++ b/app/code/Magento/MsrpConfigurableProduct/etc/di.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ -
+
- \Magento\ConfigurableProduct\Model\Product\Type\Configurable::TYPE_CODE
+ - \Magento\MsrpConfigurableProduct\Pricing\MsrpPriceCalculator
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/MsrpConfigurableProduct/etc/module.xml b/app/code/Magento/MsrpConfigurableProduct/etc/module.xml
new file mode 100644
index 0000000000000..b00e363b0b269
--- /dev/null
+++ b/app/code/Magento/MsrpConfigurableProduct/etc/module.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/MsrpConfigurableProduct/registration.php b/app/code/Magento/MsrpConfigurableProduct/registration.php
new file mode 100644
index 0000000000000..d4d58ec3c013b
--- /dev/null
+++ b/app/code/Magento/MsrpConfigurableProduct/registration.php
@@ -0,0 +1,9 @@
+getTypeId() !== Grouped::TYPE_CODE) {
+ return 0;
+ }
+
+ /** @var Grouped $groupedProduct */
+ $groupedProduct = $product->getTypeInstance();
+
+ return $groupedProduct->getChildrenMsrp($product);
+ }
+}
diff --git a/app/code/Magento/MsrpGroupedProduct/README.md b/app/code/Magento/MsrpGroupedProduct/README.md
new file mode 100644
index 0000000000000..d597ba7fc18a7
--- /dev/null
+++ b/app/code/Magento/MsrpGroupedProduct/README.md
@@ -0,0 +1,3 @@
+# MsrpGroupedProduct
+
+**MsrpGroupedProduct** provides type and resolver information for the Msrp module from the GroupedProduct module.
\ No newline at end of file
diff --git a/app/code/Magento/MsrpGroupedProduct/composer.json b/app/code/Magento/MsrpGroupedProduct/composer.json
new file mode 100644
index 0000000000000..a626f199ad6cc
--- /dev/null
+++ b/app/code/Magento/MsrpGroupedProduct/composer.json
@@ -0,0 +1,27 @@
+{
+ "name": "magento/module-msrp-grouped-product",
+ "description": "N/A",
+ "config": {
+ "sort-packages": true
+ },
+ "require": {
+ "php": "~7.1.3||~7.2.0",
+ "magento/framework": "*",
+ "magento/module-catalog": "*",
+ "magento/module-msrp": "*",
+ "magento/module-grouped-product": "*"
+ },
+ "type": "magento2-module",
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\MsrpGroupedProduct\\": ""
+ }
+ }
+}
diff --git a/app/code/Magento/MsrpGroupedProduct/etc/di.xml b/app/code/Magento/MsrpGroupedProduct/etc/di.xml
new file mode 100644
index 0000000000000..29b25f15bc2c1
--- /dev/null
+++ b/app/code/Magento/MsrpGroupedProduct/etc/di.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+ -
+
- \Magento\GroupedProduct\Model\Product\Type\Grouped::TYPE_CODE
+ - \Magento\MsrpGroupedProduct\Pricing\MsrpPriceCalculator
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/MsrpGroupedProduct/etc/module.xml b/app/code/Magento/MsrpGroupedProduct/etc/module.xml
new file mode 100644
index 0000000000000..898dd904b5dfb
--- /dev/null
+++ b/app/code/Magento/MsrpGroupedProduct/etc/module.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/MsrpGroupedProduct/registration.php b/app/code/Magento/MsrpGroupedProduct/registration.php
new file mode 100644
index 0000000000000..c5a261e66c640
--- /dev/null
+++ b/app/code/Magento/MsrpGroupedProduct/registration.php
@@ -0,0 +1,9 @@
+getId() && $subscriber->getCode()) {
if ($subscriber->confirm($code)) {
- $this->messageManager->addSuccess(__('Your subscription has been confirmed.'));
+ $this->messageManager->addSuccessMessage(__('Your subscription has been confirmed.'));
} else {
- $this->messageManager->addError(__('This is an invalid subscription confirmation code.'));
+ $this->messageManager->addErrorMessage(__('This is an invalid subscription confirmation code.'));
}
} else {
- $this->messageManager->addError(__('This is an invalid subscription ID.'));
+ $this->messageManager->addErrorMessage(__('This is an invalid subscription ID.'));
}
}
-
- $resultRedirect = $this->resultRedirectFactory->create();
- $resultRedirect->setUrl($this->_storeManager->getStore()->getBaseUrl());
- return $resultRedirect;
+ /** @var \Magento\Framework\Controller\Result\Redirect $redirect */
+ $redirect = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT);
+ $redirectUrl = $this->_storeManager->getStore()->getBaseUrl();
+ return $redirect->setUrl($redirectUrl);
}
}
diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php b/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php
index 4f46c84894f12..7557f1610b4f4 100644
--- a/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php
+++ b/app/code/Magento/Newsletter/Controller/Subscriber/NewAction.php
@@ -4,6 +4,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Newsletter\Controller\Subscriber;
use Magento\Customer\Api\AccountManagementInterface as CustomerAccountManagement;
@@ -131,7 +132,7 @@ protected function validateEmailFormat($email)
/**
* New subscription action
*
- * @return void
+ * @return \Magento\Framework\Controller\Result\Redirect
*/
public function execute()
{
@@ -160,7 +161,10 @@ public function execute()
$this->messageManager->addExceptionMessage($e, __('Something went wrong with the subscription.'));
}
}
- $this->getResponse()->setRedirect($this->_redirect->getRedirectUrl());
+ /** @var \Magento\Framework\Controller\Result\Redirect $redirect */
+ $redirect = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT);
+ $redirectUrl = $this->_redirect->getRedirectUrl();
+ return $redirect->setUrl($redirectUrl);
}
/**
diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php b/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php
index 88fa128162700..e37a3786e140a 100644
--- a/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php
+++ b/app/code/Magento/Newsletter/Controller/Subscriber/Unsubscribe.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Newsletter\Controller\Subscriber;
use Magento\Framework\App\Action\HttpGetActionInterface as HttpGetActionInterface;
@@ -15,7 +16,7 @@ class Unsubscribe extends \Magento\Newsletter\Controller\Subscriber implements H
/**
* Unsubscribe newsletter.
*
- * @return \Magento\Backend\Model\View\Result\Redirect
+ * @return \Magento\Framework\Controller\Result\Redirect
*/
public function execute()
{
@@ -25,14 +26,14 @@ public function execute()
if ($id && $code) {
try {
$this->_subscriberFactory->create()->load($id)->setCheckCode($code)->unsubscribe();
- $this->messageManager->addSuccess(__('You unsubscribed.'));
+ $this->messageManager->addSuccessMessage(__('You unsubscribed.'));
} catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addException($e, $e->getMessage());
+ $this->messageManager->addErrorMessage($e, $e->getMessage());
} catch (\Exception $e) {
- $this->messageManager->addException($e, __('Something went wrong while unsubscribing you.'));
+ $this->messageManager->addErrorMessage($e, __('Something went wrong while unsubscribing you.'));
}
}
- /** @var \Magento\Backend\Model\View\Result\Redirect $redirect */
+ /** @var \Magento\Framework\Controller\Result\Redirect $redirect */
$redirect = $this->resultFactory->create(\Magento\Framework\Controller\ResultFactory::TYPE_REDIRECT);
$redirectUrl = $this->_redirect->getRedirectUrl();
return $redirect->setUrl($redirectUrl);
diff --git a/app/code/Magento/Newsletter/Model/Plugin/CustomerPlugin.php b/app/code/Magento/Newsletter/Model/Plugin/CustomerPlugin.php
index 035013e572833..309bfadab41b3 100644
--- a/app/code/Magento/Newsletter/Model/Plugin/CustomerPlugin.php
+++ b/app/code/Magento/Newsletter/Model/Plugin/CustomerPlugin.php
@@ -6,13 +6,11 @@
namespace Magento\Newsletter\Model\Plugin;
use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository;
-use Magento\Customer\Api\Data\CustomerExtensionInterface;
use Magento\Customer\Api\Data\CustomerInterface;
+use Magento\Newsletter\Model\SubscriberFactory;
use Magento\Framework\Api\ExtensionAttributesFactory;
-use Magento\Framework\App\ObjectManager;
use Magento\Newsletter\Model\ResourceModel\Subscriber;
-use Magento\Newsletter\Model\SubscriberFactory;
-use Magento\Store\Model\StoreManagerInterface;
+use Magento\Customer\Api\Data\CustomerExtensionInterface;
/**
* Newsletter Plugin for customer
@@ -41,29 +39,21 @@ class CustomerPlugin
*/
private $customerSubscriptionStatus = [];
- /**
- * @var StoreManagerInterface
- */
- private $storeManager;
-
/**
* Initialize dependencies.
*
* @param SubscriberFactory $subscriberFactory
* @param ExtensionAttributesFactory $extensionFactory
* @param Subscriber $subscriberResource
- * @param StoreManagerInterface|null $storeManager
*/
public function __construct(
SubscriberFactory $subscriberFactory,
ExtensionAttributesFactory $extensionFactory,
- Subscriber $subscriberResource,
- StoreManagerInterface $storeManager = null
+ Subscriber $subscriberResource
) {
$this->subscriberFactory = $subscriberFactory;
$this->extensionFactory = $extensionFactory;
$this->subscriberResource = $subscriberResource;
- $this->storeManager = $storeManager ?: ObjectManager::getInstance()->get(StoreManagerInterface::class);
}
/**
@@ -164,8 +154,7 @@ public function afterDelete(CustomerRepository $subject, $result, CustomerInterf
public function afterGetById(CustomerRepository $subject, CustomerInterface $customer)
{
$extensionAttributes = $customer->getExtensionAttributes();
- $storeId = $this->storeManager->getStore()->getId();
- $customer->setStoreId($storeId);
+
if ($extensionAttributes === null) {
/** @var CustomerExtensionInterface $extensionAttributes */
$extensionAttributes = $this->extensionFactory->create(CustomerInterface::class);
diff --git a/app/code/Magento/Newsletter/Test/Unit/Model/Plugin/CustomerPluginTest.php b/app/code/Magento/Newsletter/Test/Unit/Model/Plugin/CustomerPluginTest.php
index 3be28cacc93e0..e809b7e37a432 100644
--- a/app/code/Magento/Newsletter/Test/Unit/Model/Plugin/CustomerPluginTest.php
+++ b/app/code/Magento/Newsletter/Test/Unit/Model/Plugin/CustomerPluginTest.php
@@ -10,8 +10,6 @@
use Magento\Customer\Api\Data\CustomerExtensionInterface;
use Magento\Framework\Api\ExtensionAttributesFactory;
use Magento\Newsletter\Model\ResourceModel\Subscriber;
-use Magento\Store\Model\Store;
-use Magento\Store\Model\StoreManagerInterface;
class CustomerPluginTest extends \PHPUnit\Framework\TestCase
{
@@ -55,11 +53,6 @@ class CustomerPluginTest extends \PHPUnit\Framework\TestCase
*/
private $customerMock;
- /**
- * @var StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $storeManagerMock;
-
protected function setUp()
{
$this->subscriberFactory = $this->getMockBuilder(\Magento\Newsletter\Model\SubscriberFactory::class)
@@ -94,8 +87,6 @@ protected function setUp()
->setMethods(['getExtensionAttributes'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $this->storeManagerMock = $this->createMock(StoreManagerInterface::class);
-
$this->subscriberFactory->expects($this->any())->method('create')->willReturn($this->subscriber);
$this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
@@ -105,7 +96,6 @@ protected function setUp()
'subscriberFactory' => $this->subscriberFactory,
'extensionFactory' => $this->extensionFactoryMock,
'subscriberResource' => $this->subscriberResourceMock,
- 'storeManager' => $this->storeManagerMock,
]
);
}
@@ -216,7 +206,6 @@ public function testAfterGetByIdCreatesExtensionAttributesIfItIsNotSet(
) {
$subject = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class);
$subscriber = [$subscriberStatusKey => $subscriberStatusValue];
- $this->prepareStoreData();
$this->extensionFactoryMock->expects($this->any())
->method('create')
@@ -244,7 +233,6 @@ public function testAfterGetByIdSetsIsSubscribedFlagIfItIsNotSet()
{
$subject = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class);
$subscriber = ['subscriber_id' => 1, 'subscriber_status' => 1];
- $this->prepareStoreData();
$this->customerMock->expects($this->any())
->method('getExtensionAttributes')
@@ -279,17 +267,4 @@ public function afterGetByIdDataProvider()
[null, null, false],
];
}
-
- /**
- * Prepare store information
- *
- * @return void
- */
- private function prepareStoreData()
- {
- $storeId = 1;
- $storeMock = $this->createMock(Store::class);
- $storeMock->expects($this->any())->method('getId')->willReturn($storeId);
- $this->storeManagerMock->expects($this->any())->method('getStore')->willReturn($storeMock);
- }
}
diff --git a/app/code/Magento/OfflinePayments/view/base/templates/info/pdf/checkmo.phtml b/app/code/Magento/OfflinePayments/view/base/templates/info/pdf/checkmo.phtml
new file mode 100644
index 0000000000000..4d63577319d5b
--- /dev/null
+++ b/app/code/Magento/OfflinePayments/view/base/templates/info/pdf/checkmo.phtml
@@ -0,0 +1,26 @@
+
+= $block->escapeHtml($block->getMethod()->getTitle()) ?>
+ {{pdf_row_separator}}
+getInfo()->getAdditionalInformation()): ?>
+ {{pdf_row_separator}}
+ getPayableTo()): ?>
+ = $block->escapeHtml(__('Make Check payable to: %1', $block->getPayableTo())) ?>
+ {{pdf_row_separator}}
+
+ getMailingAddress()): ?>
+ = $block->escapeHtml(__('Send Check to:')) ?>
+ {{pdf_row_separator}}
+ = /* @noEscape */ nl2br($block->escapeHtml($block->getMailingAddress())) ?>
+ {{pdf_row_separator}}
+
+
diff --git a/app/code/Magento/OfflinePayments/view/base/templates/info/pdf/purchaseorder.phtml b/app/code/Magento/OfflinePayments/view/base/templates/info/pdf/purchaseorder.phtml
new file mode 100644
index 0000000000000..4a6ea1c00b21c
--- /dev/null
+++ b/app/code/Magento/OfflinePayments/view/base/templates/info/pdf/purchaseorder.phtml
@@ -0,0 +1,11 @@
+
+= $block->escapeHtml(__('Purchase Order Number: %1', $block->getInfo()->getPoNumber())) ?>
+ {{pdf_row_separator}}
diff --git a/app/code/Magento/PageCache/Model/Cache/Server.php b/app/code/Magento/PageCache/Model/Cache/Server.php
index 349e9faffa673..7f3a4af969d7e 100644
--- a/app/code/Magento/PageCache/Model/Cache/Server.php
+++ b/app/code/Magento/PageCache/Model/Cache/Server.php
@@ -12,6 +12,9 @@
use Zend\Uri\Uri;
use Zend\Uri\UriFactory;
+/**
+ * Cache server model.
+ */
class Server
{
/**
@@ -62,8 +65,7 @@ public function getUris()
foreach ($configuredHosts as $host) {
$servers[] = UriFactory::factory('')
->setHost($host['host'])
- ->setPort(isset($host['port']) ? $host['port'] : self::DEFAULT_PORT)
- ;
+ ->setPort(isset($host['port']) ? $host['port'] : self::DEFAULT_PORT);
}
} elseif ($this->request->getHttpHost()) {
$servers[] = UriFactory::factory('')->setHost($this->request->getHttpHost())->setPort(self::DEFAULT_PORT);
diff --git a/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php b/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php
index e16584b0b17f8..7c9391ba22182 100644
--- a/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php
+++ b/app/code/Magento/PageCache/Model/System/Config/Backend/AccessList.php
@@ -28,7 +28,7 @@ public function beforeSave()
throw new LocalizedException(
new Phrase(
'Access List value "%1" is not valid. '
- .'Please use only IP addresses and host names.',
+ . 'Please use only IP addresses and host names.',
[$value]
)
);
diff --git a/app/code/Magento/PageCache/Model/Varnish/VclGenerator.php b/app/code/Magento/PageCache/Model/Varnish/VclGenerator.php
index cf5a703142c84..a50fa090de2d8 100644
--- a/app/code/Magento/PageCache/Model/Varnish/VclGenerator.php
+++ b/app/code/Magento/PageCache/Model/Varnish/VclGenerator.php
@@ -9,6 +9,9 @@
use Magento\PageCache\Model\VclGeneratorInterface;
use Magento\PageCache\Model\VclTemplateLocatorInterface;
+/**
+ * Varnish vcl generator model.
+ */
class VclGenerator implements VclGeneratorInterface
{
/**
@@ -119,7 +122,7 @@ private function getReplacements()
private function getRegexForDesignExceptions()
{
$result = '';
- $tpl = "%s (req.http.user-agent ~ \"%s\") {\n"." hash_data(\"%s\");\n"." }";
+ $tpl = "%s (req.http.user-agent ~ \"%s\") {\n" . " hash_data(\"%s\");\n" . " }";
$expressions = $this->getDesignExceptions();
@@ -143,7 +146,8 @@ private function getRegexForDesignExceptions()
/**
* Get IPs access list that can purge Varnish configuration for config file generation
- * and transform it to appropriate view
+ *
+ * Tansform it to appropriate view
*
* acl purge{
* "127.0.0.1";
@@ -157,7 +161,7 @@ private function getTransformedAccessList()
$result = array_reduce(
$this->getAccessList(),
function ($ips, $ip) use ($tpl) {
- return $ips.sprintf($tpl, trim($ip)) . "\n";
+ return $ips . sprintf($tpl, trim($ip)) . "\n";
},
''
);
@@ -216,6 +220,8 @@ private function getSslOffloadedHeader()
}
/**
+ * Get design exceptions array.
+ *
* @return array
*/
private function getDesignExceptions()
diff --git a/app/code/Magento/PageCache/Observer/SwitchPageCacheOnMaintenance.php b/app/code/Magento/PageCache/Observer/SwitchPageCacheOnMaintenance.php
new file mode 100644
index 0000000000000..7017da27eee93
--- /dev/null
+++ b/app/code/Magento/PageCache/Observer/SwitchPageCacheOnMaintenance.php
@@ -0,0 +1,108 @@
+cacheManager = $cacheManager;
+ $this->pageCacheStateStorage = $pageCacheStateStorage;
+ }
+
+ /**
+ * Switches Full Page Cache.
+ *
+ * Depending on enabling or disabling Maintenance Mode it turns off or restores Full Page Cache state.
+ *
+ * @param Observer $observer
+ * @return void
+ */
+ public function execute(Observer $observer): void
+ {
+ if ($observer->getData('isOn')) {
+ $this->pageCacheStateStorage->save($this->isFullPageCacheEnabled());
+ $this->turnOffFullPageCache();
+ } else {
+ $this->restoreFullPageCacheState();
+ }
+ }
+
+ /**
+ * Turns off Full Page Cache.
+ *
+ * @return void
+ */
+ private function turnOffFullPageCache(): void
+ {
+ if (!$this->isFullPageCacheEnabled()) {
+ return;
+ }
+
+ $this->cacheManager->clean([PageCacheType::TYPE_IDENTIFIER]);
+ $this->cacheManager->setEnabled([PageCacheType::TYPE_IDENTIFIER], false);
+ }
+
+ /**
+ * Full Page Cache state.
+ *
+ * @return bool
+ */
+ private function isFullPageCacheEnabled(): bool
+ {
+ $cacheStatus = $this->cacheManager->getStatus();
+
+ if (!array_key_exists(PageCacheType::TYPE_IDENTIFIER, $cacheStatus)) {
+ return false;
+ }
+
+ return (bool)$cacheStatus[PageCacheType::TYPE_IDENTIFIER];
+ }
+
+ /**
+ * Restores Full Page Cache state.
+ *
+ * Returns FPC to previous state that was before maintenance mode turning on.
+ *
+ * @return void
+ */
+ private function restoreFullPageCacheState(): void
+ {
+ $storedPageCacheState = $this->pageCacheStateStorage->isEnabled();
+ $this->pageCacheStateStorage->flush();
+
+ if ($storedPageCacheState) {
+ $this->cacheManager->setEnabled([PageCacheType::TYPE_IDENTIFIER], true);
+ }
+ }
+}
diff --git a/app/code/Magento/PageCache/Observer/SwitchPageCacheOnMaintenance/PageCacheState.php b/app/code/Magento/PageCache/Observer/SwitchPageCacheOnMaintenance/PageCacheState.php
new file mode 100644
index 0000000000000..e4cadf728f2ea
--- /dev/null
+++ b/app/code/Magento/PageCache/Observer/SwitchPageCacheOnMaintenance/PageCacheState.php
@@ -0,0 +1,74 @@
+flagDir = $fileSystem->getDirectoryWrite(DirectoryList::VAR_DIR);
+ }
+
+ /**
+ * Saves Full Page Cache state.
+ *
+ * Saves FPC state across requests.
+ *
+ * @param bool $state
+ * @return void
+ */
+ public function save(bool $state): void
+ {
+ $this->flagDir->writeFile(self::PAGE_CACHE_STATE_FILENAME, (string)$state);
+ }
+
+ /**
+ * Returns stored Full Page Cache state.
+ *
+ * @return bool
+ */
+ public function isEnabled(): bool
+ {
+ if (!$this->flagDir->isExist(self::PAGE_CACHE_STATE_FILENAME)) {
+ return false;
+ }
+
+ return (bool)$this->flagDir->readFile(self::PAGE_CACHE_STATE_FILENAME);
+ }
+
+ /**
+ * Flushes Page Cache state storage.
+ *
+ * @return void
+ */
+ public function flush(): void
+ {
+ $this->flagDir->delete(self::PAGE_CACHE_STATE_FILENAME);
+ }
+}
diff --git a/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml b/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml
index 34a77095d524d..ee0c32633569a 100644
--- a/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml
+++ b/app/code/Magento/PageCache/Test/Mftf/Section/AdminCacheManagementSection.xml
@@ -30,5 +30,6 @@
+
diff --git a/app/code/Magento/PageCache/Test/Mftf/Test/FlushStaticFilesCacheButtonVisibilityTest.xml b/app/code/Magento/PageCache/Test/Mftf/Test/FlushStaticFilesCacheButtonVisibilityTest.xml
new file mode 100644
index 0000000000000..bd6f7ba362bf4
--- /dev/null
+++ b/app/code/Magento/PageCache/Test/Mftf/Test/FlushStaticFilesCacheButtonVisibilityTest.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/PageCache/Test/Mftf/Test/NewProductsListWidgetTest.xml b/app/code/Magento/PageCache/Test/Mftf/Test/NewProductsListWidgetTest.xml
index c5871ddc3a373..a3c9e7b39217d 100644
--- a/app/code/Magento/PageCache/Test/Mftf/Test/NewProductsListWidgetTest.xml
+++ b/app/code/Magento/PageCache/Test/Mftf/Test/NewProductsListWidgetTest.xml
@@ -9,7 +9,7 @@
-
+
@@ -18,7 +18,7 @@
-
+
diff --git a/app/code/Magento/PageCache/Test/Unit/Observer/SwitchPageCacheOnMaintenanceTest.php b/app/code/Magento/PageCache/Test/Unit/Observer/SwitchPageCacheOnMaintenanceTest.php
new file mode 100644
index 0000000000000..2dbb815c70925
--- /dev/null
+++ b/app/code/Magento/PageCache/Test/Unit/Observer/SwitchPageCacheOnMaintenanceTest.php
@@ -0,0 +1,164 @@
+cacheManager = $this->createMock(Manager::class);
+ $this->pageCacheStateStorage = $this->createMock(PageCacheState::class);
+ $this->observer = $this->createMock(Observer::class);
+
+ $this->model = $objectManager->getObject(SwitchPageCacheOnMaintenance::class, [
+ 'cacheManager' => $this->cacheManager,
+ 'pageCacheStateStorage' => $this->pageCacheStateStorage,
+ ]);
+ }
+
+ /**
+ * Tests execute when setting maintenance mode to on.
+ *
+ * @param array $cacheStatus
+ * @param bool $cacheState
+ * @param int $flushCacheCalls
+ * @return void
+ * @dataProvider enablingPageCacheStateProvider
+ */
+ public function testExecuteWhileMaintenanceEnabling(
+ array $cacheStatus,
+ bool $cacheState,
+ int $flushCacheCalls
+ ): void {
+ $this->observer->method('getData')
+ ->with('isOn')
+ ->willReturn(true);
+ $this->cacheManager->method('getStatus')
+ ->willReturn($cacheStatus);
+
+ // Page Cache state will be stored.
+ $this->pageCacheStateStorage->expects($this->once())
+ ->method('save')
+ ->with($cacheState);
+
+ // Page Cache will be cleaned and disabled
+ $this->cacheManager->expects($this->exactly($flushCacheCalls))
+ ->method('clean')
+ ->with([PageCacheType::TYPE_IDENTIFIER]);
+ $this->cacheManager->expects($this->exactly($flushCacheCalls))
+ ->method('setEnabled')
+ ->with([PageCacheType::TYPE_IDENTIFIER], false);
+
+ $this->model->execute($this->observer);
+ }
+
+ /**
+ * Tests execute when setting Maintenance Mode to off.
+ *
+ * @param bool $storedCacheState
+ * @param int $enableCacheCalls
+ * @return void
+ * @dataProvider disablingPageCacheStateProvider
+ */
+ public function testExecuteWhileMaintenanceDisabling(bool $storedCacheState, int $enableCacheCalls): void
+ {
+ $this->observer->method('getData')
+ ->with('isOn')
+ ->willReturn(false);
+
+ $this->pageCacheStateStorage->method('isEnabled')
+ ->willReturn($storedCacheState);
+
+ // Nullify Page Cache state.
+ $this->pageCacheStateStorage->expects($this->once())
+ ->method('flush');
+
+ // Page Cache will be enabled.
+ $this->cacheManager->expects($this->exactly($enableCacheCalls))
+ ->method('setEnabled')
+ ->with([PageCacheType::TYPE_IDENTIFIER]);
+
+ $this->model->execute($this->observer);
+ }
+
+ /**
+ * Page Cache state data provider.
+ *
+ * @return array
+ */
+ public function enablingPageCacheStateProvider(): array
+ {
+ return [
+ 'page_cache_is_enable' => [
+ 'cache_status' => [PageCacheType::TYPE_IDENTIFIER => 1],
+ 'cache_state' => true,
+ 'flush_cache_calls' => 1,
+ ],
+ 'page_cache_is_missing_in_system' => [
+ 'cache_status' => [],
+ 'cache_state' => false,
+ 'flush_cache_calls' => 0,
+ ],
+ 'page_cache_is_disable' => [
+ 'cache_status' => [PageCacheType::TYPE_IDENTIFIER => 0],
+ 'cache_state' => false,
+ 'flush_cache_calls' => 0,
+ ],
+ ];
+ }
+
+ /**
+ * Page Cache state data provider.
+ *
+ * @return array
+ */
+ public function disablingPageCacheStateProvider(): array
+ {
+ return [
+ ['stored_cache_state' => true, 'enable_cache_calls' => 1],
+ ['stored_cache_state' => false, 'enable_cache_calls' => 0],
+ ];
+ }
+}
diff --git a/app/code/Magento/PageCache/etc/events.xml b/app/code/Magento/PageCache/etc/events.xml
index 7584f5f36d69c..3f0a2532ae60a 100644
--- a/app/code/Magento/PageCache/etc/events.xml
+++ b/app/code/Magento/PageCache/etc/events.xml
@@ -57,4 +57,7 @@
+
+
+
diff --git a/app/code/Magento/Payment/Model/CcConfigProvider.php b/app/code/Magento/Payment/Model/CcConfigProvider.php
index 15bdd0072a51a..497ce93c30c71 100644
--- a/app/code/Magento/Payment/Model/CcConfigProvider.php
+++ b/app/code/Magento/Payment/Model/CcConfigProvider.php
@@ -44,7 +44,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getConfig()
{
@@ -69,7 +69,7 @@ public function getIcons()
}
$types = $this->ccConfig->getCcAvailableTypes();
- foreach (array_keys($types) as $code) {
+ foreach ($types as $code => $label) {
if (!array_key_exists($code, $this->icons)) {
$asset = $this->ccConfig->createAsset('Magento_Payment::images/cc/' . strtolower($code) . '.png');
$placeholder = $this->assetSource->findSource($asset);
@@ -78,7 +78,8 @@ public function getIcons()
$this->icons[$code] = [
'url' => $asset->getUrl(),
'width' => $width,
- 'height' => $height
+ 'height' => $height,
+ 'title' => __($label),
];
}
}
diff --git a/app/code/Magento/Payment/Model/Method/Cc.php b/app/code/Magento/Payment/Model/Method/Cc.php
index c23ad5b535dd8..11629308cd46b 100644
--- a/app/code/Magento/Payment/Model/Method/Cc.php
+++ b/app/code/Magento/Payment/Model/Method/Cc.php
@@ -10,6 +10,8 @@
use Magento\Quote\Model\Quote\Payment;
/**
+ * Credit Card payment method legacy implementation.
+ *
* @method \Magento\Quote\Api\Data\PaymentMethodExtensionInterface getExtensionAttributes()
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @deprecated 100.0.8
@@ -93,6 +95,7 @@ public function __construct(
* @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function validate()
{
@@ -148,6 +151,22 @@ public function validate()
'JCB' => '/^35(2[8-9][0-9]{12,15}|[3-8][0-9]{13,16})/',
'MI' => '/^(5(0|[6-9])|63|67(?!59|6770|6774))\d*$/',
'MD' => '/^(6759(?!24|38|40|6[3-9]|70|76)|676770|676774)\d*$/',
+
+ //Hipercard
+ 'HC' => '/^((606282)|(637095)|(637568)|(637599)|(637609)|(637612))\d*$/',
+ //Elo
+ 'ELO' => '/^((509091)|(636368)|(636297)|(504175)|(438935)|(40117[8-9])|(45763[1-2])|' .
+ '(457393)|(431274)|(50990[0-2])|(5099[7-9][0-9])|(50996[4-9])|(509[1-8][0-9][0-9])|' .
+ '(5090(0[0-2]|0[4-9]|1[2-9]|[24589][0-9]|3[1-9]|6[0-46-9]|7[0-24-9]))|' .
+ '(5067(0[0-24-8]|1[0-24-9]|2[014-9]|3[0-379]|4[0-9]|5[0-3]|6[0-5]|7[0-8]))|' .
+ '(6504(0[5-9]|1[0-9]|2[0-9]|3[0-9]))|' .
+ '(6504(8[5-9]|9[0-9])|6505(0[0-9]|1[0-9]|2[0-9]|3[0-8]))|' .
+ '(6505(4[1-9]|5[0-9]|6[0-9]|7[0-9]|8[0-9]|9[0-8]))|' .
+ '(6507(0[0-9]|1[0-8]))|(65072[0-7])|(6509(0[1-9]|1[0-9]|20))|' .
+ '(6516(5[2-9]|6[0-9]|7[0-9]))|(6550(0[0-9]|1[0-9]))|' .
+ '(6550(2[1-9]|3[0-9]|4[0-9]|5[0-8])))\d*$/',
+ //Aura
+ 'AU' => '/^5078\d*$/'
];
$ccNumAndTypeMatches = isset(
@@ -189,6 +208,8 @@ public function validate()
}
/**
+ * Check if verification should be used.
+ *
* @return bool
* @api
*/
@@ -202,6 +223,8 @@ public function hasVerification()
}
/**
+ * Get list of credit cards verification reg exp.
+ *
* @return array
* @api
*/
@@ -226,6 +249,8 @@ public function getVerificationRegEx()
}
/**
+ * Validate expiration date
+ *
* @param string $expYear
* @param string $expMonth
* @return bool
@@ -276,6 +301,8 @@ public function assignData(\Magento\Framework\DataObject $data)
}
/**
+ * Get code for "other" credit cards.
+ *
* @param string $type
* @return bool
* @api
diff --git a/app/code/Magento/Payment/Test/Unit/Model/CcConfigProviderTest.php b/app/code/Magento/Payment/Test/Unit/Model/CcConfigProviderTest.php
index a8856166995fc..ff6aea44645cf 100644
--- a/app/code/Magento/Payment/Test/Unit/Model/CcConfigProviderTest.php
+++ b/app/code/Magento/Payment/Test/Unit/Model/CcConfigProviderTest.php
@@ -42,12 +42,14 @@ public function testGetConfig()
'vi' => [
'url' => 'http://cc.card/vi.png',
'width' => getimagesize($imagesDirectoryPath . 'vi.png')[0],
- 'height' => getimagesize($imagesDirectoryPath . 'vi.png')[1]
+ 'height' => getimagesize($imagesDirectoryPath . 'vi.png')[1],
+ 'title' => __('Visa'),
],
'ae' => [
'url' => 'http://cc.card/ae.png',
'width' => getimagesize($imagesDirectoryPath . 'ae.png')[0],
- 'height' => getimagesize($imagesDirectoryPath . 'ae.png')[1]
+ 'height' => getimagesize($imagesDirectoryPath . 'ae.png')[1],
+ 'title' => __('American Express'),
]
]
]
@@ -56,11 +58,13 @@ public function testGetConfig()
$ccAvailableTypesMock = [
'vi' => [
+ 'title' => 'Visa',
'fileId' => 'Magento_Payment::images/cc/vi.png',
'path' => $imagesDirectoryPath . 'vi.png',
'url' => 'http://cc.card/vi.png'
],
'ae' => [
+ 'title' => 'American Express',
'fileId' => 'Magento_Payment::images/cc/ae.png',
'path' => $imagesDirectoryPath . 'ae.png',
'url' => 'http://cc.card/ae.png'
@@ -68,7 +72,11 @@ public function testGetConfig()
];
$assetMock = $this->createMock(\Magento\Framework\View\Asset\File::class);
- $this->ccConfigMock->expects($this->once())->method('getCcAvailableTypes')->willReturn($ccAvailableTypesMock);
+ $this->ccConfigMock->expects($this->once())->method('getCcAvailableTypes')
+ ->willReturn(array_combine(
+ array_keys($ccAvailableTypesMock),
+ array_column($ccAvailableTypesMock, 'title')
+ ));
$this->ccConfigMock->expects($this->atLeastOnce())
->method('createAsset')
diff --git a/app/code/Magento/Payment/etc/payment.xml b/app/code/Magento/Payment/etc/payment.xml
index 19b5eb709c649..4afb6b01b366c 100644
--- a/app/code/Magento/Payment/etc/payment.xml
+++ b/app/code/Magento/Payment/etc/payment.xml
@@ -41,5 +41,14 @@
Maestro Domestic
+
+ Hipercard
+
+
+ Elo
+
+
+ Aura
+
diff --git a/app/code/Magento/Payment/view/base/templates/info/pdf/default.phtml b/app/code/Magento/Payment/view/base/templates/info/pdf/default.phtml
new file mode 100644
index 0000000000000..7acac62f65d38
--- /dev/null
+++ b/app/code/Magento/Payment/view/base/templates/info/pdf/default.phtml
@@ -0,0 +1,23 @@
+
+= $block->escapeHtml($block->getMethod()->getTitle()) ?>{{pdf_row_separator}}
+
+getSpecificInformation()):?>
+ $value):?>
+ = $block->escapeHtml($label) ?>:
+ = $block->escapeHtml(implode(' ', $block->getValueAsArray($value))) ?>
+ {{pdf_row_separator}}
+
+
+
+= $block->escapeHtml(implode('{{pdf_row_separator}}', $block->getChildPdfAsArray())) ?>
diff --git a/app/code/Magento/Payment/view/base/web/images/cc/au.png b/app/code/Magento/Payment/view/base/web/images/cc/au.png
new file mode 100644
index 0000000000000..04cb2df8fa332
Binary files /dev/null and b/app/code/Magento/Payment/view/base/web/images/cc/au.png differ
diff --git a/app/code/Magento/Payment/view/base/web/images/cc/elo.png b/app/code/Magento/Payment/view/base/web/images/cc/elo.png
new file mode 100644
index 0000000000000..eba0296a09104
Binary files /dev/null and b/app/code/Magento/Payment/view/base/web/images/cc/elo.png differ
diff --git a/app/code/Magento/Payment/view/base/web/images/cc/hc.png b/app/code/Magento/Payment/view/base/web/images/cc/hc.png
new file mode 100644
index 0000000000000..203e0b7e305c1
Binary files /dev/null and b/app/code/Magento/Payment/view/base/web/images/cc/hc.png differ
diff --git a/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js
index 3ac67f6f31002..1b387b384104f 100644
--- a/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js
+++ b/app/code/Magento/Payment/view/base/web/js/model/credit-card-validation/credit-card-number-validator/credit-card-type.js
@@ -110,6 +110,48 @@ define([
name: 'CVC',
size: 3
}
+ },
+ {
+ title: 'Hipercard',
+ type: 'HC',
+ pattern: '^((606282)|(637095)|(637568)|(637599)|(637609)|(637612))\\d*$',
+ gaps: [4, 8, 12],
+ lengths: [13, 16],
+ code: {
+ name: 'CVC',
+ size: 3
+ }
+ },
+ {
+ title: 'Elo',
+ type: 'ELO',
+ pattern: '^((509091)|(636368)|(636297)|(504175)|(438935)|(40117[8-9])|(45763[1-2])|' +
+ '(457393)|(431274)|(50990[0-2])|(5099[7-9][0-9])|(50996[4-9])|(509[1-8][0-9][0-9])|' +
+ '(5090(0[0-2]|0[4-9]|1[2-9]|[24589][0-9]|3[1-9]|6[0-46-9]|7[0-24-9]))|' +
+ '(5067(0[0-24-8]|1[0-24-9]|2[014-9]|3[0-379]|4[0-9]|5[0-3]|6[0-5]|7[0-8]))|' +
+ '(6504(0[5-9]|1[0-9]|2[0-9]|3[0-9]))|' +
+ '(6504(8[5-9]|9[0-9])|6505(0[0-9]|1[0-9]|2[0-9]|3[0-8]))|' +
+ '(6505(4[1-9]|5[0-9]|6[0-9]|7[0-9]|8[0-9]|9[0-8]))|' +
+ '(6507(0[0-9]|1[0-8]))|(65072[0-7])|(6509(0[1-9]|1[0-9]|20))|' +
+ '(6516(5[2-9]|6[0-9]|7[0-9]))|(6550(0[0-9]|1[0-9]))|' +
+ '(6550(2[1-9]|3[0-9]|4[0-9]|5[0-8])))\\d*$',
+ gaps: [4, 8, 12],
+ lengths: [16],
+ code: {
+ name: 'CVC',
+ size: 3
+ }
+ },
+ {
+ title: 'Aura',
+ type: 'AU',
+ pattern: '^5078\\d*$',
+ gaps: [4, 8, 12],
+ lengths: [19],
+ code: {
+ name: 'CVC',
+ size: 3
+ }
}
];
diff --git a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/ShippingOptionsCallback.php b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/ShippingOptionsCallback.php
index cb1b3388dc06a..fc3a45e1e1397 100644
--- a/app/code/Magento/Paypal/Controller/Express/AbstractExpress/ShippingOptionsCallback.php
+++ b/app/code/Magento/Paypal/Controller/Express/AbstractExpress/ShippingOptionsCallback.php
@@ -6,7 +6,17 @@
*/
namespace Magento\Paypal\Controller\Express\AbstractExpress;
-class ShippingOptionsCallback extends \Magento\Paypal\Controller\Express\AbstractExpress
+use Magento\Framework\App\CsrfAwareActionInterface;
+use Magento\Paypal\Controller\Express\AbstractExpress;
+use Magento\Framework\App\Request\InvalidRequestException;
+use Magento\Framework\App\RequestInterface;
+
+/**
+ * Returns shipping rates by server-to-server request from PayPal.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
+class ShippingOptionsCallback extends AbstractExpress implements CsrfAwareActionInterface
{
/**
* @var \Magento\Quote\Api\CartRepositoryInterface
@@ -65,4 +75,21 @@ public function execute()
$this->_objectManager->get(\Psr\Log\LoggerInterface::class)->critical($e);
}
}
+
+ /**
+ * @inheritDoc
+ */
+ public function createCsrfValidationException(
+ RequestInterface $request
+ ): ?InvalidRequestException {
+ return null;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function validateForCsrf(RequestInterface $request): ?bool
+ {
+ return true;
+ }
}
diff --git a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php
index 847388eb755a1..f4b4c39ca4021 100644
--- a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php
+++ b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php
@@ -12,6 +12,7 @@
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Session\Generic;
use Magento\Framework\Session\SessionManager;
+use Magento\Framework\Session\SessionManagerInterface;
use Magento\Paypal\Model\Payflow\Service\Request\SecureToken;
use Magento\Paypal\Model\Payflow\Transparent;
use Magento\Quote\Model\Quote;
@@ -40,7 +41,7 @@ class RequestSecureToken extends \Magento\Framework\App\Action\Action implements
private $secureTokenService;
/**
- * @var SessionManager
+ * @var SessionManager|SessionManagerInterface
*/
private $sessionManager;
@@ -56,6 +57,7 @@ class RequestSecureToken extends \Magento\Framework\App\Action\Action implements
* @param SecureToken $secureTokenService
* @param SessionManager $sessionManager
* @param Transparent $transparent
+ * @param SessionManagerInterface|null $sessionInterface
*/
public function __construct(
Context $context,
@@ -63,12 +65,13 @@ public function __construct(
Generic $sessionTransparent,
SecureToken $secureTokenService,
SessionManager $sessionManager,
- Transparent $transparent
+ Transparent $transparent,
+ SessionManagerInterface $sessionInterface = null
) {
$this->resultJsonFactory = $resultJsonFactory;
$this->sessionTransparent = $sessionTransparent;
$this->secureTokenService = $secureTokenService;
- $this->sessionManager = $sessionManager;
+ $this->sessionManager = $sessionInterface ?: $sessionManager;
$this->transparent = $transparent;
parent::__construct($context);
}
diff --git a/app/code/Magento/Paypal/Model/Express/Checkout.php b/app/code/Magento/Paypal/Model/Express/Checkout.php
index 38ba0983514b0..72f166e8d07c1 100644
--- a/app/code/Magento/Paypal/Model/Express/Checkout.php
+++ b/app/code/Magento/Paypal/Model/Express/Checkout.php
@@ -1076,6 +1076,7 @@ protected static function cmpShippingOptions(DataObject $option1, DataObject $op
*/
protected function _matchShippingMethodCode(Address $address, $selectedCode)
{
+ $address->collectShippingRates();
$options = $this->_prepareShippingOptions($address, false);
foreach ($options as $option) {
if ($selectedCode === $option['code'] // the proper case as outlined in documentation
diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml
index 97c7fbc471e97..32c2fab40e97a 100644
--- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OpenPayPalButtonCheckoutPageActionGroup.xml
@@ -8,12 +8,15 @@
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml
new file mode 100644
index 0000000000000..08ca6c7834384
--- /dev/null
+++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalExpressCheckoutConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalExpressCheckoutConfigurationActionGroup.xml
index 7bf26aceb316a..bae517ffe2f3e 100644
--- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalExpressCheckoutConfigurationActionGroup.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/PayPalExpressCheckoutConfigurationActionGroup.xml
@@ -8,18 +8,22 @@
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml
index d97e60043cc5d..ae34476e9ac0b 100644
--- a/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/Data/PaypalData.xml
@@ -38,6 +38,9 @@
0
+
+ someMerchantId
+
@@ -89,4 +92,11 @@
silver
black
+
+ myBusinessAccount@magento.com
+ myApiUsername.magento.com
+ somePassword
+ someApiSignature
+ someMerchantId
+
diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/OtherPayPalPaymentsConfigSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/OtherPayPalPaymentsConfigSection.xml
new file mode 100644
index 0000000000000..ca8438d5ee06a
--- /dev/null
+++ b/app/code/Magento/Paypal/Test/Mftf/Section/OtherPayPalPaymentsConfigSection.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml
index 3ac0bb2707556..85f94cd8691a5 100644
--- a/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/Section/PayPalExpressCheckoutConfigSection.xml
@@ -9,20 +9,24 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Paypal/Test/Mftf/Section/PaymentsConfigSection.xml b/app/code/Magento/Paypal/Test/Mftf/Section/PaymentsConfigSection.xml
new file mode 100644
index 0000000000000..35162cb7d619d
--- /dev/null
+++ b/app/code/Magento/Paypal/Test/Mftf/Section/PaymentsConfigSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml
new file mode 100644
index 0000000000000..b485fcb2a8f9a
--- /dev/null
+++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminConfigPaymentsConflictResolutionForPayPal.xml
@@ -0,0 +1,276 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml
index 565962518881b..d8b765b9b4d22 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml
@@ -15,7 +15,7 @@
+ >
wps_express
@@ -39,16 +39,16 @@
+ >
payments_pro_hosted_solution_with_express_checkout
- express_checkout_us
+ express_checkout_gb
-
+
@@ -56,19 +56,19 @@
payments_pro_hosted_solution_with_express_checkout
- express_checkout_us
+ express_checkout_gb
-
+
+ >
wps_express
@@ -98,4 +98,4 @@
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php
index 6ed8393f80658..8f216b64aa9b0 100644
--- a/app/code/Magento/Quote/Model/QuoteManagement.php
+++ b/app/code/Magento/Quote/Model/QuoteManagement.php
@@ -25,6 +25,7 @@
/**
* Class QuoteManagement
*
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.TooManyFields)
*/
@@ -356,6 +357,13 @@ public function placeOrder($cartId, PaymentInterface $paymentMethod = null)
if ($quote->getCheckoutMethod() === self::METHOD_GUEST) {
$quote->setCustomerId(null);
$quote->setCustomerEmail($quote->getBillingAddress()->getEmail());
+ if ($quote->getCustomerFirstname() === null && $quote->getCustomerLastname() === null) {
+ $quote->setCustomerFirstname($quote->getBillingAddress()->getFirstname());
+ $quote->setCustomerLastname($quote->getBillingAddress()->getLastname());
+ if ($quote->getBillingAddress()->getMiddlename() === null) {
+ $quote->setCustomerMiddlename($quote->getBillingAddress()->getMiddlename());
+ }
+ }
$quote->setCustomerIsGuest(true);
$quote->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID);
}
diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
index abecec395865d..392a815ed963c 100644
--- a/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
+++ b/app/code/Magento/Quote/Model/ResourceModel/Quote/Item/Collection.php
@@ -256,9 +256,8 @@ protected function _assignProducts(): self
foreach ($this as $item) {
/** @var ProductInterface $product */
$product = $productCollection->getItemById($item->getProductId());
- $isValidProduct = $this->isValidProduct($product);
$qtyOptions = [];
- if ($isValidProduct) {
+ if ($product && $this->isValidProduct($product)) {
$product->setCustomOptions([]);
$optionProductIds = $this->getOptionProductIds($item, $product, $productCollection);
foreach ($optionProductIds as $optionProductId) {
diff --git a/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml
index ab0db2dac643e..4ec608a18a686 100644
--- a/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml
+++ b/app/code/Magento/Quote/Test/Mftf/Test/StorefrontGuestCheckoutDisabledProductTest.xml
@@ -22,6 +22,9 @@
+
+
+
@@ -73,21 +76,22 @@
+
+
+
-
+
-
-
@@ -96,8 +100,6 @@
-
-
@@ -106,18 +108,44 @@
-
-
+
-
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php
index 72e516e35cd6e..b61f95b4eee6c 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/QuoteManagementTest.php
@@ -645,7 +645,7 @@ public function testPlaceOrderIfCustomerIsGuest()
$addressMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Address::class, ['getEmail']);
$addressMock->expects($this->once())->method('getEmail')->willReturn($email);
- $this->quoteMock->expects($this->once())->method('getBillingAddress')->with()->willReturn($addressMock);
+ $this->quoteMock->expects($this->any())->method('getBillingAddress')->with()->willReturn($addressMock);
$this->quoteMock->expects($this->once())->method('setCustomerIsGuest')->with(true)->willReturnSelf();
$this->quoteMock->expects($this->once())
diff --git a/app/code/Magento/QuoteAnalytics/composer.json b/app/code/Magento/QuoteAnalytics/composer.json
index 90dae1ec2adca..706bed674b4a9 100644
--- a/app/code/Magento/QuoteAnalytics/composer.json
+++ b/app/code/Magento/QuoteAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-quote": "*"
+ "magento/module-quote": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
index 2303d2aa14f03..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 {
@@ -75,7 +80,16 @@ public function execute(Quote $cart, array $cartItemData): void
throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku]));
}
- $result = $cart->addProduct($product, $this->createBuyRequest($qty, $customizableOptions));
+ try {
+ $result = $cart->addProduct($product, $this->createBuyRequest($qty, $customizableOptions));
+ } catch (\Exception $e) {
+ throw new GraphQlInputException(
+ __(
+ 'Could not add the product with SKU %sku to the shopping cart: %message',
+ ['sku' => $sku, 'message' => $e->getMessage()]
+ )
+ );
+ }
if (is_string($result)) {
throw new GraphQlInputException(__($result));
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php
deleted file mode 100644
index fb742477ec99b..0000000000000
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/AddressDataProvider.php
+++ /dev/null
@@ -1,94 +0,0 @@
-dataObjectConverter = $dataObjectConverter;
- }
-
- /**
- * Collect and return information about shipping and billing addresses
- *
- * @param CartInterface $cart
- * @return array
- */
- public function getCartAddresses(CartInterface $cart): array
- {
- $addressData = [];
- $shippingAddress = $cart->getShippingAddress();
- $billingAddress = $cart->getBillingAddress();
-
- if ($shippingAddress) {
- $shippingData = $this->dataObjectConverter->toFlatArray($shippingAddress, [], AddressInterface::class);
- $shippingData['address_type'] = 'SHIPPING';
- $addressData[] = array_merge($shippingData, $this->extractAddressData($shippingAddress));
- }
-
- if ($billingAddress) {
- $billingData = $this->dataObjectConverter->toFlatArray($billingAddress, [], AddressInterface::class);
- $billingData['address_type'] = 'BILLING';
- $addressData[] = array_merge($billingData, $this->extractAddressData($billingAddress));
- }
-
- return $addressData;
- }
-
- /**
- * Extract the necessary address fields from address model
- *
- * @param QuoteAddress $address
- * @return array
- */
- private function extractAddressData(QuoteAddress $address): array
- {
- $addressData = [
- 'country' => [
- 'code' => $address->getCountryId(),
- 'label' => $address->getCountry()
- ],
- 'region' => [
- 'code' => $address->getRegionCode(),
- 'label' => $address->getRegion()
- ],
- 'street' => $address->getStreet(),
- 'selected_shipping_method' => [
- 'code' => $address->getShippingMethod(),
- 'label' => $address->getShippingDescription(),
- 'free_shipping' => $address->getFreeShipping(),
- ],
- 'items_weight' => $address->getWeight(),
- 'customer_notes' => $address->getCustomerNotes()
- ];
-
- return $addressData;
- }
-}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/BillingAddressDataProvider.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/BillingAddressDataProvider.php
deleted file mode 100644
index bcd781025a6d6..0000000000000
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/BillingAddressDataProvider.php
+++ /dev/null
@@ -1,72 +0,0 @@
-cartRepository = $cartRepository;
- $this->addressMapper = $addressMapper;
- $this->dataObjectConverter = $dataObjectConverter;
- }
-
- /**
- * Collect and return information about a billing addresses
- *
- * @param CartInterface $cart
- * @return null|array
- */
- public function getCartAddresses(CartInterface $cart): ?array
- {
- $cart = $this->cartRepository->get($cart->getId());
- $billingAddress = $cart->getBillingAddress();
-
- if (!$billingAddress) {
- return null;
- }
- $billingData = $this->dataObjectConverter->toFlatArray($billingAddress, [], AddressInterface::class);
- $addressData = array_merge($billingData, $this->addressMapper->toNestedArray($billingAddress));
-
- return $addressData;
- }
-}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/Mapper/Address.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/Mapper/Address.php
deleted file mode 100644
index 64ac407e2692a..0000000000000
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/Mapper/Address.php
+++ /dev/null
@@ -1,61 +0,0 @@
- [
- 'code' => $address->getCountryId(),
- 'label' => $address->getCountry()
- ],
- 'region' => [
- 'code' => $address->getRegionCode(),
- 'label' => $address->getRegion()
- ],
- 'street' => $address->getStreet(),
- 'selected_shipping_method' => [
- 'code' => $address->getShippingMethod(),
- 'label' => $address->getShippingDescription(),
- 'free_shipping' => $address->getFreeShipping(),
- ],
- 'items_weight' => $address->getWeight(),
- 'customer_notes' => $address->getCustomerNotes()
- ];
-
- if (!$address->hasItems()) {
- return $addressData;
- }
-
- $addressItemsData = [];
- foreach ($address->getAllItems() as $addressItem) {
- $addressItemsData[] = [
- 'cart_item_id' => $addressItem->getQuoteItemId(),
- 'quantity' => $addressItem->getQty()
- ];
- }
- $addressData['cart_items'] = $addressItemsData;
-
- return $addressData;
- }
-}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/ShippingAddressesDataProvider.php b/app/code/Magento/QuoteGraphQl/Model/Cart/Address/ShippingAddressesDataProvider.php
deleted file mode 100644
index eb9d760809594..0000000000000
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/Address/ShippingAddressesDataProvider.php
+++ /dev/null
@@ -1,76 +0,0 @@
-dataObjectConverter = $dataObjectConverter;
- $this->cartRepository = $cartRepository;
- $this->addressMapper = $addressMapper;
- }
-
- /**
- * Collect and return information about shipping addresses
- *
- * @param CartInterface $cart
- * @return array
- */
- public function getCartAddresses(CartInterface $cart): array
- {
- $cart = $this->cartRepository->get($cart->getId());
- $addressData = [];
- $shippingAddresses = $cart->getAllShippingAddresses();
-
- if ($shippingAddresses) {
- foreach ($shippingAddresses as $shippingAddress) {
- $shippingData = $this->dataObjectConverter->toFlatArray($shippingAddress, [], AddressInterface::class);
- $addressData[] = array_merge($shippingData, $this->addressMapper->toNestedArray($shippingAddress));
- }
- }
-
- return $addressData;
- }
-}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php
new file mode 100644
index 0000000000000..dd6478b4873c6
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignBillingAddressToCart.php
@@ -0,0 +1,59 @@
+billingAddressManagement = $billingAddressManagement;
+ }
+
+ /**
+ * Assign billing address to cart
+ *
+ * @param CartInterface $cart
+ * @param AddressInterface $billingAddress
+ * @param bool $useForShipping
+ * @throws GraphQlInputException
+ * @throws GraphQlNoSuchEntityException
+ */
+ public function execute(
+ CartInterface $cart,
+ AddressInterface $billingAddress,
+ bool $useForShipping
+ ): void {
+ try {
+ $this->billingAddressManagement->assign($cart->getId(), $billingAddress, $useForShipping);
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php
new file mode 100644
index 0000000000000..527999b245a4c
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingAddressToCart.php
@@ -0,0 +1,57 @@
+shippingAddressManagement = $shippingAddressManagement;
+ }
+
+ /**
+ * Assign shipping address to cart
+ *
+ * @param CartInterface $cart
+ * @param AddressInterface $shippingAddress
+ * @throws GraphQlInputException
+ * @throws GraphQlNoSuchEntityException
+ */
+ public function execute(
+ CartInterface $cart,
+ AddressInterface $shippingAddress
+ ): void {
+ try {
+ $this->shippingAddressManagement->assign($cart->getId(), $shippingAddress);
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php
new file mode 100644
index 0000000000000..5b30c0774c22f
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AssignShippingMethodToCart.php
@@ -0,0 +1,82 @@
+shippingInformationFactory = $shippingInformationFactory;
+ $this->shippingInformationManagement = $shippingInformationManagement;
+ }
+
+ /**
+ * Assign shipping method to cart
+ *
+ * @param CartInterface $cart
+ * @param AddressInterface $quoteAddress
+ * @param string $carrierCode
+ * @param string $methodCode
+ * @throws GraphQlInputException
+ * @throws GraphQlNoSuchEntityException
+ */
+ public function execute(
+ CartInterface $cart,
+ AddressInterface $quoteAddress,
+ string $carrierCode,
+ string $methodCode
+ ): void {
+ /** @var ShippingInformationInterface $shippingInformation */
+ $shippingInformation = $this->shippingInformationFactory->create([
+ 'data' => [
+ /* If the address is not a shipping address (but billing) the system will find the proper shipping
+ address for the selected cart and set the information there (actual for single shipping address) */
+ ShippingInformationInterface::SHIPPING_ADDRESS => $quoteAddress,
+ ShippingInformationInterface::SHIPPING_CARRIER_CODE => $carrierCode,
+ ShippingInformationInterface::SHIPPING_METHOD_CODE => $methodCode,
+ ],
+ ]);
+
+ try {
+ $this->shippingInformationManagement->saveAddressInformation($cart->getId(), $shippingInformation);
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php
deleted file mode 100644
index 438f28980918d..0000000000000
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractDataFromCart.php
+++ /dev/null
@@ -1,50 +0,0 @@
-getAllItems() as $cartItem) {
- $productData = $cartItem->getProduct()->getData();
- $productData['model'] = $cartItem->getProduct();
-
- $items[] = [
- 'id' => $cartItem->getItemId(),
- 'qty' => $cartItem->getQty(),
- 'product' => $productData,
- 'model' => $cartItem,
- ];
- }
-
- $appliedCoupon = $cart->getCouponCode();
-
- return [
- 'items' => $items,
- 'applied_coupon' => $appliedCoupon ? ['code' => $appliedCoupon] : null
- ];
- }
-}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractQuoteAddressData.php b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractQuoteAddressData.php
new file mode 100644
index 0000000000000..840dedb4f274e
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/ExtractQuoteAddressData.php
@@ -0,0 +1,83 @@
+dataObjectConverter = $dataObjectConverter;
+ }
+
+ /**
+ * Converts Address model to flat array
+ *
+ * @param QuoteAddress $address
+ * @return array
+ */
+ public function execute(QuoteAddress $address): array
+ {
+ $addressData = $this->dataObjectConverter->toFlatArray($address, [], AddressInterface::class);
+ $addressData['model'] = $address;
+
+ if ($address->getAddressType() == AbstractAddress::TYPE_SHIPPING) {
+ $addressType = 'SHIPPING';
+ } elseif ($address->getAddressType() == AbstractAddress::TYPE_BILLING) {
+ $addressType = 'BILLING';
+ } else {
+ $addressType = null;
+ }
+
+ $addressData = array_merge($addressData, [
+ 'address_id' => $address->getId(),
+ 'address_type' => $addressType,
+ 'country' => [
+ 'code' => $address->getCountryId(),
+ 'label' => $address->getCountry()
+ ],
+ 'region' => [
+ 'code' => $address->getRegionCode(),
+ 'label' => $address->getRegion()
+ ],
+ 'street' => $address->getStreet(),
+ 'items_weight' => $address->getWeight(),
+ 'customer_notes' => $address->getCustomerNotes()
+ ]);
+
+ if (!$address->hasItems()) {
+ return $addressData;
+ }
+
+ $addressItemsData = [];
+ foreach ($address->getAllItems() as $addressItem) {
+ $addressItemsData[] = [
+ 'cart_item_id' => $addressItem->getQuoteItemId(),
+ 'quantity' => $addressItem->getQty()
+ ];
+ }
+ $addressData['cart_items'] = $addressItemsData;
+
+ return $addressData;
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php
index c3207bf478bbe..145e9c01980a5 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php
@@ -45,12 +45,12 @@ public function __construct(
* Get cart for user
*
* @param string $cartHash
- * @param int|null $userId
+ * @param int|null $customerId
* @return Quote
* @throws GraphQlAuthorizationException
* @throws GraphQlNoSuchEntityException
*/
- public function execute(string $cartHash, ?int $userId): Quote
+ public function execute(string $cartHash, ?int $customerId): Quote
{
try {
$cartId = $this->maskedQuoteIdToQuoteId->execute($cartHash);
@@ -69,14 +69,20 @@ public function execute(string $cartHash, ?int $userId): Quote
);
}
- $customerId = (int)$cart->getCustomerId();
+ if (false === (bool)$cart->getIsActive()) {
+ throw new GraphQlNoSuchEntityException(
+ __('Current user does not have an active cart.')
+ );
+ }
+
+ $cartCustomerId = (int)$cart->getCustomerId();
/* Guest cart, allow operations */
- if (!$customerId) {
+ if (!$cartCustomerId && null === $customerId) {
return $cart;
}
- if ($customerId !== $userId) {
+ if ($cartCustomerId !== $customerId) {
throw new GraphQlAuthorizationException(
__(
'The current user cannot perform operations on cart "%masked_cart_id"',
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php b/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php
new file mode 100644
index 0000000000000..1fb737d964139
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php
@@ -0,0 +1,82 @@
+quoteAddressFactory = $quoteAddressFactory;
+ $this->quoteAddressResource = $quoteAddressResource;
+ }
+
+ /**
+ * Get quote address
+ *
+ * @param int $quoteAddressId
+ * @param int|null $customerId
+ * @return AddressInterface
+ * @throws GraphQlInputException
+ * @throws GraphQlNoSuchEntityException
+ * @throws GraphQlAuthorizationException
+ */
+ public function execute(int $quoteAddressId, ?int $customerId): AddressInterface
+ {
+ $quoteAddress = $this->quoteAddressFactory->create();
+
+ $this->quoteAddressResource->load($quoteAddress, $quoteAddressId);
+ if (null === $quoteAddress->getId()) {
+ throw new GraphQlNoSuchEntityException(
+ __('Could not find a cart address with ID "%cart_address_id"', ['cart_address_id' => $quoteAddressId])
+ );
+ }
+
+ $quoteAddressCustomerId = (int)$quoteAddress->getCustomerId();
+
+ /* Guest cart, allow operations */
+ if (!$quoteAddressCustomerId && null === $customerId) {
+ return $quoteAddress;
+ }
+
+ if ($quoteAddressCustomerId !== $customerId) {
+ throw new GraphQlAuthorizationException(
+ __(
+ 'The current user cannot use cart address with ID "%cart_address_id"',
+ ['cart_address_id' => $quoteAddressId]
+ )
+ );
+ }
+ return $quoteAddress;
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php
new file mode 100644
index 0000000000000..76bdc74611131
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php
@@ -0,0 +1,67 @@
+quoteAddressFactory = $quoteAddressFactory;
+ }
+
+ /**
+ * Create QuoteAddress based on input data
+ *
+ * @param array $addressInput
+ * @return QuoteAddress
+ */
+ public function createBasedOnInputData(array $addressInput): QuoteAddress
+ {
+ $addressInput['country_id'] = $addressInput['country_code'] ?? '';
+
+ $quoteAddress = $this->quoteAddressFactory->create();
+ $quoteAddress->addData($addressInput);
+ return $quoteAddress;
+ }
+
+ /**
+ * Create QuoteAddress based on CustomerAddress
+ *
+ * @param CustomerAddress $customerAddress
+ * @return QuoteAddress
+ * @throws GraphQlInputException
+ */
+ public function createBasedOnCustomerAddress(CustomerAddress $customerAddress): QuoteAddress
+ {
+ $quoteAddress = $this->quoteAddressFactory->create();
+ try {
+ $quoteAddress->importCustomerAddressData($customerAddress);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+ return $quoteAddress;
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php
index 97b1ed09decc4..31524ea023222 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php
@@ -7,14 +7,12 @@
namespace Magento\QuoteGraphQl\Model\Cart;
-use Magento\Customer\Api\Data\AddressInterface;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
+use Magento\CustomerGraphQl\Model\Customer\Address\GetCustomerAddress;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Quote\Api\Data\CartInterface;
-use Magento\Quote\Model\Quote\Address;
-use Magento\Quote\Api\BillingAddressManagementInterface;
-use Magento\Customer\Api\AddressRepositoryInterface;
/**
* Set billing address for a specified shopping cart
@@ -22,78 +20,87 @@
class SetBillingAddressOnCart
{
/**
- * @var BillingAddressManagementInterface
+ * @var QuoteAddressFactory
*/
- private $billingAddressManagement;
+ private $quoteAddressFactory;
/**
- * @var AddressRepositoryInterface
+ * @var GetCustomer
*/
- private $addressRepository;
+ private $getCustomer;
/**
- * @var Address
+ * @var GetCustomerAddress
*/
- private $addressModel;
+ private $getCustomerAddress;
/**
- * @var CheckCustomerAccount
+ * @var AssignBillingAddressToCart
*/
- private $checkCustomerAccount;
+ private $assignBillingAddressToCart;
/**
- * @param BillingAddressManagementInterface $billingAddressManagement
- * @param AddressRepositoryInterface $addressRepository
- * @param Address $addressModel
- * @param CheckCustomerAccount $checkCustomerAccount
+ * @param QuoteAddressFactory $quoteAddressFactory
+ * @param GetCustomer $getCustomer
+ * @param GetCustomerAddress $getCustomerAddress
+ * @param AssignBillingAddressToCart $assignBillingAddressToCart
*/
public function __construct(
- BillingAddressManagementInterface $billingAddressManagement,
- AddressRepositoryInterface $addressRepository,
- Address $addressModel,
- CheckCustomerAccount $checkCustomerAccount
+ QuoteAddressFactory $quoteAddressFactory,
+ GetCustomer $getCustomer,
+ GetCustomerAddress $getCustomerAddress,
+ AssignBillingAddressToCart $assignBillingAddressToCart
) {
- $this->billingAddressManagement = $billingAddressManagement;
- $this->addressRepository = $addressRepository;
- $this->addressModel = $addressModel;
- $this->checkCustomerAccount = $checkCustomerAccount;
+ $this->quoteAddressFactory = $quoteAddressFactory;
+ $this->getCustomer = $getCustomer;
+ $this->getCustomerAddress = $getCustomerAddress;
+ $this->assignBillingAddressToCart = $assignBillingAddressToCart;
}
/**
- * @inheritdoc
+ * Set billing address for a specified shopping cart
+ *
+ * @param ContextInterface $context
+ * @param CartInterface $cart
+ * @param array $billingAddressInput
+ * @return void
+ * @throws GraphQlInputException
+ * @throws GraphQlNoSuchEntityException
*/
- public function execute(ContextInterface $context, CartInterface $cart, array $billingAddress): void
+ public function execute(ContextInterface $context, CartInterface $cart, array $billingAddressInput): void
{
- $customerAddressId = $billingAddress['customer_address_id'] ?? null;
- $addressInput = $billingAddress['address'] ?? null;
- $useForShipping = $billingAddress['use_for_shipping'] ?? false;
+ $customerAddressId = $billingAddressInput['customer_address_id'] ?? null;
+ $addressInput = $billingAddressInput['address'] ?? null;
+ $useForShipping = isset($billingAddressInput['use_for_shipping'])
+ ? (bool)$billingAddressInput['use_for_shipping'] : false;
if (null === $customerAddressId && null === $addressInput) {
throw new GraphQlInputException(
__('The billing address must contain either "customer_address_id" or "address".')
);
}
+
if ($customerAddressId && $addressInput) {
throw new GraphQlInputException(
__('The billing address cannot contain "customer_address_id" and "address" at the same time.')
);
}
+
$addresses = $cart->getAllShippingAddresses();
if ($useForShipping && count($addresses) > 1) {
throw new GraphQlInputException(
__('Using the "use_for_shipping" option with multishipping is not possible.')
);
}
+
if (null === $customerAddressId) {
- $billingAddress = $this->addressModel->addData($addressInput);
+ $billingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput);
} else {
- $this->checkCustomerAccount->execute($context->getUserId(), $context->getUserType());
-
- /** @var AddressInterface $customerAddress */
- $customerAddress = $this->addressRepository->getById($customerAddressId);
- $billingAddress = $this->addressModel->importCustomerAddressData($customerAddress);
+ $customer = $this->getCustomer->execute($context);
+ $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, (int)$customer->getId());
+ $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress);
}
- $this->billingAddressManagement->assign($cart->getId(), $billingAddress, $useForShipping);
+ $this->assignBillingAddressToCart->execute($cart, $billingAddress, $useForShipping);
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressOnCart.php
deleted file mode 100644
index b9fd5c7807d2f..0000000000000
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressOnCart.php
+++ /dev/null
@@ -1,98 +0,0 @@
-shippingAddressManagement = $shippingAddressManagement;
- $this->addressRepository = $addressRepository;
- $this->addressModel = $addressModel;
- $this->checkCustomerAccount = $checkCustomerAccount;
- }
-
- /**
- * @inheritdoc
- */
- public function execute(ContextInterface $context, CartInterface $cart, array $shippingAddresses): void
- {
- if (count($shippingAddresses) > 1) {
- throw new GraphQlInputException(
- __('You cannot specify multiple shipping addresses.')
- );
- }
- $shippingAddress = current($shippingAddresses);
- $customerAddressId = $shippingAddress['customer_address_id'] ?? null;
- $addressInput = $shippingAddress['address'] ?? null;
-
- if (null === $customerAddressId && null === $addressInput) {
- throw new GraphQlInputException(
- __('The shipping address must contain either "customer_address_id" or "address".')
- );
- }
- if ($customerAddressId && $addressInput) {
- throw new GraphQlInputException(
- __('The shipping address cannot contain "customer_address_id" and "address" at the same time.')
- );
- }
- if (null === $customerAddressId) {
- $shippingAddress = $this->addressModel->addData($addressInput);
- } else {
- $this->checkCustomerAccount->execute($context->getUserId(), $context->getUserType());
-
- /** @var AddressInterface $customerAddress */
- $customerAddress = $this->addressRepository->getById($customerAddressId);
- $shippingAddress = $this->addressModel->importCustomerAddressData($customerAddress);
- }
-
- $this->shippingAddressManagement->assign($cart->getId(), $shippingAddress);
- }
-}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php
new file mode 100644
index 0000000000000..bbca7721754cb
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php
@@ -0,0 +1,95 @@
+quoteAddressFactory = $quoteAddressFactory;
+ $this->getCustomer = $getCustomer;
+ $this->getCustomerAddress = $getCustomerAddress;
+ $this->assignShippingAddressToCart = $assignShippingAddressToCart;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(ContextInterface $context, CartInterface $cart, array $shippingAddressesInput): void
+ {
+ if (count($shippingAddressesInput) > 1) {
+ throw new GraphQlInputException(
+ __('You cannot specify multiple shipping addresses.')
+ );
+ }
+ $shippingAddressInput = current($shippingAddressesInput);
+ $customerAddressId = $shippingAddressInput['customer_address_id'] ?? null;
+ $addressInput = $shippingAddressInput['address'] ?? null;
+
+ if (null === $customerAddressId && null === $addressInput) {
+ throw new GraphQlInputException(
+ __('The shipping address must contain either "customer_address_id" or "address".')
+ );
+ }
+
+ if ($customerAddressId && $addressInput) {
+ throw new GraphQlInputException(
+ __('The shipping address cannot contain "customer_address_id" and "address" at the same time.')
+ );
+ }
+
+ if (null === $customerAddressId) {
+ $shippingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput);
+ } else {
+ $customer = $this->getCustomer->execute($context);
+ $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, (int)$customer->getId());
+ $shippingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress($customerAddress);
+ }
+
+ $this->assignShippingAddressToCart->execute($cart, $shippingAddress);
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCartInterface.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCartInterface.php
index c5da3db75add7..eb0f3522102cf 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCartInterface.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCartInterface.php
@@ -7,7 +7,10 @@
namespace Magento\QuoteGraphQl\Model\Cart;
+use Magento\Framework\GraphQl\Exception\GraphQlAuthenticationException;
+use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
+use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Quote\Api\Data\CartInterface;
@@ -24,9 +27,12 @@ interface SetShippingAddressesOnCartInterface
*
* @param ContextInterface $context
* @param CartInterface $cart
- * @param array $shippingAddresses
+ * @param array $shippingAddressesInput
* @return void
* @throws GraphQlInputException
+ * @throws GraphQlAuthorizationException
+ * @throws GraphQlAuthenticationException
+ * @throws GraphQlNoSuchEntityException
*/
- public function execute(ContextInterface $context, CartInterface $cart, array $shippingAddresses): void;
+ public function execute(ContextInterface $context, CartInterface $cart, array $shippingAddressesInput): void;
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodOnCart.php
deleted file mode 100644
index a630b2d07c7df..0000000000000
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodOnCart.php
+++ /dev/null
@@ -1,101 +0,0 @@
-shippingInformationManagement = $shippingInformationManagement;
- $this->quoteAddressResource = $quoteAddressResource;
- $this->quoteAddressFactory = $quoteAddressFactory;
- $this->shippingInformationFactory = $shippingInformationFactory;
- }
-
- /**
- * Sets shipping method for a specified shopping cart address
- *
- * @param Quote $cart
- * @param int $cartAddressId
- * @param string $carrierCode
- * @param string $methodCode
- * @throws GraphQlInputException
- * @throws GraphQlNoSuchEntityException
- */
- public function execute(Quote $cart, int $cartAddressId, string $carrierCode, string $methodCode): void
- {
- $quoteAddress = $this->quoteAddressFactory->create();
- $this->quoteAddressResource->load($quoteAddress, $cartAddressId);
-
- /** @var ShippingInformation $shippingInformation */
- $shippingInformation = $this->shippingInformationFactory->create();
-
- /* If the address is not a shipping address (but billing) the system will find the proper shipping address for
- the selected cart and set the information there (actual for single shipping address) */
- $shippingInformation->setShippingAddress($quoteAddress);
- $shippingInformation->setShippingCarrierCode($carrierCode);
- $shippingInformation->setShippingMethodCode($methodCode);
-
- try {
- $this->shippingInformationManagement->saveAddressInformation($cart->getId(), $shippingInformation);
- } catch (NoSuchEntityException $exception) {
- throw new GraphQlNoSuchEntityException(__($exception->getMessage()));
- } catch (StateException $exception) {
- throw new GraphQlInputException(__($exception->getMessage()));
- } catch (InputException $exception) {
- throw new GraphQlInputException(__($exception->getMessage()));
- }
- }
-}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php
new file mode 100644
index 0000000000000..37e0118423745
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php
@@ -0,0 +1,76 @@
+getQuoteAddress = $getQuoteAddress;
+ $this->assignShippingMethodToCart = $assignShippingMethodToCart;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(ContextInterface $context, CartInterface $cart, array $shippingMethodsInput): void
+ {
+ if (count($shippingMethodsInput) > 1) {
+ throw new GraphQlInputException(
+ __('You cannot specify multiple shipping methods.')
+ );
+ }
+ $shippingMethodInput = current($shippingMethodsInput);
+
+ if (!isset($shippingMethodInput['cart_address_id']) || empty($shippingMethodInput['cart_address_id'])) {
+ throw new GraphQlInputException(__('Required parameter "cart_address_id" is missing.'));
+ }
+ $cartAddressId = $shippingMethodInput['cart_address_id'];
+
+ if (!isset($shippingMethodInput['carrier_code']) || empty($shippingMethodInput['carrier_code'])) {
+ throw new GraphQlInputException(__('Required parameter "carrier_code" is missing.'));
+ }
+ $carrierCode = $shippingMethodInput['carrier_code'];
+
+ if (!isset($shippingMethodInput['method_code']) || empty($shippingMethodInput['method_code'])) {
+ throw new GraphQlInputException(__('Required parameter "method_code" is missing.'));
+ }
+ $methodCode = $shippingMethodInput['method_code'];
+
+ $quoteAddress = $this->getQuoteAddress->execute($cartAddressId, $context->getUserId());
+
+ $this->assignShippingMethodToCart->execute($cart, $quoteAddress, $carrierCode, $methodCode);
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCartInterface.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCartInterface.php
new file mode 100644
index 0000000000000..fa6c6cf0923e4
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCartInterface.php
@@ -0,0 +1,38 @@
+groupFactory($option->getType());
+ $optionTypeRenderer->setOption($option);
$priceValueUnits = $this->priceUnitLabel->getData($option->getPriceType());
$selectedOptionValueData = [
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php
index f4335b262c854..82ffd0970d672 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AddSimpleProductsToCart.php
@@ -11,9 +11,7 @@
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\Framework\Stdlib\ArrayManager;
use Magento\QuoteGraphQl\Model\Cart\AddProductsToCart;
-use Magento\QuoteGraphQl\Model\Cart\ExtractDataFromCart;
use Magento\QuoteGraphQl\Model\Cart\GetCartForUser;
/**
@@ -22,11 +20,6 @@
*/
class AddSimpleProductsToCart implements ResolverInterface
{
- /**
- * @var ArrayManager
- */
- private $arrayManager;
-
/**
* @var GetCartForUser
*/
@@ -38,26 +31,15 @@ class AddSimpleProductsToCart implements ResolverInterface
private $addProductsToCart;
/**
- * @var ExtractDataFromCart
- */
- private $extractDataFromCart;
-
- /**
- * @param ArrayManager $arrayManager
* @param GetCartForUser $getCartForUser
* @param AddProductsToCart $addProductsToCart
- * @param ExtractDataFromCart $extractDataFromCart
*/
public function __construct(
- ArrayManager $arrayManager,
GetCartForUser $getCartForUser,
- AddProductsToCart $addProductsToCart,
- ExtractDataFromCart $extractDataFromCart
+ AddProductsToCart $addProductsToCart
) {
- $this->arrayManager = $arrayManager;
$this->getCartForUser = $getCartForUser;
$this->addProductsToCart = $addProductsToCart;
- $this->extractDataFromCart = $extractDataFromCart;
}
/**
@@ -65,25 +47,25 @@ public function __construct(
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
- $cartHash = $this->arrayManager->get('input/cart_id', $args);
- $cartItems = $this->arrayManager->get('input/cartItems', $args);
-
- if (!isset($cartHash)) {
- throw new GraphQlInputException(__('Missing key "cart_id" in cart data'));
+ if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
+ throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
+ $maskedCartId = $args['input']['cart_id'];
- if (!isset($cartItems) || !is_array($cartItems) || empty($cartItems)) {
- throw new GraphQlInputException(__('Missing key "cartItems" in cart data'));
+ if (!isset($args['input']['cartItems']) || empty($args['input']['cartItems'])
+ || !is_array($args['input']['cartItems'])
+ ) {
+ throw new GraphQlInputException(__('Required parameter "cartItems" is missing'));
}
+ $cartItems = $args['input']['cartItems'];
- $currentUserId = $context->getUserId();
- $cart = $this->getCartForUser->execute((string)$cartHash, $currentUserId);
-
+ $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
$this->addProductsToCart->execute($cart, $cartItems);
- $cartData = $this->extractDataFromCart->execute($cart);
return [
- 'cart' => $cartData,
+ 'cart' => [
+ 'model' => $cart,
+ ],
];
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php
new file mode 100644
index 0000000000000..ca69b763929be
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php
@@ -0,0 +1,34 @@
+getCouponCode();
+
+ return $appliedCoupon ? ['code' => $appliedCoupon] : null;
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php
index ec59416d49371..5429d5333b25b 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ApplyCouponToCart.php
@@ -50,12 +50,12 @@ public function __construct(
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
- if (!isset($args['input']['cart_id'])) {
+ if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
$maskedCartId = $args['input']['cart_id'];
- if (!isset($args['input']['coupon_code'])) {
+ if (!isset($args['input']['coupon_code']) || empty($args['input']['coupon_code'])) {
throw new GraphQlInputException(__('Required parameter "coupon_code" is missing'));
}
$couponCode = $args['input']['coupon_code'];
@@ -74,15 +74,16 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
try {
$this->couponManagement->set($cartId, $couponCode);
- } catch (NoSuchEntityException $exception) {
- throw new GraphQlNoSuchEntityException(__($exception->getMessage()));
- } catch (CouldNotSaveException $exception) {
- throw new LocalizedException(__($exception->getMessage()));
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (CouldNotSaveException $e) {
+ throw new LocalizedException(__($e->getMessage()), $e);
}
- $data['cart']['applied_coupon'] = [
- 'code' => $couponCode,
+ return [
+ 'cart' => [
+ 'model' => $cart,
+ ],
];
- return $data;
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/AvailablePaymentMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AvailablePaymentMethods.php
new file mode 100644
index 0000000000000..907d778550593
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AvailablePaymentMethods.php
@@ -0,0 +1,68 @@
+informationManagement = $informationManagement;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
+ {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" value should be specified'));
+ }
+
+ $cart = $value['model'];
+ return $this->getPaymentMethodsData($cart);
+ }
+
+ /**
+ * Collect and return information about available payment methods
+ *
+ * @param CartInterface $cart
+ * @return array
+ */
+ private function getPaymentMethodsData(CartInterface $cart): array
+ {
+ $paymentInformation = $this->informationManagement->getPaymentInformation($cart->getId());
+ $paymentMethods = $paymentInformation->getPaymentMethods();
+
+ $paymentMethodsData = [];
+ foreach ($paymentMethods as $paymentMethod) {
+ $paymentMethodsData[] = [
+ 'title' => $paymentMethod->getTitle(),
+ 'code' => $paymentMethod->getCode(),
+ ];
+ }
+ return $paymentMethodsData;
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php
index 7de5a4ac05c24..a6bb0b0d04df1 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php
@@ -11,7 +11,8 @@
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\QuoteGraphQl\Model\Cart\Address\BillingAddressDataProvider;
+use Magento\Quote\Api\Data\CartInterface;
+use Magento\QuoteGraphQl\Model\Cart\ExtractQuoteAddressData;
/**
* @inheritdoc
@@ -19,17 +20,16 @@
class BillingAddress implements ResolverInterface
{
/**
- * @var BillingAddressDataProvider
+ * @var ExtractQuoteAddressData
*/
- private $addressDataProvider;
+ private $extractQuoteAddressData;
/**
- * @param BillingAddressDataProvider $addressDataProvider
+ * @param ExtractQuoteAddressData $extractQuoteAddressData
*/
- public function __construct(
- BillingAddressDataProvider $addressDataProvider
- ) {
- $this->addressDataProvider = $addressDataProvider;
+ public function __construct(ExtractQuoteAddressData $extractQuoteAddressData)
+ {
+ $this->extractQuoteAddressData = $extractQuoteAddressData;
}
/**
@@ -40,9 +40,15 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
if (!isset($value['model'])) {
throw new LocalizedException(__('"model" value should be specified'));
}
-
+ /** @var CartInterface $cart */
$cart = $value['model'];
- return $this->addressDataProvider->getCartAddresses($cart);
+ $billingAddress = $cart->getBillingAddress();
+ if (null === $billingAddress) {
+ return null;
+ }
+
+ $addressData = $this->extractQuoteAddressData->execute($billingAddress);
+ return $addressData;
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart.php
index 5023c186f1e6c..db74b1fa4174b 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/Cart.php
@@ -12,18 +12,12 @@
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\QuoteGraphQl\Model\Cart\GetCartForUser;
-use Magento\QuoteGraphQl\Model\Cart\ExtractDataFromCart;
/**
* @inheritdoc
*/
class Cart implements ResolverInterface
{
- /**
- * @var ExtractDataFromCart
- */
- private $extractDataFromCart;
-
/**
* @var GetCartForUser
*/
@@ -31,14 +25,11 @@ class Cart implements ResolverInterface
/**
* @param GetCartForUser $getCartForUser
- * @param ExtractDataFromCart $extractDataFromCart
*/
public function __construct(
- GetCartForUser $getCartForUser,
- ExtractDataFromCart $extractDataFromCart
+ GetCartForUser $getCartForUser
) {
$this->getCartForUser = $getCartForUser;
- $this->extractDataFromCart = $extractDataFromCart;
}
/**
@@ -46,7 +37,7 @@ public function __construct(
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
- if (!isset($args['cart_id'])) {
+ if (!isset($args['cart_id']) || empty($args['cart_id'])) {
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
$maskedCartId = $args['cart_id'];
@@ -54,14 +45,8 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
$currentUserId = $context->getUserId();
$cart = $this->getCartForUser->execute($maskedCartId, $currentUserId);
- $data = array_merge(
- [
- 'cart_id' => $maskedCartId,
- 'model' => $cart
- ],
- $this->extractDataFromCart->execute($cart)
- );
-
- return $data;
+ return [
+ 'model' => $cart,
+ ];
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php
new file mode 100644
index 0000000000000..da6619d15a489
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartItems.php
@@ -0,0 +1,48 @@
+getAllItems() as $cartItem) {
+ /**
+ * @var QuoteItem $cartItem
+ */
+ $productData = $cartItem->getProduct()->getData();
+ $productData['model'] = $cartItem->getProduct();
+
+ $itemsData[] = [
+ 'id' => $cartItem->getItemId(),
+ 'qty' => $cartItem->getQty(),
+ 'product' => $productData,
+ 'model' => $cartItem,
+ ];
+ }
+ return $itemsData;
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php
index c21d869ddac7d..5a708ceaedc28 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php
@@ -50,7 +50,7 @@ public function __construct(
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
- if (!isset($args['input']['cart_id'])) {
+ if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
$maskedCartId = $args['input']['cart_id'];
@@ -61,15 +61,16 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
try {
$this->couponManagement->remove($cartId);
- } catch (NoSuchEntityException $exception) {
- throw new GraphQlNoSuchEntityException(__($exception->getMessage()));
- } catch (CouldNotDeleteException $exception) {
- throw new LocalizedException(__($exception->getMessage()));
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (CouldNotDeleteException $e) {
+ throw new LocalizedException(__($e->getMessage()), $e);
}
- $data['cart']['applied_coupon'] = [
- 'code' => '',
+ return [
+ 'cart' => [
+ 'model' => $cart,
+ ],
];
- return $data;
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php
new file mode 100644
index 0000000000000..63e66f121f555
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveItemFromCart.php
@@ -0,0 +1,78 @@
+getCartForUser = $getCartForUser;
+ $this->cartItemRepository = $cartItemRepository;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ 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.'));
+ }
+ $maskedCartId = $args['input']['cart_id'];
+
+ if (!isset($args['input']['cart_item_id']) || empty($args['input']['cart_item_id'])) {
+ throw new GraphQlInputException(__('Required parameter "cart_item_id" is missing.'));
+ }
+ $itemId = $args['input']['cart_item_id'];
+
+ $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
+
+ try {
+ $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId);
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+
+ return [
+ 'cart' => [
+ 'model' => $cart,
+ ],
+ ];
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php
similarity index 60%
rename from app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php
rename to app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php
index 69544672bf12e..7a99b04638ac3 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php
@@ -11,27 +11,12 @@
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\QuoteGraphQl\Model\Cart\Address\AddressDataProvider;
/**
* @inheritdoc
*/
-class CartAddresses implements ResolverInterface
+class SelectedPaymentMethod implements ResolverInterface
{
- /**
- * @var AddressDataProvider
- */
- private $addressDataProvider;
-
- /**
- * @param AddressDataProvider $addressDataProvider
- */
- public function __construct(
- AddressDataProvider $addressDataProvider
- ) {
- $this->addressDataProvider = $addressDataProvider;
- }
-
/**
* @inheritdoc
*/
@@ -41,8 +26,17 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
throw new LocalizedException(__('"model" value should be specified'));
}
+ /** @var \Magento\Quote\Model\Quote $cart */
$cart = $value['model'];
- return $this->addressDataProvider->getCartAddresses($cart);
+ $payment = $cart->getPayment();
+ if (!$payment) {
+ return [];
+ }
+
+ return [
+ 'code' => $payment->getMethod(),
+ 'purchase_order_number' => $payment->getPoNumber(),
+ ];
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetBillingAddressOnCart.php
index 01a35f4b4152f..f7c9a4b0697b8 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetBillingAddressOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetBillingAddressOnCart.php
@@ -11,13 +11,10 @@
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\Framework\Stdlib\ArrayManager;
use Magento\QuoteGraphQl\Model\Cart\GetCartForUser;
use Magento\QuoteGraphQl\Model\Cart\SetBillingAddressOnCart as SetBillingAddressOnCartModel;
/**
- * Class SetBillingAddressOnCart
- *
* Mutation resolver for setting billing address for shopping cart
*/
class SetBillingAddressOnCart implements ResolverInterface
@@ -27,11 +24,6 @@ class SetBillingAddressOnCart implements ResolverInterface
*/
private $getCartForUser;
- /**
- * @var ArrayManager
- */
- private $arrayManager;
-
/**
* @var SetBillingAddressOnCartModel
*/
@@ -39,16 +31,13 @@ class SetBillingAddressOnCart implements ResolverInterface
/**
* @param GetCartForUser $getCartForUser
- * @param ArrayManager $arrayManager
* @param SetBillingAddressOnCartModel $setBillingAddressOnCart
*/
public function __construct(
GetCartForUser $getCartForUser,
- ArrayManager $arrayManager,
SetBillingAddressOnCartModel $setBillingAddressOnCart
) {
$this->getCartForUser = $getCartForUser;
- $this->arrayManager = $arrayManager;
$this->setBillingAddressOnCart = $setBillingAddressOnCart;
}
@@ -57,26 +46,23 @@ public function __construct(
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
- $billingAddress = $this->arrayManager->get('input/billing_address', $args);
- $maskedCartId = $this->arrayManager->get('input/cart_id', $args);
-
- if (!$maskedCartId) {
+ if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
- if (!$billingAddress) {
+ $maskedCartId = $args['input']['cart_id'];
+
+ if (!isset($args['input']['billing_address']) || empty($args['input']['billing_address'])) {
throw new GraphQlInputException(__('Required parameter "billing_address" is missing'));
}
+ $billingAddress = $args['input']['billing_address'];
- $maskedCartId = $args['input']['cart_id'];
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
-
$this->setBillingAddressOnCart->execute($context, $cart, $billingAddress);
return [
'cart' => [
- 'cart_id' => $maskedCartId,
'model' => $cart,
- ]
+ ],
];
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
new file mode 100644
index 0000000000000..d1dcb4a48a76b
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetPaymentMethodOnCart.php
@@ -0,0 +1,100 @@
+getCartForUser = $getCartForUser;
+ $this->paymentMethodManagement = $paymentMethodManagement;
+ $this->paymentFactory = $paymentFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ 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.'));
+ }
+ $maskedCartId = $args['input']['cart_id'];
+
+ if (!isset($args['input']['payment_method']['code']) || empty($args['input']['payment_method']['code'])) {
+ throw new GraphQlInputException(__('Required parameter "code" for "payment_method" is missing.'));
+ }
+ $paymentMethodCode = $args['input']['payment_method']['code'];
+
+ $poNumber = isset($args['input']['payment_method']['purchase_order_number'])
+ && empty($args['input']['payment_method']['purchase_order_number'])
+ ? $args['input']['payment_method']['purchase_order_number']
+ : null;
+
+ $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
+ $payment = $this->paymentFactory->create([
+ 'data' => [
+ PaymentInterface::KEY_METHOD => $paymentMethodCode,
+ PaymentInterface::KEY_PO_NUMBER => $poNumber,
+ PaymentInterface::KEY_ADDITIONAL_DATA => [],
+ ]
+ ]);
+
+ try {
+ $this->paymentMethodManagement->set($cart->getId(), $payment);
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+
+ return [
+ 'cart' => [
+ 'model' => $cart,
+ ],
+ ];
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingAddressesOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingAddressesOnCart.php
index b024e7b77af40..c3e1d371fe6a4 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingAddressesOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingAddressesOnCart.php
@@ -11,62 +11,33 @@
use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\Framework\Stdlib\ArrayManager;
-use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
-use Magento\Quote\Model\ShippingAddressManagementInterface;
use Magento\QuoteGraphQl\Model\Cart\GetCartForUser;
use Magento\QuoteGraphQl\Model\Cart\SetShippingAddressesOnCartInterface;
/**
- * Class SetShippingAddressesOnCart
- *
* Mutation resolver for setting shipping addresses for shopping cart
*/
class SetShippingAddressesOnCart implements ResolverInterface
{
- /**
- * @var MaskedQuoteIdToQuoteIdInterface
- */
- private $maskedQuoteIdToQuoteId;
-
- /**
- * @var ShippingAddressManagementInterface
- */
- private $shippingAddressManagement;
-
/**
* @var GetCartForUser
*/
private $getCartForUser;
- /**
- * @var ArrayManager
- */
- private $arrayManager;
-
/**
* @var SetShippingAddressesOnCartInterface
*/
private $setShippingAddressesOnCart;
/**
- * @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
- * @param ShippingAddressManagementInterface $shippingAddressManagement
* @param GetCartForUser $getCartForUser
- * @param ArrayManager $arrayManager
* @param SetShippingAddressesOnCartInterface $setShippingAddressesOnCart
*/
public function __construct(
- MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
- ShippingAddressManagementInterface $shippingAddressManagement,
GetCartForUser $getCartForUser,
- ArrayManager $arrayManager,
SetShippingAddressesOnCartInterface $setShippingAddressesOnCart
) {
- $this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId;
- $this->shippingAddressManagement = $shippingAddressManagement;
$this->getCartForUser = $getCartForUser;
- $this->arrayManager = $arrayManager;
$this->setShippingAddressesOnCart = $setShippingAddressesOnCart;
}
@@ -75,26 +46,23 @@ public function __construct(
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
- $shippingAddresses = $this->arrayManager->get('input/shipping_addresses', $args);
- $maskedCartId = $this->arrayManager->get('input/cart_id', $args);
-
- if (!$maskedCartId) {
+ if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
- if (!$shippingAddresses) {
+ $maskedCartId = $args['input']['cart_id'];
+
+ if (!isset($args['input']['shipping_addresses']) || empty($args['input']['shipping_addresses'])) {
throw new GraphQlInputException(__('Required parameter "shipping_addresses" is missing'));
}
+ $shippingAddresses = $args['input']['shipping_addresses'];
- $maskedCartId = $args['input']['cart_id'];
$cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
-
$this->setShippingAddressesOnCart->execute($context, $cart, $shippingAddresses);
return [
'cart' => [
- 'cart_id' => $maskedCartId,
'model' => $cart,
- ]
+ ],
];
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php
index 920829f5d67b1..e69ba47e7adf5 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetShippingMethodsOnCart.php
@@ -11,45 +11,34 @@
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\Framework\Stdlib\ArrayManager;
use Magento\QuoteGraphQl\Model\Cart\GetCartForUser;
-use Magento\QuoteGraphQl\Model\Cart\SetShippingMethodOnCart;
+use Magento\QuoteGraphQl\Model\Cart\SetShippingMethodsOnCartInterface;
/**
- * Class SetShippingMethodsOnCart
- *
* Mutation resolver for setting shipping methods for shopping cart
*/
class SetShippingMethodsOnCart implements ResolverInterface
{
/**
- * @var SetShippingMethodOnCart
- */
- private $setShippingMethodOnCart;
-
- /**
- * @var ArrayManager
+ * @var GetCartForUser
*/
- private $arrayManager;
+ private $getCartForUser;
/**
- * @var GetCartForUser
+ * @var SetShippingMethodsOnCartInterface
*/
- private $getCartForUser;
+ private $setShippingMethodsOnCart;
/**
- * @param ArrayManager $arrayManager
* @param GetCartForUser $getCartForUser
- * @param SetShippingMethodOnCart $setShippingMethodOnCart
+ * @param SetShippingMethodsOnCartInterface $setShippingMethodsOnCart
*/
public function __construct(
- ArrayManager $arrayManager,
GetCartForUser $getCartForUser,
- SetShippingMethodOnCart $setShippingMethodOnCart
+ SetShippingMethodsOnCartInterface $setShippingMethodsOnCart
) {
- $this->arrayManager = $arrayManager;
$this->getCartForUser = $getCartForUser;
- $this->setShippingMethodOnCart = $setShippingMethodOnCart;
+ $this->setShippingMethodsOnCart = $setShippingMethodsOnCart;
}
/**
@@ -57,43 +46,23 @@ public function __construct(
*/
public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
{
- $shippingMethods = $this->arrayManager->get('input/shipping_methods', $args);
- $maskedCartId = $this->arrayManager->get('input/cart_id', $args);
-
- if (!$maskedCartId) {
+ if (!isset($args['input']['cart_id']) || empty($args['input']['cart_id'])) {
throw new GraphQlInputException(__('Required parameter "cart_id" is missing'));
}
- if (!$shippingMethods) {
- throw new GraphQlInputException(__('Required parameter "shipping_methods" is missing'));
- }
+ $maskedCartId = $args['input']['cart_id'];
- $shippingMethod = reset($shippingMethods); // This point can be extended for multishipping
-
- if (!$shippingMethod['cart_address_id']) {
- throw new GraphQlInputException(__('Required parameter "cart_address_id" is missing'));
- }
- if (!$shippingMethod['shipping_carrier_code']) {
- throw new GraphQlInputException(__('Required parameter "shipping_carrier_code" is missing'));
- }
- if (!$shippingMethod['shipping_method_code']) {
- throw new GraphQlInputException(__('Required parameter "shipping_method_code" is missing'));
+ if (!isset($args['input']['shipping_methods']) || empty($args['input']['shipping_methods'])) {
+ throw new GraphQlInputException(__('Required parameter "shipping_methods" is missing'));
}
+ $shippingMethods = $args['input']['shipping_methods'];
- $userId = $context->getUserId();
- $cart = $this->getCartForUser->execute((string) $maskedCartId, $userId);
-
- $this->setShippingMethodOnCart->execute(
- $cart,
- $shippingMethod['cart_address_id'],
- $shippingMethod['shipping_carrier_code'],
- $shippingMethod['shipping_method_code']
- );
+ $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
+ $this->setShippingMethodsOnCart->execute($context, $cart, $shippingMethods);
return [
'cart' => [
- 'cart_id' => $maskedCartId,
- 'model' => $cart
- ]
+ 'model' => $cart,
+ ],
];
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php
new file mode 100644
index 0000000000000..a9e0ba59d15d9
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/AvailableShippingMethods.php
@@ -0,0 +1,77 @@
+dataObjectConverter = $dataObjectConverter;
+ $this->shippingMethodConverter = $shippingMethodConverter;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(Field $field, $context, ResolveInfo $info, array $value = null, array $args = null)
+ {
+ if (!isset($value['model'])) {
+ throw new LocalizedException(__('"model" values should be specified'));
+ }
+ $address = $value['model'];
+
+ // Allow shipping rates by setting country id for new addresses
+ if (!$address->getCountryId() && $address->getCountryCode()) {
+ $address->setCountryId($address->getCountryCode());
+ }
+
+ $address->setCollectShippingRates(true);
+ $address->collectShippingRates();
+ $cart = $address->getQuote();
+
+ $methods = [];
+ $shippingRates = $address->getGroupedAllShippingRates();
+ foreach ($shippingRates as $carrierRates) {
+ foreach ($carrierRates as $rate) {
+ $methods[] = $this->dataObjectConverter->toFlatArray(
+ $this->shippingMethodConverter->modelToDataObject($rate, $cart->getQuoteCurrencyCode()),
+ [],
+ ShippingMethodInterface::class
+ );
+ }
+ }
+ return $methods;
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php
new file mode 100644
index 0000000000000..c58affa064c89
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddress/SelectedShippingMethod.php
@@ -0,0 +1,43 @@
+getShippingMethod()) {
+ list($carrierCode, $methodCode) = explode('_', $address->getShippingMethod(), 2);
+ $shippingAmount = $address->getShippingAmount();
+ }
+
+ return [
+ 'carrier_code' => $carrierCode ?? null,
+ 'method_code' => $methodCode ?? null,
+ 'label' => $address->getShippingDescription(),
+ 'amount' => $shippingAmount ?? null,
+ ];
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php
index caa0eee22d702..eb3b0966740eb 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php
@@ -11,7 +11,8 @@
use Magento\Framework\GraphQl\Config\Element\Field;
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
-use Magento\QuoteGraphQl\Model\Cart\Address\ShippingAddressesDataProvider;
+use Magento\Quote\Model\Quote;
+use Magento\QuoteGraphQl\Model\Cart\ExtractQuoteAddressData;
/**
* @inheritdoc
@@ -19,17 +20,16 @@
class ShippingAddresses implements ResolverInterface
{
/**
- * @var ShippingAddressesDataProvider
+ * @var ExtractQuoteAddressData
*/
- private $addressDataProvider;
+ private $extractQuoteAddressData;
/**
- * @param ShippingAddressesDataProvider $addressDataProvider
+ * @param ExtractQuoteAddressData $extractQuoteAddressData
*/
- public function __construct(
- ShippingAddressesDataProvider $addressDataProvider
- ) {
- $this->addressDataProvider = $addressDataProvider;
+ public function __construct(ExtractQuoteAddressData $extractQuoteAddressData)
+ {
+ $this->extractQuoteAddressData = $extractQuoteAddressData;
}
/**
@@ -40,9 +40,17 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
if (!isset($value['model'])) {
throw new LocalizedException(__('"model" value should be specified'));
}
-
+ /** @var Quote $cart */
$cart = $value['model'];
- return $this->addressDataProvider->getCartAddresses($cart);
+ $addressesData = [];
+ $shippingAddresses = $cart->getAllShippingAddresses();
+
+ if (count($shippingAddresses)) {
+ foreach ($shippingAddresses as $shippingAddress) {
+ $addressesData[] = $this->extractQuoteAddressData->execute($shippingAddress);
+ }
+ }
+ return $addressesData;
}
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php
new file mode 100644
index 0000000000000..78a07506556c0
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/UpdateCartItems.php
@@ -0,0 +1,118 @@
+getCartForUser = $getCartForUser;
+ $this->cartItemRepository = $cartItemRepository;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ 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.'));
+ }
+ $maskedCartId = $args['input']['cart_id'];
+
+ if (!isset($args['input']['cart_items']) || empty($args['input']['cart_items'])
+ || !is_array($args['input']['cart_items'])
+ ) {
+ throw new GraphQlInputException(__('Required parameter "cart_items" is missing.'));
+ }
+ $cartItems = $args['input']['cart_items'];
+
+ $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
+
+ try {
+ $this->processCartItems($cart, $cartItems);
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__($e->getMessage()), $e);
+ }
+
+ return [
+ 'cart' => [
+ 'model' => $cart,
+ ],
+ ];
+ }
+
+ /**
+ * Process cart items
+ *
+ * @param Quote $cart
+ * @param array $items
+ * @throws GraphQlInputException
+ * @throws LocalizedException
+ */
+ private function processCartItems(Quote $cart, array $items): void
+ {
+ foreach ($items as $item) {
+ if (!isset($item['cart_item_id']) || empty($item['cart_item_id'])) {
+ throw new GraphQlInputException(__('Required parameter "cart_item_id" for "cart_items" is missing.'));
+ }
+ $itemId = $item['cart_item_id'];
+
+ if (!isset($item['quantity'])) {
+ throw new GraphQlInputException(__('Required parameter "quantity" for "cart_items" is missing.'));
+ }
+ $qty = (float)$item['quantity'];
+
+ $cartItem = $cart->getItemById($itemId);
+ if ($cartItem === false) {
+ throw new GraphQlNoSuchEntityException(
+ __('Could not find cart item with id: %1.', $item['cart_item_id'])
+ );
+ }
+
+ if ($qty <= 0.0) {
+ $this->cartItemRepository->deleteById((int)$cart->getId(), $itemId);
+ } else {
+ $cartItem->setQty($qty);
+ $this->cartItemRepository->save($cartItem);
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/etc/di.xml b/app/code/Magento/QuoteGraphQl/etc/di.xml
index 63ad9e193b955..0697761a2a2a6 100644
--- a/app/code/Magento/QuoteGraphQl/etc/di.xml
+++ b/app/code/Magento/QuoteGraphQl/etc/di.xml
@@ -11,6 +11,8 @@
- SimpleCartItem
+ - VirtualCartItem
+ - ConfigurableCartItem
diff --git a/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml b/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml
index 86bc954ae4ac4..c7389cf667845 100644
--- a/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml
+++ b/app/code/Magento/QuoteGraphQl/etc/graphql/di.xml
@@ -7,5 +7,7 @@
-->
+ type="Magento\QuoteGraphQl\Model\Cart\SetShippingAddressesOnCart"/>
+
diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
index 3448d640618d4..79cea3855f6f3 100644
--- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
@@ -2,30 +2,61 @@
# See COPYING.txt for license details.
type Query {
- Cart(cart_id: String!): Cart @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Cart") @doc(description:"Returns information about shopping cart")
+ cart(cart_id: String!): Cart @resolver (class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Cart") @doc(description:"Returns information about shopping cart")
}
type Mutation {
createEmptyCart: String @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CreateEmptyCart") @doc(description:"Creates an empty shopping cart for a guest or logged in user")
- applyCouponToCart(input: ApplyCouponToCartInput): ApplyCouponToCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Coupon\\ApplyCouponToCart")
- removeCouponFromCart(input: RemoveCouponFromCartInput): RemoveCouponFromCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\Coupon\\RemoveCouponFromCart")
- setShippingAddressesOnCart(input: SetShippingAddressesOnCartInput): SetShippingAddressesOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetShippingAddressesOnCart")
+ addSimpleProductsToCart(input: AddSimpleProductsToCartInput): AddSimpleProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart")
+ addVirtualProductsToCart(input: AddVirtualProductsToCartInput): AddVirtualProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart")
applyCouponToCart(input: ApplyCouponToCartInput): ApplyCouponToCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ApplyCouponToCart")
removeCouponFromCart(input: RemoveCouponFromCartInput): RemoveCouponFromCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\RemoveCouponFromCart")
+ updateCartItems(input: UpdateCartItemsInput): UpdateCartItemsOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\UpdateCartItems")
+ removeItemFromCart(input: RemoveItemFromCartInput): RemoveItemFromCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\RemoveItemFromCart")
+ setShippingAddressesOnCart(input: SetShippingAddressesOnCartInput): SetShippingAddressesOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetShippingAddressesOnCart")
setBillingAddressOnCart(input: SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetBillingAddressOnCart")
setShippingMethodsOnCart(input: SetShippingMethodsOnCartInput): SetShippingMethodsOnCartOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SetShippingMethodsOnCart")
- addSimpleProductsToCart(input: AddSimpleProductsToCartInput): AddSimpleProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart")
+ setPaymentMethodOnCart(input: SetPaymentMethodOnCartInput): SetPaymentMethodOnCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\SetPaymentMethodOnCart")
}
-input SetShippingAddressesOnCartInput {
+input AddSimpleProductsToCartInput {
cart_id: String!
- shipping_addresses: [ShippingAddressInput!]!
+ cartItems: [SimpleProductCartItemInput!]!
}
-input ShippingAddressInput {
- customer_address_id: Int # Can be provided in one-page checkout and is required for multi-shipping checkout
- address: CartAddressInput
- cart_items: [CartItemQuantityInput!]
+input SimpleProductCartItemInput {
+ data: CartItemInput!
+ customizable_options:[CustomizableOptionInput!]
+}
+
+input AddVirtualProductsToCartInput {
+ cart_id: String!
+ cartItems: [VirtualProductCartItemInput!]!
+}
+
+input VirtualProductCartItemInput {
+ data: CartItemInput!
+ customizable_options:[CustomizableOptionInput!]
+}
+
+input CartItemInput {
+ sku: String!
+ qty: Float!
+}
+
+input CustomizableOptionInput {
+ id: Int!
+ value: String!
+}
+
+input ApplyCouponToCartInput {
+ cart_id: String!
+ coupon_code: String!
+}
+
+input UpdateCartItemsInput {
+ cart_id: String!
+ cart_items: [CartItemQuantityInput!]!
}
input CartItemQuantityInput {
@@ -33,6 +64,21 @@ input CartItemQuantityInput {
quantity: Float!
}
+input RemoveItemFromCartInput {
+ cart_id: String!
+ cart_item_id: Int!
+}
+
+input SetShippingAddressesOnCartInput {
+ cart_id: String!
+ shipping_addresses: [ShippingAddressInput!]!
+}
+
+input ShippingAddressInput {
+ customer_address_id: Int # If provided then will be used address from address book
+ address: CartAddressInput
+}
+
input SetBillingAddressOnCartInput {
cart_id: String!
billing_address: BillingAddressInput!
@@ -59,38 +105,39 @@ input CartAddressInput {
input SetShippingMethodsOnCartInput {
cart_id: String!
- shipping_methods: [ShippingMethodForAddressInput!]!
+ shipping_methods: [ShippingMethodInput!]!
}
-input ShippingMethodForAddressInput {
+input ShippingMethodInput {
cart_address_id: Int!
- shipping_carrier_code: String!
- shipping_method_code: String!
+ carrier_code: String!
+ method_code: String!
}
-type SetBillingAddressOnCartOutput {
- cart: Cart!
+input SetPaymentMethodOnCartInput {
+ cart_id: String!
+ payment_method: PaymentMethodInput!
}
-type SetShippingAddressesOnCartOutput {
+input PaymentMethodInput {
+ code: String! @doc(description:"Payment method code")
+ purchase_order_number: String @doc(description:"Purchase order number")
+}
+
+type SetPaymentMethodOnCartOutput {
cart: Cart!
}
-type SetShippingMethodsOnCartOutput {
+type SetBillingAddressOnCartOutput {
cart: Cart!
}
-# If no address is provided, the system get address assigned to a quote
-# If there's no address at all - the system returns all shipping methods
-input AvailableShippingMethodsOnCartInput {
- cart_id: String!
- customer_address_id: Int
- address: CartAddressInput
+type SetShippingAddressesOnCartOutput {
+ cart: Cart!
}
-input ApplyCouponToCartInput {
- cart_id: String!
- coupon_code: String!
+type SetShippingMethodsOnCartOutput {
+ cart: Cart!
}
type ApplyCouponToCartOutput {
@@ -98,14 +145,16 @@ type ApplyCouponToCartOutput {
}
type Cart {
- cart_id: String
- items: [CartItemInterface]
- applied_coupon: AppliedCoupon
+ items: [CartItemInterface] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartItems")
+ applied_coupon: AppliedCoupon @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\AppliedCoupon")
shipping_addresses: [CartAddress]! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddresses")
billing_address: CartAddress! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\BillingAddress")
+ available_payment_methods: [AvailablePaymentMethod] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AvailablePaymentMethods") @doc(description: "Available payment methods")
+ selected_payment_method: SelectedPaymentMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\SelectedPaymentMethod")
}
type CartAddress {
+ address_id: Int
firstname: String
lastname: String
company: String
@@ -116,15 +165,15 @@ type CartAddress {
country: CartAddressCountry
telephone: String
address_type: AdressTypeEnum
- selected_shipping_method: CheckoutShippingMethod
- available_shipping_methods: [CheckoutShippingMethod]
+ available_shipping_methods: [AvailableShippingMethod] @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddress\\AvailableShippingMethods")
+ selected_shipping_method: SelectedShippingMethod @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\ShippingAddress\\SelectedShippingMethod")
items_weight: Float
customer_notes: String
cart_items: [CartItemQuantity]
}
type CartItemQuantity {
- cart_item_id: String!
+ cart_item_id: Int!
quantity: Float!
}
@@ -138,12 +187,33 @@ type CartAddressCountry {
label: String
}
-type CheckoutShippingMethod {
- code: String
+type SelectedShippingMethod {
+ carrier_code: String
+ method_code: String
label: String
- free_shipping: Boolean!
+ amount: Float
+}
+
+type AvailableShippingMethod {
+ carrier_code: String!
+ carrier_title: String!
+ method_code: String!
+ method_title: String!
error_message: String
- # TODO: Add more complex structure for shipping rates
+ amount: Float!
+ base_amount: Float!
+ price_excl_tax: Float!
+ price_incl_tax: Float!
+}
+
+type AvailablePaymentMethod {
+ code: String @doc(description: "The payment method code")
+ title: String @doc(description: "The payment method title.")
+}
+
+type SelectedPaymentMethod {
+ code: String @doc(description: "The payment method code")
+ purchase_order_number: String @doc(description: "The purchase order number.")
}
enum AdressTypeEnum {
@@ -163,22 +233,19 @@ type RemoveCouponFromCartOutput {
cart: Cart
}
-input AddSimpleProductsToCartInput {
- cart_id: String!
- cartItems: [SimpleProductCartItemInput!]!
+type AddSimpleProductsToCartOutput {
+ cart: Cart!
}
-input SimpleProductCartItemInput {
- data: CartItemInput!
- customizable_options:[CustomizableOptionInput!]
+type AddVirtualProductsToCartOutput {
+ cart: Cart!
}
-input CustomizableOptionInput {
- id: Int!
- value: String!
+type UpdateCartItemsOutput {
+ cart: Cart!
}
-type AddSimpleProductsToCartOutput {
+type RemoveItemFromCartOutput {
cart: Cart!
}
@@ -186,9 +253,8 @@ type SimpleCartItem implements CartItemInterface @doc(description: "Simple Cart
customizable_options: [SelectedCustomizableOption] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CustomizableOptions")
}
-input CartItemInput {
- sku: String!
- qty: Float!
+type VirtualCartItem implements CartItemInterface @doc(description: "Virtual Cart Item") {
+ customizable_options: [SelectedCustomizableOption] @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CustomizableOptions")
}
interface CartItemInterface @typeResolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\CartItemTypeResolver") {
@@ -219,8 +285,3 @@ type CartItemSelectedOptionValuePrice {
units: String!
type: PriceTypeEnum!
}
-
-input CartItemDetailsInput {
- sku: String!
- qty: Float!
-}
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Customer/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Customer/Collection.php
index bc5ceda53481e..aa01e33caf3d2 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Customer/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Customer/Collection.php
@@ -4,12 +4,13 @@
* See COPYING.txt for license details.
*/
-/**
- * Customers Report collection
- */
namespace Magento\Reports\Model\ResourceModel\Customer;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
+ * Customers Report collection.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @api
* @since 100.0.2
@@ -91,6 +92,7 @@ class Collection extends \Magento\Customer\Model\ResourceModel\Customer\Collecti
* @param mixed $connection
* @param string $modelName
*
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -109,7 +111,8 @@ public function __construct(
\Magento\Quote\Model\ResourceModel\Quote\Item\CollectionFactory $quoteItemFactory,
\Magento\Sales\Model\ResourceModel\Order\Collection $orderResource,
\Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
- $modelName = self::CUSTOMER_MODEL_NAME
+ $modelName = self::CUSTOMER_MODEL_NAME,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
parent::__construct(
$entityFactory,
@@ -124,7 +127,8 @@ public function __construct(
$entitySnapshot,
$fieldsetConfig,
$connection,
- $modelName
+ $modelName,
+ $resourceModelPool
);
$this->orderResource = $orderResource;
$this->quoteRepository = $quoteRepository;
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
index fd9adbe734101..d89a118bff94b 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Order/Collection.php
@@ -445,7 +445,7 @@ public function getDateRange($range, $customStart, $customEnd, $returnObjects =
break;
case 'custom':
- $dateStart = $customStart ? $customStart : $dateEnd;
+ $dateStart = $customStart ? $customStart : $dateStart;
$dateEnd = $customEnd ? $customEnd : $dateEnd;
break;
@@ -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/Reports/Model/ResourceModel/Product/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Collection.php
index 337c87f6da03d..451007960a1ce 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Product/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Collection.php
@@ -5,13 +5,20 @@
*/
/**
- * Products Report collection
- *
* @author Magento Core Team
*/
namespace Magento\Reports\Model\ResourceModel\Product;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
+ * Products Report collection.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @api
* @since 100.0.2
@@ -89,7 +96,13 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param \Magento\Catalog\Model\Product\Type $productType
* @param \Magento\Quote\Model\ResourceModel\Quote\Collection $quoteResource
* @param mixed $connection
- *
+ * @param ProductLimitationFactory|null $productLimitationFactory
+ * @param MetadataPool|null $metadataPool
+ * @param TableMaintainer|null $tableMaintainer
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface $resourceModelPool
+ * @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -116,7 +129,13 @@ public function __construct(
\Magento\Reports\Model\Event\TypeFactory $eventTypeFactory,
\Magento\Catalog\Model\Product\Type $productType,
\Magento\Quote\Model\ResourceModel\Quote\Collection $quoteResource,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ ProductLimitationFactory $productLimitationFactory = null,
+ MetadataPool $metadataPool = null,
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->setProductEntityId($product->getEntityIdField());
$this->setProductEntityTableName($product->getEntityTable());
@@ -141,7 +160,13 @@ public function __construct(
$customerSession,
$dateTime,
$groupManagement,
- $connection
+ $connection,
+ $productLimitationFactory,
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
$this->_eventTypeFactory = $eventTypeFactory;
$this->_productType = $productType;
@@ -149,7 +174,8 @@ public function __construct(
}
/**
- * Set Type for COUNT SQL Select
+ * Set Type for COUNT SQL Select.
+ *
* @codeCoverageIgnore
*
* @param int $type
@@ -162,7 +188,8 @@ public function setSelectCountSqlType($type)
}
/**
- * Set product entity id
+ * Set product entity id.
+ *
* @codeCoverageIgnore
*
* @param string $entityId
@@ -175,7 +202,8 @@ public function setProductEntityId($entityId)
}
/**
- * Get product entity id
+ * Get product entity id.
+ *
* @codeCoverageIgnore
*
* @return int
@@ -186,7 +214,8 @@ public function getProductEntityId()
}
/**
- * Set product entity table name
+ * Set product entity table name.
+ *
* @codeCoverageIgnore
*
* @param string $value
@@ -199,7 +228,8 @@ public function setProductEntityTableName($value)
}
/**
- * Get product entity table name
+ * Get product entity table name.
+ *
* @codeCoverageIgnore
*
* @return string
@@ -210,7 +240,8 @@ public function getProductEntityTableName()
}
/**
- * Get product attribute set id
+ * Get product attribute set id.
+ *
* @codeCoverageIgnore
*
* @return int
@@ -221,7 +252,8 @@ public function getProductAttributeSetId()
}
/**
- * Set product attribute set id
+ * Set product attribute set id.
+ *
* @codeCoverageIgnore
*
* @param int $value
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Index/Collection/AbstractCollection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Index/Collection/AbstractCollection.php
index 7371bc4359f46..bec8faaee0ca7 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Product/Index/Collection/AbstractCollection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Index/Collection/AbstractCollection.php
@@ -5,13 +5,20 @@
*/
/**
- * Reports Product Index Abstract Product Resource Collection
- *
* @author Magento Core Team
*/
namespace Magento\Reports\Model\ResourceModel\Product\Index\Collection;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\Framework\EntityManager\MetadataPool;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
+
/**
+ * Reports Product Index Abstract Product Resource Collection.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @api
* @since 100.0.2
@@ -53,7 +60,12 @@ abstract class AbstractCollection extends \Magento\Catalog\Model\ResourceModel\P
* @param \Magento\Customer\Api\GroupManagementInterface $groupManagement
* @param \Magento\Customer\Model\Visitor $customerVisitor
* @param mixed $connection
- *
+ * @param ProductLimitationFactory|null $productLimitationFactory
+ * @param MetadataPool|null $metadataPool
+ * @param TableMaintainer|null $tableMaintainer
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -77,7 +89,13 @@ public function __construct(
\Magento\Framework\Stdlib\DateTime $dateTime,
\Magento\Customer\Api\GroupManagementInterface $groupManagement,
\Magento\Customer\Model\Visitor $customerVisitor,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ ProductLimitationFactory $productLimitationFactory = null,
+ MetadataPool $metadataPool = null,
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
parent::__construct(
$entityFactory,
@@ -99,7 +117,13 @@ public function __construct(
$customerSession,
$dateTime,
$groupManagement,
- $connection
+ $connection,
+ $productLimitationFactory,
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
$this->_customerVisitor = $customerVisitor;
}
@@ -181,7 +205,8 @@ protected function _getWhereCondition()
}
/**
- * Set customer id, that will be used in 'whereCondition'
+ * Set customer id, that will be used in 'whereCondition'.
+ *
* @codeCoverageIgnore
*
* @param int $id
diff --git a/app/code/Magento/Reports/Model/ResourceModel/Product/Lowstock/Collection.php b/app/code/Magento/Reports/Model/ResourceModel/Product/Lowstock/Collection.php
index 732d819e3b2cd..8bf50f4c1b8e7 100644
--- a/app/code/Magento/Reports/Model/ResourceModel/Product/Lowstock/Collection.php
+++ b/app/code/Magento/Reports/Model/ResourceModel/Product/Lowstock/Collection.php
@@ -5,15 +5,21 @@
*/
/**
- * Product Low Stock Report Collection
- *
* @author Magento Core Team
*/
namespace Magento\Reports\Model\ResourceModel\Product\Lowstock;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Exception\LocalizedException;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
/**
+ * Product Low Stock Report Collection.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @api
* @since 100.0.2
@@ -78,7 +84,13 @@ class Collection extends \Magento\Reports\Model\ResourceModel\Product\Collection
* @param \Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration
* @param \Magento\CatalogInventory\Model\ResourceModel\Stock\Item $itemResource
* @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection
- *
+ * @param ProductLimitationFactory|null $productLimitationFactory
+ * @param MetadataPool|null $metadataPool
+ * @param TableMaintainer|null $tableMaintainer
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface|null $resourceModelPool
+ * @throws LocalizedException
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -108,7 +120,13 @@ public function __construct(
\Magento\CatalogInventory\Api\StockRegistryInterface $stockRegistry,
\Magento\CatalogInventory\Api\StockConfigurationInterface $stockConfiguration,
\Magento\CatalogInventory\Model\ResourceModel\Stock\Item $itemResource,
- \Magento\Framework\DB\Adapter\AdapterInterface $connection = null
+ \Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
+ ProductLimitationFactory $productLimitationFactory = null,
+ MetadataPool $metadataPool = null,
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
parent::__construct(
$entityFactory,
@@ -134,7 +152,13 @@ public function __construct(
$eventTypeFactory,
$productType,
$quoteResource,
- $connection
+ $connection,
+ $productLimitationFactory,
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
$this->stockRegistry = $stockRegistry;
$this->stockConfiguration = $stockConfiguration;
diff --git a/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Product/CollectionTest.php b/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
index 038d37a990442..cb4d51e0c540d 100644
--- a/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
+++ b/app/code/Magento/Reports/Test/Unit/Model/ResourceModel/Product/CollectionTest.php
@@ -12,6 +12,7 @@
use Magento\Catalog\Model\Product\Type as ProductType;
use Magento\Catalog\Model\ResourceModel\Helper;
use Magento\Catalog\Model\ResourceModel\Product as ResourceProduct;
+use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
use Magento\Catalog\Model\ResourceModel\Url;
use Magento\Customer\Api\GroupManagementInterface;
use Magento\Customer\Model\Session;
@@ -25,7 +26,9 @@
use Magento\Framework\Data\Collection\EntityFactory;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\DB\Select;
+use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Event\ManagerInterface;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
use Magento\Framework\Module\Manager;
use Magento\Framework\Stdlib\DateTime;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
@@ -34,6 +37,7 @@
use Magento\Quote\Model\ResourceModel\Quote\Collection;
use Magento\Reports\Model\Event\TypeFactory;
use Magento\Reports\Model\ResourceModel\Product\Collection as ProductCollection;
+use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\StoreManagerInterface;
use Psr\Log\LoggerInterface;
@@ -78,46 +82,6 @@ class CollectionTest extends \PHPUnit\Framework\TestCase
protected function setUp()
{
$this->objectManager = new ObjectManager($this);
- $context = $this->createPartialMock(Context::class, ['getResource', 'getEavConfig']);
- $entityFactoryMock = $this->createMock(EntityFactory::class);
- $loggerMock = $this->createMock(LoggerInterface::class);
- $fetchStrategyMock = $this->createMock(FetchStrategyInterface::class);
- $eventManagerMock = $this->createMock(ManagerInterface::class);
- $eavConfigMock = $this->createMock(Config::class);
- $this->resourceMock = $this->createPartialMock(ResourceConnection::class, ['getTableName', 'getConnection']);
- $eavEntityFactoryMock = $this->createMock(EavEntityFactory::class);
- $resourceHelperMock = $this->createMock(Helper::class);
- $universalFactoryMock = $this->createMock(UniversalFactory::class);
- $storeManagerMock = $this->createPartialMockForAbstractClass(
- StoreManagerInterface::class,
- ['getStore', 'getId']
- );
- $moduleManagerMock = $this->createMock(Manager::class);
- $productFlatStateMock = $this->createMock(State::class);
- $scopeConfigMock = $this->createMock(ScopeConfigInterface::class);
- $optionFactoryMock = $this->createMock(OptionFactory::class);
- $catalogUrlMock = $this->createMock(Url::class);
- $localeDateMock = $this->createMock(TimezoneInterface::class);
- $customerSessionMock = $this->createMock(Session::class);
- $dateTimeMock = $this->createMock(DateTime::class);
- $groupManagementMock = $this->createMock(GroupManagementInterface::class);
- $eavConfig = $this->createPartialMock(Config::class, ['getEntityType']);
- $entityType = $this->createMock(Type::class);
-
- $eavConfig->expects($this->atLeastOnce())->method('getEntityType')->willReturn($entityType);
- $context->expects($this->atLeastOnce())->method('getResource')->willReturn($this->resourceMock);
- $context->expects($this->atLeastOnce())->method('getEavConfig')->willReturn($eavConfig);
-
- $defaultAttributes = $this->createPartialMock(DefaultAttributes::class, ['_getDefaultAttributes']);
- $productMock = $this->objectManager->getObject(
- ResourceProduct::class,
- ['context' => $context, 'defaultAttributes' => $defaultAttributes]
- );
-
- $this->eventTypeFactoryMock = $this->createMock(TypeFactory::class);
- $productTypeMock = $this->createMock(ProductType::class);
- $quoteResourceMock = $this->createMock(Collection::class);
- $this->connectionMock = $this->createPartialMockForAbstractClass(AdapterInterface::class, ['select']);
$this->selectMock = $this->createPartialMock(
Select::class,
[
@@ -130,39 +94,65 @@ protected function setUp()
'having',
]
);
-
- $storeManagerMock->expects($this->atLeastOnce())->method('getStore')->willReturn($storeManagerMock);
- $storeManagerMock->expects($this->atLeastOnce())->method('getId')->willReturn(1);
- $universalFactoryMock->expects($this->atLeastOnce())->method('create')->willReturn($productMock);
+ $this->connectionMock = $this->createMock(AdapterInterface::class);
+ $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock);
+ $this->resourceMock = $this->createPartialMock(ResourceConnection::class, ['getTableName', 'getConnection']);
$this->resourceMock->expects($this->atLeastOnce())->method('getTableName')->willReturn('test_table');
$this->resourceMock->expects($this->atLeastOnce())->method('getConnection')->willReturn($this->connectionMock);
- $this->connectionMock->expects($this->atLeastOnce())->method('select')->willReturn($this->selectMock);
+ $eavConfig = $this->createPartialMock(Config::class, ['getEntityType']);
+ $eavConfig->expects($this->atLeastOnce())->method('getEntityType')->willReturn($this->createMock(Type::class));
+ $context = $this->createPartialMock(Context::class, ['getResource', 'getEavConfig']);
+ $context->expects($this->atLeastOnce())->method('getResource')->willReturn($this->resourceMock);
+ $context->expects($this->atLeastOnce())->method('getEavConfig')->willReturn($eavConfig);
+ $storeMock = $this->createMock(StoreInterface::class);
+ $storeMock->expects($this->atLeastOnce())->method('getId')->willReturn(1);
+ $storeManagerMock = $this->createMock(StoreManagerInterface::class);
+ $storeManagerMock->expects($this->atLeastOnce())->method('getStore')->willReturn($storeMock);
+ $productMock = $this->objectManager->getObject(
+ ResourceProduct::class,
+ [
+ 'context' => $context,
+ 'defaultAttributes' => $this->createPartialMock(
+ DefaultAttributes::class,
+ ['_getDefaultAttributes']
+ )
+ ]
+ );
+ $resourceModelPoolMock = $this->createMock(ResourceModelPoolInterface::class);
+ $resourceModelPoolMock->expects($this->atLeastOnce())->method('get')->willReturn($productMock);
+ $this->eventTypeFactoryMock = $this->createMock(TypeFactory::class);
$this->collection = new ProductCollection(
- $entityFactoryMock,
- $loggerMock,
- $fetchStrategyMock,
- $eventManagerMock,
- $eavConfigMock,
+ $this->createMock(EntityFactory::class),
+ $this->createMock(LoggerInterface::class),
+ $this->createMock(FetchStrategyInterface::class),
+ $this->createMock(ManagerInterface::class),
+ $this->createMock(Config::class),
$this->resourceMock,
- $eavEntityFactoryMock,
- $resourceHelperMock,
- $universalFactoryMock,
+ $this->createMock(EavEntityFactory::class),
+ $this->createMock(Helper::class),
+ $this->createMock(UniversalFactory::class),
$storeManagerMock,
- $moduleManagerMock,
- $productFlatStateMock,
- $scopeConfigMock,
- $optionFactoryMock,
- $catalogUrlMock,
- $localeDateMock,
- $customerSessionMock,
- $dateTimeMock,
- $groupManagementMock,
+ $this->createMock(Manager::class),
+ $this->createMock(State::class),
+ $this->createMock(ScopeConfigInterface::class),
+ $this->createMock(OptionFactory::class),
+ $this->createMock(Url::class),
+ $this->createMock(TimezoneInterface::class),
+ $this->createMock(Session::class),
+ $this->createMock(DateTime::class),
+ $this->createMock(GroupManagementInterface::class),
$productMock,
$this->eventTypeFactoryMock,
- $productTypeMock,
- $quoteResourceMock,
- $this->connectionMock
+ $this->createMock(ProductType::class),
+ $this->createMock(Collection::class),
+ $this->connectionMock,
+ $this->createMock(ProductLimitationFactory::class),
+ $this->createMock(MetadataPool::class),
+ $this->createMock(\Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer::class),
+ $this->createMock(\Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver::class),
+ $this->createMock(\Magento\Framework\Indexer\DimensionFactory::class),
+ $resourceModelPoolMock
);
}
@@ -262,25 +252,4 @@ public function testAddViewsCount()
$this->collection->addViewsCount();
}
-
- /**
- * Get mock for abstract class with methods.
- *
- * @param string $className
- * @param array $methods
- *
- * @return \PHPUnit_Framework_MockObject_MockObject
- */
- private function createPartialMockForAbstractClass($className, $methods)
- {
- return $this->getMockForAbstractClass(
- $className,
- [],
- '',
- true,
- true,
- true,
- $methods
- );
- }
}
diff --git a/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php b/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php
index 3033a31ff1723..d4e50a9e43d68 100644
--- a/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php
+++ b/app/code/Magento/Review/Model/ResourceModel/Review/Product/Collection.php
@@ -5,10 +5,14 @@
*/
namespace Magento\Review\Model\ResourceModel\Review\Product;
+use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Catalog\Model\Indexer\Product\Price\PriceTableResolver;
+use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
use Magento\Framework\DB\Select;
use Magento\Framework\EntityManager\MetadataPool;
-use Magento\Catalog\Model\ResourceModel\Product\Collection\ProductLimitationFactory;
+use Magento\Framework\Indexer\DimensionFactory;
+use Magento\Framework\Model\ResourceModel\ResourceModelPoolInterface;
/**
* Review Product Collection
@@ -88,7 +92,10 @@ class Collection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
* @param \Magento\Framework\DB\Adapter\AdapterInterface|null $connection
* @param ProductLimitationFactory|null $productLimitationFactory
* @param MetadataPool|null $metadataPool
- *
+ * @param TableMaintainer|null $tableMaintainer
+ * @param PriceTableResolver|null $priceTableResolver
+ * @param DimensionFactory|null $dimensionFactory
+ * @param ResourceModelPoolInterface|null $resourceModelPool
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -115,7 +122,11 @@ public function __construct(
\Magento\Review\Model\Rating\Option\VoteFactory $voteFactory,
\Magento\Framework\DB\Adapter\AdapterInterface $connection = null,
ProductLimitationFactory $productLimitationFactory = null,
- MetadataPool $metadataPool = null
+ MetadataPool $metadataPool = null,
+ TableMaintainer $tableMaintainer = null,
+ PriceTableResolver $priceTableResolver = null,
+ DimensionFactory $dimensionFactory = null,
+ ResourceModelPoolInterface $resourceModelPool = null
) {
$this->_ratingFactory = $ratingFactory;
$this->_voteFactory = $voteFactory;
@@ -141,7 +152,11 @@ public function __construct(
$groupManagement,
$connection,
$productLimitationFactory,
- $metadataPool
+ $metadataPool,
+ $tableMaintainer,
+ $priceTableResolver,
+ $dimensionFactory,
+ $resourceModelPool
);
}
diff --git a/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php b/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php
index 6256194cef53b..f35d6eac27ea8 100644
--- a/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php
+++ b/app/code/Magento/Review/Observer/CatalogBlockProductCollectionBeforeToHtmlObserver.php
@@ -7,6 +7,9 @@
use Magento\Framework\Event\ObserverInterface;
+/**
+ * Review block observer.
+ */
class CatalogBlockProductCollectionBeforeToHtmlObserver implements ObserverInterface
{
/**
@@ -35,7 +38,9 @@ public function execute(\Magento\Framework\Event\Observer $observer)
{
$productCollection = $observer->getEvent()->getCollection();
if ($productCollection instanceof \Magento\Framework\Data\Collection) {
- $productCollection->load();
+ if (!$productCollection->isLoaded()) {
+ $productCollection->load();
+ }
$this->_reviewFactory->create()->appendSummary($productCollection);
}
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/Review/view/frontend/web/js/process-reviews.js b/app/code/Magento/Review/view/frontend/web/js/process-reviews.js
index d1c40959e3ec2..88c61fa38af34 100644
--- a/app/code/Magento/Review/view/frontend/web/js/process-reviews.js
+++ b/app/code/Magento/Review/view/frontend/web/js/process-reviews.js
@@ -20,7 +20,7 @@ define([
showLoader: false,
loaderContext: $('.product.data.items')
}).done(function (data) {
- $('#product-review-container').html(data);
+ $('#product-review-container').html(data).trigger('contentUpdated');
$('[data-role="product-review"] .pages a').each(function (index, element) {
$(element).click(function (event) { //eslint-disable-line max-nested-callbacks
processReviews($(element).attr('href'), true);
diff --git a/app/code/Magento/ReviewAnalytics/composer.json b/app/code/Magento/ReviewAnalytics/composer.json
index 73f534451580c..a82d4328ca159 100644
--- a/app/code/Magento/ReviewAnalytics/composer.json
+++ b/app/code/Magento/ReviewAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-review": "*"
+ "magento/module-review": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/Robots/Model/Config/Value.php b/app/code/Magento/Robots/Model/Config/Value.php
index c4e17e55f1262..16a5a486e1078 100644
--- a/app/code/Magento/Robots/Model/Config/Value.php
+++ b/app/code/Magento/Robots/Model/Config/Value.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Robots\Model\Config;
use Magento\Framework\App\Cache\TypeListInterface;
@@ -32,12 +33,11 @@ class Value extends ConfigValue implements IdentityInterface
const CACHE_TAG = 'robots';
/**
- * Model cache tag for clear cache in after save and after delete
+ * @inheritdoc
*
- * @var string
* @since 100.2.0
*/
- protected $_cacheTag = true;
+ protected $_cacheTag = [self::CACHE_TAG];
/**
* @var StoreManagerInterface
diff --git a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php
index 5ab1379b96cf6..e216e2ae658ba 100644
--- a/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php
+++ b/app/code/Magento/Rule/Model/Condition/Product/AbstractProduct.php
@@ -95,8 +95,8 @@ abstract class AbstractProduct extends \Magento\Rule\Model\Condition\AbstractCon
* @param \Magento\Catalog\Model\ResourceModel\Product $productResource
* @param \Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\Collection $attrSetCollection
* @param \Magento\Framework\Locale\FormatInterface $localeFormat
- * @param ProductCategoryList|null $categoryList
* @param array $data
+ * @param ProductCategoryList|null $categoryList
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -514,6 +514,10 @@ public function loadArray($arr)
) ? $this->_localeFormat->getNumber(
$arr['is_value_parsed']
) : false;
+ } elseif (!empty($arr['operator']) && $arr['operator'] == '()') {
+ if (isset($arr['value'])) {
+ $arr['value'] = preg_replace('/\s*,\s*/', ',', $arr['value']);
+ }
}
return parent::loadArray($arr);
@@ -695,6 +699,7 @@ protected function _getAttributeSetId($productId)
/**
* Correct '==' and '!=' operators
+ *
* Categories can't be equal because product is included categories selected by administrator and in their parents
*
* @return string
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
index d15c218a60b47..6b87c1fe39d8b 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/AbstractForm.php
@@ -6,6 +6,7 @@
namespace Magento\Sales\Block\Adminhtml\Order\Create\Form;
use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Customer\Api\Data\AttributeMetadataInterface;
/**
* Sales Order Create Form Abstract Block
@@ -57,8 +58,7 @@ public function __construct(
}
/**
- * Prepare global layout
- * Add renderers to \Magento\Framework\Data\Form
+ * Prepare global layout. Add renderers to \Magento\Framework\Data\Form
*
* @return $this
*/
@@ -152,7 +152,7 @@ protected function _addAdditionalFormElementData(\Magento\Framework\Data\Form\El
/**
* Add rendering EAV attributes to Form element
*
- * @param \Magento\Customer\Api\Data\AttributeMetadataInterface[] $attributes
+ * @param AttributeMetadataInterface[] $attributes
* @param \Magento\Framework\Data\Form\AbstractForm $form
* @return $this
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
@@ -176,8 +176,8 @@ protected function _addAttributesToForm($attributes, \Magento\Framework\Data\For
[
'name' => $attribute->getAttributeCode(),
'label' => __($attribute->getStoreLabel()),
- 'class' => $attribute->getFrontendClass(),
- 'required' => $attribute->isRequired()
+ 'class' => $this->getValidationClasses($attribute),
+ 'required' => $attribute->isRequired(),
]
);
if ($inputType == 'multiline') {
@@ -227,4 +227,58 @@ public function getFormValues()
{
return [];
}
+
+ /**
+ * Retrieve frontend classes according validation rules
+ *
+ * @param AttributeMetadataInterface $attribute
+ *
+ * @return string
+ */
+ private function getValidationClasses(AttributeMetadataInterface $attribute) : string
+ {
+ $out = [];
+ $out[] = $attribute->getFrontendClass();
+
+ $textClasses = $this->getTextLengthValidateClasses($attribute);
+ if (!empty($textClasses)) {
+ $out = array_merge($out, $textClasses);
+ }
+
+ $out = !empty($out) ? implode(' ', array_unique(array_filter($out))) : '';
+ return $out;
+ }
+
+ /**
+ * Retrieve validation classes by min_text_length and max_text_length rules
+ *
+ * @param AttributeMetadataInterface $attribute
+ *
+ * @return array
+ */
+ private function getTextLengthValidateClasses(AttributeMetadataInterface $attribute) : array
+ {
+ $classes = [];
+
+ $validateRules = $attribute->getValidationRules();
+ if (!empty($validateRules)) {
+ foreach ($validateRules as $rule) {
+ switch ($rule->getName()) {
+ case 'min_text_length':
+ $classes[] = 'minimum-length-' . $rule->getValue();
+ break;
+
+ case 'max_text_length':
+ $classes[] = 'maximum-length-' . $rule->getValue();
+ break;
+ }
+ }
+
+ if (!empty($classes)) {
+ $classes[] = 'validate-length';
+ }
+ }
+
+ return $classes;
+ }
}
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php
index bb24d2ae15a34..03915c0499367 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Form/Account.php
@@ -9,7 +9,6 @@
use Magento\Framework\Api\ExtensibleDataObjectConverter;
use Magento\Framework\Data\Form\Element\AbstractElement;
use Magento\Framework\Pricing\PriceCurrencyInterface;
-use Magento\Store\Model\ScopeInterface;
/**
* Create order account form
@@ -133,8 +132,7 @@ protected function _prepareForm()
$this->_addAttributesToForm($attributes, $fieldset);
$this->_form->addFieldNameSuffix('order[account]');
- $storeId = (int)$this->_sessionQuote->getStoreId();
- $this->_form->setValues($this->extractValuesFromAttributes($attributes, $storeId));
+ $this->_form->setValues($this->extractValuesFromAttributes($attributes));
return $this;
}
@@ -192,10 +190,9 @@ public function getFormValues()
* Extract the form values from attributes.
*
* @param array $attributes
- * @param int $storeId
* @return array
*/
- private function extractValuesFromAttributes(array $attributes, int $storeId): array
+ private function extractValuesFromAttributes(array $attributes): array
{
$formValues = $this->getFormValues();
foreach ($attributes as $code => $attribute) {
@@ -203,26 +200,8 @@ private function extractValuesFromAttributes(array $attributes, int $storeId): a
if (isset($defaultValue) && !isset($formValues[$code])) {
$formValues[$code] = $defaultValue;
}
- if ($code === 'group_id' && empty($defaultValue)) {
- $formValues[$code] = $this->getDefaultCustomerGroup($storeId);
- }
}
return $formValues;
}
-
- /**
- * Gets default customer group.
- *
- * @param int $storeId
- * @return string|null
- */
- private function getDefaultCustomerGroup(int $storeId): ?string
- {
- return $this->_scopeConfig->getValue(
- 'customer/create_account/default_group',
- ScopeInterface::SCOPE_STORE,
- $storeId
- );
- }
}
diff --git a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php
index 3295b244f323e..ceb231248ef5e 100644
--- a/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php
+++ b/app/code/Magento/Sales/Controller/Adminhtml/Order/Invoice/NewAction.php
@@ -1,18 +1,21 @@
registry = $registry;
$this->resultPageFactory = $resultPageFactory;
- parent::__construct($context);
$this->invoiceService = $invoiceService;
+ $this->orderRepository = $orderRepository ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(OrderRepositoryInterface::class);
}
/**
@@ -78,14 +91,11 @@ public function execute()
{
$orderId = $this->getRequest()->getParam('order_id');
$invoiceData = $this->getRequest()->getParam('invoice', []);
- $invoiceItems = isset($invoiceData['items']) ? $invoiceData['items'] : [];
+ $invoiceItems = $invoiceData['items'] ?? [];
try {
/** @var \Magento\Sales\Model\Order $order */
- $order = $this->_objectManager->create(\Magento\Sales\Model\Order::class)->load($orderId);
- if (!$order->getId()) {
- throw new \Magento\Framework\Exception\LocalizedException(__('The order no longer exists.'));
- }
+ $order = $this->orderRepository->get($orderId);
if (!$order->canInvoice()) {
throw new \Magento\Framework\Exception\LocalizedException(
diff --git a/app/code/Magento/Sales/Controller/Guest/Form.php b/app/code/Magento/Sales/Controller/Guest/Form.php
index 8b4467cb538fa..04bb66f3d5b6e 100644
--- a/app/code/Magento/Sales/Controller/Guest/Form.php
+++ b/app/code/Magento/Sales/Controller/Guest/Form.php
@@ -4,40 +4,72 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Sales\Controller\Guest;
-class Form extends \Magento\Framework\App\Action\Action
+use Magento\Customer\Model\Session as CustomerSession;
+use Magento\Framework\App\Action\Context;
+use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Controller\Result\Redirect;
+use Magento\Framework\View\Result\Page;
+use Magento\Framework\View\Result\PageFactory;
+use Magento\Sales\Helper\Guest as GuestHelper;
+
+/**
+ * Class Form
+ */
+class Form extends \Magento\Framework\App\Action\Action implements HttpGetActionInterface
{
/**
- * @var \Magento\Framework\View\Result\PageFactory
+ * @var PageFactory
*/
protected $resultPageFactory;
/**
- * @param \Magento\Framework\App\Action\Context $context
- * @param \Magento\Framework\View\Result\PageFactory $resultPageFactory
+ * @var CustomerSession|null
+ */
+ private $customerSession;
+
+ /**
+ * @var GuestHelper|null
+ */
+ private $guestHelper;
+
+ /**
+ * @param Context $context
+ * @param PageFactory $resultPageFactory
+ * @param CustomerSession|null $customerSession
+ * @param GuestHelper|null $guestHelper
*/
public function __construct(
- \Magento\Framework\App\Action\Context $context,
- \Magento\Framework\View\Result\PageFactory $resultPageFactory
+ Context $context,
+ PageFactory $resultPageFactory,
+ CustomerSession $customerSession = null,
+ GuestHelper $guestHelper = null
) {
parent::__construct($context);
$this->resultPageFactory = $resultPageFactory;
+ $this->customerSession = $customerSession ?: ObjectManager::getInstance()->get(CustomerSession::class);
+ $this->guestHelper = $guestHelper ?: ObjectManager::getInstance()->get(GuestHelper::class);
}
/**
* Order view form page
*
- * @return \Magento\Framework\Controller\Result\Redirect|\Magento\Framework\View\Result\Page
+ * @return Redirect|Page
*/
public function execute()
{
- if ($this->_objectManager->get(\Magento\Customer\Model\Session::class)->isLoggedIn()) {
+ if ($this->customerSession->isLoggedIn()) {
return $this->resultRedirectFactory->create()->setPath('customer/account/');
}
+
$resultPage = $this->resultPageFactory->create();
$resultPage->getConfig()->getTitle()->set(__('Orders and Returns'));
- $this->_objectManager->get(\Magento\Sales\Helper\Guest::class)->getBreadcrumbs($resultPage);
+ $this->guestHelper->getBreadcrumbs($resultPage);
+
return $resultPage;
}
}
diff --git a/app/code/Magento/Sales/Model/AdminOrder/Create.php b/app/code/Magento/Sales/Model/AdminOrder/Create.php
index 088ad5a61f6c3..063433140566a 100644
--- a/app/code/Magento/Sales/Model/AdminOrder/Create.php
+++ b/app/code/Magento/Sales/Model/AdminOrder/Create.php
@@ -23,6 +23,7 @@
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @since 100.0.2
*/
class Create extends \Magento\Framework\DataObject implements \Magento\Checkout\Model\Cart\CartInterface
@@ -582,6 +583,7 @@ public function initFromOrder(\Magento\Sales\Model\Order $order)
}
$quote->getShippingAddress()->unsCachedItemsAll();
+ $quote->getBillingAddress()->unsCachedItemsAll();
$quote->setTotalsCollectedFlag(false);
$this->quoteRepository->save($quote);
diff --git a/app/code/Magento/Sales/Model/Order.php b/app/code/Magento/Sales/Model/Order.php
index f3e926f109c35..48deddb2fe5ac 100644
--- a/app/code/Magento/Sales/Model/Order.php
+++ b/app/code/Magento/Sales/Model/Order.php
@@ -1311,12 +1311,12 @@ public function getTrackingNumbers()
* Retrieve shipping method
*
* @param bool $asObject return carrier code and shipping method data as object
- * @return string|\Magento\Framework\DataObject
+ * @return string|null|\Magento\Framework\DataObject
*/
public function getShippingMethod($asObject = false)
{
$shippingMethod = parent::getShippingMethod();
- if (!$asObject) {
+ if (!$asObject || !$shippingMethod) {
return $shippingMethod;
} else {
list($carrierCode, $method) = explode('_', $shippingMethod, 2);
diff --git a/app/code/Magento/Sales/Model/Order/Address/Validator.php b/app/code/Magento/Sales/Model/Order/Address/Validator.php
index 31cb5bb1f60ca..5d3186781e7d7 100644
--- a/app/code/Magento/Sales/Model/Order/Address/Validator.php
+++ b/app/code/Magento/Sales/Model/Order/Address/Validator.php
@@ -49,8 +49,8 @@ class Validator
/**
* @param DirectoryHelper $directoryHelper
- * @param CountryFactory $countryFactory
- * @param EavConfig $eavConfig
+ * @param CountryFactory $countryFactory
+ * @param EavConfig $eavConfig
*/
public function __construct(
DirectoryHelper $directoryHelper,
@@ -61,6 +61,17 @@ public function __construct(
$this->countryFactory = $countryFactory;
$this->eavConfig = $eavConfig ?: ObjectManager::getInstance()
->get(EavConfig::class);
+ }
+
+ /**
+ * Validate address.
+ *
+ * @param \Magento\Sales\Model\Order\Address $address
+ * @return array
+ */
+ public function validate(Address $address)
+ {
+ $warnings = [];
if ($this->isTelephoneRequired()) {
$this->required['telephone'] = 'Phone Number';
@@ -73,16 +84,7 @@ public function __construct(
if ($this->isFaxRequired()) {
$this->required['fax'] = 'Fax';
}
- }
- /**
- *
- * @param \Magento\Sales\Model\Order\Address $address
- * @return array
- */
- public function validate(Address $address)
- {
- $warnings = [];
foreach ($this->required as $code => $label) {
if (!$address->hasData($code)) {
$warnings[] = sprintf('"%s" is required. Enter and try again.', $label);
@@ -195,7 +197,10 @@ protected function isStateRequired($countryId)
}
/**
+ * Check whether telephone is required for address.
+ *
* @return bool
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function isTelephoneRequired()
{
@@ -203,7 +208,10 @@ protected function isTelephoneRequired()
}
/**
+ * Check whether company is required for address.
+ *
* @return bool
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function isCompanyRequired()
{
@@ -211,7 +219,10 @@ protected function isCompanyRequired()
}
/**
+ * Check whether telephone is required for address.
+ *
* @return bool
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
protected function isFaxRequired()
{
diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender/OrderSender.php b/app/code/Magento/Sales/Model/Order/Email/Sender/OrderSender.php
index f06da0de0fd00..bfbe1fb4fd7ff 100644
--- a/app/code/Magento/Sales/Model/Order/Email/Sender/OrderSender.php
+++ b/app/code/Magento/Sales/Model/Order/Email/Sender/OrderSender.php
@@ -55,10 +55,10 @@ class OrderSender extends Sender
* @param OrderIdentity $identityContainer
* @param Order\Email\SenderBuilderFactory $senderBuilderFactory
* @param \Psr\Log\LoggerInterface $logger
+ * @param Renderer $addressRenderer
* @param PaymentHelper $paymentHelper
* @param OrderResource $orderResource
* @param \Magento\Framework\App\Config\ScopeConfigInterface $globalConfig
- * @param Renderer $addressRenderer
* @param ManagerInterface $eventManager
*/
public function __construct(
@@ -97,7 +97,7 @@ public function __construct(
*/
public function send(Order $order, $forceSyncMode = false)
{
- $order->setSendEmail(true);
+ $order->setSendEmail($this->identityContainer->isEnabled());
if (!$this->globalConfig->getValue('sales_email/general/async_sending') || $forceSyncMode) {
if ($this->checkAndSend($order)) {
diff --git a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php
index a7d749ec04c7d..ed9e38822245f 100644
--- a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php
+++ b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php
@@ -106,7 +106,7 @@ protected function configureEmailTemplate()
$this->transportBuilder->setTemplateIdentifier($this->templateContainer->getTemplateId());
$this->transportBuilder->setTemplateOptions($this->templateContainer->getTemplateOptions());
$this->transportBuilder->setTemplateVars($this->templateContainer->getTemplateVars());
- $this->transportBuilder->setFromByStore(
+ $this->transportBuilder->setFromByScope(
$this->identityContainer->getEmailIdentity(),
$this->identityContainer->getStore()->getId()
);
diff --git a/app/code/Magento/Sales/Model/Service/InvoiceService.php b/app/code/Magento/Sales/Model/Service/InvoiceService.php
index ba6ae7eb14ba7..02242e92c8bf5 100644
--- a/app/code/Magento/Sales/Model/Service/InvoiceService.php
+++ b/app/code/Magento/Sales/Model/Service/InvoiceService.php
@@ -190,6 +190,11 @@ private function prepareItemsQty(Order $order, array $qtys = [])
if ($orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) {
$qtys[$orderItem->getId()] = $orderItem->getQtyOrdered() - $orderItem->getQtyInvoiced();
} else {
+ $parentItem = $orderItem->getParentItem();
+ $parentItemId = $parentItem ? $parentItem->getId() : null;
+ if ($parentItemId && isset($qtys[$parentItemId])) {
+ $qtys[$orderItem->getId()] = $qtys[$parentItemId];
+ }
continue;
}
}
diff --git a/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php b/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php
index 2716e860243bf..a75690536e760 100644
--- a/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php
+++ b/app/code/Magento/Sales/Setup/Patch/Data/FillQuoteAddressIdInSalesOrderAddress.php
@@ -8,15 +8,15 @@
use Magento\Eav\Model\Config;
use Magento\Framework\App\State;
-use Magento\Quote\Model\QuoteFactory;
-use Magento\Sales\Model\OrderFactory;
-use Magento\Sales\Model\ResourceModel\Order\Address\CollectionFactory as AddressCollectionFactory;
-use Magento\Framework\App\ResourceConnection;
-use Magento\Sales\Setup\SalesSetupFactory;
+use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchVersionInterface;
-use Magento\Framework\Setup\ModuleDataSetupInterface;
+use Magento\Sales\Model\Order\Address;
+use Magento\Sales\Setup\SalesSetupFactory;
+/**
+ * Fills quote_address_id in table sales_order_address if it is empty.
+ */
class FillQuoteAddressIdInSalesOrderAddress implements DataPatchInterface, PatchVersionInterface
{
/**
@@ -24,11 +24,6 @@ class FillQuoteAddressIdInSalesOrderAddress implements DataPatchInterface, Patch
*/
private $moduleDataSetup;
- /**
- * @var SalesSetupFactory
- */
- private $salesSetupFactory;
-
/**
* @var State
*/
@@ -40,44 +35,22 @@ class FillQuoteAddressIdInSalesOrderAddress implements DataPatchInterface, Patch
private $eavConfig;
/**
- * @var AddressCollectionFactory
- */
- private $addressCollectionFactory;
-
- /**
- * @var OrderFactory
- */
- private $orderFactory;
-
- /**
- * @var QuoteFactory
- */
- private $quoteFactory;
-
- /**
- * PatchInitial constructor.
* @param ModuleDataSetupInterface $moduleDataSetup
+ * @param State $state
+ * @param Config $eavConfig
*/
public function __construct(
ModuleDataSetupInterface $moduleDataSetup,
- SalesSetupFactory $salesSetupFactory,
State $state,
- Config $eavConfig,
- AddressCollectionFactory $addressCollectionFactory,
- OrderFactory $orderFactory,
- QuoteFactory $quoteFactory
+ Config $eavConfig
) {
$this->moduleDataSetup = $moduleDataSetup;
- $this->salesSetupFactory = $salesSetupFactory;
$this->state = $state;
$this->eavConfig = $eavConfig;
- $this->addressCollectionFactory = $addressCollectionFactory;
- $this->orderFactory = $orderFactory;
- $this->quoteFactory = $quoteFactory;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function apply()
{
@@ -96,32 +69,12 @@ public function apply()
*/
public function fillQuoteAddressIdInSalesOrderAddress(ModuleDataSetupInterface $setup)
{
- $addressTable = $setup->getTable('sales_order_address');
- $updateOrderAddress = $setup->getConnection()
- ->select()
- ->joinInner(
- ['sales_order' => $setup->getTable('sales_order')],
- $addressTable . '.parent_id = sales_order.entity_id',
- ['quote_address_id' => 'quote_address.address_id']
- )
- ->joinInner(
- ['quote_address' => $setup->getTable('quote_address')],
- 'sales_order.quote_id = quote_address.quote_id
- AND ' . $addressTable . '.address_type = quote_address.address_type',
- []
- )
- ->where(
- $addressTable . '.quote_address_id IS NULL'
- );
- $updateOrderAddress = $setup->getConnection()->updateFromSelect(
- $updateOrderAddress,
- $addressTable
- );
- $setup->getConnection()->query($updateOrderAddress);
+ $this->fillQuoteAddressIdInSalesOrderAddressByType($setup, Address::TYPE_SHIPPING);
+ $this->fillQuoteAddressIdInSalesOrderAddressByType($setup, Address::TYPE_BILLING);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getDependencies()
{
@@ -131,7 +84,7 @@ public static function getDependencies()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getVersion()
{
@@ -139,10 +92,99 @@ public static function getVersion()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAliases()
{
return [];
}
+
+ /**
+ * Fill quote_address_id in sales_order_address by type.
+ *
+ * @param ModuleDataSetupInterface $setup
+ * @param string $addressType
+ * @throws \Zend_Db_Statement_Exception
+ */
+ private function fillQuoteAddressIdInSalesOrderAddressByType(ModuleDataSetupInterface $setup, $addressType)
+ {
+ $salesConnection = $setup->getConnection('sales');
+
+ $orderTable = $setup->getTable('sales_order', 'sales');
+ $orderAddressTable = $setup->getTable('sales_order_address', 'sales');
+
+ $query = $salesConnection
+ ->select()
+ ->from(
+ ['sales_order_address' => $orderAddressTable],
+ ['entity_id', 'address_type']
+ )
+ ->joinInner(
+ ['sales_order' => $orderTable],
+ 'sales_order_address.parent_id = sales_order.entity_id',
+ ['quote_id' => 'sales_order.quote_id']
+ )
+ ->where('sales_order_address.quote_address_id IS NULL')
+ ->where('sales_order_address.address_type = ?', $addressType)
+ ->order('sales_order_address.entity_id');
+
+ $batchSize = 5000;
+ $result = $salesConnection->query($query);
+ $count = $result->rowCount();
+ $batches = ceil($count / $batchSize);
+
+ for ($batch = $batches; $batch > 0; $batch--) {
+ $query->limitPage($batch, $batchSize);
+ $result = $salesConnection->fetchAssoc($query);
+
+ $this->fillQuoteAddressIdInSalesOrderAddressProcessBatch($setup, $result, $addressType);
+ }
+ }
+
+ /**
+ * Process filling quote_address_id in sales_order_address in batch.
+ *
+ * @param ModuleDataSetupInterface $setup
+ * @param array $orderAddresses
+ * @param string $addressType
+ */
+ private function fillQuoteAddressIdInSalesOrderAddressProcessBatch(
+ ModuleDataSetupInterface $setup,
+ array $orderAddresses,
+ $addressType
+ ) {
+ $salesConnection = $setup->getConnection('sales');
+ $quoteConnection = $setup->getConnection('checkout');
+
+ $quoteAddressTable = $setup->getTable('quote_address', 'checkout');
+ $quoteTable = $setup->getTable('quote', 'checkout');
+ $salesOrderAddressTable = $setup->getTable('sales_order_address', 'sales');
+
+ $query = $quoteConnection
+ ->select()
+ ->from(
+ ['quote_address' => $quoteAddressTable],
+ ['quote_id', 'address_id']
+ )
+ ->joinInner(
+ ['quote' => $quoteTable],
+ 'quote_address.quote_id = quote.entity_id',
+ []
+ )
+ ->where('quote.entity_id in (?)', array_column($orderAddresses, 'quote_id'))
+ ->where('address_type = ?', $addressType);
+
+ $quoteAddresses = $quoteConnection->fetchAssoc($query);
+
+ foreach ($orderAddresses as $orderAddress) {
+ $bind = [
+ 'quote_address_id' => $quoteAddresses[$orderAddress['quote_id']]['address_id'] ?? null,
+ ];
+ $where = [
+ 'entity_id = ?' => $orderAddress['entity_id']
+ ];
+
+ $salesConnection->update($salesOrderAddressTable, $bind, $where);
+ }
+ }
}
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml
index c814a886a2b33..0061411d576e2 100644
--- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml
@@ -44,7 +44,6 @@
-
@@ -58,9 +57,17 @@
-
+
+
+
+
+
+
+
-
-
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml
index aea04c8abfa60..0e09f3933c1aa 100644
--- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml
@@ -18,7 +18,8 @@
-
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml
index eed9f80c251c8..a116a23dc02cd 100644
--- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderGridActionGroup.xml
@@ -14,7 +14,6 @@
-
@@ -74,4 +73,9 @@
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderStatusFormFillAndSaveActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderStatusFormFillAndSaveActionGroup.xml
new file mode 100644
index 0000000000000..8108577145421
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderStatusFormFillAndSaveActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusExistsInGridActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusExistsInGridActionGroup.xml
new file mode 100644
index 0000000000000..5f69f52987688
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusExistsInGridActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveDuplicateErrorActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveDuplicateErrorActionGroup.xml
new file mode 100644
index 0000000000000..5b4c3115744c9
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveDuplicateErrorActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveSuccessActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveSuccessActionGroup.xml
new file mode 100644
index 0000000000000..d82f4b9dd25e8
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AssertOrderStatusFormSaveSuccessActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusData.xml b/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusData.xml
new file mode 100644
index 0000000000000..aecd7fcf1b703
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Data/OrderStatusData.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ order_status
+ orderLabel
+
+
+ pending
+ orderLabel
+
+
+ order_status
+ Suspected Fraud
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderStatusPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderStatusPage.xml
new file mode 100644
index 0000000000000..b158e4923074a
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderStatusPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceMainActionsSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceMainActionsSection.xml
index bc7fc8145af33..011500fac3f69 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceMainActionsSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminInvoiceMainActionsSection.xml
@@ -9,7 +9,7 @@
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormActionSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormActionSection.xml
index 2f6149dfa1cb7..027962282b2c3 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormActionSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormActionSection.xml
@@ -12,5 +12,8 @@
+
+
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusFormSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusFormSection.xml
new file mode 100644
index 0000000000000..1058b2d6f2177
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusFormSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusGridSection.xml
new file mode 100644
index 0000000000000..b624639281187
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderStatusGridSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml
index 717022322698f..b716047a39008 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/OrdersGridSection.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminAvailabilityCreditMemoWithNoPaymentTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminAvailabilityCreditMemoWithNoPaymentTest.xml
index 9180636db7821..e405173429b2c 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminAvailabilityCreditMemoWithNoPaymentTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminAvailabilityCreditMemoWithNoPaymentTest.xml
@@ -25,6 +25,7 @@
+
@@ -33,62 +34,53 @@
+
+
+
+
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
+
-
-
+
+
-
+
-
+
-
+
-
-
-
-
-
-
-
+
+
-
-
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml
new file mode 100644
index 0000000000000..85ef563e10db7
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminChangeCustomerGroupInNewOrder.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $grabGroupValue
+ 3
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml
index 7c83f35468ce6..f869841153aea 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml
@@ -81,7 +81,7 @@
-
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml
index 099cf7fbce914..ce66409ed9b3c 100644
--- a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateInvoiceTest.xml
@@ -71,7 +71,6 @@
-
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingCodeTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingCodeTest.xml
new file mode 100644
index 0000000000000..40a731410a899
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingCodeTest.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingLabelTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingLabelTest.xml
new file mode 100644
index 0000000000000..d1381bbb1efb0
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusDuplicatingLabelTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusTest.xml
new file mode 100644
index 0000000000000..c2daaac84dd42
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCreateOrderStatusTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml
new file mode 100644
index 0000000000000..d418751c736e1
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminSubmitsOrderWithAndWithoutFieldsValidationTest.xml
@@ -0,0 +1,88 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php
index 05c99c9f9ef98..87e27fdb2206b 100644
--- a/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Controller/Adminhtml/Order/Invoice/NewActionTest.php
@@ -6,12 +6,14 @@
namespace Magento\Sales\Test\Unit\Controller\Adminhtml\Order\Invoice;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Sales\Api\OrderRepositoryInterface;
/**
* Class NewActionTest
* @package Magento\Sales\Controller\Adminhtml\Order\Invoice
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.TooManyFields)
*/
class NewActionTest extends \PHPUnit\Framework\TestCase
{
@@ -90,6 +92,11 @@ class NewActionTest extends \PHPUnit\Framework\TestCase
*/
protected $invoiceServiceMock;
+ /**
+ * @var OrderRepositoryInterface|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $orderRepositoryMock;
+
protected function setUp()
{
$objectManager = new ObjectManager($this);
@@ -215,12 +222,15 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
+ $this->orderRepositoryMock = $this->createMock(OrderRepositoryInterface::class);
+
$this->controller = $objectManager->getObject(
\Magento\Sales\Controller\Adminhtml\Order\Invoice\NewAction::class,
[
'context' => $contextMock,
'resultPageFactory' => $this->resultPageFactoryMock,
- 'invoiceService' => $this->invoiceServiceMock
+ 'invoiceService' => $this->invoiceServiceMock,
+ 'orderRepository' => $this->orderRepositoryMock
]
);
}
@@ -250,19 +260,17 @@ public function testExecute()
$orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
->disableOriginalConstructor()
- ->setMethods(['load', 'getId', 'canInvoice'])
+ ->setMethods(['load', 'canInvoice'])
->getMock();
- $orderMock->expects($this->once())
- ->method('load')
- ->with($orderId)
- ->willReturnSelf();
- $orderMock->expects($this->once())
- ->method('getId')
- ->willReturn($orderId);
$orderMock->expects($this->once())
->method('canInvoice')
->willReturn(true);
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('get')
+ ->with($orderId)
+ ->willReturn($orderMock);
+
$this->invoiceServiceMock->expects($this->once())
->method('prepareInvoice')
->with($orderMock, [])
@@ -285,11 +293,7 @@ public function testExecute()
->with(true)
->will($this->returnValue($commentText));
- $this->objectManagerMock->expects($this->at(0))
- ->method('create')
- ->with(\Magento\Sales\Model\Order::class)
- ->willReturn($orderMock);
- $this->objectManagerMock->expects($this->at(1))
+ $this->objectManagerMock->expects($this->once())
->method('get')
->with(\Magento\Backend\Model\Session::class)
->will($this->returnValue($this->sessionMock));
@@ -318,19 +322,12 @@ public function testExecuteNoOrder()
$orderMock = $this->getMockBuilder(\Magento\Sales\Model\Order::class)
->disableOriginalConstructor()
- ->setMethods(['load', 'getId', 'canInvoice'])
+ ->setMethods(['canInvoice'])
->getMock();
- $orderMock->expects($this->once())
- ->method('load')
- ->with($orderId)
- ->willReturnSelf();
- $orderMock->expects($this->once())
- ->method('getId')
- ->willReturn(null);
- $this->objectManagerMock->expects($this->at(0))
- ->method('create')
- ->with(\Magento\Sales\Model\Order::class)
+ $this->orderRepositoryMock->expects($this->once())
+ ->method('get')
+ ->with($orderId)
->willReturn($orderMock);
$resultRedirect = $this->getMockBuilder(\Magento\Backend\Model\View\Result\Redirect::class)
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php
index 46c44c03b1514..88053ea684ce8 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/OrderSenderTest.php
@@ -64,7 +64,7 @@ public function testSend($configValue, $forceSyncMode, $emailSendingResult, $sen
$this->orderMock->expects($this->once())
->method('setSendEmail')
- ->with(true);
+ ->with($emailSendingResult);
$this->globalConfig->expects($this->once())
->method('getValue')
@@ -72,7 +72,7 @@ public function testSend($configValue, $forceSyncMode, $emailSendingResult, $sen
->willReturn($configValue);
if (!$configValue || $forceSyncMode) {
- $this->identityContainerMock->expects($this->once())
+ $this->identityContainerMock->expects($this->exactly(2))
->method('isEnabled')
->willReturn($emailSendingResult);
@@ -118,7 +118,7 @@ public function testSend($configValue, $forceSyncMode, $emailSendingResult, $sen
$this->orderMock->expects($this->once())
->method('setEmailSent')
- ->with(true);
+ ->with($emailSendingResult);
$this->orderResourceMock->expects($this->once())
->method('saveAttribute')
@@ -210,7 +210,7 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte
->with('sales_email/general/async_sending')
->willReturn(false);
- $this->identityContainerMock->expects($this->once())
+ $this->identityContainerMock->expects($this->exactly(2))
->method('isEnabled')
->willReturn(true);
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php
index 759d60d9e6613..24cd54e3a46b3 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/SenderBuilderTest.php
@@ -76,7 +76,7 @@ protected function setUp()
'setTemplateIdentifier',
'setTemplateOptions',
'setTemplateVars',
- 'setFromByStore',
+ 'setFromByScope',
]
);
@@ -103,7 +103,7 @@ protected function setUp()
->method('getEmailIdentity')
->will($this->returnValue($emailIdentity));
$this->transportBuilder->expects($this->once())
- ->method('setFromByStore')
+ ->method('setFromByScope')
->with($this->equalTo($emailIdentity), 1);
$this->identityContainerMock->expects($this->once())
@@ -146,7 +146,7 @@ public function testSend()
->method('getId')
->willReturn(1);
$this->transportBuilder->expects($this->once())
- ->method('setFromByStore')
+ ->method('setFromByScope')
->with($identity, 1);
$this->transportBuilder->expects($this->once())
->method('addTo')
@@ -176,7 +176,7 @@ public function testSendCopyTo()
->method('addTo')
->with($this->equalTo('example@mail.com'));
$this->transportBuilder->expects($this->once())
- ->method('setFromByStore')
+ ->method('setFromByScope')
->with($identity, 1);
$this->identityContainerMock->expects($this->once())
->method('getStore')
diff --git a/app/code/Magento/Sales/etc/di.xml b/app/code/Magento/Sales/etc/di.xml
index 5a5dd925a3098..68fcd17122bd2 100644
--- a/app/code/Magento/Sales/etc/di.xml
+++ b/app/code/Magento/Sales/etc/di.xml
@@ -1015,4 +1015,9 @@
+
+
+
+
+
diff --git a/app/code/Magento/Sales/view/frontend/email/shipment_new.html b/app/code/Magento/Sales/view/frontend/email/shipment_new.html
index 8af49f322c682..84f5acb29ea3b 100644
--- a/app/code/Magento/Sales/view/frontend/email/shipment_new.html
+++ b/app/code/Magento/Sales/view/frontend/email/shipment_new.html
@@ -53,7 +53,7 @@ {{trans "Your Shipment #%shipment_id for Order #%order_id" shipment_id=$ship
{{/depend}}
- {{block class='Magento\\Framework\\View\\Element\\Template' area='frontend' template='Magento_Sales::email/shipment/track.phtml' shipment=$shipment order=$order}}
+ {{layout handle="sales_email_order_shipment_track" shipment=$shipment order=$order}}
diff --git a/app/code/Magento/Sales/view/frontend/email/shipment_new_guest.html b/app/code/Magento/Sales/view/frontend/email/shipment_new_guest.html
index df1677f56a500..bb181126724da 100644
--- a/app/code/Magento/Sales/view/frontend/email/shipment_new_guest.html
+++ b/app/code/Magento/Sales/view/frontend/email/shipment_new_guest.html
@@ -51,7 +51,7 @@ {{trans "Your Shipment #%shipment_id for Order #%order_id" shipment_id=$ship
{{/depend}}
- {{block class='Magento\\Framework\\View\\Element\\Template' area='frontend' template='Magento_Sales::email/shipment/track.phtml' shipment=$shipment order=$order}}
+ {{layout handle="sales_email_order_shipment_track" shipment=$shipment order=$order}}
diff --git a/app/code/Magento/Sales/view/frontend/layout/sales_email_order_shipment_track.xml b/app/code/Magento/Sales/view/frontend/layout/sales_email_order_shipment_track.xml
new file mode 100644
index 0000000000000..91414663951d3
--- /dev/null
+++ b/app/code/Magento/Sales/view/frontend/layout/sales_email_order_shipment_track.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml b/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
index 9b3633fde60b4..a2ab3d02b13ea 100644
--- a/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
+++ b/app/code/Magento/Sales/view/frontend/templates/reorder/sidebar.phtml
@@ -57,7 +57,7 @@
diff --git a/app/code/Magento/SalesAnalytics/composer.json b/app/code/Magento/SalesAnalytics/composer.json
index 64424c8f5bc61..b77dcd7e71c65 100644
--- a/app/code/Magento/SalesAnalytics/composer.json
+++ b/app/code/Magento/SalesAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-sales": "*"
+ "magento/module-sales": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/SalesGraphQl/Model/Resolver/Orders.php b/app/code/Magento/SalesGraphQl/Model/Resolver/Orders.php
index 5802115d44b5e..3e592cf061dfc 100644
--- a/app/code/Magento/SalesGraphQl/Model/Resolver/Orders.php
+++ b/app/code/Magento/SalesGraphQl/Model/Resolver/Orders.php
@@ -11,7 +11,7 @@
use Magento\Framework\GraphQl\Query\ResolverInterface;
use Magento\Framework\GraphQl\Schema\Type\ResolveInfo;
use Magento\Sales\Model\ResourceModel\Order\CollectionFactoryInterface;
-use Magento\CustomerGraphQl\Model\Customer\CheckCustomerAccount;
+use Magento\CustomerGraphQl\Model\Customer\GetCustomer;
/**
* Orders data reslover
@@ -24,20 +24,20 @@ class Orders implements ResolverInterface
private $collectionFactory;
/**
- * @var CheckCustomerAccount
+ * @var GetCustomer
*/
- private $checkCustomerAccount;
+ private $getCustomer;
/**
* @param CollectionFactoryInterface $collectionFactory
- * @param CheckCustomerAccount $checkCustomerAccount
+ * @param GetCustomer $getCustomer
*/
public function __construct(
CollectionFactoryInterface $collectionFactory,
- CheckCustomerAccount $checkCustomerAccount
+ GetCustomer $getCustomer
) {
$this->collectionFactory = $collectionFactory;
- $this->checkCustomerAccount = $checkCustomerAccount;
+ $this->getCustomer = $getCustomer;
}
/**
@@ -50,11 +50,10 @@ public function resolve(
array $value = null,
array $args = null
) {
- $customerId = $context->getUserId();
- $this->checkCustomerAccount->execute($customerId, $context->getUserType());
+ $customer = $this->getCustomer->execute($context);
$items = [];
- $orders = $this->collectionFactory->create($customerId);
+ $orders = $this->collectionFactory->create($customer->getId());
/** @var \Magento\Sales\Model\Order $order */
foreach ($orders as $order) {
diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php
index da05fd98e609b..cfafe110df22b 100644
--- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php
+++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Generate.php
@@ -7,9 +7,13 @@
use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\SalesRule\Model\CouponGenerator;
+use Magento\Framework\MessageQueue\PublisherInterface;
+use Magento\SalesRule\Api\Data\CouponGenerationSpecInterfaceFactory;
/**
* Generate promo quote
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Generate extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implements HttpPostActionInterface
{
@@ -18,6 +22,16 @@ class Generate extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote imple
*/
private $couponGenerator;
+ /**
+ * @var PublisherInterface
+ */
+ private $messagePublisher;
+
+ /**
+ * @var CouponGenerationSpecInterfaceFactory
+ */
+ private $generationSpecFactory;
+
/**
* Generate constructor.
* @param \Magento\Backend\App\Action\Context $context
@@ -25,17 +39,27 @@ class Generate extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote imple
* @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
* @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter
* @param CouponGenerator|null $couponGenerator
+ * @param PublisherInterface|null $publisher
+ * @param CouponGenerationSpecInterfaceFactory|null $generationSpecFactory
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\Registry $coreRegistry,
\Magento\Framework\App\Response\Http\FileFactory $fileFactory,
\Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter,
- CouponGenerator $couponGenerator = null
+ CouponGenerator $couponGenerator = null,
+ PublisherInterface $publisher = null,
+ CouponGenerationSpecInterfaceFactory $generationSpecFactory = null
) {
parent::__construct($context, $coreRegistry, $fileFactory, $dateFilter);
$this->couponGenerator = $couponGenerator ?:
$this->_objectManager->get(CouponGenerator::class);
+ $this->messagePublisher = $publisher ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(PublisherInterface::class);
+ $this->generationSpecFactory = $generationSpecFactory ?:
+ \Magento\Framework\App\ObjectManager::getInstance()->get(
+ CouponGenerationSpecInterfaceFactory::class
+ );
}
/**
@@ -64,9 +88,14 @@ public function execute()
$data = $inputFilter->getUnescaped();
}
- $couponCodes = $this->couponGenerator->generateCodes($data);
- $generated = count($couponCodes);
- $this->messageManager->addSuccessMessage(__('%1 coupon(s) have been generated.', $generated));
+ $data['quantity'] = isset($data['qty']) ? $data['qty'] : null;
+
+ $couponSpec = $this->generationSpecFactory->create(['data' => $data]);
+
+ $this->messagePublisher->publish('sales_rule.codegenerator', $couponSpec);
+ $this->messageManager->addSuccessMessage(
+ __('Message is added to queue, wait to get your coupons soon')
+ );
$this->_view->getLayout()->initMessages();
$result['messages'] = $this->_view->getLayout()->getMessagesBlock()->getGroupedHtml();
} catch (\Magento\Framework\Exception\InputException $inputException) {
diff --git a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php
index 388679e6d9eff..7d55d18b770e2 100644
--- a/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php
+++ b/app/code/Magento/SalesRule/Controller/Adminhtml/Promo/Quote/Save.php
@@ -7,6 +7,7 @@
namespace Magento\SalesRule\Controller\Adminhtml\Promo\Quote;
use Magento\Framework\App\Action\HttpPostActionInterface;
+use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
@@ -20,6 +21,11 @@ class Save extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implement
* @var TimezoneInterface
*/
private $timezone;
+
+ /**
+ * @var DataPersistorInterface
+ */
+ private $dataPersistor;
/**
* @param \Magento\Backend\App\Action\Context $context
@@ -27,18 +33,23 @@ class Save extends \Magento\SalesRule\Controller\Adminhtml\Promo\Quote implement
* @param \Magento\Framework\App\Response\Http\FileFactory $fileFactory
* @param \Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter
* @param TimezoneInterface $timezone
+ * @param DataPersistorInterface $dataPersistor
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Framework\Registry $coreRegistry,
\Magento\Framework\App\Response\Http\FileFactory $fileFactory,
\Magento\Framework\Stdlib\DateTime\Filter\Date $dateFilter,
- TimezoneInterface $timezone = null
+ TimezoneInterface $timezone = null,
+ DataPersistorInterface $dataPersistor = null
) {
parent::__construct($context, $coreRegistry, $fileFactory, $dateFilter);
$this->timezone = $timezone ?? \Magento\Framework\App\ObjectManager::getInstance()->get(
TimezoneInterface::class
);
+ $this->dataPersistor = $dataPersistor ?? \Magento\Framework\App\ObjectManager::getInstance()->get(
+ DataPersistorInterface::class
+ );
}
/**
@@ -73,12 +84,8 @@ public function execute()
$data
);
$data = $inputFilter->getUnescaped();
- $id = $this->getRequest()->getParam('rule_id');
- if ($id) {
- $model->load($id);
- if ($id != $model->getId()) {
- throw new \Magento\Framework\Exception\LocalizedException(__('The wrong rule is specified.'));
- }
+ if (!$this->checkRuleExists($model)) {
+ throw new \Magento\Framework\Exception\LocalizedException(__('The wrong rule is specified.'));
}
$session = $this->_objectManager->get(\Magento\Backend\Model\Session::class);
@@ -89,6 +96,7 @@ public function execute()
$this->messageManager->addErrorMessage($errorMessage);
}
$session->setPageData($data);
+ $this->dataPersistor->set('sale_rule', $data);
$this->_redirect('sales_rule/*/edit', ['id' => $model->getId()]);
return;
}
@@ -147,4 +155,22 @@ public function execute()
}
$this->_redirect('sales_rule/*/');
}
+
+ /**
+ * Check if Cart Price Rule with provided id exists.
+ *
+ * @param \Magento\SalesRule\Model\Rule $model
+ * @return bool
+ */
+ private function checkRuleExists(\Magento\SalesRule\Model\Rule $model): bool
+ {
+ $id = $this->getRequest()->getParam('rule_id');
+ if ($id) {
+ $model->load($id);
+ if ($model->getId() != $id) {
+ return false;
+ }
+ }
+ return true;
+ }
}
diff --git a/app/code/Magento/SalesRule/Model/Coupon/Consumer.php b/app/code/Magento/SalesRule/Model/Coupon/Consumer.php
new file mode 100644
index 0000000000000..2354c72a3e293
--- /dev/null
+++ b/app/code/Magento/SalesRule/Model/Coupon/Consumer.php
@@ -0,0 +1,85 @@
+logger = $logger;
+ $this->couponManager = $couponManager;
+ $this->filesystem = $filesystem;
+ $this->notifier = $notifier;
+ }
+
+ /**
+ * Consumer logic.
+ *
+ * @param CouponGenerationSpecInterface $exportInfo
+ * @return void
+ */
+ public function process(CouponGenerationSpecInterface $exportInfo)
+ {
+ try {
+ $this->couponManager->generate($exportInfo);
+
+ $this->notifier->addMajor(
+ __('Your coupons are ready'),
+ __('You can check your coupons at sales rule page')
+ );
+ } catch (LocalizedException $exception) {
+ $this->notifier->addCritical(
+ __('Error during coupons generator process occurred'),
+ __('Error during coupons generator process occurred. Please check logs for detail')
+ );
+ $this->logger->critical(
+ 'Something went wrong while coupons generator process. ' . $exception->getMessage()
+ );
+ }
+ }
+}
diff --git a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
index 5e6f3847c8e31..3e7b6ea281501 100644
--- a/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
+++ b/app/code/Magento/SalesRule/Model/ResourceModel/Rule/Collection.php
@@ -9,6 +9,9 @@
use Magento\Framework\DB\Select;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Quote\Model\Quote\Address;
+use Magento\SalesRule\Api\Data\CouponInterface;
+use Magento\SalesRule\Model\Coupon;
+use Magento\SalesRule\Model\Rule;
/**
* Sales Rules resource collection model.
@@ -107,12 +110,15 @@ protected function mapAssociatedEntities($entityType, $objectField)
$associatedEntities = $this->getConnection()->fetchAll($select);
- array_map(function ($associatedEntity) use ($entityInfo, $ruleIdField, $objectField) {
- $item = $this->getItemByColumnValue($ruleIdField, $associatedEntity[$ruleIdField]);
- $itemAssociatedValue = $item->getData($objectField) === null ? [] : $item->getData($objectField);
- $itemAssociatedValue[] = $associatedEntity[$entityInfo['entity_id_field']];
- $item->setData($objectField, $itemAssociatedValue);
- }, $associatedEntities);
+ array_map(
+ function ($associatedEntity) use ($entityInfo, $ruleIdField, $objectField) {
+ $item = $this->getItemByColumnValue($ruleIdField, $associatedEntity[$ruleIdField]);
+ $itemAssociatedValue = $item->getData($objectField) ?? [];
+ $itemAssociatedValue[] = $associatedEntity[$entityInfo['entity_id_field']];
+ $item->setData($objectField, $itemAssociatedValue);
+ },
+ $associatedEntities
+ );
}
/**
@@ -141,6 +147,7 @@ protected function _afterLoad()
* @param string $couponCode
* @param string|null $now
* @param Address $address allow extensions to further filter out rules based on quote address
+ * @throws \Zend_Db_Select_Exception
* @use $this->addWebsiteGroupDateFilter()
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
* @return $this
@@ -153,32 +160,24 @@ public function setValidationFilter(
Address $address = null
) {
if (!$this->getFlag('validation_filter')) {
- /* We need to overwrite joinLeft if coupon is applied */
- $this->getSelect()->reset();
- parent::_initSelect();
+ $this->prepareSelect($websiteId, $customerGroupId, $now);
- $this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now);
- $select = $this->getSelect();
+ $noCouponRules = $this->getNoCouponCodeSelect();
- $connection = $this->getConnection();
- if (strlen($couponCode)) {
- $noCouponWhereCondition = $connection->quoteInto(
- 'main_table.coupon_type = ?',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
- );
- $relatedRulesIds = $this->getCouponRelatedRuleIds($couponCode);
-
- $select->where(
- $noCouponWhereCondition . ' OR main_table.rule_id IN (?)',
- $relatedRulesIds,
- Select::TYPE_CONDITION
- );
+ if ($couponCode) {
+ $couponRules = $this->getCouponCodeSelect($couponCode);
+
+ $allAllowedRules = $this->getConnection()->select();
+ $allAllowedRules->union([$noCouponRules, $couponRules], Select::SQL_UNION_ALL);
+
+ $wrapper = $this->getConnection()->select();
+ $wrapper->from($allAllowedRules);
+
+ $this->_select = $wrapper;
} else {
- $this->addFieldToFilter(
- 'main_table.coupon_type',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON
- );
+ $this->_select = $noCouponRules;
}
+
$this->setOrder('sort_order', self::SORT_ORDER_ASC);
$this->setFlag('validation_filter', true);
}
@@ -187,72 +186,98 @@ public function setValidationFilter(
}
/**
- * Get rules ids related to coupon code
+ * Recreate the default select object for specific needs of salesrule evaluation with coupon codes.
*
- * @param string $couponCode
- * @return array
+ * @param int $websiteId
+ * @param int $customerGroupId
+ * @param string $now
*/
- private function getCouponRelatedRuleIds(string $couponCode): array
+ private function prepareSelect($websiteId, $customerGroupId, $now)
{
- $connection = $this->getConnection();
- $select = $connection->select()->from(
- ['main_table' => $this->getTable('salesrule')],
- 'rule_id'
+ $this->getSelect()->reset();
+ parent::_initSelect();
+
+ $this->addWebsiteGroupDateFilter($websiteId, $customerGroupId, $now);
+ }
+
+ /**
+ * Return select object to determine all active rules not needing a coupon code.
+ *
+ * @return Select
+ */
+ private function getNoCouponCodeSelect()
+ {
+ $noCouponSelect = clone $this->getSelect();
+
+ $noCouponSelect->where(
+ 'main_table.coupon_type = ?',
+ Rule::COUPON_TYPE_NO_COUPON
);
- $select->joinLeft(
- ['rule_coupons' => $this->getTable('salesrule_coupon')],
- $connection->quoteInto(
- 'main_table.rule_id = rule_coupons.rule_id AND main_table.coupon_type != ?',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_NO_COUPON,
- null
- )
+
+ $noCouponSelect->columns([Coupon::KEY_CODE => new \Zend_Db_Expr('NULL')]);
+
+ return $noCouponSelect;
+ }
+
+ /**
+ * Determine all active rules that are valid for the given coupon code.
+ *
+ * @param string $couponCode
+ * @return Select
+ */
+ private function getCouponCodeSelect($couponCode)
+ {
+ $couponSelect = clone $this->getSelect();
+
+ $this->joinCouponTable($couponCode, $couponSelect);
+
+ $notExpired = $this->getConnection()->quoteInto(
+ '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
+ $this->_date->date()->format('Y-m-d')
);
- $autoGeneratedCouponCondition = [
- $connection->quoteInto(
- "main_table.coupon_type = ?",
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_AUTO
- ),
- $connection->quoteInto(
- "rule_coupons.type = ?",
- \Magento\SalesRule\Api\Data\CouponInterface::TYPE_GENERATED
- ),
- ];
-
- $orWhereConditions = [
- "(" . implode($autoGeneratedCouponCondition, " AND ") . ")",
- $connection->quoteInto(
- '(main_table.coupon_type = ? AND main_table.use_auto_generation = 1 AND rule_coupons.type = 1)',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
- ),
- $connection->quoteInto(
- '(main_table.coupon_type = ? AND main_table.use_auto_generation = 0 AND rule_coupons.type = 0)',
- \Magento\SalesRule\Model\Rule::COUPON_TYPE_SPECIFIC
- ),
- ];
-
- $andWhereConditions = [
- $connection->quoteInto(
- 'rule_coupons.code = ?',
- $couponCode
- ),
- $connection->quoteInto(
- '(rule_coupons.expiration_date IS NULL OR rule_coupons.expiration_date >= ?)',
- $this->_date->date()->format('Y-m-d')
- ),
- ];
-
- $orWhereCondition = implode(' OR ', $orWhereConditions);
- $andWhereCondition = implode(' AND ', $andWhereConditions);
-
- $select->where(
- '(' . $orWhereCondition . ') AND ' . $andWhereCondition,
+ $isAutogenerated =
+ $this->getConnection()->quoteInto('main_table.coupon_type = ?', Rule::COUPON_TYPE_AUTO)
+ . ' AND ' .
+ $this->getConnection()->quoteInto('rule_coupons.type = ?', CouponInterface::TYPE_GENERATED);
+
+ $isValidSpecific =
+ $this->getConnection()->quoteInto('(main_table.coupon_type = ?)', Rule::COUPON_TYPE_SPECIFIC)
+ . ' AND (' .
+ '(main_table.use_auto_generation = 1 AND rule_coupons.type = 1)'
+ . ' OR ' .
+ '(main_table.use_auto_generation = 0 AND rule_coupons.type = 0)'
+ . ')';
+
+ $couponSelect->where(
+ "$notExpired AND ($isAutogenerated OR $isValidSpecific)",
null,
Select::TYPE_CONDITION
);
- $select->group('main_table.rule_id');
- return $connection->fetchCol($select);
+ return $couponSelect;
+ }
+
+ /**
+ * Join coupong table to select.
+ *
+ * @param string $couponCode
+ * @param Select $couponSelect
+ */
+ private function joinCouponTable($couponCode, Select $couponSelect)
+ {
+ $couponJoinCondition =
+ 'main_table.rule_id = rule_coupons.rule_id'
+ . ' AND ' .
+ $this->getConnection()->quoteInto('main_table.coupon_type <> ?', Rule::COUPON_TYPE_NO_COUPON)
+ . ' AND ' .
+ $this->getConnection()->quoteInto('rule_coupons.code = ?', $couponCode);
+
+ $couponSelect->joinInner(
+ ['rule_coupons' => $this->getTable('salesrule_coupon')],
+ $couponJoinCondition,
+ [Coupon::KEY_CODE]
+ );
}
/**
diff --git a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php
index 89ec2b84572fc..cf6301cb31a9c 100644
--- a/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php
+++ b/app/code/Magento/SalesRule/Model/Rule/Condition/Address.php
@@ -61,6 +61,7 @@ public function __construct(
public function loadAttributeOptions()
{
$attributes = [
+ 'base_subtotal_with_discount' => __('Subtotal (Excl. Tax)'),
'base_subtotal' => __('Subtotal'),
'total_qty' => __('Total Items Quantity'),
'weight' => __('Total Weight'),
diff --git a/app/code/Magento/SalesRule/Model/Rule/DataProvider.php b/app/code/Magento/SalesRule/Model/Rule/DataProvider.php
index 916825776373d..25f0ef91eae68 100644
--- a/app/code/Magento/SalesRule/Model/Rule/DataProvider.php
+++ b/app/code/Magento/SalesRule/Model/Rule/DataProvider.php
@@ -5,6 +5,7 @@
*/
namespace Magento\SalesRule\Model\Rule;
+use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\SalesRule\Model\ResourceModel\Rule\Collection;
use Magento\SalesRule\Model\ResourceModel\Rule\CollectionFactory;
use Magento\SalesRule\Model\Rule;
@@ -36,6 +37,11 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
*/
protected $metadataValueProvider;
+ /**
+ * @var DataPersistorInterface
+ */
+ private $dataPersistor;
+
/**
* Initialize dependencies.
*
@@ -47,6 +53,7 @@ class DataProvider extends \Magento\Ui\DataProvider\AbstractDataProvider
* @param Metadata\ValueProvider $metadataValueProvider
* @param array $meta
* @param array $data
+ * @param DataPersistorInterface $dataPersistor
*/
public function __construct(
$name,
@@ -56,12 +63,16 @@ public function __construct(
\Magento\Framework\Registry $registry,
\Magento\SalesRule\Model\Rule\Metadata\ValueProvider $metadataValueProvider,
array $meta = [],
- array $data = []
+ array $data = [],
+ DataPersistorInterface $dataPersistor = null
) {
$this->collection = $collectionFactory->create();
$this->coreRegistry = $registry;
$this->metadataValueProvider = $metadataValueProvider;
$meta = array_replace_recursive($this->getMetadataValues(), $meta);
+ $this->dataPersistor = $dataPersistor ?? \Magento\Framework\App\ObjectManager::getInstance()->get(
+ DataPersistorInterface::class
+ );
parent::__construct($name, $primaryFieldName, $requestFieldName, $meta, $data);
}
@@ -77,7 +88,7 @@ protected function getMetadataValues()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getData()
{
@@ -93,6 +104,13 @@ public function getData()
$this->loadedData[$rule->getId()] = $rule->getData();
}
+ $data = $this->dataPersistor->get('sale_rule');
+ if (!empty($data)) {
+ $rule = $this->collection->getNewEmptyItem();
+ $rule->setData($data);
+ $this->loadedData[$rule->getId()] = $rule->getData();
+ $this->dataPersistor->clear('sale_rule');
+ }
return $this->loadedData;
}
diff --git a/app/code/Magento/SalesRule/Model/Validator.php b/app/code/Magento/SalesRule/Model/Validator.php
index 5c0f97ae0b08b..ea0221d8f072d 100644
--- a/app/code/Magento/SalesRule/Model/Validator.php
+++ b/app/code/Magento/SalesRule/Model/Validator.php
@@ -182,6 +182,8 @@ protected function _getRules(Address $address = null)
}
/**
+ * Address id getter.
+ *
* @param Address $address
* @return string
*/
@@ -327,21 +329,7 @@ public function processShippingAmount(Address $address)
$baseDiscountAmount = $rule->getDiscountAmount();
break;
case \Magento\SalesRule\Model\Rule::CART_FIXED_ACTION:
- $cartRules = $address->getCartFixedRules();
- if (!isset($cartRules[$rule->getId()])) {
- $cartRules[$rule->getId()] = $rule->getDiscountAmount();
- }
- if ($cartRules[$rule->getId()] > 0) {
- $quoteAmount = $this->priceCurrency->convert($cartRules[$rule->getId()], $quote->getStore());
- $discountAmount = min($shippingAmount - $address->getShippingDiscountAmount(), $quoteAmount);
- $baseDiscountAmount = min(
- $baseShippingAmount - $address->getBaseShippingDiscountAmount(),
- $cartRules[$rule->getId()]
- );
- $cartRules[$rule->getId()] -= $baseDiscountAmount;
- }
-
- $address->setCartFixedRules($cartRules);
+ // Shouldn't be proceed according to MAGETWO-96403
break;
}
@@ -519,6 +507,8 @@ public function sortItemsByPriority($items, Address $address = null)
}
/**
+ * Rule total items getter.
+ *
* @param int $key
* @return array
* @throws \Magento\Framework\Exception\LocalizedException
@@ -533,6 +523,8 @@ public function getRuleItemTotalsInfo($key)
}
/**
+ * Decrease rule items count.
+ *
* @param int $key
* @return $this
*/
diff --git a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml
index e5907e1e9c0f5..210259f474ee9 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/ActionGroup/AdminCartPriceRuleActionGroup.xml
@@ -38,4 +38,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml
index 521734ab9f292..8f6e63534b0ca 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Data/SalesRuleData.xml
@@ -189,4 +189,8 @@
10
1
+
+
+ by_fixed
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml
index 7628ecf468827..c8da82407457d 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Section/AdminCartPriceRulesFormSection.xml
@@ -48,9 +48,12 @@
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml
index fbcc871a69b97..ab085dc5ae137 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCartRulesAppliedForProductInCartTest.xml
@@ -21,26 +21,30 @@
-
-
200
500
+
+
+
-
+
+
+
+
@@ -57,12 +61,14 @@
+
+
@@ -92,7 +98,7 @@
-
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml
index 271477070d8cd..7b350c0208cc1 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Test/AdminCreateCartPriceRuleForGeneratedCouponTest.xml
@@ -53,7 +53,14 @@
-
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml
index ed05f8b27e5ca..84537fb69ed41 100644
--- a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml
+++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontAutoGeneratedCouponCodeTest.xml
@@ -56,8 +56,19 @@
stepKey="clickManageCouponCodes"/>
-
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCategoryRulesShouldApplyToComplexProductsTest.xml b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCategoryRulesShouldApplyToComplexProductsTest.xml
new file mode 100644
index 0000000000000..d8c5b42dbaaaf
--- /dev/null
+++ b/app/code/Magento/SalesRule/Test/Mftf/Test/StorefrontCategoryRulesShouldApplyToComplexProductsTest.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/Test/Unit/Controller/Adminhtml/Promo/Quote/GenerateTest.php b/app/code/Magento/SalesRule/Test/Unit/Controller/Adminhtml/Promo/Quote/GenerateTest.php
index 2ef77d72a8af5..66970f28598b6 100644
--- a/app/code/Magento/SalesRule/Test/Unit/Controller/Adminhtml/Promo/Quote/GenerateTest.php
+++ b/app/code/Magento/SalesRule/Test/Unit/Controller/Adminhtml/Promo/Quote/GenerateTest.php
@@ -8,6 +8,7 @@
use Magento\Framework\App\ObjectManager;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use Magento\SalesRule\Model\CouponGenerator;
+use Magento\SalesRule\Api\Data\CouponGenerationSpecInterfaceFactory;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -50,6 +51,9 @@ class GenerateTest extends \PHPUnit\Framework\TestCase
/** @var CouponGenerator | \PHPUnit_Framework_MockObject_MockObject */
private $couponGenerator;
+ /** @var CouponGenerationSpecInterfaceFactory | \PHPUnit_Framework_MockObject_MockObject */
+ private $couponGenerationSpec;
+
/**
* Test setup
*/
@@ -98,6 +102,9 @@ protected function setUp()
$this->couponGenerator = $this->getMockBuilder(CouponGenerator::class)
->disableOriginalConstructor()
->getMock();
+ $this->couponGenerationSpec = $this->getMockBuilder(CouponGenerationSpecInterfaceFactory::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->objectManagerHelper = new ObjectManagerHelper($this);
$this->model = $this->objectManagerHelper->getObject(
@@ -107,7 +114,8 @@ protected function setUp()
'coreRegistry' => $this->registryMock,
'fileFactory' => $this->fileFactoryMock,
'dateFilter' => $this->dateMock,
- 'couponGenerator' => $this->couponGenerator
+ 'couponGenerator' => $this->couponGenerator,
+ 'generationSpecFactory' => $this->couponGenerationSpec
]
);
}
@@ -144,9 +152,10 @@ public function testExecute()
$this->requestMock->expects($this->once())
->method('getParams')
->willReturn($requestData);
- $this->couponGenerator->expects($this->once())
- ->method('generateCodes')
- ->with($requestData)
+ $requestData['quantity'] = isset($requestData['qty']) ? $requestData['qty'] : null;
+ $this->couponGenerationSpec->expects($this->once())
+ ->method('create')
+ ->with(['data' => $requestData])
->willReturn(['some_data', 'some_data_2']);
$this->messageManager->expects($this->once())
->method('addSuccessMessage');
diff --git a/app/code/Magento/SalesRule/Test/Unit/Model/ValidatorTest.php b/app/code/Magento/SalesRule/Test/Unit/Model/ValidatorTest.php
index 42448565791c5..e86068946ca78 100644
--- a/app/code/Magento/SalesRule/Test/Unit/Model/ValidatorTest.php
+++ b/app/code/Magento/SalesRule/Test/Unit/Model/ValidatorTest.php
@@ -5,6 +5,13 @@
*/
namespace Magento\SalesRule\Test\Unit\Model;
+use Magento\Framework\Pricing\PriceCurrencyInterface;
+use Magento\Quote\Model\Quote;
+use Magento\SalesRule\Model\Rule;
+use Magento\SalesRule\Model\Validator;
+use Magento\Store\Model\Store;
+use PHPUnit\Framework\MockObject\MockObject;
+
/**
* Class ValidatorTest
* @@SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -17,50 +24,55 @@ class ValidatorTest extends \PHPUnit\Framework\TestCase
protected $helper;
/**
- * @var \Magento\SalesRule\Model\Validator
+ * @var Validator
*/
protected $model;
/**
- * @var \Magento\Quote\Model\Quote\Item|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Quote\Model\Quote\Item|MockObject
*/
protected $item;
/**
- * @var \Magento\Quote\Model\Quote\Address|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Quote\Model\Quote\Address|MockObject
*/
protected $addressMock;
/**
- * @var \Magento\SalesRule\Model\RulesApplier|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\SalesRule\Model\RulesApplier|MockObject
*/
protected $rulesApplier;
/**
- * @var \Magento\SalesRule\Model\Validator\Pool|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\SalesRule\Model\Validator\Pool|MockObject
*/
protected $validators;
/**
- * @var \Magento\SalesRule\Model\Utility|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\SalesRule\Model\Utility|MockObject
*/
protected $utility;
/**
- * @var \Magento\SalesRule\Model\ResourceModel\Rule\Collection|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\SalesRule\Model\ResourceModel\Rule\Collection|MockObject
*/
protected $ruleCollection;
/**
- * @var \Magento\Catalog\Helper\Data|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Catalog\Helper\Data|MockObject
*/
protected $catalogData;
/**
- * @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject
+ * @var \Magento\Framework\Message\ManagerInterface|MockObject
*/
protected $messageManager;
+ /**
+ * @var PriceCurrencyInterface|MockObject
+ */
+ private $priceCurrency;
+
protected function setUp()
{
$this->helper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
@@ -74,6 +86,7 @@ protected function setUp()
->setMethods(
[
'getShippingAmountForDiscount',
+ 'getBaseShippingAmountForDiscount',
'getQuote',
'getCustomAttributesCodes',
'setCartFixedRules'
@@ -81,7 +94,7 @@ protected function setUp()
)
->getMock();
- /** @var \Magento\Quote\Model\Quote\Item\AbstractItem|\PHPUnit_Framework_MockObject_MockObject $item */
+ /** @var \Magento\Quote\Model\Quote\Item\AbstractItem|MockObject $item */
$this->item = $this->createPartialMock(
\Magento\Quote\Model\Quote\Item::class,
['__wakeup', 'getAddress', 'getParentItemId']
@@ -100,10 +113,13 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
$ruleCollectionFactoryMock = $this->prepareRuleCollectionMock($this->ruleCollection);
+ $this->priceCurrency = $this->getMockBuilder(PriceCurrencyInterface::class)
+ ->disableOriginalConstructor()
+ ->getMock();
- /** @var \Magento\SalesRule\Model\Validator|\PHPUnit_Framework_MockObject_MockObject $validator */
+ /** @var Validator|MockObject $validator */
$this->model = $this->helper->getObject(
- \Magento\SalesRule\Model\Validator::class,
+ Validator::class,
[
'context' => $context,
'registry' => $registry,
@@ -112,7 +128,8 @@ protected function setUp()
'utility' => $this->utility,
'rulesApplier' => $this->rulesApplier,
'validators' => $this->validators,
- 'messageManager' => $this->messageManager
+ 'messageManager' => $this->messageManager,
+ 'priceCurrency' => $this->priceCurrency
]
);
$this->model->setWebsiteId(1);
@@ -131,7 +148,7 @@ protected function setUp()
}
/**
- * @return \Magento\Quote\Model\Quote\Item|\PHPUnit_Framework_MockObject_MockObject
+ * @return \Magento\Quote\Model\Quote\Item|MockObject
*/
protected function getQuoteItemMock()
{
@@ -145,8 +162,8 @@ protected function getQuoteItemMock()
$itemSimple = $this->createPartialMock(\Magento\Quote\Model\Quote\Item::class, ['getAddress', '__wakeup']);
$itemSimple->expects($this->any())->method('getAddress')->will($this->returnValue($this->addressMock));
- /** @var $quote \Magento\Quote\Model\Quote */
- $quote = $this->createPartialMock(\Magento\Quote\Model\Quote::class, ['getStoreId', '__wakeup']);
+ /** @var $quote Quote */
+ $quote = $this->createPartialMock(Quote::class, ['getStoreId', '__wakeup']);
$quote->expects($this->any())->method('getStoreId')->will($this->returnValue(1));
$itemData = include $fixturePath . 'quote_item_downloadable.php';
@@ -168,7 +185,7 @@ public function testCanApplyRules()
$this->model->getCouponCode()
);
$item = $this->getQuoteItemMock();
- $rule = $this->createMock(\Magento\SalesRule\Model\Rule::class);
+ $rule = $this->createMock(Rule::class);
$actionsCollection = $this->createPartialMock(\Magento\Rule\Model\Action\Collection::class, ['validate']);
$actionsCollection->expects($this->any())
->method('validate')
@@ -278,7 +295,7 @@ public function testApplyRulesThatAppliedRuleIdsAreCollected()
public function testInit()
{
$this->assertInstanceOf(
- \Magento\SalesRule\Model\Validator::class,
+ Validator::class,
$this->model->init(
$this->model->getWebsiteId(),
$this->model->getCustomerGroupId(),
@@ -314,7 +331,7 @@ public function testCanApplyDiscount()
public function testInitTotalsCanApplyDiscount()
{
$rule = $this->createPartialMock(
- \Magento\SalesRule\Model\Rule::class,
+ Rule::class,
['getSimpleAction', 'getActions', 'getId']
);
$item1 = $this->getMockForAbstractClass(
@@ -337,7 +354,7 @@ public function testInitTotalsCanApplyDiscount()
$rule->expects($this->any())
->method('getSimpleAction')
- ->willReturn(\Magento\SalesRule\Model\Rule::CART_FIXED_ACTION);
+ ->willReturn(Rule::CART_FIXED_ACTION);
$iterator = new \ArrayIterator([$rule]);
$this->ruleCollection->expects($this->once())->method('getIterator')->willReturn($iterator);
$validator = $this->getMockBuilder(\Magento\Framework\Validator\AbstractValidator::class)
@@ -392,7 +409,7 @@ public function testInitTotalsNoItems()
/**
* @param $ruleCollection
- * @return \PHPUnit_Framework_MockObject_MockObject
+ * @return MockObject
*/
protected function prepareRuleCollectionMock($ruleCollection)
{
@@ -427,14 +444,14 @@ public function testProcessShippingAmountNoRules()
$this->model->getCouponCode()
);
$this->assertInstanceOf(
- \Magento\SalesRule\Model\Validator::class,
+ Validator::class,
$this->model->processShippingAmount($this->setupAddressMock())
);
}
public function testProcessShippingAmountProcessDisabled()
{
- $ruleMock = $this->getMockBuilder(\Magento\SalesRule\Model\Rule::class)
+ $ruleMock = $this->getMockBuilder(Rule::class)
->disableOriginalConstructor()
->setMethods([])
->getMock();
@@ -448,51 +465,54 @@ public function testProcessShippingAmountProcessDisabled()
$this->model->getCouponCode()
);
$this->assertInstanceOf(
- \Magento\SalesRule\Model\Validator::class,
+ Validator::class,
$this->model->processShippingAmount($this->setupAddressMock())
);
}
/**
+ * Tests shipping amounts according to rule simple action.
+ *
* @param string $action
+ * @param int $ruleDiscount
+ * @param int $shippingDiscount
* @dataProvider dataProviderActions
*/
- public function testProcessShippingAmountActions($action)
+ public function testProcessShippingAmountActions($action, $ruleDiscount, $shippingDiscount): void
{
- $discountAmount = 50;
+ $shippingAmount = 5;
- $ruleMock = $this->getMockBuilder(\Magento\SalesRule\Model\Rule::class)
+ $ruleMock = $this->getMockBuilder(Rule::class)
->disableOriginalConstructor()
->setMethods(['getApplyToShipping', 'getSimpleAction', 'getDiscountAmount'])
->getMock();
- $ruleMock->expects($this->any())
- ->method('getApplyToShipping')
+ $ruleMock->method('getApplyToShipping')
->willReturn(true);
- $ruleMock->expects($this->any())
- ->method('getDiscountAmount')
- ->willReturn($discountAmount);
- $ruleMock->expects($this->any())
- ->method('getSimpleAction')
+ $ruleMock->method('getDiscountAmount')
+ ->willReturn($ruleDiscount);
+ $ruleMock->method('getSimpleAction')
->willReturn($action);
$iterator = new \ArrayIterator([$ruleMock]);
- $this->ruleCollection->expects($this->any())
- ->method('getIterator')
+ $this->ruleCollection->method('getIterator')
->willReturn($iterator);
- $this->utility->expects($this->any())
- ->method('canProcessRule')
+ $this->utility->method('canProcessRule')
->willReturn(true);
+ $this->priceCurrency->method('convert')
+ ->willReturn($ruleDiscount);
+
$this->model->init(
$this->model->getWebsiteId(),
$this->model->getCustomerGroupId(),
$this->model->getCouponCode()
);
- $this->assertInstanceOf(
- \Magento\SalesRule\Model\Validator::class,
- $this->model->processShippingAmount($this->setupAddressMock(5))
- );
+
+ $addressMock = $this->setupAddressMock($shippingAmount);
+
+ self::assertInstanceOf(Validator::class, $this->model->processShippingAmount($addressMock));
+ self::assertEquals($shippingDiscount, $addressMock->getShippingDiscountAmount());
}
/**
@@ -501,44 +521,48 @@ public function testProcessShippingAmountActions($action)
public static function dataProviderActions()
{
return [
- [\Magento\SalesRule\Model\Rule::TO_PERCENT_ACTION],
- [\Magento\SalesRule\Model\Rule::BY_PERCENT_ACTION],
- [\Magento\SalesRule\Model\Rule::TO_FIXED_ACTION],
- [\Magento\SalesRule\Model\Rule::BY_FIXED_ACTION],
- [\Magento\SalesRule\Model\Rule::CART_FIXED_ACTION],
+ [Rule::TO_PERCENT_ACTION, 50, 2.5],
+ [Rule::BY_PERCENT_ACTION, 50, 2.5],
+ [Rule::TO_FIXED_ACTION, 5, 0],
+ [Rule::BY_FIXED_ACTION, 5, 5],
+ [Rule::CART_FIXED_ACTION, 5, 0],
];
}
/**
* @param null|int $shippingAmount
- * @return \PHPUnit_Framework_MockObject_MockObject
+ * @return MockObject
*/
protected function setupAddressMock($shippingAmount = null)
{
- $storeMock = $this->getMockBuilder(\Magento\Store\Model\Store::class)
+ $storeMock = $this->getMockBuilder(Store::class)
->disableOriginalConstructor()
->setMethods([])
->getMock();
- $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class)
+
+ $quoteMock = $this->getMockBuilder(Quote::class)
->disableOriginalConstructor()
->setMethods(['setAppliedRuleIds', 'getStore'])
->getMock();
- $quoteMock->expects($this->any())
- ->method('getStore')
+
+ $quoteMock->method('getStore')
->willReturn($storeMock);
- $quoteMock->expects($this->any())
- ->method('setAppliedRuleIds')
+
+ $quoteMock->method('setAppliedRuleIds')
->willReturnSelf();
- $this->addressMock->expects($this->any())
- ->method('getShippingAmountForDiscount')
+ $this->addressMock->method('getShippingAmountForDiscount')
->willReturn($shippingAmount);
- $this->addressMock->expects($this->any())
- ->method('getQuote')
+
+ $this->addressMock->method('getBaseShippingAmountForDiscount')
+ ->willReturn($shippingAmount);
+
+ $this->addressMock->method('getQuote')
->willReturn($quoteMock);
- $this->addressMock->expects($this->any())
- ->method('getCustomAttributesCodes')
+
+ $this->addressMock->method('getCustomAttributesCodes')
->willReturn([]);
+
return $this->addressMock;
}
@@ -546,7 +570,7 @@ public function testReset()
{
$this->utility->expects($this->once())
->method('resetRoundingDeltas');
- $quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class)
+ $quoteMock = $this->getMockBuilder(Quote::class)
->disableOriginalConstructor()
->getMock();
$addressMock = $this->getMockBuilder(\Magento\Quote\Model\Quote\Address::class)
@@ -560,6 +584,6 @@ public function testReset()
$this->model->getCustomerGroupId(),
$this->model->getCouponCode()
);
- $this->assertInstanceOf(\Magento\SalesRule\Model\Validator::class, $this->model->reset($addressMock));
+ $this->assertInstanceOf(Validator::class, $this->model->reset($addressMock));
}
}
diff --git a/app/code/Magento/SalesRule/etc/communication.xml b/app/code/Magento/SalesRule/etc/communication.xml
new file mode 100644
index 0000000000000..4c905fa83e2fd
--- /dev/null
+++ b/app/code/Magento/SalesRule/etc/communication.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/etc/queue.xml b/app/code/Magento/SalesRule/etc/queue.xml
new file mode 100644
index 0000000000000..8217a0b9f6c1a
--- /dev/null
+++ b/app/code/Magento/SalesRule/etc/queue.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/etc/queue_consumer.xml b/app/code/Magento/SalesRule/etc/queue_consumer.xml
new file mode 100644
index 0000000000000..9eb585f48e8e3
--- /dev/null
+++ b/app/code/Magento/SalesRule/etc/queue_consumer.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/etc/queue_publisher.xml b/app/code/Magento/SalesRule/etc/queue_publisher.xml
new file mode 100644
index 0000000000000..0863fba2307c5
--- /dev/null
+++ b/app/code/Magento/SalesRule/etc/queue_publisher.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/etc/queue_topology.xml b/app/code/Magento/SalesRule/etc/queue_topology.xml
new file mode 100644
index 0000000000000..fd6a9bf36721c
--- /dev/null
+++ b/app/code/Magento/SalesRule/etc/queue_topology.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml b/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml
index 9b579f47759a6..570eb0bf151f0 100644
--- a/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml
+++ b/app/code/Magento/SalesRule/view/adminhtml/ui_component/sales_rule_form.xml
@@ -452,7 +452,7 @@
discount_step
-
+
-
- sales_rule
diff --git a/app/code/Magento/SalesRule/view/adminhtml/web/js/form/element/apply_to_shipping.js b/app/code/Magento/SalesRule/view/adminhtml/web/js/form/element/apply_to_shipping.js
new file mode 100644
index 0000000000000..dfb3f909345b3
--- /dev/null
+++ b/app/code/Magento/SalesRule/view/adminhtml/web/js/form/element/apply_to_shipping.js
@@ -0,0 +1,37 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'Magento_Ui/js/form/element/single-checkbox-toggle-notice'
+], function (Checkbox) {
+ 'use strict';
+
+ return Checkbox.extend({
+ defaults: {
+ imports: {
+ toggleDisabled: '${ $.parentName }.simple_action:value'
+ }
+ },
+
+ /**
+ * Toggle element disabled state according to simple action value.
+ *
+ * @param {String} action
+ */
+ toggleDisabled: function (action) {
+ switch (action) {
+ case 'cart_fixed':
+ this.disabled(true);
+ break;
+ default:
+ this.disabled(false);
+ }
+
+ if (this.disabled()) {
+ this.checked(false);
+ }
+ }
+ });
+});
diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml
new file mode 100644
index 0000000000000..e0b3d4b850bbb
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml b/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml
new file mode 100644
index 0000000000000..1518adad01347
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/Data/SearchTermData.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ Query text
+ 1
+ http://example.com/
+ 0
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Metadata/search_term-meta.xml b/app/code/Magento/Search/Test/Mftf/Metadata/search_term-meta.xml
new file mode 100644
index 0000000000000..0bd2dc9be4855
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/Metadata/search_term-meta.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+ string
+ integer
+ string
+ integer
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml
index 9e04bbb12a796..81b025c9554e2 100644
--- a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml
+++ b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchResultsSection.xml
@@ -14,6 +14,7 @@
-
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml
index 2b08e9b4b85ec..3c2909b59c0de 100644
--- a/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml
+++ b/app/code/Magento/Search/Test/Mftf/Section/StorefrontQuickSearchSection.xml
@@ -11,5 +11,6 @@
diff --git a/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml b/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml
new file mode 100644
index 0000000000000..67ccb51bf401e
--- /dev/null
+++ b/app/code/Magento/Search/Test/Mftf/Test/AdminMassDeleteSearchTermEntityTest.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Shipping/Block/DataProviders/Tracking/DeliveryDateTitle.php b/app/code/Magento/Shipping/Block/DataProviders/Tracking/DeliveryDateTitle.php
new file mode 100644
index 0000000000000..ec1ee277a5a51
--- /dev/null
+++ b/app/code/Magento/Shipping/Block/DataProviders/Tracking/DeliveryDateTitle.php
@@ -0,0 +1,27 @@
+getCarrier() ? __('Delivered on:') : '';
+ }
+}
diff --git a/app/code/Magento/Shipping/view/frontend/layout/shipping_tracking_popup.xml b/app/code/Magento/Shipping/view/frontend/layout/shipping_tracking_popup.xml
index 1f5b0ae4630ad..67d03da2599bf 100644
--- a/app/code/Magento/Shipping/view/frontend/layout/shipping_tracking_popup.xml
+++ b/app/code/Magento/Shipping/view/frontend/layout/shipping_tracking_popup.xml
@@ -8,7 +8,11 @@
-
+
diff --git a/app/code/Magento/Shipping/view/frontend/templates/tracking/details.phtml b/app/code/Magento/Shipping/view/frontend/templates/tracking/details.phtml
index 9253b47f82f5d..e8584d8f6ad51 100644
--- a/app/code/Magento/Shipping/view/frontend/templates/tracking/details.phtml
+++ b/app/code/Magento/Shipping/view/frontend/templates/tracking/details.phtml
@@ -77,7 +77,7 @@ $number = is_object($track) ? $track->getTracking() : $track['number'];
getDeliverydate()): ?>
- = $block->escapeHtml(__('Delivered on:')) ?>
+ = $block->escapeHtml($parentBlock->getDeliveryDateTitle()->getTitle($track)) ?>
= /* @noEscape */ $parentBlock->formatDeliveryDateTime($track->getDeliverydate(), $track->getDeliverytime()) ?>
diff --git a/app/code/Magento/Sitemap/Block/Adminhtml/Edit/Form.php b/app/code/Magento/Sitemap/Block/Adminhtml/Edit/Form.php
index 5e90cf6e12f6e..91b6a8446894b 100644
--- a/app/code/Magento/Sitemap/Block/Adminhtml/Edit/Form.php
+++ b/app/code/Magento/Sitemap/Block/Adminhtml/Edit/Form.php
@@ -48,6 +48,8 @@ protected function _construct()
}
/**
+ * Configure form for sitemap.
+ *
* @return $this
*/
protected function _prepareForm()
@@ -73,7 +75,8 @@ protected function _prepareForm()
'name' => 'sitemap_filename',
'required' => true,
'note' => __('example: sitemap.xml'),
- 'value' => $model->getSitemapFilename()
+ 'value' => $model->getSitemapFilename(),
+ 'class' => 'validate-length maximum-length-32'
]
);
diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php
index 9592ab6f57c55..c8e2d356b3ffb 100644
--- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php
+++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Generate.php
@@ -7,10 +7,15 @@
namespace Magento\Sitemap\Controller\Adminhtml\Sitemap;
use Magento\Backend\App\Action;
+use Magento\Framework\App\Action\HttpGetActionInterface;
+use Magento\Sitemap\Controller\Adminhtml\Sitemap;
use Magento\Store\Model\App\Emulation;
use Magento\Framework\App\ObjectManager;
-class Generate extends \Magento\Sitemap\Controller\Adminhtml\Sitemap
+/**
+ * Generate sitemap file
+ */
+class Generate extends Sitemap implements HttpGetActionInterface
{
/** @var \Magento\Store\Model\App\Emulation $appEmulation */
private $appEmulation;
@@ -44,14 +49,7 @@ public function execute()
// if sitemap record exists
if ($sitemap->getId()) {
try {
- //We need to emulate to get the correct frontend URL for the product images
- $this->appEmulation->startEnvironmentEmulation(
- $sitemap->getStoreId(),
- \Magento\Framework\App\Area::AREA_FRONTEND,
- true
- );
$sitemap->generateXml();
-
$this->messageManager->addSuccessMessage(
__('The sitemap "%1" has been generated.', $sitemap->getSitemapFilename())
);
@@ -59,8 +57,6 @@ public function execute()
$this->messageManager->addErrorMessage($e->getMessage());
} catch (\Exception $e) {
$this->messageManager->addExceptionMessage($e, __('We can\'t generate the sitemap right now.'));
- } finally {
- $this->appEmulation->stopEnvironmentEmulation();
}
} else {
$this->messageManager->addErrorMessage(__('We can\'t find a sitemap to generate.'));
diff --git a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Save.php b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Save.php
index 1e0d1cb248f00..5230de0429778 100644
--- a/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Save.php
+++ b/app/code/Magento/Sitemap/Controller/Adminhtml/Sitemap/Save.php
@@ -5,12 +5,74 @@
*/
namespace Magento\Sitemap\Controller\Adminhtml\Sitemap;
-use Magento\Backend\App\Action;
+use Magento\Backend\App\Action\Context;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\Framework\Controller;
+use Magento\Framework\Validator\StringLength;
+use Magento\MediaStorage\Model\File\Validator\AvailablePath;
+use Magento\Sitemap\Model\SitemapFactory;
-class Save extends \Magento\Sitemap\Controller\Adminhtml\Sitemap
+/**
+ * Save sitemap controller.
+ */
+class Save extends \Magento\Sitemap\Controller\Adminhtml\Sitemap implements HttpPostActionInterface
{
+ /**
+ * Maximum length of sitemap filename
+ */
+ const MAX_FILENAME_LENGTH = 32;
+
+ /**
+ * @var StringLength
+ */
+ private $stringValidator;
+
+ /**
+ * @var AvailablePath
+ */
+ private $pathValidator;
+
+ /**
+ * @var \Magento\Sitemap\Helper\Data
+ */
+ private $sitemapHelper;
+
+ /**
+ * @var \Magento\Framework\Filesystem
+ */
+ private $filesystem;
+
+ /**
+ * @var SitemapFactory
+ */
+ private $sitemapFactory;
+
+ /**
+ * Save constructor.
+ * @param Context $context
+ * @param StringLength $stringValidator
+ * @param AvailablePath $pathValidator
+ * @param \Magento\Sitemap\Helper\Data $sitemapHelper
+ * @param \Magento\Framework\Filesystem $filesystem
+ * @param SitemapFactory $sitemapFactory
+ */
+ public function __construct(
+ Context $context,
+ StringLength $stringValidator = null,
+ AvailablePath $pathValidator = null,
+ \Magento\Sitemap\Helper\Data $sitemapHelper = null,
+ \Magento\Framework\Filesystem $filesystem = null,
+ SitemapFactory $sitemapFactory = null
+ ) {
+ parent::__construct($context);
+ $this->stringValidator = $stringValidator ?: $this->_objectManager->get(StringLength::class);
+ $this->pathValidator = $pathValidator ?: $this->_objectManager->get(AvailablePath::class);
+ $this->sitemapHelper = $sitemapHelper ?: $this->_objectManager->get(\Magento\Sitemap\Helper\Data::class);
+ $this->filesystem = $filesystem ?: $this->_objectManager->get(\Magento\Framework\Filesystem::class);
+ $this->sitemapFactory = $sitemapFactory ?: $this->_objectManager->get(SitemapFactory::class);
+ }
+
/**
* Validate path for generation
*
@@ -23,17 +85,25 @@ protected function validatePath(array $data)
if (!empty($data['sitemap_filename']) && !empty($data['sitemap_path'])) {
$data['sitemap_path'] = '/' . ltrim($data['sitemap_path'], '/');
$path = rtrim($data['sitemap_path'], '\\/') . '/' . $data['sitemap_filename'];
- /** @var $validator \Magento\MediaStorage\Model\File\Validator\AvailablePath */
- $validator = $this->_objectManager->create(\Magento\MediaStorage\Model\File\Validator\AvailablePath::class);
- /** @var $helper \Magento\Sitemap\Helper\Data */
- $helper = $this->_objectManager->get(\Magento\Sitemap\Helper\Data::class);
- $validator->setPaths($helper->getValidPaths());
- if (!$validator->isValid($path)) {
- foreach ($validator->getMessages() as $message) {
+ $this->pathValidator->setPaths($this->sitemapHelper->getValidPaths());
+ if (!$this->pathValidator->isValid($path)) {
+ foreach ($this->pathValidator->getMessages() as $message) {
+ $this->messageManager->addErrorMessage($message);
+ }
+ // save data in session
+ $this->_session->setFormData($data);
+ // redirect to edit form
+ return false;
+ }
+
+ $filename = rtrim($data['sitemap_filename']);
+ $this->stringValidator->setMax(self::MAX_FILENAME_LENGTH);
+ if (!$this->stringValidator->isValid($filename)) {
+ foreach ($this->stringValidator->getMessages() as $message) {
$this->messageManager->addErrorMessage($message);
}
// save data in session
- $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setFormData($data);
+ $this->_session->setFormData($data);
// redirect to edit form
return false;
}
@@ -49,9 +119,8 @@ protected function validatePath(array $data)
*/
protected function clearSiteMap(\Magento\Sitemap\Model\Sitemap $model)
{
- /** @var \Magento\Framework\Filesystem\Directory\Write $directory */
- $directory = $this->_objectManager->get(\Magento\Framework\Filesystem::class)
- ->getDirectoryWrite(DirectoryList::ROOT);
+ /** @var \Magento\Framework\Filesystem $directory */
+ $directory = $this->filesystem->getDirectoryWrite(DirectoryList::ROOT);
if ($this->getRequest()->getParam('sitemap_id')) {
$model->load($this->getRequest()->getParam('sitemap_id'));
@@ -74,7 +143,7 @@ protected function saveData($data)
{
// init model and set data
/** @var \Magento\Sitemap\Model\Sitemap $model */
- $model = $this->_objectManager->create(\Magento\Sitemap\Model\Sitemap::class);
+ $model = $this->sitemapFactory->create();
$this->clearSiteMap($model);
$model->setData($data);
@@ -85,13 +154,13 @@ protected function saveData($data)
// display success message
$this->messageManager->addSuccessMessage(__('You saved the sitemap.'));
// clear previously saved data from session
- $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setFormData(false);
+ $this->_session->setFormData(false);
return $model->getId();
} catch (\Exception $e) {
// display error message
$this->messageManager->addErrorMessage($e->getMessage());
// save data in session
- $this->_objectManager->get(\Magento\Backend\Model\Session::class)->setFormData($data);
+ $this->_session->setFormData($data);
}
return false;
}
diff --git a/app/code/Magento/Sitemap/Model/EmailNotification.php b/app/code/Magento/Sitemap/Model/EmailNotification.php
new file mode 100644
index 0000000000000..27c042870a1d6
--- /dev/null
+++ b/app/code/Magento/Sitemap/Model/EmailNotification.php
@@ -0,0 +1,105 @@
+inlineTranslation = $inlineTranslation;
+ $this->scopeConfig = $scopeConfig;
+ $this->transportBuilder = $transportBuilder;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Send's error email if sitemap generated with errors.
+ *
+ * @param array| $errors
+ */
+ public function sendErrors($errors)
+ {
+ $this->inlineTranslation->suspend();
+ try {
+ $this->transportBuilder->setTemplateIdentifier(
+ $this->scopeConfig->getValue(
+ Observer::XML_PATH_ERROR_TEMPLATE,
+ ScopeInterface::SCOPE_STORE
+ )
+ )->setTemplateOptions(
+ [
+ 'area' => FrontNameResolver::AREA_CODE,
+ 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
+ ]
+ )->setTemplateVars(
+ ['warnings' => join("\n", $errors)]
+ )->setFrom(
+ $this->scopeConfig->getValue(
+ Observer::XML_PATH_ERROR_IDENTITY,
+ ScopeInterface::SCOPE_STORE
+ )
+ )->addTo(
+ $this->scopeConfig->getValue(
+ Observer::XML_PATH_ERROR_RECIPIENT,
+ ScopeInterface::SCOPE_STORE
+ )
+ );
+
+ $transport = $this->transportBuilder->getTransport();
+ $transport->sendMessage();
+ } catch (\Exception $e) {
+ $this->logger->error('Sitemap sendErrors: '.$e->getMessage());
+ } finally {
+ $this->inlineTranslation->resume();
+ }
+ }
+}
diff --git a/app/code/Magento/Sitemap/Model/Observer.php b/app/code/Magento/Sitemap/Model/Observer.php
index a536ec998b827..ce74d738c4bc3 100644
--- a/app/code/Magento/Sitemap/Model/Observer.php
+++ b/app/code/Magento/Sitemap/Model/Observer.php
@@ -5,6 +5,11 @@
*/
namespace Magento\Sitemap\Model;
+use Magento\Sitemap\Model\EmailNotification as SitemapEmail;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Sitemap\Model\ResourceModel\Sitemap\CollectionFactory;
+use Magento\Store\Model\ScopeInterface;
+
/**
* Sitemap module observer
*
@@ -44,47 +49,32 @@ class Observer
*
* @var \Magento\Framework\App\Config\ScopeConfigInterface
*/
- protected $_scopeConfig;
+ private $scopeConfig;
/**
* @var \Magento\Sitemap\Model\ResourceModel\Sitemap\CollectionFactory
*/
- protected $_collectionFactory;
-
- /**
- * @var \Magento\Framework\Mail\Template\TransportBuilder
- */
- protected $_transportBuilder;
-
- /**
- * @var \Magento\Store\Model\StoreManagerInterface
- */
- protected $_storeManager;
+ private $collectionFactory;
/**
- * @var \Magento\Framework\Translate\Inline\StateInterface
+ * @var $emailNotification
*/
- protected $inlineTranslation;
+ private $emailNotification;
/**
- * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
- * @param \Magento\Sitemap\Model\ResourceModel\Sitemap\CollectionFactory $collectionFactory
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder
- * @param \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation
+ * Observer constructor.
+ * @param ScopeConfigInterface $scopeConfig
+ * @param CollectionFactory $collectionFactory
+ * @param EmailNotification $emailNotification
*/
public function __construct(
- \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig,
- \Magento\Sitemap\Model\ResourceModel\Sitemap\CollectionFactory $collectionFactory,
- \Magento\Store\Model\StoreManagerInterface $storeManager,
- \Magento\Framework\Mail\Template\TransportBuilder $transportBuilder,
- \Magento\Framework\Translate\Inline\StateInterface $inlineTranslation
+ ScopeConfigInterface $scopeConfig,
+ CollectionFactory $collectionFactory,
+ SitemapEmail $emailNotification
) {
- $this->_scopeConfig = $scopeConfig;
- $this->_collectionFactory = $collectionFactory;
- $this->_storeManager = $storeManager;
- $this->_transportBuilder = $transportBuilder;
- $this->inlineTranslation = $inlineTranslation;
+ $this->scopeConfig = $scopeConfig;
+ $this->collectionFactory = $collectionFactory;
+ $this->emailNotification = $emailNotification;
}
/**
@@ -97,17 +87,20 @@ public function __construct(
public function scheduledGenerateSitemaps()
{
$errors = [];
-
+ $recipient = $this->scopeConfig->getValue(
+ Observer::XML_PATH_ERROR_RECIPIENT,
+ ScopeInterface::SCOPE_STORE
+ );
// check if scheduled generation enabled
- if (!$this->_scopeConfig->isSetFlag(
+ if (!$this->scopeConfig->isSetFlag(
self::XML_PATH_GENERATION_ENABLED,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE
)
) {
return;
}
- $collection = $this->_collectionFactory->create();
+ $collection = $this->collectionFactory->create();
/* @var $collection \Magento\Sitemap\Model\ResourceModel\Sitemap\Collection */
foreach ($collection as $sitemap) {
/* @var $sitemap \Magento\Sitemap\Model\Sitemap */
@@ -117,41 +110,8 @@ public function scheduledGenerateSitemaps()
$errors[] = $e->getMessage();
}
}
-
- if ($errors && $this->_scopeConfig->getValue(
- self::XML_PATH_ERROR_RECIPIENT,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
- )
- ) {
- $this->inlineTranslation->suspend();
-
- $this->_transportBuilder->setTemplateIdentifier(
- $this->_scopeConfig->getValue(
- self::XML_PATH_ERROR_TEMPLATE,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
- )
- )->setTemplateOptions(
- [
- 'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE,
- 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
- ]
- )->setTemplateVars(
- ['warnings' => join("\n", $errors)]
- )->setFrom(
- $this->_scopeConfig->getValue(
- self::XML_PATH_ERROR_IDENTITY,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
- )
- )->addTo(
- $this->_scopeConfig->getValue(
- self::XML_PATH_ERROR_RECIPIENT,
- \Magento\Store\Model\ScopeInterface::SCOPE_STORE
- )
- );
- $transport = $this->_transportBuilder->getTransport();
- $transport->sendMessage();
-
- $this->inlineTranslation->resume();
+ if ($errors && $recipient) {
+ $this->emailNotification->sendErrors($errors);
}
}
}
diff --git a/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php b/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php
index 82024b3b015e5..4d678f5c1cb94 100644
--- a/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php
+++ b/app/code/Magento/Sitemap/Model/ResourceModel/Catalog/Product.php
@@ -389,6 +389,7 @@ protected function _prepareProduct(array $productRow, $storeId)
*/
protected function _loadProductImages($product, $storeId)
{
+ $this->_storeManager->setCurrentStore($storeId);
/** @var $helper \Magento\Sitemap\Helper\Data */
$helper = $this->_sitemapData;
$imageIncludePolicy = $helper->getProductImageIncludePolicy($storeId);
diff --git a/app/code/Magento/Sitemap/Model/Sitemap.php b/app/code/Magento/Sitemap/Model/Sitemap.php
index d58ff732c81d7..c35e20d997d85 100644
--- a/app/code/Magento/Sitemap/Model/Sitemap.php
+++ b/app/code/Magento/Sitemap/Model/Sitemap.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Sitemap\Model;
use Magento\Config\Model\Config\Reader\Source\Deployed\DocumentRoot;
@@ -15,6 +16,8 @@
use Magento\Sitemap\Model\ResourceModel\Sitemap as SitemapResource;
/**
+ * Sitemap model.
+ *
* @method string getSitemapType()
* @method \Magento\Sitemap\Model\Sitemap setSitemapType(string $value)
* @method string getSitemapFilename()
@@ -154,12 +157,11 @@ class Sitemap extends \Magento\Framework\Model\AbstractModel implements \Magento
protected $dateTime;
/**
- * Model cache tag for clear cache in after save and after delete
+ * @inheritdoc
*
- * @var string
* @since 100.2.0
*/
- protected $_cacheTag = true;
+ protected $_cacheTag = [Value::CACHE_TAG];
/**
* Item resolver
diff --git a/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/SaveTest.php b/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/SaveTest.php
index f77954101df7c..00f51b7e6c23f 100644
--- a/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/SaveTest.php
+++ b/app/code/Magento/Sitemap/Test/Unit/Controller/Adminhtml/Sitemap/SaveTest.php
@@ -7,51 +7,83 @@
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
use Magento\Framework\Controller\ResultFactory;
+use Magento\Sitemap\Controller\Adminhtml\Sitemap\Save;
+/**
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class SaveTest extends \PHPUnit\Framework\TestCase
{
/**
* @var \Magento\Sitemap\Controller\Adminhtml\Sitemap\Save
*/
- protected $saveController;
+ private $saveController;
/**
* @var \Magento\Backend\App\Action\Context
*/
- protected $context;
-
- /**
- * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
- */
- protected $objectManagerHelper;
+ private $contextMock;
/**
* @var \Magento\Framework\HTTP\PhpEnvironment\Request|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $requestMock;
+ private $requestMock;
/**
* @var \Magento\Framework\Controller\ResultFactory|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $resultFactoryMock;
+ private $resultFactoryMock;
/**
* @var \Magento\Backend\Model\View\Result\Redirect|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $resultRedirectMock;
+ private $resultRedirectMock;
/**
* @var \Magento\Framework\ObjectManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $objectManagerMock;
+ private $objectManagerMock;
/**
* @var \Magento\Framework\Message\ManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
- protected $messageManagerMock;
+ private $messageManagerMock;
+
+ /**
+ * @var \Magento\Framework\Validator\StringLength|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $lengthValidator;
+
+ /**
+ * @var \Magento\MediaStorage\Model\File\Validator\AvailablePath|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $pathValidator;
+
+ /**
+ * @var \Magento\Sitemap\Helper\Data|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $helper;
+
+ /**
+ * @var \Magento\Framework\Filesystem|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $fileSystem;
+
+ /**
+ * @var \Magento\Sitemap\Model\SitemapFactory|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $siteMapFactory;
+
+ /**
+ * @var \Magento\Backend\Model\Session|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $session;
protected function setUp()
{
+ $this->contextMock = $this->getMockBuilder(\Magento\Backend\App\Action\Context::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->requestMock = $this->getMockBuilder(\Magento\Framework\App\RequestInterface::class)
->disableOriginalConstructor()
->setMethods(['getPostValue'])
@@ -66,27 +98,48 @@ protected function setUp()
->getMock();
$this->messageManagerMock = $this->getMockBuilder(\Magento\Framework\Message\ManagerInterface::class)
->getMock();
-
+ $this->helper = $this->getMockBuilder(\Magento\Sitemap\Helper\Data::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->resultFactoryMock->expects($this->once())
->method('create')
->with(ResultFactory::TYPE_REDIRECT)
->willReturn($this->resultRedirectMock);
+ $this->session = $this->getMockBuilder(\Magento\Backend\Model\Session::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['setFormData'])
+ ->getMock();
- $this->objectManagerHelper = new ObjectManagerHelper($this);
- $this->context = $this->objectManagerHelper->getObject(
- \Magento\Backend\App\Action\Context::class,
- [
- 'resultFactory' => $this->resultFactoryMock,
- 'request' => $this->requestMock,
- 'messageManager' => $this->messageManagerMock,
- 'objectManager' => $this->objectManagerMock
- ]
- );
- $this->saveController = $this->objectManagerHelper->getObject(
- \Magento\Sitemap\Controller\Adminhtml\Sitemap\Save::class,
- [
- 'context' => $this->context
- ]
+ $this->contextMock->expects($this->once())
+ ->method('getMessageManager')
+ ->willReturn($this->messageManagerMock);
+ $this->contextMock->expects($this->once())
+ ->method('getRequest')
+ ->willReturn($this->requestMock);
+ $this->contextMock->expects($this->once())
+ ->method('getResultFactory')
+ ->willReturn($this->resultFactoryMock);
+ $this->contextMock->expects($this->once())
+ ->method('getSession')
+ ->willReturn($this->session);
+
+ $this->lengthValidator = $this->getMockBuilder(\Magento\Framework\Validator\StringLength::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->pathValidator =
+ $this->getMockBuilder(\Magento\MediaStorage\Model\File\Validator\AvailablePath::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->fileSystem = $this->createMock(\Magento\Framework\Filesystem::class);
+ $this->siteMapFactory = $this->createMock(\Magento\Sitemap\Model\SitemapFactory::class);
+
+ $this->saveController = new Save(
+ $this->contextMock,
+ $this->lengthValidator,
+ $this->pathValidator,
+ $this->helper,
+ $this->fileSystem,
+ $this->siteMapFactory
);
}
@@ -105,11 +158,8 @@ public function testSaveEmptyDataShouldRedirectToDefault()
public function testTryToSaveInvalidDataShouldFailWithErrors()
{
- $validatorClass = \Magento\MediaStorage\Model\File\Validator\AvailablePath::class;
- $helperClass = \Magento\Sitemap\Helper\Data::class;
$validPaths = [];
$messages = ['message1', 'message2'];
- $sessionClass = \Magento\Backend\Model\Session::class;
$data = ['sitemap_filename' => 'sitemap_filename', 'sitemap_path' => '/sitemap_path'];
$siteMapId = 1;
@@ -121,37 +171,83 @@ public function testTryToSaveInvalidDataShouldFailWithErrors()
->with('sitemap_id')
->willReturn($siteMapId);
- $validator = $this->createMock($validatorClass);
- $validator->expects($this->once())
+ $this->pathValidator->expects($this->once())
->method('setPaths')
->with($validPaths)
->willReturnSelf();
- $validator->expects($this->once())
+ $this->pathValidator->expects($this->once())
->method('isValid')
->with('/sitemap_path/sitemap_filename')
->willReturn(false);
- $validator->expects($this->once())
+ $this->pathValidator->expects($this->once())
->method('getMessages')
->willReturn($messages);
- $helper = $this->createMock($helperClass);
- $helper->expects($this->once())
+ $this->helper->expects($this->once())
->method('getValidPaths')
->willReturn($validPaths);
- $session = $this->createPartialMock($sessionClass, ['setFormData']);
- $session->expects($this->once())
+ $this->session->expects($this->once())
->method('setFormData')
->with($data)
->willReturnSelf();
- $this->objectManagerMock->expects($this->once())
- ->method('create')
- ->with($validatorClass)
- ->willReturn($validator);
- $this->objectManagerMock->expects($this->any())
- ->method('get')
- ->willReturnMap([[$helperClass, $helper], [$sessionClass, $session]]);
+ $this->messageManagerMock->expects($this->at(0))
+ ->method('addErrorMessage')
+ ->withConsecutive(
+ [$messages[0]],
+ [$messages[1]]
+ )
+ ->willReturnSelf();
+
+ $this->resultRedirectMock->expects($this->once())
+ ->method('setPath')
+ ->with('adminhtml/*/edit', ['sitemap_id' => $siteMapId])
+ ->willReturnSelf();
+
+ $this->assertSame($this->resultRedirectMock, $this->saveController->execute());
+ }
+
+ public function testTryToSaveInvalidFileNameShouldFailWithErrors()
+ {
+ $validPaths = [];
+ $messages = ['message1', 'message2'];
+ $data = ['sitemap_filename' => 'sitemap_filename', 'sitemap_path' => '/sitemap_path'];
+ $siteMapId = 1;
+
+ $this->requestMock->expects($this->once())
+ ->method('getPostValue')
+ ->willReturn($data);
+ $this->requestMock->expects($this->once())
+ ->method('getParam')
+ ->with('sitemap_id')
+ ->willReturn($siteMapId);
+
+ $this->lengthValidator->expects($this->once())
+ ->method('isValid')
+ ->with('sitemap_filename')
+ ->willReturn(false);
+ $this->lengthValidator->expects($this->once())
+ ->method('getMessages')
+ ->willReturn($messages);
+
+ $this->pathValidator->expects($this->once())
+ ->method('setPaths')
+ ->with($validPaths)
+ ->willReturnSelf();
+ $this->pathValidator->expects($this->once())
+ ->method('isValid')
+ ->with('/sitemap_path/sitemap_filename')
+ ->willReturn(true);
+
+ $this->helper->expects($this->once())
+ ->method('getValidPaths')
+ ->willReturn($validPaths);
+
+ $this->session->expects($this->once())
+ ->method('setFormData')
+ ->with($data)
+ ->willReturnSelf();
$this->messageManagerMock->expects($this->at(0))
->method('addErrorMessage')
diff --git a/app/code/Magento/Sitemap/Test/Unit/Model/EmailNotificationTest.php b/app/code/Magento/Sitemap/Test/Unit/Model/EmailNotificationTest.php
new file mode 100644
index 0000000000000..eafb47c086bac
--- /dev/null
+++ b/app/code/Magento/Sitemap/Test/Unit/Model/EmailNotificationTest.php
@@ -0,0 +1,134 @@
+objectManagerMock = $this->getMockBuilder(ObjectManagerInterface::class)
+ ->getMock();
+ $this->scopeConfigMock = $this->getMockBuilder(ScopeConfigInterface::class)
+ ->getMock();
+ $this->transportBuilderMock = $this->getMockBuilder(TransportBuilder::class)
+ ->disableOriginalConstructor()
+ ->getMock();
+ $this->inlineTranslationMock = $this->getMockBuilder(StateInterface::class)
+ ->getMock();
+
+ $this->objectManager = new ObjectManager($this);
+ $this->model = $this->objectManager->getObject(
+ EmailNotification::class,
+ [
+ 'inlineTranslation' => $this->inlineTranslationMock,
+ 'scopeConfig' => $this->scopeConfigMock,
+ 'transportBuilder' => $this->transportBuilderMock,
+ ]
+ );
+ }
+
+ public function testSendErrors()
+ {
+ $exception = 'Sitemap Exception';
+ $transport = $this->createMock(TransportInterface::class);
+
+ $this->scopeConfigMock->expects($this->at(0))
+ ->method('getValue')
+ ->with(
+ Observer::XML_PATH_ERROR_TEMPLATE,
+ ScopeInterface::SCOPE_STORE
+ )
+ ->willReturn('error-recipient@example.com');
+
+ $this->inlineTranslationMock->expects($this->once())
+ ->method('suspend');
+
+ $this->transportBuilderMock->expects($this->once())
+ ->method('setTemplateIdentifier')
+ ->will($this->returnSelf());
+
+ $this->transportBuilderMock->expects($this->once())
+ ->method('setTemplateOptions')
+ ->with([
+ 'area' => FrontNameResolver::AREA_CODE,
+ 'store' => Store::DEFAULT_STORE_ID,
+ ])
+ ->will($this->returnSelf());
+
+ $this->transportBuilderMock->expects($this->once())
+ ->method('setTemplateVars')
+ ->with(['warnings' => $exception])
+ ->will($this->returnSelf());
+
+ $this->transportBuilderMock->expects($this->once())
+ ->method('setFrom')
+ ->will($this->returnSelf());
+
+ $this->transportBuilderMock->expects($this->once())
+ ->method('addTo')
+ ->will($this->returnSelf());
+
+ $this->transportBuilderMock->expects($this->once())
+ ->method('getTransport')
+ ->willReturn($transport);
+
+ $transport->expects($this->once())
+ ->method('sendMessage');
+
+ $this->inlineTranslationMock->expects($this->once())
+ ->method('resume');
+
+ $this->model->sendErrors(['Sitemap Exception']);
+ }
+}
diff --git a/app/code/Magento/Sitemap/Test/Unit/Model/ObserverTest.php b/app/code/Magento/Sitemap/Test/Unit/Model/ObserverTest.php
index ac88f23ff9d69..09f5418bbd762 100644
--- a/app/code/Magento/Sitemap/Test/Unit/Model/ObserverTest.php
+++ b/app/code/Magento/Sitemap/Test/Unit/Model/ObserverTest.php
@@ -5,10 +5,14 @@
*/
namespace Magento\Sitemap\Test\Unit\Model;
+use Magento\Framework\App\Area;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Sitemap\Model\EmailNotification;
+use Magento\Store\Model\App\Emulation;
/**
* Class ObserverTest
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class ObserverTest extends \PHPUnit\Framework\TestCase
@@ -33,21 +37,6 @@ class ObserverTest extends \PHPUnit\Framework\TestCase
*/
private $collectionFactoryMock;
- /**
- * @var \Magento\Framework\Mail\Template\TransportBuilder|\PHPUnit_Framework_MockObject_MockObject
- */
- private $transportBuilderMock;
-
- /**
- * @var \Magento\Store\Model\StoreManagerInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $storeManagerMock;
-
- /**
- * @var \Magento\Framework\Translate\Inline\StateInterface|\PHPUnit_Framework_MockObject_MockObject
- */
- private $inlineTranslationMock;
-
/**
* @var \Magento\Sitemap\Model\ResourceModel\Sitemap\Collection|\PHPUnit_Framework_MockObject_MockObject
*/
@@ -63,6 +52,16 @@ class ObserverTest extends \PHPUnit\Framework\TestCase
*/
private $objectManagerMock;
+ /**
+ * @var Emulation|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $appEmulationMock;
+
+ /**
+ * @var EmailNotification|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $emailNotificationMock;
+
protected function setUp()
{
$this->objectManagerMock = $this->getMockBuilder(\Magento\Framework\ObjectManagerInterface::class)
@@ -74,28 +73,28 @@ protected function setUp()
)->disableOriginalConstructor()
->setMethods(['create'])
->getMock();
- $this->transportBuilderMock = $this->getMockBuilder(\Magento\Framework\Mail\Template\TransportBuilder::class)
- ->disableOriginalConstructor()
- ->getMock();
- $this->storeManagerMock = $this->getMockBuilder(\Magento\Store\Model\StoreManagerInterface::class)
- ->getMock();
- $this->inlineTranslationMock = $this->getMockBuilder(\Magento\Framework\Translate\Inline\StateInterface::class)
- ->getMock();
$this->sitemapCollectionMock = $this->createPartialMock(
\Magento\Sitemap\Model\ResourceModel\Sitemap\Collection::class,
['getIterator']
);
- $this->sitemapMock = $this->createPartialMock(\Magento\Sitemap\Model\Sitemap::class, ['generateXml']);
-
+ $this->sitemapMock = $this->createPartialMock(
+ \Magento\Sitemap\Model\Sitemap::class,
+ [
+ 'generateXml',
+ 'getStoreId',
+ ]
+ );
+ $this->appEmulationMock = $this->createMock(Emulation::class);
+ $this->emailNotificationMock = $this->createMock(EmailNotification::class);
$this->objectManager = new ObjectManager($this);
+
$this->observer = $this->objectManager->getObject(
\Magento\Sitemap\Model\Observer::class,
[
'scopeConfig' => $this->scopeConfigMock,
'collectionFactory' => $this->collectionFactoryMock,
- 'storeManager' => $this->storeManagerMock,
- 'transportBuilder' => $this->transportBuilderMock,
- 'inlineTranslation' => $this->inlineTranslationMock
+ 'appEmulation' => $this->appEmulationMock,
+ 'emailNotification' => $this->emailNotificationMock
]
);
}
@@ -103,7 +102,7 @@ protected function setUp()
public function testScheduledGenerateSitemapsSendsExceptionEmail()
{
$exception = 'Sitemap Exception';
- $transport = $this->createMock(\Magento\Framework\Mail\TransportInterface::class);
+ $storeId = 1;
$this->scopeConfigMock->expects($this->once())->method('isSetFlag')->willReturn(true);
@@ -115,11 +114,15 @@ public function testScheduledGenerateSitemapsSendsExceptionEmail()
->method('getIterator')
->willReturn(new \ArrayIterator([$this->sitemapMock]));
+ $this->sitemapMock->expects($this->at(0))
+ ->method('getStoreId')
+ ->willReturn($storeId);
+
$this->sitemapMock->expects($this->once())
->method('generateXml')
->willThrowException(new \Exception($exception));
- $this->scopeConfigMock->expects($this->at(1))
+ $this->scopeConfigMock->expects($this->at(0))
->method('getValue')
->with(
\Magento\Sitemap\Model\Observer::XML_PATH_ERROR_RECIPIENT,
@@ -127,44 +130,6 @@ public function testScheduledGenerateSitemapsSendsExceptionEmail()
)
->willReturn('error-recipient@example.com');
- $this->inlineTranslationMock->expects($this->once())
- ->method('suspend');
-
- $this->transportBuilderMock->expects($this->once())
- ->method('setTemplateIdentifier')
- ->will($this->returnSelf());
-
- $this->transportBuilderMock->expects($this->once())
- ->method('setTemplateOptions')
- ->with([
- 'area' => \Magento\Backend\App\Area\FrontNameResolver::AREA_CODE,
- 'store' => \Magento\Store\Model\Store::DEFAULT_STORE_ID,
- ])
- ->will($this->returnSelf());
-
- $this->transportBuilderMock->expects($this->once())
- ->method('setTemplateVars')
- ->with(['warnings' => $exception])
- ->will($this->returnSelf());
-
- $this->transportBuilderMock->expects($this->once())
- ->method('setFrom')
- ->will($this->returnSelf());
-
- $this->transportBuilderMock->expects($this->once())
- ->method('addTo')
- ->will($this->returnSelf());
-
- $this->transportBuilderMock->expects($this->once())
- ->method('getTransport')
- ->willReturn($transport);
-
- $transport->expects($this->once())
- ->method('sendMessage');
-
- $this->inlineTranslationMock->expects($this->once())
- ->method('resume');
-
$this->observer->scheduledGenerateSitemaps();
}
}
diff --git a/app/code/Magento/Store/Model/Group.php b/app/code/Magento/Store/Model/Group.php
index ccc3c65491422..19f104c9f3790 100644
--- a/app/code/Magento/Store/Model/Group.php
+++ b/app/code/Magento/Store/Model/Group.php
@@ -100,18 +100,24 @@ class Group extends \Magento\Framework\Model\AbstractExtensibleModel implements
*/
private $eventManager;
+ /**
+ * @var \Magento\MessageQueue\Api\PoisonPillPutInterface
+ */
+ private $pillPut;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
* @param \Magento\Framework\Api\ExtensionAttributesFactory $extensionFactory
* @param \Magento\Framework\Api\AttributeValueFactory $customAttributeFactory
* @param \Magento\Config\Model\ResourceModel\Config\Data $configDataResource
- * @param \Magento\Store\Model\Store $store
- * @param \Magento\Store\Model\StoreManagerInterface $storeManager
- * @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
- * @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
+ * @param ResourceModel\Store\CollectionFactory $storeListFactory
+ * @param StoreManagerInterface $storeManager
+ * @param \Magento\Framework\Model\ResourceModel\AbstractResource|null $resource
+ * @param \Magento\Framework\Data\Collection\AbstractDb|null $resourceCollection
* @param array $data
* @param \Magento\Framework\Event\ManagerInterface|null $eventManager
+ * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -125,13 +131,16 @@ public function __construct(
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
array $data = [],
- \Magento\Framework\Event\ManagerInterface $eventManager = null
+ \Magento\Framework\Event\ManagerInterface $eventManager = null,
+ \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null
) {
$this->_configDataResource = $configDataResource;
$this->_storeListFactory = $storeListFactory;
$this->_storeManager = $storeManager;
$this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\Event\ManagerInterface::class);
+ $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class);
parent::__construct(
$context,
$registry,
@@ -244,6 +253,8 @@ public function getStoreCodes()
}
/**
+ * Get stores count
+ *
* @return int
*/
public function getStoresCount()
@@ -349,6 +360,8 @@ public function isCanDelete()
}
/**
+ * Get default store id
+ *
* @return mixed
*/
public function getDefaultStoreId()
@@ -365,6 +378,8 @@ public function setDefaultStoreId($defaultStoreId)
}
/**
+ * Get root category id
+ *
* @return mixed
*/
public function getRootCategoryId()
@@ -381,6 +396,8 @@ public function setRootCategoryId($rootCategoryId)
}
/**
+ * Get website id
+ *
* @return mixed
*/
public function getWebsiteId()
@@ -397,7 +414,7 @@ public function setWebsiteId($websiteId)
}
/**
- * @return $this
+ * @inheritdoc
*/
public function beforeDelete()
{
@@ -445,6 +462,7 @@ public function afterSave()
$this->_storeManager->reinitStores();
$this->eventManager->dispatch($this->_eventPrefix . '_save', ['group' => $group]);
});
+ $this->pillPut->put();
return parent::afterSave();
}
@@ -473,7 +491,7 @@ public function getIdentities()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getName()
{
@@ -507,7 +525,7 @@ public function setCode($code)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getExtensionAttributes()
{
@@ -515,7 +533,7 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setExtensionAttributes(
\Magento\Store\Api\Data\GroupExtensionInterface $extensionAttributes
@@ -524,7 +542,7 @@ public function setExtensionAttributes(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.1.0
*/
public function getScopeType()
@@ -533,7 +551,7 @@ public function getScopeType()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.1.0
*/
public function getScopeTypeName()
diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php
index c1ad5bdcfc068..b2a515b198b11 100644
--- a/app/code/Magento/Store/Model/Store.php
+++ b/app/code/Magento/Store/Model/Store.php
@@ -326,6 +326,11 @@ class Store extends AbstractExtensibleModel implements
*/
private $eventManager;
+ /**
+ * @var \Magento\MessageQueue\Api\PoisonPillPutInterface
+ */
+ private $pillPut;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -352,6 +357,7 @@ class Store extends AbstractExtensibleModel implements
* @param bool $isCustomEntryPoint
* @param array $data optional generic object data
* @param \Magento\Framework\Event\ManagerInterface|null $eventManager
+ * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -380,7 +386,8 @@ public function __construct(
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
$isCustomEntryPoint = false,
array $data = [],
- \Magento\Framework\Event\ManagerInterface $eventManager = null
+ \Magento\Framework\Event\ManagerInterface $eventManager = null,
+ \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null
) {
$this->_coreFileStorageDatabase = $coreFileStorageDatabase;
$this->_config = $config;
@@ -401,6 +408,8 @@ public function __construct(
$this->websiteRepository = $websiteRepository;
$this->eventManager = $eventManager ?: \Magento\Framework\App\ObjectManager::getInstance()
->get(\Magento\Framework\Event\ManagerInterface::class);
+ $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class);
parent::__construct(
$context,
$registry,
@@ -1077,6 +1086,7 @@ public function afterSave()
$this->getResource()->addCommitCallback(function () use ($event, $store) {
$this->eventManager->dispatch($event, ['store' => $store]);
});
+ $this->pillPut->put();
return parent::afterSave();
}
diff --git a/app/code/Magento/Store/Model/Website.php b/app/code/Magento/Store/Model/Website.php
index c9a7d0013fe06..383b36fd63228 100644
--- a/app/code/Magento/Store/Model/Website.php
+++ b/app/code/Magento/Store/Model/Website.php
@@ -159,6 +159,11 @@ class Website extends \Magento\Framework\Model\AbstractExtensibleModel implement
*/
protected $_currencyFactory;
+ /**
+ * @var \Magento\MessageQueue\Api\PoisonPillPutInterface
+ */
+ private $pillPut;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -174,6 +179,7 @@ class Website extends \Magento\Framework\Model\AbstractExtensibleModel implement
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
+ * @param \Magento\MessageQueue\Api\PoisonPillPutInterface|null $pillPut
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
@@ -190,7 +196,8 @@ public function __construct(
\Magento\Directory\Model\CurrencyFactory $currencyFactory,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ \Magento\MessageQueue\Api\PoisonPillPutInterface $pillPut = null
) {
parent::__construct(
$context,
@@ -208,10 +215,12 @@ public function __construct(
$this->_websiteFactory = $websiteFactory;
$this->_storeManager = $storeManager;
$this->_currencyFactory = $currencyFactory;
+ $this->pillPut = $pillPut ?: \Magento\Framework\App\ObjectManager::getInstance()
+ ->get(\Magento\MessageQueue\Api\PoisonPillPutInterface::class);
}
/**
- * init model
+ * Init model
*
* @return void
*/
@@ -495,6 +504,8 @@ public function getWebsiteGroupStore()
}
/**
+ * Get default group id
+ *
* @return mixed
*/
public function getDefaultGroupId()
@@ -511,6 +522,8 @@ public function setDefaultGroupId($defaultGroupId)
}
/**
+ * Get code
+ *
* @return mixed
*/
public function getCode()
@@ -543,7 +556,7 @@ public function setName($name)
}
/**
- * @return $this
+ * @inheritdoc
*/
public function beforeDelete()
{
@@ -581,7 +594,7 @@ public function afterSave()
if ($this->isObjectNew()) {
$this->_storeManager->reinitStores();
}
-
+ $this->pillPut->put();
return parent::afterSave();
}
@@ -635,8 +648,7 @@ public function getDefaultStore()
}
/**
- * Retrieve default stores select object
- * Select fields website_id, store_id
+ * Retrieve default stores select object, select fields website_id, store_id
*
* @param bool $withDefault include/exclude default admin website
* @return \Magento\Framework\DB\Select
@@ -671,7 +683,7 @@ public function getIdentities()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.1.0
*/
public function getScopeType()
@@ -680,7 +692,7 @@ public function getScopeType()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @since 100.1.0
*/
public function getScopeTypeName()
@@ -689,7 +701,7 @@ public function getScopeTypeName()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getExtensionAttributes()
{
@@ -697,7 +709,7 @@ public function getExtensionAttributes()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function setExtensionAttributes(
\Magento\Store\Api\Data\WebsiteExtensionInterface $extensionAttributes
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml
index 91fe4fccddb91..7f1a63d3db6f2 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml
@@ -25,4 +25,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml
index ef8d77c8824ff..ca614ec24138c 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateWebsiteActionGroup.xml
@@ -36,4 +36,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml
index 58e1781d69eab..cf2cabdcc2399 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteStoreViewActionGroup.xml
@@ -26,4 +26,35 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml
index 58fd0a3f0bc2b..1721e3185402e 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminDeleteWebsiteActionGroup.xml
@@ -19,6 +19,7 @@
+
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml
index cfb2c7e6347c3..0960dfb47c368 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminSwitchWebsiteActionGroup.xml
@@ -11,6 +11,7 @@
+
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml
index 8e32b819aa954..8a1d830661aad 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomStoreActionGroup.xml
@@ -23,4 +23,33 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomWebsiteActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomWebsiteActionGroup.xml
index cc6a1fb62ea5f..da3ce02a80f28 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomWebsiteActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/DeleteCustomWebsiteActionGroup.xml
@@ -13,15 +13,14 @@
-
-
-
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreFrontProductValidationActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreFrontProductValidationActionGroup.xml
new file mode 100644
index 0000000000000..f11394c643ad7
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StoreFrontProductValidationActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml
index efd3a8c6b8cad..d30fc1e5a2a35 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/StorefrontSwitchStoreViewActionGroup.xml
@@ -17,4 +17,9 @@
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml
index f636336524f01..ae605256a2819 100644
--- a/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml
+++ b/app/code/Magento/Store/Test/Mftf/Data/WebsiteData.xml
@@ -23,4 +23,8 @@
Custom Website
custom_website
-
+
+ website_upd
+ code_upd
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteWebsiteSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteWebsiteSection.xml
index fea7dc07c8287..1bdf7f0c22c4e 100644
--- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteWebsiteSection.xml
+++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresDeleteWebsiteSection.xml
@@ -8,6 +8,6 @@
diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml
index b02e9adaed45e..d7006fd01b2ff 100644
--- a/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml
+++ b/app/code/Magento/Store/Test/Mftf/Section/AdminStoresGridSection.xml
@@ -21,5 +21,8 @@
+
+
+
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml b/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml
index 8004b750a4d1f..416808e58e6b3 100644
--- a/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml
+++ b/app/code/Magento/Store/Test/Mftf/Section/StorefrontHeaderSection.xml
@@ -13,5 +13,6 @@
+
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateWebsiteTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateWebsiteTest.xml
new file mode 100644
index 0000000000000..1608d0b7b5a25
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateWebsiteTest.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreGroupTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreGroupTest.xml
new file mode 100644
index 0000000000000..652537f7864cd
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreGroupTest.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml
new file mode 100644
index 0000000000000..fc1dcb5ee1a24
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteStoreViewTest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateWebsiteTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateWebsiteTest.xml
new file mode 100644
index 0000000000000..6b666126569ae
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminUpdateWebsiteTest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Store/composer.json b/app/code/Magento/Store/composer.json
index ebaa32b95f48b..da408f105ccb6 100644
--- a/app/code/Magento/Store/composer.json
+++ b/app/code/Magento/Store/composer.json
@@ -7,6 +7,7 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
+ "magento/module-message-queue": "*",
"magento/module-catalog": "*",
"magento/module-config": "*",
"magento/module-directory": "*",
diff --git a/app/code/Magento/Swatches/view/adminhtml/web/css/swatches.css b/app/code/Magento/Swatches/view/adminhtml/web/css/swatches.css
index ef635c48e3466..b0ea10b1ed968 100644
--- a/app/code/Magento/Swatches/view/adminhtml/web/css/swatches.css
+++ b/app/code/Magento/Swatches/view/adminhtml/web/css/swatches.css
@@ -153,6 +153,18 @@
min-width: 65px;
}
+[class^=swatch-col],
+[class^=col-]:not(.col-draggable):not(.col-default) {
+ min-width: 150px;
+}
+
+#swatch-visual-options-panel,
+#swatch-text-options-panel,
+#manage-options-panel {
+ overflow: auto;
+ width: 100%;
+}
+
.data-table .col-swatch-min-width input[type="text"] {
padding: inherit;
}
diff --git a/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml b/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml
index 3492f83fd1828..d817000f7bc46 100644
--- a/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml
+++ b/app/code/Magento/Swatches/view/frontend/templates/product/layered/renderer.phtml
@@ -17,7 +17,7 @@
-
+
@@ -32,10 +32,8 @@
- getSwatchPath('swatch_thumb',
- $swatchData['swatches'][$option]['value']); ?>
- getSwatchPath('swatch_image',
- $swatchData['swatches'][$option]['value']); ?>
+ getSwatchPath('swatch_thumb', $swatchData['swatches'][$option]['value']); ?>
+ getSwatchPath('swatch_image', $swatchData['swatches'][$option]['value']); ?>
= /* @escapeNotVerified */ $swatchData['swatches'][$option]['value'] ?>
-
+
diff --git a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
index bd611d0cc1863..424d691de5f11 100644
--- a/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
+++ b/app/code/Magento/Swatches/view/frontend/web/js/swatch-renderer.js
@@ -511,7 +511,7 @@ define([
// Add more button
if (moreLimit === countAttributes++) {
- html += '' + moreText + ' ';
+ html += '' + moreText + ' ';
}
id = this.id;
@@ -924,7 +924,8 @@ define([
$productPrice = $product.find(this.options.selectorProductPrice),
options = _.object(_.keys($widget.optionsMap), {}),
result,
- tierPriceHtml;
+ tierPriceHtml,
+ isShow;
$widget.element.find('.' + $widget.options.classes.attributeClass + '[option-selected]').each(function () {
var attributeId = $(this).attr('attribute-id');
@@ -941,11 +942,9 @@ define([
}
);
- if (typeof result != 'undefined' && result.oldPrice.amount !== result.finalPrice.amount) {
- $(this.options.slyOldPriceSelector).show();
- } else {
- $(this.options.slyOldPriceSelector).hide();
- }
+ isShow = typeof result != 'undefined' && result.oldPrice.amount !== result.finalPrice.amount;
+
+ $product.find(this.options.slyOldPriceSelector)[isShow ? 'show' : 'hide']();
if (typeof result != 'undefined' && result.tierPrices.length) {
if (this.options.tierPriceTemplate) {
@@ -1239,6 +1238,11 @@ define([
if (!_.isUndefined(gallery)) {
gallery.updateData(imagesToUpdate);
+ } else {
+ context.find(this.options.mediaGallerySelector).on('gallery:loaded', function (loadedGallery) {
+ loadedGallery = context.find(this.options.mediaGallerySelector).data('gallery');
+ loadedGallery.updateData(imagesToUpdate);
+ }.bind(this));
}
if (isInitial) {
@@ -1249,6 +1253,7 @@ define([
dataMergeStrategy: this.options.gallerySwitchStrategy
});
}
+
} else if (justAnImage && justAnImage.img) {
context.find('.product-image-photo').attr('src', justAnImage.img);
}
diff --git a/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php b/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php
index 87f65ef311ac2..208833733ae3f 100644
--- a/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php
+++ b/app/code/Magento/Tax/Plugin/Checkout/CustomerData/Cart.php
@@ -6,6 +6,10 @@
namespace Magento\Tax\Plugin\Checkout\CustomerData;
+/**
+ * Process quote items price, considering tax configuration.
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
+ */
class Cart
{
/**
@@ -68,6 +72,16 @@ public function afterGetSectionData(\Magento\Checkout\CustomerData\Cart $subject
$this->itemPriceRenderer->setItem($item);
$this->itemPriceRenderer->setTemplate('checkout/cart/item/price/sidebar.phtml');
$result['items'][$key]['product_price']=$this->itemPriceRenderer->toHtml();
+ if ($this->itemPriceRenderer->displayPriceExclTax()) {
+ $result['items'][$key]['product_price_value'] = $item->getCalculationPrice();
+ } elseif ($this->itemPriceRenderer->displayPriceInclTax()) {
+ $result['items'][$key]['product_price_value'] = $item->getPriceInclTax();
+ } elseif ($this->itemPriceRenderer->displayBothPrices()) {
+ //unset product price value in case price already has been set as scalar value.
+ unset($result['items'][$key]['product_price_value']);
+ $result['items'][$key]['product_price_value']['incl_tax'] = $item->getPriceInclTax();
+ $result['items'][$key]['product_price_value']['excl_tax'] = $item->getCalculationPrice();
+ }
}
}
}
diff --git a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml
index 4741898b0ab86..628d189823a52 100644
--- a/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml
+++ b/app/code/Magento/Tax/Test/Mftf/Test/AdminTaxReportGridTest.xml
@@ -136,9 +136,8 @@
-
-
+
diff --git a/app/code/Magento/Tax/Test/Unit/Model/Calculation/RateRepositoryTest.php b/app/code/Magento/Tax/Test/Unit/Model/Calculation/RateRepositoryTest.php
index bf49f3d479132..77da6950fecf7 100644
--- a/app/code/Magento/Tax/Test/Unit/Model/Calculation/RateRepositoryTest.php
+++ b/app/code/Magento/Tax/Test/Unit/Model/Calculation/RateRepositoryTest.php
@@ -252,7 +252,7 @@ private function getTaxRateMock(array $taxRateData)
foreach ($taxRateData as $key => $value) {
// convert key from snake case to upper case
$taxRateMock->expects($this->any())
- ->method('get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key))))
+ ->method('get' . str_replace('_', '', ucwords($key, '_')))
->will($this->returnValue($value));
}
diff --git a/app/code/Magento/Tax/Test/Unit/Model/Sales/Total/Quote/ShippingTest.php b/app/code/Magento/Tax/Test/Unit/Model/Sales/Total/Quote/ShippingTest.php
index 77e25d6f14574..2bfebc984bb81 100644
--- a/app/code/Magento/Tax/Test/Unit/Model/Sales/Total/Quote/ShippingTest.php
+++ b/app/code/Magento/Tax/Test/Unit/Model/Sales/Total/Quote/ShippingTest.php
@@ -133,7 +133,7 @@ private function getMockObject($className, array $objectState)
$getterValueMap = [];
$methods = ['__wakeup'];
foreach ($objectState as $key => $value) {
- $getterName = 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $key)));
+ $getterName = 'get' . str_replace('_', '', ucwords($key, '_'));
$getterValueMap[$getterName] = $value;
$methods[] = $getterName;
}
diff --git a/app/code/Magento/Tax/etc/di.xml b/app/code/Magento/Tax/etc/di.xml
index 096f8359fadd3..3b46b0f9e258c 100644
--- a/app/code/Magento/Tax/etc/di.xml
+++ b/app/code/Magento/Tax/etc/di.xml
@@ -143,6 +143,7 @@
- tax_calculation_rule_id
+ - main_table.code
- tax_calculation_rate_id
- cd.customer_tax_class_id
- cd.product_tax_class_id
@@ -154,6 +155,7 @@
- tax_calculation_rule_id
+ - main_table.code
- tax_calculation_rate_id
- cd.customer_tax_class_id
- cd.product_tax_class_id
diff --git a/app/code/Magento/Tax/view/adminhtml/templates/class/page/edit.phtml b/app/code/Magento/Tax/view/adminhtml/templates/class/page/edit.phtml
deleted file mode 100644
index 18e86549a1ff9..0000000000000
--- a/app/code/Magento/Tax/view/adminhtml/templates/class/page/edit.phtml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
- = $block->getBackButtonHtml() ?>
- = $block->getResetButtonHtml() ?>
- = $block->getDeleteButtonHtml() ?>
- = $block->getSaveButtonHtml() ?>
-
-= $block->getRenameFormHtml() ?>
-
diff --git a/app/code/Magento/Tax/view/adminhtml/web/js/page/validate.js b/app/code/Magento/Tax/view/adminhtml/web/js/page/validate.js
deleted file mode 100644
index a49f199ba56b6..0000000000000
--- a/app/code/Magento/Tax/view/adminhtml/web/js/page/validate.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/**
- * Copyright © Magento, Inc. All rights reserved.
- * See COPYING.txt for license details.
- */
-
-define([
- 'jquery',
- 'mage/mage'
-], function (jQuery) {
- 'use strict';
-
- return function (data, element) {
- jQuery(element).mage('form').mage('validation');
- };
-});
diff --git a/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/tax.js b/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/tax.js
index b21be98531ba9..2b1f387f5c8c4 100644
--- a/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/tax.js
+++ b/app/code/Magento/Tax/view/frontend/web/js/view/checkout/summary/tax.js
@@ -12,13 +12,16 @@ define([
'Magento_Checkout/js/view/summary/abstract-total',
'Magento_Checkout/js/model/quote',
'Magento_Checkout/js/model/totals',
- 'mage/translate'
-], function (ko, Component, quote, totals, $t) {
+ 'mage/translate',
+ 'underscore'
+], function (ko, Component, quote, totals, $t, _) {
'use strict';
var isTaxDisplayedInGrandTotal = window.checkoutConfig.includeTaxInGrandTotal,
isFullTaxSummaryDisplayed = window.checkoutConfig.isFullTaxSummaryDisplayed,
- isZeroTaxDisplayed = window.checkoutConfig.isZeroTaxDisplayed;
+ isZeroTaxDisplayed = window.checkoutConfig.isZeroTaxDisplayed,
+ taxAmount = 0,
+ rates = 0;
return Component.extend({
defaults: {
@@ -98,6 +101,33 @@ define([
return this.getFormattedPrice(amount);
},
+ /**
+ * @param {*} parent
+ * @param {*} percentage
+ * @return {*|String}
+ */
+ getTaxAmount: function (parent, percentage) {
+ var totalPercentage = 0;
+
+ taxAmount = parent.amount;
+ rates = parent.rates;
+ _.each(rates, function (rate) {
+ totalPercentage += parseFloat(rate.percent);
+ });
+
+ return this.getFormattedPrice(this.getPercentAmount(taxAmount, totalPercentage, percentage));
+ },
+
+ /**
+ * @param {*} amount
+ * @param {*} totalPercentage
+ * @param {*} percentage
+ * @return {*|String}
+ */
+ getPercentAmount: function (amount, totalPercentage, percentage) {
+ return parseFloat(amount * percentage / totalPercentage);
+ },
+
/**
* @return {Array}
*/
diff --git a/app/code/Magento/Tax/view/frontend/web/template/checkout/cart/totals/tax.html b/app/code/Magento/Tax/view/frontend/web/template/checkout/cart/totals/tax.html
index 9c45e73db6fa4..45c468096abe1 100644
--- a/app/code/Magento/Tax/view/frontend/web/template/checkout/cart/totals/tax.html
+++ b/app/code/Magento/Tax/view/frontend/web/template/checkout/cart/totals/tax.html
@@ -32,18 +32,16 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Tax/view/frontend/web/template/checkout/summary/tax.html b/app/code/Magento/Tax/view/frontend/web/template/checkout/summary/tax.html
index 0f2e3251bcfdb..5f1ac86e38ffd 100644
--- a/app/code/Magento/Tax/view/frontend/web/template/checkout/summary/tax.html
+++ b/app/code/Magento/Tax/view/frontend/web/template/checkout/summary/tax.html
@@ -43,18 +43,16 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
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/Tinymce3/view/base/web/tiny_mce/classes/Formatter.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/Formatter.js
index f74282afd32a6..b7266779d3a35 100644
--- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/Formatter.js
+++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/classes/Formatter.js
@@ -1970,7 +1970,7 @@
node.appendChild(dom.doc.createTextNode(invisibleChar));
node = node.firstChild;
- // Insert caret container after the formated node
+ // Insert caret container after the formatted node
dom.insertAfter(caretContainer, formatNode);
// Move selection to text node
diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js
index 2d9d859caa6fa..60dd358414be1 100644
--- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js
+++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_jquery_src.js
@@ -15796,7 +15796,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
node.appendChild(dom.doc.createTextNode(invisibleChar));
node = node.firstChild;
- // Insert caret container after the formated node
+ // Insert caret container after the formatted node
dom.insertAfter(caretContainer, formatNode);
// Move selection to text node
diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js
index aaa207da3e4a9..ed0b7cb0e50a2 100644
--- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js
+++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_prototype_src.js
@@ -16646,7 +16646,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
node.appendChild(dom.doc.createTextNode(invisibleChar));
node = node.firstChild;
- // Insert caret container after the formated node
+ // Insert caret container after the formatted node
dom.insertAfter(caretContainer, formatNode);
// Move selection to text node
diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js
index 8448152ed5d2f..1c53062dd9690 100644
--- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js
+++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/tiny_mce_src.js
@@ -16620,7 +16620,7 @@ tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
node.appendChild(dom.doc.createTextNode(invisibleChar));
node = node.firstChild;
- // Insert caret container after the formated node
+ // Insert caret container after the formatted node
dom.insertAfter(caretContainer, formatNode);
// Move selection to text node
diff --git a/app/code/Magento/Ui/Component/MassAction.php b/app/code/Magento/Ui/Component/MassAction.php
index ea39e19a65f52..4cca8d4c012bb 100644
--- a/app/code/Magento/Ui/Component/MassAction.php
+++ b/app/code/Magento/Ui/Component/MassAction.php
@@ -6,6 +6,8 @@
namespace Magento\Ui\Component;
/**
+ * Mass action UI component.
+ *
* @api
* @since 100.0.2
*/
@@ -21,7 +23,12 @@ public function prepare()
$config = $this->getConfiguration();
foreach ($this->getChildComponents() as $actionComponent) {
- $config['actions'][] = $actionComponent->getConfiguration();
+ $componentConfig = $actionComponent->getConfiguration();
+ $disabledAction = $componentConfig['actionDisable'] ?? false;
+ if ($disabledAction) {
+ continue;
+ }
+ $config['actions'][] = $componentConfig;
}
$origConfig = $this->getConfiguration();
diff --git a/app/code/Magento/Ui/Component/Wysiwyg/Config.php b/app/code/Magento/Ui/Component/Wysiwyg/Config.php
index 48014a0160c41..d88a255927876 100644
--- a/app/code/Magento/Ui/Component/Wysiwyg/Config.php
+++ b/app/code/Magento/Ui/Component/Wysiwyg/Config.php
@@ -13,7 +13,7 @@ class Config implements ConfigInterface
/**
* Return WYSIWYG configuration
*
- * @return \Magento\Framework\DataObject
+ * @return array
*/
public function getConfig()
{
diff --git a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php
index b983e56b8aee2..b06c655939b1c 100644
--- a/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php
+++ b/app/code/Magento/Ui/Controller/Adminhtml/Index/Render.php
@@ -86,6 +86,18 @@ public function execute()
$contentType = $this->contentTypeResolver->resolve($component->getContext());
$this->getResponse()->setHeader('Content-Type', $contentType, true);
+ } else {
+ /** @var \Magento\Framework\Controller\Result\Json $resultJson */
+ $resultJson = $this->resultJsonFactory->create();
+ $resultJson->setStatusHeader(
+ \Zend\Http\Response::STATUS_CODE_403,
+ \Zend\Http\AbstractMessage::VERSION_11,
+ 'Forbidden'
+ );
+ return $resultJson->setData([
+ 'error' => $this->escaper->escapeHtml('Forbidden'),
+ 'errorcode' => 403
+ ]);
}
} catch (\Magento\Framework\Exception\LocalizedException $e) {
$this->logger->critical($e);
diff --git a/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml b/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml
index 75d445f1ee04e..3d4efa13ce3a0 100644
--- a/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml
+++ b/app/code/Magento/Ui/Test/Mftf/Section/AdminMessagesSection.xml
@@ -12,5 +12,6 @@
+
diff --git a/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php b/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php
index 05b35fb017b4b..2bba8686490b6 100644
--- a/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php
+++ b/app/code/Magento/Ui/Test/Unit/Controller/Adminhtml/Index/RenderTest.php
@@ -3,12 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Ui\Test\Unit\Controller\Adminhtml\Index;
+use Magento\Framework\Controller\Result\Json;
+use Magento\Framework\Escaper;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use Magento\Framework\View\Element\UiComponent\ContextInterface;
use Magento\Ui\Controller\Adminhtml\Index\Render;
use Magento\Ui\Model\UiComponentTypeResolver;
-use Magento\Framework\View\Element\UiComponent\ContextInterface;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager as ObjectManagerHelper;
+use Zend\Http\AbstractMessage;
+use Zend\Http\Response;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -97,6 +102,11 @@ class RenderTest extends \PHPUnit\Framework\TestCase
*/
private $loggerMock;
+ /**
+ * @var Escaper|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $escaperMock;
+
protected function setUp()
{
$this->requestMock = $this->getMockBuilder(\Magento\Framework\App\Request\Http::class)
@@ -170,6 +180,10 @@ protected function setUp()
$this->uiComponentTypeResolverMock = $this->getMockBuilder(UiComponentTypeResolver::class)
->disableOriginalConstructor()
->getMock();
+ $this->escaperMock = $this->createMock(Escaper::class);
+ $this->escaperMock->expects($this->any())
+ ->method('escapeHtml')
+ ->willReturnArgument(0);
$this->objectManagerHelper = new ObjectManagerHelper($this);
@@ -181,6 +195,7 @@ protected function setUp()
'contentTypeResolver' => $this->uiComponentTypeResolverMock,
'resultJsonFactory' => $this->resultJsonFactoryMock,
'logger' => $this->loggerMock,
+ 'escaper' => $this->escaperMock,
]
);
}
@@ -201,7 +216,7 @@ public function testExecuteAjaxRequestException()
->method('appendBody')
->willThrowException(new \Exception('exception'));
- $jsonResultMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class)
+ $jsonResultMock = $this->getMockBuilder(Json::class)
->disableOriginalConstructor()
->setMethods(['setData'])
->getMock();
@@ -290,6 +305,34 @@ public function testExecuteAjaxRequestWithoutPermissions(array $dataProviderConf
$name = 'test-name';
$renderedData = 'data';
+ if (false === $isAllowed) {
+ $jsonResultMock = $this->getMockBuilder(Json::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['setStatusHeader', 'setData'])
+ ->getMock();
+
+ $jsonResultMock->expects($this->at(0))
+ ->method('setStatusHeader')
+ ->with(
+ Response::STATUS_CODE_403,
+ AbstractMessage::VERSION_11,
+ 'Forbidden'
+ )
+ ->willReturnSelf();
+
+ $jsonResultMock->expects($this->at(1))
+ ->method('setData')
+ ->with([
+ 'error' => 'Forbidden',
+ 'errorcode' => 403
+ ])
+ ->willReturnSelf();
+
+ $this->resultJsonFactoryMock->expects($this->any())
+ ->method('create')
+ ->willReturn($jsonResultMock);
+ }
+
$this->requestMock->expects($this->any())
->method('getParam')
->with('namespace')
diff --git a/app/code/Magento/Ui/i18n/en_US.csv b/app/code/Magento/Ui/i18n/en_US.csv
index d8021c3aa8087..1ce2692886ae3 100644
--- a/app/code/Magento/Ui/i18n/en_US.csv
+++ b/app/code/Magento/Ui/i18n/en_US.csv
@@ -191,4 +191,5 @@ CSV,CSV
"Please enter at least {0} characters.","Please enter at least {0} characters."
"Please enter a value between {0} and {1} characters long.","Please enter a value between {0} and {1} characters long."
"Please enter a value between {0} and {1}.","Please enter a value between {0} and {1}."
-"was not uploaded","was not uploaded"
\ No newline at end of file
+"was not uploaded","was not uploaded"
+"The file upload field is disabled.","The file upload field is disabled."
diff --git a/app/code/Magento/Ui/view/base/templates/stepswizard.phtml b/app/code/Magento/Ui/view/base/templates/stepswizard.phtml
index 05a537b9a6559..78e73e0cd9a69 100644
--- a/app/code/Magento/Ui/view/base/templates/stepswizard.phtml
+++ b/app/code/Magento/Ui/view/base/templates/stepswizard.phtml
@@ -13,14 +13,14 @@
- getSteps() as $step) { ?>
+ getSteps() as $step): ?>
= /* @escapeNotVerified */ $step->getContent() ?>
-
+
diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition.map.xml b/app/code/Magento/Ui/view/base/ui_component/etc/definition.map.xml
index ccd702c23ea65..8f82b98112f18 100644
--- a/app/code/Magento/Ui/view/base/ui_component/etc/definition.map.xml
+++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition.map.xml
@@ -14,6 +14,7 @@
- settings/label
- settings/type
- settings/url
+ - settings/actionDisable
-
- settings/confirm/title
- settings/confirm/message
diff --git a/app/code/Magento/Ui/view/base/ui_component/etc/definition/action.xsd b/app/code/Magento/Ui/view/base/ui_component/etc/definition/action.xsd
index b10ee00818ebc..4dc97910935d6 100644
--- a/app/code/Magento/Ui/view/base/ui_component/etc/definition/action.xsd
+++ b/app/code/Magento/Ui/view/base/ui_component/etc/definition/action.xsd
@@ -49,6 +49,13 @@
+
+
+
+ Disable and remove this action.
+
+
+
@@ -82,6 +89,13 @@
+
+
+
+ Disable and remove this action.
+
+
+
diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js
index 1d52fc78d7a85..cbbfbdb127ad7 100644
--- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js
+++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/dynamic-rows.js
@@ -543,6 +543,7 @@ define([
data = this.createHeaderTemplate(cell.config);
cell.config.labelVisible = false;
_.extend(data, {
+ defaultLabelVisible: data.visible(),
label: cell.config.label,
name: cell.name,
required: !!cell.config.validation,
diff --git a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/record.js b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/record.js
index 3987507ece54f..9a9d478904775 100644
--- a/app/code/Magento/Ui/view/base/web/js/dynamic-rows/record.js
+++ b/app/code/Magento/Ui/view/base/web/js/dynamic-rows/record.js
@@ -245,7 +245,7 @@ define([
label = _.findWhere(this.parentComponent().labels(), {
name: index
});
- label.visible() !== state ? label.visible(state) : false;
+ label.defaultLabelVisible && label.visible(state);
} else {
elems[curElem].visible(state);
}
diff --git a/app/code/Magento/Ui/view/base/web/js/form/components/button.js b/app/code/Magento/Ui/view/base/web/js/form/components/button.js
index df85af5824d92..bfc2eb2b8852b 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/components/button.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/components/button.js
@@ -45,7 +45,8 @@ define([
.observe([
'visible',
'disabled',
- 'title'
+ 'title',
+ 'childError'
]);
},
diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/date.js b/app/code/Magento/Ui/view/base/web/js/form/element/date.js
index a5eb7d5d1f570..4e532c9d48cc6 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/element/date.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/element/date.js
@@ -122,10 +122,12 @@ define([
shiftedValue = moment.tz(value, 'UTC').tz(this.storeTimeZone);
} else {
dateFormat = this.shiftedValue() ? this.outputDateFormat : this.inputDateFormat;
-
shiftedValue = moment(value, dateFormat);
}
+ if (!shiftedValue.isValid()) {
+ shiftedValue = moment(value, this.inputDateFormat);
+ }
shiftedValue = shiftedValue.format(this.pickerDateTimeFormat);
} else {
shiftedValue = '';
diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js
index 357571350a268..f28569caa0053 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/element/file-uploader.js
@@ -16,7 +16,8 @@ define([
'Magento_Ui/js/form/element/abstract',
'mage/backend/notification',
'mage/translate',
- 'jquery/file-uploader'
+ 'jquery/file-uploader',
+ 'mage/adminhtml/tools'
], function ($, _, utils, uiAlert, validator, Element, notification, $t) {
'use strict';
@@ -348,6 +349,12 @@ define([
allowed = this.isFileAllowed(file),
target = $(e.target);
+ if (this.disabled()) {
+ this.notifyError($t('The file upload field is disabled.'));
+
+ return;
+ }
+
if (allowed.passed) {
target.on('fileuploadsend', function (event, postData) {
postData.data.append('param_name', this.paramName);
diff --git a/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js b/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js
index 1b6dd9f1c57ec..0eaacdc32567b 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/element/post-code.js
@@ -20,6 +20,26 @@ define([
}
},
+ /**
+ * Initializes observable properties of instance
+ *
+ * @returns {Abstract} Chainable.
+ */
+ initObservable: function () {
+ this._super();
+
+ /**
+ * equalityComparer function
+ *
+ * @returns boolean.
+ */
+ this.value.equalityComparer = function (oldValue, newValue) {
+ return !oldValue && !newValue || oldValue === newValue;
+ };
+
+ return this;
+ },
+
/**
* @param {String} value
*/
diff --git a/app/code/Magento/Ui/view/base/web/js/grid/data-storage.js b/app/code/Magento/Ui/view/base/web/js/grid/data-storage.js
index dad67da3ea8ad..547cdab16cdf1 100644
--- a/app/code/Magento/Ui/view/base/web/js/grid/data-storage.js
+++ b/app/code/Magento/Ui/view/base/web/js/grid/data-storage.js
@@ -199,7 +199,7 @@ define([
},
/**
- * Caches requests object with provdided parameters
+ * Caches requests object with provided parameters
* and data object associated with it.
*
* @param {Object} data - Data associated with request.
diff --git a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
index be8fd2ce9fcef..8ebbf88775b86 100644
--- a/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
+++ b/app/code/Magento/Ui/view/base/web/js/lib/validation/validator.js
@@ -48,6 +48,10 @@ define([
params :
[params];
+ if (typeof message === 'function') {
+ message = message.call(rule);
+ }
+
message = params.reduce(function (msg, param, idx) {
return msg.replace(new RegExp('\\{' + idx + '\\}', 'g'), param);
}, message);
diff --git a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js
index 77de8a1ceb0ed..48515b668f80d 100644
--- a/app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js
+++ b/app/code/Magento/Ui/view/base/web/js/lib/view/utils/bindings.js
@@ -89,7 +89,7 @@ define([
/**
* Adds specified bindings to each DOM element in
- * collection and evalutes them with provided context.
+ * collection and evaluates them with provided context.
*
* @param {(Object|Function)} data - Either bindings object or a function
* which returns bindings data for each element in collection.
diff --git a/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/default.html b/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/default.html
index 1a21e1b2f1c71..6da4f82fa8b9e 100644
--- a/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/default.html
+++ b/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/default.html
@@ -41,7 +41,7 @@
diff --git a/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/grid.html b/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/grid.html
index d0b12549bd66d..e5d73a62b329e 100644
--- a/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/grid.html
+++ b/app/code/Magento/Ui/view/base/web/templates/dynamic-rows/templates/grid.html
@@ -58,7 +58,7 @@
+
+
+
+
+
+
+ This element contains invalid data. Please resolve this before saving.
+
+
+
+
diff --git a/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/uploader.html b/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/uploader.html
index a92b85cb47401..cf4e2243b5886 100644
--- a/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/uploader.html
+++ b/app/code/Magento/Ui/view/base/web/templates/form/element/uploader/uploader.html
@@ -6,7 +6,7 @@
-->
-
+
diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html
index 56244422a6b43..1ad0e7505ec9d 100644
--- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html
+++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select-optgroup.html
@@ -19,7 +19,7 @@
css: {
_selected: $parent.root.isSelected(option.value),
_hover: $parent.root.isHovered(option, $element),
- _expended: $parent.root.getLevelVisibility($data),
+ _expended: $parent.root.getLevelVisibility($data) || $data.visible,
_unclickable: $parent.root.isLabelDecoration($data),
_last: $parent.root.addLastElement($data),
'_with-checkbox': $parent.root.showCheckbox
diff --git a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html
index 82205de4156ad..b9425c020c0e9 100644
--- a/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html
+++ b/app/code/Magento/Ui/view/base/web/templates/grid/filters/elements/ui-select.html
@@ -160,7 +160,7 @@
css: {
_selected: $parent.isSelectedValue(option),
_hover: $parent.isHovered(option, $element),
- _expended: $parent.getLevelVisibility($data),
+ _expended: $parent.getLevelVisibility($data) && $parent.showLevels($data),
_unclickable: $parent.isLabelDecoration($data),
_last: $parent.addLastElement($data),
'_with-checkbox': $parent.showCheckbox
@@ -174,6 +174,7 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AdminUrlRewriteGridActionGroup.xml b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AdminUrlRewriteGridActionGroup.xml
new file mode 100644
index 0000000000000..1a9248ef36789
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/AdminUrlRewriteGridActionGroup.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/StorefrontUrlRewriteRedirectActionGroup.xml b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/StorefrontUrlRewriteRedirectActionGroup.xml
new file mode 100644
index 0000000000000..a299e1689d6a7
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/ActionGroup/StorefrontUrlRewriteRedirectActionGroup.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Data/UrlRewriteData.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Data/UrlRewriteData.xml
new file mode 100644
index 0000000000000..3692e82072afc
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Data/UrlRewriteData.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+ test-test-test.html
+ http://www.example.com/
+ 302
+ Temporary (302)
+ 1
+ Default Store View
+ End To End Test
+
+
+ test-aspx-test.aspx
+ http://www.example.com/
+ 302
+ Temporary (302)
+ 1
+ Default Store View
+ Update Url Rewrite
+
+
+ wishlist
+ https://marketplace.magento.com/
+ 301
+ Permanent (301)
+ 1
+ Default Store View
+ test_description_relative path
+
+
+ wishlist
+ https://marketplace.magento.com/
+ 302
+ Temporary (302)
+ 1
+ Default Store View
+ test_description_relative path
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml b/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml
new file mode 100644
index 0000000000000..0738b17d6e0f0
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/MetaData/url_rewrite-meta.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ integer
+ integer
+ string
+ string
+ string
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Metadata/url_rewrite-meta.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Metadata/url_rewrite-meta.xml
new file mode 100644
index 0000000000000..0738b17d6e0f0
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Metadata/url_rewrite-meta.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+ integer
+ integer
+ string
+ string
+ string
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteEditPage.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteEditPage.xml
new file mode 100644
index 0000000000000..b43e0e05ad55d
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteEditPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteProductPage.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteProductPage.xml
new file mode 100644
index 0000000000000..645396bc778e9
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Page/AdminUrlRewriteProductPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteEditSection.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteEditSection.xml
new file mode 100644
index 0000000000000..52939607f5377
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteEditSection.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml
index 7c21acdf943ba..7b789845fe249 100644
--- a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteIndexSection.xml
@@ -19,5 +19,8 @@
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteProductSection.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteProductSection.xml
new file mode 100644
index 0000000000000..3650ec56d1391
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Section/AdminUrlRewriteProductSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminAutoUpdateURLRewriteWhenCategoryIsDeletedTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminAutoUpdateURLRewriteWhenCategoryIsDeletedTest.xml
new file mode 100644
index 0000000000000..52d313b21f3e1
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminAutoUpdateURLRewriteWhenCategoryIsDeletedTest.xml
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml
new file mode 100644
index 0000000000000..ee3682c8ad4d7
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCheckUrlRewritesInCatalogCategoriesAfterChangingUrlKeyForStoreViewAndMovingCategoryTest.xml
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddNoRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddNoRedirectTest.xml
new file mode 100644
index 0000000000000..a7a7c0c73d826
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddNoRedirectTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddPermanentRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddPermanentRedirectTest.xml
new file mode 100644
index 0000000000000..974550bb92214
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddPermanentRedirectTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml
new file mode 100644
index 0000000000000..c64019ea38acc
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCMSPageUrlRewriteAndAddPermanentRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCMSPageUrlRewriteAndAddPermanentRedirectTest.xml
new file mode 100644
index 0000000000000..358aa58aba0f7
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCMSPageUrlRewriteAndAddPermanentRedirectTest.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCMSPageUrlRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCMSPageUrlRewriteAndAddTemporaryRedirectTest.xml
new file mode 100644
index 0000000000000..e6ee9b484059d
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCMSPageUrlRewriteAndAddTemporaryRedirectTest.xml
@@ -0,0 +1,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCategoryUrlRewriteAndAddPermanentRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCategoryUrlRewriteAndAddPermanentRedirectTest.xml
new file mode 100644
index 0000000000000..b123bc14cb1ed
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomCategoryUrlRewriteAndAddPermanentRedirectTest.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomProductUrlRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomProductUrlRewriteAndAddTemporaryRedirectTest.xml
new file mode 100644
index 0000000000000..711d5389b013b
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateCustomProductUrlRewriteAndAddTemporaryRedirectTest.xml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductURLRewriteAndAddNoRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductURLRewriteAndAddNoRedirectTest.xml
new file mode 100644
index 0000000000000..f8d297c92a176
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductURLRewriteAndAddNoRedirectTest.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductURLRewriteWithCategoryAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductURLRewriteWithCategoryAndAddTemporaryRedirectTest.xml
new file mode 100644
index 0000000000000..ae18ab33ba6ce
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductURLRewriteWithCategoryAndAddTemporaryRedirectTest.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddPermanentRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddPermanentRedirectTest.xml
new file mode 100644
index 0000000000000..66c586d4fe891
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddPermanentRedirectTest.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddTemporaryRedirectTest.xml
new file mode 100644
index 0000000000000..2d797a12bedf5
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductUrLRewriteAndAddTemporaryRedirectTest.xml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductWithSeveralWebsitesAndCheckURLRewritesTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductWithSeveralWebsitesAndCheckURLRewritesTest.xml
new file mode 100644
index 0000000000000..83c1e5c0a5e0a
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminCreateProductWithSeveralWebsitesAndCheckURLRewritesTest.xml
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCustomUrlRewriteTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCustomUrlRewriteTest.xml
new file mode 100644
index 0000000000000..cf45931029778
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminDeleteCustomUrlRewriteTest.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddAspxRequestPathTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddAspxRequestPathTest.xml
new file mode 100644
index 0000000000000..072753505223d
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddAspxRequestPathTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddNoRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddNoRedirectTest.xml
new file mode 100644
index 0000000000000..80b9dbe41bf59
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddNoRedirectTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddPermanentRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddPermanentRedirectTest.xml
new file mode 100644
index 0000000000000..be9fd1d83c8f1
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddPermanentRedirectTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml
new file mode 100644
index 0000000000000..7453b7b5a43f3
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCategoryUrlRewriteAndAddTemporaryRedirectTest.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCustomURLRewritesPermanentTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCustomURLRewritesPermanentTest.xml
new file mode 100644
index 0000000000000..8339eb63abef1
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCustomURLRewritesPermanentTest.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCustomURLRewritesTemporaryTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCustomURLRewritesTemporaryTest.xml
new file mode 100644
index 0000000000000..07d578cbbeca4
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateCustomURLRewritesTemporaryTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateProductUrlRewriteAndAddTemporaryRedirectTest.xml b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateProductUrlRewriteAndAddTemporaryRedirectTest.xml
new file mode 100644
index 0000000000000..ea370d8419583
--- /dev/null
+++ b/app/code/Magento/UrlRewrite/Test/Mftf/Test/AdminUpdateProductUrlRewriteAndAddTemporaryRedirectTest.xml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/EntityUrl.php b/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/EntityUrl.php
index 1c25ffd1e9ff7..c842d660a6176 100644
--- a/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/EntityUrl.php
+++ b/app/code/Magento/UrlRewriteGraphQl/Model/Resolver/EntityUrl.php
@@ -76,6 +76,7 @@ public function resolve(
$result = [
'id' => $urlRewrite->getEntityId(),
'canonical_url' => $urlRewrite->getTargetPath(),
+ 'relative_url' => $urlRewrite->getTargetPath(),
'type' => $this->sanitizeType($urlRewrite->getEntityType())
];
}
diff --git a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls
index dae695c69a33c..5aea482a0fe02 100644
--- a/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/UrlRewriteGraphQl/etc/schema.graphqls
@@ -1,16 +1,17 @@
# Copyright © Magento, Inc. All rights reserved.
# See COPYING.txt for license details.
-type EntityUrl @doc(description: "EntityUrl is an output object containing the `id`, `canonical_url`, and `type` attributes") {
- id: Int @doc(description: "The ID assigned to the object associated with the specified url. This could be a product ID, category ID, or page ID.")
- canonical_url: String @doc(description: "The internal relative URL. If the specified url is a redirect, the query returns the redirected URL, not the original.")
- type: UrlRewriteEntityTypeEnum @doc(description: "One of PRODUCT, CATEGORY, or CMS_PAGE.")
-}
-
type Query {
urlResolver(url: String!): EntityUrl @resolver(class: "Magento\\UrlRewriteGraphQl\\Model\\Resolver\\EntityUrl") @doc(description: "The urlResolver query returns the relative URL for a specified product, category or CMS page")
}
+type EntityUrl @doc(description: "EntityUrl is an output object containing the `id`, `relative_url`, and `type` attributes") {
+ id: Int @doc(description: "The ID assigned to the object associated with the specified url. This could be a product ID, category ID, or page ID.")
+ canonical_url: String @deprecated(reason: "The canonical_url field is deprecated, use relative_url instead.")
+ relative_url: String @doc(description: "The internal relative URL. If the specified url is a redirect, the query returns the redirected URL, not the original.")
+ type: UrlRewriteEntityTypeEnum @doc(description: "One of PRODUCT, CATEGORY, or CMS_PAGE.")
+}
+
enum UrlRewriteEntityTypeEnum {
}
diff --git a/app/code/Magento/Variable/view/adminhtml/web/variables.js b/app/code/Magento/Variable/view/adminhtml/web/variables.js
index 47f027f27102d..bf8bfbc570ce2 100644
--- a/app/code/Magento/Variable/view/adminhtml/web/variables.js
+++ b/app/code/Magento/Variable/view/adminhtml/web/variables.js
@@ -16,7 +16,8 @@ define([
'Magento_Variable/js/custom-directive-generator',
'Magento_Ui/js/lib/spinner',
'jquery/ui',
- 'prototype'
+ 'prototype',
+ 'mage/adminhtml/tools'
], function (jQuery, notification, $t, wysiwyg, registry, mageApply, utils, configGenerator, customGenerator, loader) {
'use strict';
diff --git a/app/code/Magento/Vault/view/frontend/web/template/payment/form.html b/app/code/Magento/Vault/view/frontend/web/template/payment/form.html
index b5593626fb15c..5f32281686a65 100644
--- a/app/code/Magento/Vault/view/frontend/web/template/payment/form.html
+++ b/app/code/Magento/Vault/view/frontend/web/template/payment/form.html
@@ -19,7 +19,8 @@
diff --git a/app/code/Magento/VaultGraphQl/Model/Resolver/DeletePaymentToken.php b/app/code/Magento/VaultGraphQl/Model/Resolver/DeletePaymentToken.php
new file mode 100644
index 0000000000000..cbdbbdcf010b6
--- /dev/null
+++ b/app/code/Magento/VaultGraphQl/Model/Resolver/DeletePaymentToken.php
@@ -0,0 +1,79 @@
+getCustomer = $getCustomer;
+ $this->paymentTokenManagement = $paymentTokenManagement;
+ $this->paymentTokenRepository = $paymentTokenRepository;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ if (!isset($args['public_hash'])) {
+ throw new GraphQlInputException(__('Specify the "public_hash" value.'));
+ }
+
+ $customer = $this->getCustomer->execute($context);
+
+ $token = $this->paymentTokenManagement->getByPublicHash($args['public_hash'], $customer->getId());
+ if (!$token) {
+ throw new GraphQlNoSuchEntityException(
+ __('Could not find a token using public hash: %1', $args['public_hash'])
+ );
+ }
+
+ return ['result' => $this->paymentTokenRepository->delete($token)];
+ }
+}
diff --git a/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokens.php b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokens.php
new file mode 100644
index 0000000000000..1563eaedf6b9b
--- /dev/null
+++ b/app/code/Magento/VaultGraphQl/Model/Resolver/PaymentTokens.php
@@ -0,0 +1,68 @@
+paymentTokenManagement = $paymentTokenManagement;
+ $this->getCustomer = $getCustomer;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function resolve(
+ Field $field,
+ $context,
+ ResolveInfo $info,
+ array $value = null,
+ array $args = null
+ ) {
+ $customer = $this->getCustomer->execute($context);
+
+ $tokens = $this->paymentTokenManagement->getVisibleAvailableTokens($customer->getId());
+ $result = [];
+
+ foreach ($tokens as $token) {
+ $result[] = [
+ 'public_hash' => $token->getPublicHash(),
+ 'payment_method_code' => $token->getPaymentMethodCode(),
+ 'type' => $token->getType(),
+ 'details' => $token->getTokenDetails(),
+ ];
+ }
+ return ['items' => $result];
+ }
+}
diff --git a/app/code/Magento/VaultGraphQl/README.md b/app/code/Magento/VaultGraphQl/README.md
new file mode 100644
index 0000000000000..afcb1d83f2771
--- /dev/null
+++ b/app/code/Magento/VaultGraphQl/README.md
@@ -0,0 +1,5 @@
+# VaultGraphQl
+
+**VaultGraphQl** provides type and resolver information for the GraphQl module
+to generate Vault (stored payment information) information endpoints. This module also
+provides mutations for modifying a payment token.
diff --git a/app/code/Magento/VaultGraphQl/composer.json b/app/code/Magento/VaultGraphQl/composer.json
new file mode 100644
index 0000000000000..455d24bfc11f8
--- /dev/null
+++ b/app/code/Magento/VaultGraphQl/composer.json
@@ -0,0 +1,23 @@
+{
+ "name": "magento/module-vault-graph-ql",
+ "description": "N/A",
+ "type": "magento2-module",
+ "require": {
+ "php": "~7.1.3||~7.2.0",
+ "magento/framework": "*",
+ "magento/module-vault": "*",
+ "magento/module-customer-graph-ql": "*"
+ },
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\VaultGraphQl\\": ""
+ }
+ }
+}
diff --git a/app/code/Magento/VaultGraphQl/etc/module.xml b/app/code/Magento/VaultGraphQl/etc/module.xml
new file mode 100644
index 0000000000000..f821d9fa67041
--- /dev/null
+++ b/app/code/Magento/VaultGraphQl/etc/module.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/app/code/Magento/VaultGraphQl/etc/schema.graphqls b/app/code/Magento/VaultGraphQl/etc/schema.graphqls
new file mode 100644
index 0000000000000..cdaeced027f6f
--- /dev/null
+++ b/app/code/Magento/VaultGraphQl/etc/schema.graphqls
@@ -0,0 +1,31 @@
+# Copyright © Magento, Inc. All rights reserved.
+# See COPYING.txt for license details.
+
+type Mutation {
+ deletePaymentToken(public_hash: String!): DeletePaymentTokenOutput @resolver(class: "\\Magento\\VaultGraphQl\\Model\\Resolver\\DeletePaymentToken") @doc(description:"Delete a customer payment token")
+}
+
+type DeletePaymentTokenOutput {
+ result: Boolean!
+ customerPaymentTokens: CustomerPaymentTokens @resolver(class: "\\Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokens")
+}
+
+type Query {
+ customerPaymentTokens: CustomerPaymentTokens @doc(description: "Return a list of customer payment tokens") @resolver(class: "\\Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokens")
+}
+
+type CustomerPaymentTokens @resolver(class: "\\Magento\\VaultGraphQl\\Model\\Resolver\\PaymentTokens") {
+ items: [PaymentToken]! @doc(description: "An array of payment tokens")
+}
+
+type PaymentToken @doc(description: "The stored payment method available to the customer") {
+ public_hash: String! @doc(description: "The public hash of the token")
+ payment_method_code: String! @doc(description: "The payment method code associated with the token")
+ type: PaymentTokenTypeEnum!
+ details: String @doc(description: "Stored account details")
+}
+
+enum PaymentTokenTypeEnum @doc(description: "The list of available payment token types") {
+ card
+ account
+}
diff --git a/app/code/Magento/VaultGraphQl/registration.php b/app/code/Magento/VaultGraphQl/registration.php
new file mode 100644
index 0000000000000..3f48c00f0709e
--- /dev/null
+++ b/app/code/Magento/VaultGraphQl/registration.php
@@ -0,0 +1,10 @@
+getDocBlock();
if (!$docBlock) {
throw new \LogicException(
- 'The docBlock of the method '.
- $method->getDeclaringClass()->getName() . '::' . $method->getName() . ' is empty.'
+ 'The docBlock of the method ' .
+ $method->getDeclaringClass()->getName() . '::' . $method->getName() . ' is empty.'
);
}
return $this->_typeProcessor->getDescription($docBlock);
diff --git a/app/code/Magento/Widget/Block/Adminhtml/Widget/Catalog/Category/Chooser.php b/app/code/Magento/Widget/Block/Adminhtml/Widget/Catalog/Category/Chooser.php
index 7e6ba87860307..230598a7e263d 100644
--- a/app/code/Magento/Widget/Block/Adminhtml/Widget/Catalog/Category/Chooser.php
+++ b/app/code/Magento/Widget/Block/Adminhtml/Widget/Catalog/Category/Chooser.php
@@ -3,14 +3,11 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+namespace Magento\Widget\Block\Adminhtml\Widget\Catalog\Category;
/**
* Category chooser for widget's layout updates
- *
- * @author Magento Core Team
*/
-namespace Magento\Widget\Block\Adminhtml\Widget\Catalog\Category;
-
class Chooser extends \Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser
{
/**
@@ -18,7 +15,7 @@ class Chooser extends \Magento\Catalog\Block\Adminhtml\Category\Widget\Chooser
*
* @param \Magento\Framework\Data\Tree\Node|array $node
* @param int $level
- * @return string
+ * @return array
*/
protected function _getNodeJson($node, $level = 0)
{
diff --git a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml
index 642ad6a268201..969ab58b04876 100644
--- a/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml
+++ b/app/code/Magento/Widget/Test/Mftf/ActionGroup/AdminCreateWidgetActionGroup.xml
@@ -48,7 +48,7 @@
-
+
@@ -65,4 +65,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml
index 003b398d5650e..eebd6c10b5085 100644
--- a/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml
+++ b/app/code/Magento/Widget/Test/Mftf/Section/AdminNewWidgetSection.xml
@@ -17,6 +17,7 @@
+
diff --git a/app/code/Magento/Wishlist/Block/AbstractBlock.php b/app/code/Magento/Wishlist/Block/AbstractBlock.php
index bb8138fb87a3e..981a0da1d241f 100644
--- a/app/code/Magento/Wishlist/Block/AbstractBlock.php
+++ b/app/code/Magento/Wishlist/Block/AbstractBlock.php
@@ -228,7 +228,7 @@ public function hasDescription($item)
}
/**
- * Retrieve formated Date
+ * Retrieve formatted Date
*
* @param string $date
* @deprecated
diff --git a/app/code/Magento/Wishlist/Block/Cart/Item/Renderer/Actions/MoveToWishlist.php b/app/code/Magento/Wishlist/Block/Cart/Item/Renderer/Actions/MoveToWishlist.php
index 823849ed41047..eba1f7da72742 100644
--- a/app/code/Magento/Wishlist/Block/Cart/Item/Renderer/Actions/MoveToWishlist.php
+++ b/app/code/Magento/Wishlist/Block/Cart/Item/Renderer/Actions/MoveToWishlist.php
@@ -10,6 +10,8 @@
use Magento\Wishlist\Helper\Data;
/**
+ * Class MoveToWishlist
+ *
* @api
* @since 100.0.2
*/
diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Actions.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Actions.php
index cafb6a5291481..40882ae00dae1 100644
--- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Actions.php
+++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Actions.php
@@ -5,14 +5,15 @@
*/
/**
- * Wishlist for item column in customer wishlist
- *
* @author Magento Core Team
*/
namespace Magento\Wishlist\Block\Customer\Wishlist\Item\Column;
/**
+ * Model for item column in customer wishlist.
+ *
* @api
+ * @deprecated
* @since 100.0.2
*/
class Actions extends \Magento\Wishlist\Block\Customer\Wishlist\Item\Column
diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Comment.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Comment.php
index 2d75956858a0a..53f67626e956d 100644
--- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Comment.php
+++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Comment.php
@@ -5,14 +5,15 @@
*/
/**
- * Wishlist block customer item cart column
- *
* @author Magento Core Team
*/
namespace Magento\Wishlist\Block\Customer\Wishlist\Item\Column;
/**
+ * Wishlist block customer item cart column.
+ *
* @api
+ * @deprecated
* @since 100.0.2
*/
class Comment extends \Magento\Wishlist\Block\Customer\Wishlist\Item\Column
diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Edit.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Edit.php
index 53ca78c63524d..c4c786961694b 100644
--- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Edit.php
+++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Edit.php
@@ -5,14 +5,15 @@
*/
/**
- * Edit item in customer wishlist table
- *
* @author Magento Core Team
*/
namespace Magento\Wishlist\Block\Customer\Wishlist\Item\Column;
/**
+ * Edit item in customer wishlist table.
+ *
* @api
+ * @deprecated
* @since 100.0.2
*/
class Edit extends \Magento\Wishlist\Block\Customer\Wishlist\Item\Column
diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Info.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Info.php
index 33fb0f7325cdd..b7eaf53fc23b5 100644
--- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Info.php
+++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Info.php
@@ -5,14 +5,15 @@
*/
/**
- * Wishlist block customer item cart column
- *
* @author Magento Core Team
*/
namespace Magento\Wishlist\Block\Customer\Wishlist\Item\Column;
/**
+ * Wishlist block customer item cart column.
+ *
* @api
+ * @deprecated
* @since 100.0.2
*/
class Info extends \Magento\Wishlist\Block\Customer\Wishlist\Item\Column
diff --git a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Remove.php b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Remove.php
index 57703b9300db8..09f5014edead6 100644
--- a/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Remove.php
+++ b/app/code/Magento/Wishlist/Block/Customer/Wishlist/Item/Column/Remove.php
@@ -5,14 +5,15 @@
*/
/**
- * Delete item column in customer wishlist table
- *
* @author Magento Core Team
*/
namespace Magento\Wishlist\Block\Customer\Wishlist\Item\Column;
/**
+ * Delete item column in customer wishlist table
+ *
* @api
+ * @deprecated
* @since 100.0.2
*/
class Remove extends \Magento\Wishlist\Block\Customer\Wishlist\Item\Column
diff --git a/app/code/Magento/Wishlist/Controller/Index/Allcart.php b/app/code/Magento/Wishlist/Controller/Index/Allcart.php
index 8463a00c866c5..958e0f49ca1b6 100644
--- a/app/code/Magento/Wishlist/Controller/Index/Allcart.php
+++ b/app/code/Magento/Wishlist/Controller/Index/Allcart.php
@@ -5,14 +5,17 @@
*/
namespace Magento\Wishlist\Controller\Index;
+use Magento\Framework\App\Action\HttpPostActionInterface;
use Magento\Framework\Data\Form\FormKey\Validator;
-use Magento\Framework\App\Action;
use Magento\Framework\App\Action\Context;
use Magento\Wishlist\Controller\WishlistProviderInterface;
use Magento\Wishlist\Model\ItemCarrier;
use Magento\Framework\Controller\ResultFactory;
-class Allcart extends \Magento\Wishlist\Controller\AbstractIndex
+/**
+ * Action Add All to Cart
+ */
+class Allcart extends \Magento\Wishlist\Controller\AbstractIndex implements HttpPostActionInterface
{
/**
* @var WishlistProviderInterface
diff --git a/app/code/Magento/Wishlist/Controller/Index/Cart.php b/app/code/Magento/Wishlist/Controller/Index/Cart.php
index 0e826d83a52f6..da37609d688e7 100644
--- a/app/code/Magento/Wishlist/Controller/Index/Cart.php
+++ b/app/code/Magento/Wishlist/Controller/Index/Cart.php
@@ -3,16 +3,19 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Wishlist\Controller\Index;
-use Magento\Framework\App\Action;
use Magento\Catalog\Model\Product\Exception as ProductException;
+use Magento\Framework\App\Action;
use Magento\Framework\Controller\ResultFactory;
/**
+ * Add wishlist item to shopping cart and remove from wishlist controller.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Cart extends \Magento\Wishlist\Controller\AbstractIndex
+class Cart extends \Magento\Wishlist\Controller\AbstractIndex implements Action\HttpPostActionInterface
{
/**
* @var \Magento\Wishlist\Controller\WishlistProviderInterface
@@ -195,12 +198,12 @@ public function execute()
}
}
} catch (ProductException $e) {
- $this->messageManager->addError(__('This product(s) is out of stock.'));
+ $this->messageManager->addErrorMessage(__('This product(s) is out of stock.'));
} catch (\Magento\Framework\Exception\LocalizedException $e) {
- $this->messageManager->addNotice($e->getMessage());
+ $this->messageManager->addNoticeMessage($e->getMessage());
$redirectUrl = $configureUrl;
} catch (\Exception $e) {
- $this->messageManager->addException($e, __('We can\'t add the item to the cart right now.'));
+ $this->messageManager->addExceptionMessage($e, __('We can\'t add the item to the cart right now.'));
}
$this->helper->calculate();
diff --git a/app/code/Magento/Wishlist/Controller/Index/Fromcart.php b/app/code/Magento/Wishlist/Controller/Index/Fromcart.php
index 49396004427f2..52d0951f1670c 100644
--- a/app/code/Magento/Wishlist/Controller/Index/Fromcart.php
+++ b/app/code/Magento/Wishlist/Controller/Index/Fromcart.php
@@ -8,7 +8,6 @@
use Magento\Checkout\Helper\Cart as CartHelper;
use Magento\Checkout\Model\Cart as CheckoutCart;
-use Magento\Customer\Model\Session;
use Magento\Framework\App\Action;
use Magento\Framework\Data\Form\FormKey\Validator;
use Magento\Framework\Escaper;
@@ -19,9 +18,11 @@
use Magento\Wishlist\Helper\Data as WishlistHelper;
/**
+ * Add cart item to wishlist and remove from cart controller.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class Fromcart extends \Magento\Wishlist\Controller\AbstractIndex
+class Fromcart extends \Magento\Wishlist\Controller\AbstractIndex implements Action\HttpPostActionInterface
{
/**
* @var WishlistProviderInterface
diff --git a/app/code/Magento/Wishlist/Setup/Patch/Data/ConvertSerializedData.php b/app/code/Magento/Wishlist/Setup/Patch/Data/ConvertSerializedData.php
index 76f27756d8270..0e809c4703921 100644
--- a/app/code/Magento/Wishlist/Setup/Patch/Data/ConvertSerializedData.php
+++ b/app/code/Magento/Wishlist/Setup/Patch/Data/ConvertSerializedData.php
@@ -3,20 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\Wishlist\Setup\Patch\Data;
-use Magento\Framework\DB\FieldDataConverterFactory;
use Magento\Framework\DB\DataConverter\SerializedToJson;
-use Magento\Framework\DB\Select\QueryModifierFactory;
+use Magento\Framework\DB\FieldDataConverterFactory;
use Magento\Framework\DB\Query\Generator as QueryGenerator;
-use Magento\Framework\App\ResourceConnection;
+use Magento\Framework\DB\Select\QueryModifierFactory;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchVersionInterface;
/**
- * Class ConvertSerializedData
- * @package Magento\Wishlist\Setup\Patch
+ * Convert serialized wishlist item data.
*/
class ConvertSerializedData implements DataPatchInterface, PatchVersionInterface
{
@@ -60,7 +57,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function apply()
{
@@ -68,7 +65,7 @@ public function apply()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getDependencies()
{
@@ -76,7 +73,7 @@ public static function getDependencies()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getVersion()
{
@@ -84,13 +81,19 @@ public static function getVersion()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAliases()
{
return [];
}
-
+
+ /**
+ * Convert serialized whishlist item data.
+ *
+ * @throws \Magento\Framework\DB\FieldDataConversionException
+ * @throws \Magento\Framework\Exception\LocalizedException
+ */
private function convertSerializedData()
{
$connection = $this->moduleDataSetup->getConnection();
diff --git a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml
index 7bb42e12d1451..a1c5b9eae5c49 100644
--- a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml
+++ b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/StorefrontCustomerWishlistActionGroup.xml
@@ -28,7 +28,7 @@
-
+
@@ -88,4 +88,13 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml b/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml
index 811871bf685ae..c6a9704698b05 100644
--- a/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml
+++ b/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml
@@ -12,5 +12,7 @@
+ JohnDoe123456789@example.com,JohnDoe987654321@example.com,JohnDoe123456abc@example.com
+ Sharing message.
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Page/StorefrontCustomerWishlistSharePage.xml b/app/code/Magento/Wishlist/Test/Mftf/Page/StorefrontCustomerWishlistSharePage.xml
new file mode 100644
index 0000000000000..6d6151648c5ee
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/Page/StorefrontCustomerWishlistSharePage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml
index 7a767e42e82bf..ef619726b76ab 100644
--- a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml
+++ b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistProductSection.xml
@@ -19,6 +19,8 @@
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistShareSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistShareSection.xml
new file mode 100644
index 0000000000000..76b99ba56a327
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/Section/StorefrontCustomerWishlistShareSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistEntityTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistEntityTest.xml
new file mode 100644
index 0000000000000..87c5ed950949f
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/Test/StorefrontShareWishlistEntityTest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php
index d89f6e43e07be..e9061f1f3d5f8 100644
--- a/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php
+++ b/app/code/Magento/Wishlist/Test/Unit/Controller/Index/CartTest.php
@@ -735,7 +735,7 @@ public function testExecuteWithoutQuantityArrayAndOutOfStock()
->willThrowException(new ProductException(__('Test Phrase')));
$this->messageManagerMock->expects($this->once())
- ->method('addError')
+ ->method('addErrorMessage')
->with('This product(s) is out of stock.', null)
->willReturnSelf();
@@ -901,7 +901,7 @@ public function testExecuteWithoutQuantityArrayAndConfigurable()
->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('message')));
$this->messageManagerMock->expects($this->once())
- ->method('addNotice')
+ ->method('addNoticeMessage')
->with('message', null)
->willReturnSelf();
@@ -1073,7 +1073,7 @@ public function testExecuteWithEditQuantity()
->willThrowException(new \Magento\Framework\Exception\LocalizedException(__('message')));
$this->messageManagerMock->expects($this->once())
- ->method('addNotice')
+ ->method('addNoticeMessage')
->with('message', null)
->willReturnSelf();
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 d3f21dda9ccde..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
@@ -18,6 +18,7 @@
+
@@ -40,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/Wishlist/view/frontend/templates/item/column/review.phtml b/app/code/Magento/Wishlist/view/frontend/templates/item/column/review.phtml
new file mode 100644
index 0000000000000..9120cc9fa684e
--- /dev/null
+++ b/app/code/Magento/Wishlist/view/frontend/templates/item/column/review.phtml
@@ -0,0 +1,10 @@
+getItem()->getProduct();
+?>
+= $block->getReviewsSummaryHtml($product, 'short') ?>
diff --git a/app/code/Magento/WishlistAnalytics/composer.json b/app/code/Magento/WishlistAnalytics/composer.json
index fc69afe2907ab..747f2a4baaaa9 100644
--- a/app/code/Magento/WishlistAnalytics/composer.json
+++ b/app/code/Magento/WishlistAnalytics/composer.json
@@ -4,7 +4,8 @@
"require": {
"php": "~7.1.3||~7.2.0",
"magento/framework": "*",
- "magento/module-wishlist": "*"
+ "magento/module-wishlist": "*",
+ "magento/module-analytics": "*"
},
"type": "magento2-module",
"license": [
diff --git a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php
index e3a788af2ea7e..792928ab61aaf 100644
--- a/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php
+++ b/app/code/Magento/WishlistGraphQl/Model/Resolver/WishlistResolver.php
@@ -13,6 +13,7 @@
use Magento\Wishlist\Model\ResourceModel\Wishlist as WishlistResourceModel;
use Magento\Wishlist\Model\Wishlist;
use Magento\Wishlist\Model\WishlistFactory;
+use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
/**
* Fetches the Wishlist data according to the GraphQL schema
@@ -51,6 +52,10 @@ public function resolve(
) {
$customerId = $context->getUserId();
+ /* Guest checking */
+ if (!$customerId && 0 === $customerId) {
+ throw new GraphQlAuthorizationException(__('The current user cannot perform operations on wishlist'));
+ }
/** @var Wishlist $wishlist */
$wishlist = $this->wishlistFactory->create();
$this->wishlistResource->load($wishlist, $customerId, 'customer_id');
diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/_headings-group.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/_headings-group.less
index 832c66b7988e0..bf7ee7850f9d0 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/_headings-group.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/header/_headings-group.less
@@ -42,4 +42,5 @@
color: @page-title__color;
font-size: @page-title__font-size;
margin-bottom: 0;
+ word-break: break-all;
}
diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_actions-bar.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_actions-bar.less
index 08434727ccc9c..8499ecaa48c12 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_actions-bar.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_actions-bar.less
@@ -45,6 +45,10 @@
.page-actions {
@_page-action__indent: 1.3rem;
+ &.floating-header {
+ &:extend(.page-actions-buttons all);
+ }
+
.page-main-actions & {
&._fixed {
left: @page-wrapper__indent-left;
diff --git a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_store-scope.less b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_store-scope.less
index c0c94eaaf3507..05e6350c88d8e 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_store-scope.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Backend/web/css/source/module/main/_store-scope.less
@@ -38,7 +38,7 @@
.admin__legend {
.admin__field-tooltip {
margin-left: -@indent__base;
- margin-top: -.2rem;
+ margin-top: .5rem;
}
}
}
diff --git a/app/design/adminhtml/Magento/backend/Magento_Rma/web/css/source/_module.less b/app/design/adminhtml/Magento/backend/Magento_Rma/web/css/source/_module.less
index c405707ee7bbe..16c84047b529d 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Rma/web/css/source/_module.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Rma/web/css/source/_module.less
@@ -7,13 +7,21 @@
.rma-request-details,
.rma-wrapper .order-shipping-address {
float: left;
- #mix-grid .width(6,12);
+ /**
+ * @codingStandardsIgnoreStart
+ */
+ #mix-grid .width(6, 12);
+ //@codingStandardsIgnoreEnd
}
.rma-confirmation,
- .rma-wrapper .order-return-address {
+ .rma-wrapper .order-return-address, .rma-wrapper .order-shipping-method {
float: right;
- #mix-grid .width(6,12);
+ /**
+ * @codingStandardsIgnoreStart
+ */
+ #mix-grid .width(6, 12);
+ //@codingStandardsIgnoreEnd
}
}
diff --git a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_order-account.less b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_order-account.less
index e14bcbcddd47f..f66e94940c55d 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_order-account.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Sales/web/css/source/module/order/_order-account.less
@@ -27,3 +27,19 @@
width: 50%;
}
}
+
+.page-create-order {
+ .order-details {
+ &:not(.order-details-existing-customer) {
+ .order-account-information {
+ .field-email {
+ margin-left: -30px;
+ }
+
+ .field-group_id {
+ margin-right: 30px;
+ }
+ }
+ }
+ }
+}
diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less
index d55608ade4a05..946d11db2d1a2 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/_data-grid.less
@@ -392,6 +392,7 @@ body._in-resize {
overflow: hidden;
padding: 0;
vertical-align: top;
+ vertical-align: middle;
width: @control-checkbox-radio__size + @data-grid-checkbox-cell-inner__padding-horizontal * 2;
&:hover {
@@ -1074,8 +1075,10 @@ body._in-resize {
}
.data-grid-checkbox-cell-inner {
- margin: @data-grid-checkbox-cell-inner__padding-top @data-grid-checkbox-cell-inner__padding-horizontal .9rem;
+ display: unset;
+ margin: 0 @data-grid-checkbox-cell-inner__padding-horizontal 0;
padding: 0;
+ text-align: center;
}
// Content Hierarchy specific
diff --git a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less
index 6e03e1d0cebaa..e37e08f3b667d 100644
--- a/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less
+++ b/app/design/adminhtml/Magento/backend/Magento_Ui/web/css/source/module/data-grid/data-grid-header/_data-grid-filters.less
@@ -99,7 +99,7 @@
}
.action-menu {
- max-height: 3.85rem * @data-grid-search-control-action-menu-item__quantity; // ToDo UI: change static item height
+ max-height: 3.85rem * @data-grid-search-control-action-menu-item__quantity; // @todo: change static item height
overflow-y: auto;
z-index: @data-grid-search-menu__z-index;
}
@@ -354,6 +354,7 @@
.admin__current-filters-list-wrap {
width: 100%;
+ word-break: break-all;
}
.admin__current-filters-list {
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less
index cd089232412dc..d1fe33c4fe77d 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-dropdown.less
@@ -234,6 +234,7 @@
border: 0;
display: inline;
margin: 0;
+ width: 6rem;
body._keyfocus &:focus {
box-shadow: none;
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less
index 4c364bed688bc..61bd94cf3f49c 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-multiselect.less
@@ -338,7 +338,7 @@
border-top: @action-multiselect-tree-lines;
height: 1px;
top: @action-multiselect-menu-item__padding + @action-multiselect-tree-arrow__size/2;
- width: @action-multiselect-tree-menu-item__margin-left + @action-multiselect-menu-item__padding;
+ width: @action-multiselect-tree-menu-item__margin-left;
}
// Vertical dotted line
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-select.less b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-select.less
index 1cc61bef5da07..1c45fe6946ba0 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-select.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/actions/_actions-select.less
@@ -7,6 +7,14 @@
// Actions -> Action select
// _____________________________________________
+//
+// Variables
+// _____________________________________________
+
+@_dropdown__padding-right: @action__height;
+@_triangle__height: @button-marker-triangle__height;
+@_triangle__width: @button-marker-triangle__width;
+
// Action select have the same visual styles and functionality as native
.action-select-wrap {
@_action-select__border-color: @button__border-color;
@@ -18,9 +26,9 @@
.action-select {
.action-toggle-triangle(
- @_dropdown__padding-right: @_action-select-toggle__size;
- @_triangle__height: @button-marker-triangle__height;
- @_triangle__width: @button-marker-triangle__width;
+ @_dropdown__padding-right;
+ @_triangle__height;
+ @_triangle__width;
);
.lib-text-overflow-ellipsis();
@@ -108,12 +116,9 @@
min-width: 100%;
position: static;
- ._parent._visible {
- position: relative;
- }
-
.action-submenu {
position: absolute;
+ right: -100%;
}
}
}
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
index 4621349eb2803..15cd295885892 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/components/_messages.less
@@ -111,7 +111,7 @@
content: @alert-icon__error__content;
font-size: @alert-icon__error__font-size;
left: 2.2rem;
- margin-top: -.9rem;
+ margin-top: -1.1rem;
}
}
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 4b77c518d3df8..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
@@ -122,7 +122,7 @@
> .admin__field-control {
#mix-grid .column(@field-control-grid__column, @field-grid__columns);
input[type="checkbox"] {
- margin-top: 1.2rem;
+ margin-top: @indent__s;
}
}
@@ -542,7 +542,6 @@
& > .admin__field {
&:first-child {
position: static;
-
& > .admin__field-label {
#mix-grid .column(@field-label-grid__column, @field-grid__columns);
cursor: pointer;
@@ -697,6 +696,7 @@
margin: 0;
opacity: 1;
position: static;
+ width: 100%;
}
}
& > .admin__field-label {
diff --git a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-table.less b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-table.less
index a9035a9a7e47d..697d11fb57d67 100644
--- a/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-table.less
+++ b/app/design/adminhtml/Magento/backend/web/css/source/forms/fields/_control-table.less
@@ -118,7 +118,7 @@
}
&._fit {
- width: 1px;
+ width: auto;
}
}
diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_fields.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_fields.less
index 4479c070a4e17..8dec680b58726 100644
--- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_fields.less
+++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_fields.less
@@ -55,31 +55,3 @@
}
}
}
-
-//
-// Desktop
-// _____________________________________________
-
-.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
- // ToDo UI: remove with global blank theme .field.required update
- .opc-wrapper {
- .fieldset {
- > .field {
- &.required,
- &._required {
- position: relative;
-
- > label {
- padding-right: 25px;
-
- &:after {
- margin-left: @indent__s;
- position: absolute;
- top: 9px;
- }
- }
- }
- }
- }
- }
-}
diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less
index 5f8134193c67f..35445b0989e86 100644
--- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less
+++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_payments.less
@@ -209,6 +209,13 @@
.fieldset {
> .field {
margin: 0 0 @indent__base;
+
+ &.choice {
+ &:before {
+ padding: 0;
+ width: 0;
+ }
+ }
&.type {
.control {
diff --git a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less
index 39b9a051e6592..664726ddfd798 100644
--- a/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less
+++ b/app/design/frontend/Magento/blank/Magento_Checkout/web/css/source/module/checkout/_tooltip.less
@@ -65,6 +65,10 @@
@_icon-font-color-active: false
);
+ &:before {
+ padding-left : 1px;
+ }
+
&:focus {
._keyfocus & {
.lib-css(z-index, @checkout-tooltip__hover__z-index);
diff --git a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less
index d4a81027cc3d7..54ba530092cc0 100644
--- a/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less
+++ b/app/design/frontend/Magento/blank/Magento_Customer/web/css/source/_module.less
@@ -388,6 +388,17 @@
position: relative;
}
}
+
+ .form.search.advanced {
+ .field.price {
+ .with-addon {
+ .input-text {
+ flex-basis: auto;
+ width: 100%;
+ }
+ }
+ }
+ }
}
//
@@ -421,7 +432,7 @@
> .field {
> .control {
- width: 55%;
+ width: 80%;
}
}
}
@@ -452,6 +463,7 @@
.form.send.confirmation,
.form.password.forget,
.form.create.account,
+ .form.search.advanced,
.form.form-orders-search {
min-width: 600px;
width: 50%;
diff --git a/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_module.less
index 3847393a2f046..298ccbf58e687 100644
--- a/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_module.less
+++ b/app/design/frontend/Magento/blank/Magento_Sales/web/css/source/_module.less
@@ -294,6 +294,14 @@
}
}
}
+ .order-items.table-wrapper {
+ .col.price,
+ .col.qty,
+ .col.subtotal,
+ .col.msrp {
+ text-align: left;
+ }
+ }
}
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
diff --git a/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less b/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less
index 28aa3f187e95c..b7271e3c1e248 100644
--- a/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less
+++ b/app/design/frontend/Magento/blank/Magento_Swatches/web/css/source/_module.less
@@ -91,15 +91,19 @@
&-options {
margin-top: @indent__s;
+ &:focus {
+ box-shadow: none;
+ }
+
.swatch-option-tooltip-layered .title {
.lib-css(color, @swatch-option-tooltip-layered-title__color);
- width: 100%;
- height: 20px;
- position: absolute;
bottom: -5px;
+ height: 20px;
left: 0;
- text-align: center;
margin-bottom: @indent__s;
+ position: absolute;
+ text-align: center;
+ width: 100%;
}
}
@@ -110,7 +114,7 @@
.lib-css(color, @attr-swatch-option__color);
&.selected {
- .lib-css(blackground, @attr-swatch-option__selected__background);
+ .lib-css(background, @attr-swatch-option__selected__background);
.lib-css(border, @attr-swatch-option__selected__border);
.lib-css(color, @attr-swatch-option__selected__color);
}
@@ -132,15 +136,19 @@
text-align: center;
text-overflow: ellipsis;
+ &:focus {
+ box-shadow: @focus__box-shadow;
+ }
+
&.text {
.lib-css(background, @swatch-option-text__background);
.lib-css(color, @swatch-option-text__color);
font-size: @font-size__s;
font-weight: @font-weight__bold;
line-height: 20px;
- padding: 4px 8px;
- min-width: 22px;
margin-right: 7px;
+ min-width: 22px;
+ padding: 4px 8px;
&.selected {
.lib-css(background-color, @swatch-option-text__selected__background-color) !important;
diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less
index f15509ceb63eb..3b4da1d1ae6f5 100644
--- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/_module.less
@@ -294,6 +294,12 @@
}
.product-options-wrapper {
+ .fieldset {
+ &:focus {
+ box-shadow: none;
+ }
+ }
+
.fieldset-product-options-inner {
.legend {
.lib-css(font-weight, @font-weight__semibold);
@@ -391,6 +397,7 @@
.box-tocart {
&:extend(.abs-box-tocart all);
+
.field.qty {
}
@@ -534,6 +541,15 @@
}
}
+ .block-compare {
+ .action {
+ &.delete {
+ &:extend(.abs-remove-button-for-blocks all);
+ right: initial;
+ }
+ }
+ }
+
.action.tocart {
border-radius: 0;
}
diff --git a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less
index d477c08fc9553..e5915969c91b9 100644
--- a/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less
+++ b/app/design/frontend/Magento/luma/Magento_Catalog/web/css/source/module/_listings.less
@@ -397,7 +397,7 @@
.page-products.page-layout-3columns {
.products-grid {
.product-item {
- margin-left: 2%;
+ margin-left: 0;
width: calc(~'(100% - 4%) / 3');
&:nth-child(3n + 1) {
diff --git a/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less
index 0f91f857a715c..31859a46d3efe 100644
--- a/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_CatalogSearch/web/css/source/_module.less
@@ -213,7 +213,7 @@
// Mobile
// _____________________________________________
-.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) {
+.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
.block-search {
margin-top: @indent__s;
}
diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less
index 6be6010fd2d2d..71814cd0f0422 100644
--- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less
+++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_cart.less
@@ -527,6 +527,18 @@
// Desktop
// _____________________________________________
+.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__s) {
+ .cart-container {
+ .block.crosssell {
+ .products-grid {
+ .product-item-actions {
+ margin: 0 0 @indent__s;
+ }
+ }
+ }
+ }
+}
+
.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
.checkout-cart-index {
.page-main {
diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less
index 7265d7bd61c51..5ca3322403102 100644
--- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less
+++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/_minicart.less
@@ -416,7 +416,7 @@
}
}
-.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) {
+.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
.minicart-wrapper {
margin-top: @indent__s;
}
diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less
index 308b034026e18..920e68994c666 100644
--- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less
+++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_order-summary.less
@@ -152,14 +152,14 @@
}
.product-item-name-block {
- display: table-cell;
+ display: block;
padding-right: @indent__xs;
text-align: left;
}
.subtotal {
- display: table-cell;
- text-align: right;
+ display: block;
+ text-align: left;
}
.price {
@@ -231,3 +231,27 @@
}
}
}
+
+//
+// Tablet
+// _____________________________________________
+
+@media only screen and (max-width: @screen__m) {
+ .opc-block-summary {
+ .product-item {
+ .product-item-inner {
+ display: block;
+ }
+
+ .product-item-name-block {
+ display: block;
+ text-align: left;
+ }
+
+ .subtotal {
+ display: block;
+ text-align: left;
+ }
+ }
+ }
+}
diff --git a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less
index dd9db0e715308..eb9c069053661 100644
--- a/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less
+++ b/app/design/frontend/Magento/luma/Magento_Checkout/web/css/source/module/checkout/_payments.less
@@ -63,10 +63,13 @@
}
}
}
-
+ /**
+ * @codingStandardsIgnoreStart
+ */
#po_number {
margin-bottom: 20px;
}
+ // @codingStandardsIgnoreEnd
}
.payment-method-title {
@@ -116,7 +119,8 @@
margin: 0 0 @indent__base;
.primary {
- .action-update {
+ .action-update {
+ margin-bottom: 20px;
margin-right: 0;
}
}
diff --git a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
index ec6762de3d46d..bd8ddde98c506 100755
--- a/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Customer/web/css/source/_module.less
@@ -161,6 +161,7 @@
.table-wrapper {
.lib-css(margin-bottom, @indent__base);
border-bottom: 1px solid @account-table-border-bottom-color;
+ overflow-x: auto;
&:last-child {
margin-bottom: 0;
@@ -375,7 +376,7 @@
.fieldset {
> .field {
> .control {
- width: 55%;
+ width: 80%;
}
}
}
@@ -407,6 +408,7 @@
.form.send.confirmation,
.form.password.forget,
.form.create.account,
+ .form.search.advanced,
.form.form-orders-search {
min-width: 600px;
width: 50%;
@@ -607,4 +609,15 @@
position: relative;
}
}
+
+ .form.search.advanced {
+ .field.price {
+ .with-addon {
+ .input-text {
+ flex-basis: auto;
+ width: 100%;
+ }
+ }
+ }
+ }
}
diff --git a/app/design/frontend/Magento/luma/Magento_GroupedProduct/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_GroupedProduct/web/css/source/_module.less
index 088372808aa6a..e9c40f6386246 100644
--- a/app/design/frontend/Magento/luma/Magento_GroupedProduct/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_GroupedProduct/web/css/source/_module.less
@@ -70,6 +70,10 @@
clear: left;
}
}
+
+ .box-tocart {
+ margin-top: @indent__s;
+ }
}
}
@@ -133,9 +137,18 @@
}
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
- .table-wrapper.grouped {
- .lib-css(margin-left, -@layout__width-xs-indent);
- .lib-css(margin-right, -@layout__width-xs-indent);
+ .product-add-form {
+ .table-wrapper.grouped {
+ .lib-css(margin-left, -@layout__width-xs-indent);
+ .lib-css(margin-right, -@layout__width-xs-indent);
+ .table.data.grouped {
+ tr {
+ td {
+ padding: 5px 10px 5px 15px;
+ }
+ }
+ }
+ }
}
}
diff --git a/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less
index 38f7c4ca873fd..a94e2cae46b14 100644
--- a/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Multishipping/web/css/source/_module.less
@@ -345,6 +345,22 @@
.data.table {
&:extend(.abs-checkout-order-review all);
+ &.table-order-review {
+ > tbody {
+ > tr {
+ > td {
+ &.col {
+ &.subtotal {
+ border-bottom: none;
+ }
+ &.qty {
+ text-align: center;
+ }
+ }
+ }
+ }
+ }
+ }
}
}
diff --git a/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less
index 9ccd6c190ec0e..d7ee1319c9a43 100644
--- a/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Newsletter/web/css/source/_module.less
@@ -81,3 +81,24 @@
width: 34%;
}
}
+
+//
+// Mobile
+// _____________________________________________
+
+.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
+ .block {
+ &.newsletter {
+ input {
+ font-size: 12px;
+ padding-left: 30px;
+ }
+
+ .field {
+ .control:before {
+ font-size: 13px;
+ }
+ }
+ }
+ }
+}
diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_new.html b/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_new.html
index 8c4084fcaf496..e467aa843e2f4 100644
--- a/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_new.html
+++ b/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_new.html
@@ -51,7 +51,7 @@ {{trans "Your Shipment #%shipment_id for Order #%order_id" shipment_id=$ship
{{/depend}}
- {{block class='Magento\\Framework\\View\\Element\\Template' area='frontend' template='Magento_Sales::email/shipment/track.phtml' shipment=$shipment order=$order}}
+ {{layout handle="sales_email_order_shipment_track" shipment=$shipment order=$order}}
diff --git a/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_new_guest.html b/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_new_guest.html
index 68f1886986c5b..385110f8f037e 100644
--- a/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_new_guest.html
+++ b/app/design/frontend/Magento/luma/Magento_Sales/email/shipment_new_guest.html
@@ -49,7 +49,7 @@ {{trans "Your Shipment #%shipment_id for Order #%order_id" shipment_id=$ship
{{/depend}}
- {{block class='Magento\\Framework\\View\\Element\\Template' area='frontend' template='Magento_Sales::email/shipment/track.phtml' shipment=$shipment order=$order}}
+ {{layout handle="sales_email_order_shipment_track" shipment=$shipment order=$order}}
diff --git a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less
index ab2eb1764f781..1be46c8239ee2 100644
--- a/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Sales/web/css/source/_module.less
@@ -398,6 +398,17 @@
&.orders-recent {
&:extend(.abs-account-table-margin-mobile all);
&:extend(.abs-no-border-top all);
+ .table-order-items {
+ &.table {
+ tbody {
+ > tr {
+ > td.col {
+ padding-left: 0;
+ }
+ }
+ }
+ }
+ }
}
}
@@ -561,13 +572,13 @@
margin: 0 @tab-control__margin-right 0 0;
a {
- padding: @tab-control__padding-top @tab-control__padding-right;
+ padding: @tab-control__padding-top @indent__base;
}
strong {
border-bottom: 0;
margin-bottom: -1px;
- padding: @tab-control__padding-top @tab-control__padding-right @tab-control__padding-bottom + 1 @tab-control__padding-left;
+ padding: @tab-control__padding-top @indent__base @tab-control__padding-bottom + 1 @indent__base;
}
}
}
@@ -693,3 +704,19 @@
}
}
}
+
+.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__l) {
+ .order-links {
+ .item {
+ margin: 0 @tab-control__margin-right 0 0;
+
+ a {
+ padding: @tab-control__padding-top @tab-control__padding-right;
+ }
+
+ strong {
+ padding: @tab-control__padding-top @tab-control__padding-right @tab-control__padding-bottom + 1 @tab-control__padding-left;
+ }
+ }
+ }
+}
diff --git a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less
index 99da7716f9274..1f1ea93d0b54a 100644
--- a/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Theme/web/css/source/_module.less
@@ -77,6 +77,14 @@
.lib-vendor-prefix-flex-grow(1);
}
+ .page-main {
+ > .page-title-wrapper {
+ .page-title {
+ word-break: break-all;
+ }
+ }
+ }
+
//
// Header
// ---------------------------------------------
@@ -445,7 +453,7 @@
}
}
-.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__s) {
+.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
.logo {
margin-bottom: 13px;
margin-top: 4px;
diff --git a/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less b/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less
index 2db14c05ccad7..85e8aeb0b515c 100644
--- a/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less
+++ b/app/design/frontend/Magento/luma/Magento_Wishlist/web/css/source/_module.less
@@ -395,9 +395,7 @@
width: auto;
}
}
-}
-.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
.wishlist-index-index {
.product-item-inner {
@_shadow: 3px 4px 4px 0 rgba(0, 0, 0, .3);
diff --git a/app/etc/di.xml b/app/etc/di.xml
index 6cf169c1d2277..d0b45ea16c855 100755
--- a/app/etc/di.xml
+++ b/app/etc/di.xml
@@ -38,7 +38,7 @@
-
+
@@ -153,6 +153,7 @@
+
@@ -1756,4 +1757,11 @@
+
+
+ Magento\Framework\Lock\Backend\Cache
+ 10000
+ 20
+
+
diff --git a/auth.json.sample b/auth.json.sample
index 48b13ee8b69da..be1c70cfe1e18 100644
--- a/auth.json.sample
+++ b/auth.json.sample
@@ -1,7 +1,4 @@
{
- "github-oauth": {
- "github.com": ""
- },
"http-basic": {
"repo.magento.com": {
"username": "",
diff --git a/composer.json b/composer.json
index 7120fe77b0e55..50b22238e0d01 100644
--- a/composer.json
+++ b/composer.json
@@ -35,7 +35,7 @@
"colinmollenhour/credis": "1.10.0",
"colinmollenhour/php-redis-session-abstract": "~1.4.0",
"composer/composer": "^1.6",
- "elasticsearch/elasticsearch": "~2.0|~5.1",
+ "elasticsearch/elasticsearch": "~2.0|~5.1|~6.1",
"magento/composer": "~1.4.0",
"magento/magento-composer-installer": ">=0.1.11",
"magento/zendframework1": "~1.14.1",
@@ -84,7 +84,7 @@
"require-dev": {
"friendsofphp/php-cs-fixer": "~2.13.0",
"lusitanian/oauth": "~0.8.10",
- "magento/magento2-functional-testing-framework": "~2.3.13",
+ "magento/magento2-functional-testing-framework": "~2.3.14",
"pdepend/pdepend": "2.5.2",
"phpmd/phpmd": "@stable",
"phpunit/phpunit": "~6.5.0",
@@ -149,6 +149,7 @@
"magento/module-downloadable-import-export": "*",
"magento/module-eav": "*",
"magento/module-elasticsearch": "*",
+ "magento/module-elasticsearch-6": "*",
"magento/module-email": "*",
"magento/module-encryption-key": "*",
"magento/module-fedex": "*",
@@ -180,6 +181,8 @@
"magento/module-media-storage": "*",
"magento/module-message-queue": "*",
"magento/module-msrp": "*",
+ "magento/module-msrp-configurable-product": "*",
+ "magento/module-msrp-grouped-product": "*",
"magento/module-multishipping": "*",
"magento/module-mysql-mq": "*",
"magento/module-new-relic-reporting": "*",
@@ -236,6 +239,7 @@
"magento/module-usps": "*",
"magento/module-variable": "*",
"magento/module-vault": "*",
+ "magento/module-vault-graph-ql": "*",
"magento/module-version": "*",
"magento/module-webapi": "*",
"magento/module-webapi-async": "*",
diff --git a/composer.lock b/composer.lock
index 48aef8b603d4a..a7131f4a16eec 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "f22c780b1ed27ba951c0562e20078a70",
+ "content-hash": "60007664938710edf52eadddd7551867",
"packages": [
{
"name": "braintree/braintree_php",
@@ -257,16 +257,16 @@
},
{
"name": "composer/composer",
- "version": "1.8.3",
+ "version": "1.8.4",
"source": {
"type": "git",
"url": "https://github.com/composer/composer.git",
- "reference": "a6a3b44581398b7135c7baa0557b7c5b10808b47"
+ "reference": "bc364c2480c17941e2135cfc568fa41794392534"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/composer/composer/zipball/a6a3b44581398b7135c7baa0557b7c5b10808b47",
- "reference": "a6a3b44581398b7135c7baa0557b7c5b10808b47",
+ "url": "https://api.github.com/repos/composer/composer/zipball/bc364c2480c17941e2135cfc568fa41794392534",
+ "reference": "bc364c2480c17941e2135cfc568fa41794392534",
"shasum": ""
},
"require": {
@@ -333,7 +333,7 @@
"dependency",
"package"
],
- "time": "2019-01-30T07:31:34+00:00"
+ "time": "2019-02-11T09:52:10+00:00"
},
{
"name": "composer/semver",
@@ -535,29 +535,31 @@
},
{
"name": "elasticsearch/elasticsearch",
- "version": "v5.4.0",
+ "version": "v6.1.0",
"source": {
"type": "git",
"url": "https://github.com/elastic/elasticsearch-php.git",
- "reference": "d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59"
+ "reference": "b237a37b2cdf23a5a17fd3576cdea771394ad00d"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59",
- "reference": "d3c5b55ad94f5053ca76c48585b4cde2cdc6bc59",
+ "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/b237a37b2cdf23a5a17fd3576cdea771394ad00d",
+ "reference": "b237a37b2cdf23a5a17fd3576cdea771394ad00d",
"shasum": ""
},
"require": {
+ "ext-json": ">=1.3.7",
"guzzlehttp/ringphp": "~1.0",
- "php": "^5.6|^7.0",
+ "php": "^7.0",
"psr/log": "~1.0"
},
"require-dev": {
"cpliakas/git-wrapper": "~1.0",
"doctrine/inflector": "^1.1",
"mockery/mockery": "0.9.4",
- "phpunit/phpunit": "^4.7|^5.4",
- "sami/sami": "~3.2",
+ "phpstan/phpstan-shim": "0.8.3",
+ "phpunit/phpunit": "6.3.0",
+ "squizlabs/php_codesniffer": "3.0.2",
"symfony/finder": "^2.8",
"symfony/yaml": "^2.8"
},
@@ -586,7 +588,7 @@
"elasticsearch",
"search"
],
- "time": "2019-01-08T18:57:00+00:00"
+ "time": "2019-01-08T18:53:46+00:00"
},
{
"name": "guzzlehttp/ringphp",
@@ -1910,7 +1912,7 @@
},
{
"name": "symfony/css-selector",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/css-selector.git",
@@ -2026,16 +2028,16 @@
},
{
"name": "symfony/filesystem",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
- "reference": "7c16ebc2629827d4ec915a52ac809768d060a4ee"
+ "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/7c16ebc2629827d4ec915a52ac809768d060a4ee",
- "reference": "7c16ebc2629827d4ec915a52ac809768d060a4ee",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/e16b9e471703b2c60b95f14d31c1239f68f11601",
+ "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601",
"shasum": ""
},
"require": {
@@ -2072,20 +2074,20 @@
],
"description": "Symfony Filesystem Component",
"homepage": "https://symfony.com",
- "time": "2019-01-16T20:35:37+00:00"
+ "time": "2019-02-07T11:40:08+00:00"
},
{
"name": "symfony/finder",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
- "reference": "ef71816cbb264988bb57fe6a73f610888b9aa70c"
+ "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/ef71816cbb264988bb57fe6a73f610888b9aa70c",
- "reference": "ef71816cbb264988bb57fe6a73f610888b9aa70c",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/267b7002c1b70ea80db0833c3afe05f0fbde580a",
+ "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a",
"shasum": ""
},
"require": {
@@ -2121,7 +2123,7 @@
],
"description": "Symfony Finder Component",
"homepage": "https://symfony.com",
- "time": "2019-01-16T20:35:37+00:00"
+ "time": "2019-02-23T15:42:05+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -2751,16 +2753,16 @@
},
{
"name": "zendframework/zend-db",
- "version": "2.9.3",
+ "version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-db.git",
- "reference": "5b4f2c42f94c9f7f4b2f456a0ebe459fab12b3d9"
+ "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zendframework/zend-db/zipball/5b4f2c42f94c9f7f4b2f456a0ebe459fab12b3d9",
- "reference": "5b4f2c42f94c9f7f4b2f456a0ebe459fab12b3d9",
+ "url": "https://api.github.com/repos/zendframework/zend-db/zipball/77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e",
+ "reference": "77022f06f6ffd384fa86d22ab8d8bbdb925a1e8e",
"shasum": ""
},
"require": {
@@ -2771,7 +2773,7 @@
"phpunit/phpunit": "^5.7.25 || ^6.4.4",
"zendframework/zend-coding-standard": "~1.0.0",
"zendframework/zend-eventmanager": "^2.6.2 || ^3.0",
- "zendframework/zend-hydrator": "^1.1 || ^2.1",
+ "zendframework/zend-hydrator": "^1.1 || ^2.1 || ^3.0",
"zendframework/zend-servicemanager": "^2.7.5 || ^3.0.3"
},
"suggest": {
@@ -2805,7 +2807,7 @@
"db",
"zf"
],
- "time": "2018-04-09T13:21:36+00:00"
+ "time": "2019-02-25T11:37:45+00:00"
},
{
"name": "zendframework/zend-di",
@@ -4375,16 +4377,16 @@
},
{
"name": "zendframework/zend-uri",
- "version": "2.6.1",
+ "version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/zendframework/zend-uri.git",
- "reference": "3b6463645c6766f78ce537c70cb4fdabee1e725f"
+ "reference": "b2785cd38fe379a784645449db86f21b7739b1ee"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/3b6463645c6766f78ce537c70cb4fdabee1e725f",
- "reference": "3b6463645c6766f78ce537c70cb4fdabee1e725f",
+ "url": "https://api.github.com/repos/zendframework/zend-uri/zipball/b2785cd38fe379a784645449db86f21b7739b1ee",
+ "reference": "b2785cd38fe379a784645449db86f21b7739b1ee",
"shasum": ""
},
"require": {
@@ -4399,8 +4401,8 @@
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "2.6.x-dev",
- "dev-develop": "2.7.x-dev"
+ "dev-master": "2.7.x-dev",
+ "dev-develop": "2.8.x-dev"
}
},
"autoload": {
@@ -4418,7 +4420,7 @@
"uri",
"zf"
],
- "time": "2018-04-30T13:40:08+00:00"
+ "time": "2019-02-27T21:39:04+00:00"
},
{
"name": "zendframework/zend-validator",
@@ -4887,16 +4889,16 @@
},
{
"name": "codeception/phpunit-wrapper",
- "version": "6.5.1",
+ "version": "6.6.1",
"source": {
"type": "git",
"url": "https://github.com/Codeception/phpunit-wrapper.git",
- "reference": "d78f9eb9c4300a5924cc27dee03e8c1a96fcf5f3"
+ "reference": "d0da25a98bcebeb15d97c2ad3b2de6166b6e7a0c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/d78f9eb9c4300a5924cc27dee03e8c1a96fcf5f3",
- "reference": "d78f9eb9c4300a5924cc27dee03e8c1a96fcf5f3",
+ "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/d0da25a98bcebeb15d97c2ad3b2de6166b6e7a0c",
+ "reference": "d0da25a98bcebeb15d97c2ad3b2de6166b6e7a0c",
"shasum": ""
},
"require": {
@@ -4910,7 +4912,7 @@
},
"require-dev": {
"codeception/specify": "*",
- "vlucas/phpdotenv": "^2.4"
+ "vlucas/phpdotenv": "^3.0"
},
"type": "library",
"autoload": {
@@ -4929,24 +4931,24 @@
}
],
"description": "PHPUnit classes used by Codeception",
- "time": "2019-01-13T10:34:55+00:00"
+ "time": "2019-02-26T20:47:39+00:00"
},
{
"name": "codeception/stub",
- "version": "2.0.4",
+ "version": "2.1.0",
"source": {
"type": "git",
"url": "https://github.com/Codeception/Stub.git",
- "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e"
+ "reference": "853657f988942f7afb69becf3fd0059f192c705a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Codeception/Stub/zipball/f50bc271f392a2836ff80690ce0c058efe1ae03e",
- "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e",
+ "url": "https://api.github.com/repos/Codeception/Stub/zipball/853657f988942f7afb69becf3fd0059f192c705a",
+ "reference": "853657f988942f7afb69becf3fd0059f192c705a",
"shasum": ""
},
"require": {
- "phpunit/phpunit": ">=4.8 <8.0"
+ "codeception/phpunit-wrapper": ">6.0.15 <6.1.0 | ^6.6.1 | ^7.7.1 | ^8.0.3"
},
"type": "library",
"autoload": {
@@ -4959,7 +4961,7 @@
"MIT"
],
"description": "Flexible Stub wrapper for PHPUnit's Mock Builder",
- "time": "2018-07-26T11:55:37+00:00"
+ "time": "2019-03-02T15:35:10+00:00"
},
{
"name": "consolidation/annotated-command",
@@ -5059,16 +5061,16 @@
},
{
"name": "consolidation/config",
- "version": "1.1.1",
+ "version": "1.2.1",
"source": {
"type": "git",
"url": "https://github.com/consolidation/config.git",
- "reference": "925231dfff32f05b787e1fddb265e789b939cf4c"
+ "reference": "cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/consolidation/config/zipball/925231dfff32f05b787e1fddb265e789b939cf4c",
- "reference": "925231dfff32f05b787e1fddb265e789b939cf4c",
+ "url": "https://api.github.com/repos/consolidation/config/zipball/cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1",
+ "reference": "cac1279bae7efb5c7fb2ca4c3ba4b8eb741a96c1",
"shasum": ""
},
"require": {
@@ -5077,9 +5079,9 @@
"php": ">=5.4.0"
},
"require-dev": {
- "g1a/composer-test-scenarios": "^1",
+ "g1a/composer-test-scenarios": "^3",
+ "php-coveralls/php-coveralls": "^1",
"phpunit/phpunit": "^5",
- "satooshi/php-coveralls": "^1.0",
"squizlabs/php_codesniffer": "2.*",
"symfony/console": "^2.5|^3|^4",
"symfony/yaml": "^2.8.11|^3|^4"
@@ -5089,6 +5091,33 @@
},
"type": "library",
"extra": {
+ "scenarios": {
+ "symfony4": {
+ "require-dev": {
+ "symfony/console": "^4.0"
+ },
+ "config": {
+ "platform": {
+ "php": "7.1.3"
+ }
+ }
+ },
+ "symfony2": {
+ "require-dev": {
+ "symfony/console": "^2.8",
+ "symfony/event-dispatcher": "^2.8",
+ "phpunit/phpunit": "^4.8.36"
+ },
+ "remove": [
+ "php-coveralls/php-coveralls"
+ ],
+ "config": {
+ "platform": {
+ "php": "5.4.8"
+ }
+ }
+ }
+ },
"branch-alias": {
"dev-master": "1.x-dev"
}
@@ -5109,7 +5138,7 @@
}
],
"description": "Provide configuration services for a commandline tool.",
- "time": "2018-10-24T17:55:35+00:00"
+ "time": "2019-03-03T19:37:04+00:00"
},
{
"name": "consolidation/log",
@@ -5259,16 +5288,16 @@
},
{
"name": "consolidation/robo",
- "version": "1.4.4",
+ "version": "1.4.6",
"source": {
"type": "git",
"url": "https://github.com/consolidation/Robo.git",
- "reference": "8bec6a6ea54a7d03d56552a4250c49dec3b3083d"
+ "reference": "d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/consolidation/Robo/zipball/8bec6a6ea54a7d03d56552a4250c49dec3b3083d",
- "reference": "8bec6a6ea54a7d03d56552a4250c49dec3b3083d",
+ "url": "https://api.github.com/repos/consolidation/Robo/zipball/d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3",
+ "reference": "d4805a1abbc730e9a6d64ede2eba56f91a2b4eb3",
"shasum": ""
},
"require": {
@@ -5363,7 +5392,7 @@
}
],
"description": "Modern task runner",
- "time": "2019-02-08T20:59:23+00:00"
+ "time": "2019-02-17T05:32:27+00:00"
},
{
"name": "consolidation/self-update",
@@ -6591,16 +6620,16 @@
},
{
"name": "magento/magento2-functional-testing-framework",
- "version": "2.3.13",
+ "version": "2.3.14",
"source": {
"type": "git",
"url": "https://github.com/magento/magento2-functional-testing-framework.git",
- "reference": "2c8a4c3557c9a8412eb2ea50ce3f69abc2f47ba1"
+ "reference": "b4002b3fe53884895921b44cf519d42918e3c7c6"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/2c8a4c3557c9a8412eb2ea50ce3f69abc2f47ba1",
- "reference": "2c8a4c3557c9a8412eb2ea50ce3f69abc2f47ba1",
+ "url": "https://api.github.com/repos/magento/magento2-functional-testing-framework/zipball/b4002b3fe53884895921b44cf519d42918e3c7c6",
+ "reference": "b4002b3fe53884895921b44cf519d42918e3c7c6",
"shasum": ""
},
"require": {
@@ -6660,7 +6689,7 @@
"magento",
"testing"
],
- "time": "2019-01-29T15:31:14+00:00"
+ "time": "2019-02-19T16:03:22+00:00"
},
{
"name": "mikey179/vfsStream",
@@ -7813,6 +7842,7 @@
"mock",
"xunit"
],
+ "abandoned": true,
"time": "2018-08-09T05:50:03+00:00"
},
{
@@ -8556,16 +8586,16 @@
},
{
"name": "symfony/browser-kit",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/browser-kit.git",
- "reference": "ee4462581eb54bf34b746e4a5d522a4f21620160"
+ "reference": "61d85c5af2fc058014c7c89504c3944e73a086f0"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ee4462581eb54bf34b746e4a5d522a4f21620160",
- "reference": "ee4462581eb54bf34b746e4a5d522a4f21620160",
+ "url": "https://api.github.com/repos/symfony/browser-kit/zipball/61d85c5af2fc058014c7c89504c3944e73a086f0",
+ "reference": "61d85c5af2fc058014c7c89504c3944e73a086f0",
"shasum": ""
},
"require": {
@@ -8609,20 +8639,20 @@
],
"description": "Symfony BrowserKit Component",
"homepage": "https://symfony.com",
- "time": "2019-01-16T21:31:25+00:00"
+ "time": "2019-02-23T15:17:42+00:00"
},
{
"name": "symfony/config",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
- "reference": "25a2e7abe0d97e70282537292e3df45cf6da7b98"
+ "reference": "7f70d79c7a24a94f8e98abb988049403a53d7b31"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/config/zipball/25a2e7abe0d97e70282537292e3df45cf6da7b98",
- "reference": "25a2e7abe0d97e70282537292e3df45cf6da7b98",
+ "url": "https://api.github.com/repos/symfony/config/zipball/7f70d79c7a24a94f8e98abb988049403a53d7b31",
+ "reference": "7f70d79c7a24a94f8e98abb988049403a53d7b31",
"shasum": ""
},
"require": {
@@ -8672,7 +8702,7 @@
],
"description": "Symfony Config Component",
"homepage": "https://symfony.com",
- "time": "2019-01-30T11:44:30+00:00"
+ "time": "2019-02-23T15:17:42+00:00"
},
{
"name": "symfony/contracts",
@@ -8744,16 +8774,16 @@
},
{
"name": "symfony/dependency-injection",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
- "reference": "72c14cbc0c27706b9b4c33b9cd7a280972ff4806"
+ "reference": "cdadb3765df7c89ac93628743913b92bb91f1704"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/72c14cbc0c27706b9b4c33b9cd7a280972ff4806",
- "reference": "72c14cbc0c27706b9b4c33b9cd7a280972ff4806",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/cdadb3765df7c89ac93628743913b92bb91f1704",
+ "reference": "cdadb3765df7c89ac93628743913b92bb91f1704",
"shasum": ""
},
"require": {
@@ -8813,20 +8843,20 @@
],
"description": "Symfony DependencyInjection Component",
"homepage": "https://symfony.com",
- "time": "2019-01-30T17:51:38+00:00"
+ "time": "2019-02-23T15:17:42+00:00"
},
{
"name": "symfony/dom-crawler",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/dom-crawler.git",
- "reference": "d8476760b04cdf7b499c8718aa437c20a9155103"
+ "reference": "53c97769814c80a84a8403efcf3ae7ae966d53bb"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d8476760b04cdf7b499c8718aa437c20a9155103",
- "reference": "d8476760b04cdf7b499c8718aa437c20a9155103",
+ "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/53c97769814c80a84a8403efcf3ae7ae966d53bb",
+ "reference": "53c97769814c80a84a8403efcf3ae7ae966d53bb",
"shasum": ""
},
"require": {
@@ -8870,20 +8900,20 @@
],
"description": "Symfony DomCrawler Component",
"homepage": "https://symfony.com",
- "time": "2019-01-16T20:35:37+00:00"
+ "time": "2019-02-23T15:17:42+00:00"
},
{
"name": "symfony/http-foundation",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/http-foundation.git",
- "reference": "8d2318b73e0a1bc75baa699d00ebe2ae8b595a39"
+ "reference": "850a667d6254ccf6c61d853407b16f21c4579c77"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8d2318b73e0a1bc75baa699d00ebe2ae8b595a39",
- "reference": "8d2318b73e0a1bc75baa699d00ebe2ae8b595a39",
+ "url": "https://api.github.com/repos/symfony/http-foundation/zipball/850a667d6254ccf6c61d853407b16f21c4579c77",
+ "reference": "850a667d6254ccf6c61d853407b16f21c4579c77",
"shasum": ""
},
"require": {
@@ -8924,20 +8954,20 @@
],
"description": "Symfony HttpFoundation Component",
"homepage": "https://symfony.com",
- "time": "2019-01-29T09:49:29+00:00"
+ "time": "2019-02-26T08:03:39+00:00"
},
{
"name": "symfony/options-resolver",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/options-resolver.git",
- "reference": "831b272963a8aa5a0613a1a7f013322d8161bbbb"
+ "reference": "3896e5a7d06fd15fa4947694c8dcdd371ff147d1"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/options-resolver/zipball/831b272963a8aa5a0613a1a7f013322d8161bbbb",
- "reference": "831b272963a8aa5a0613a1a7f013322d8161bbbb",
+ "url": "https://api.github.com/repos/symfony/options-resolver/zipball/3896e5a7d06fd15fa4947694c8dcdd371ff147d1",
+ "reference": "3896e5a7d06fd15fa4947694c8dcdd371ff147d1",
"shasum": ""
},
"require": {
@@ -8978,7 +9008,7 @@
"configuration",
"options"
],
- "time": "2019-01-16T21:31:25+00:00"
+ "time": "2019-02-23T15:17:42+00:00"
},
{
"name": "symfony/polyfill-php70",
@@ -9096,7 +9126,7 @@
},
{
"name": "symfony/stopwatch",
- "version": "v4.2.3",
+ "version": "v4.2.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/stopwatch.git",
@@ -9146,16 +9176,16 @@
},
{
"name": "symfony/yaml",
- "version": "v3.4.22",
+ "version": "v3.4.23",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
- "reference": "ba11776e9e6c15ad5759a07bffb15899bac75c2d"
+ "reference": "57f1ce82c997f5a8701b89ef970e36bb657fd09c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/ba11776e9e6c15ad5759a07bffb15899bac75c2d",
- "reference": "ba11776e9e6c15ad5759a07bffb15899bac75c2d",
+ "url": "https://api.github.com/repos/symfony/yaml/zipball/57f1ce82c997f5a8701b89ef970e36bb657fd09c",
+ "reference": "57f1ce82c997f5a8701b89ef970e36bb657fd09c",
"shasum": ""
},
"require": {
@@ -9201,7 +9231,7 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
- "time": "2019-01-16T10:59:17+00:00"
+ "time": "2019-02-23T15:06:07+00:00"
},
{
"name": "theseer/fdomdocument",
diff --git a/dev/tests/acceptance/RoboFile.php b/dev/tests/acceptance/RoboFile.php
deleted file mode 100644
index e6e9e591bbd8b..0000000000000
--- a/dev/tests/acceptance/RoboFile.php
+++ /dev/null
@@ -1,175 +0,0 @@
-getBaseCmd("build:project"));
- }
-
- /**
- * Generate all Tests in PHP OR Generate set of tests via passing array of tests
- *
- * @param array $tests
- * @param array $opts
- * @return \Robo\Result
- */
- function generateTests(array $tests, $opts = [
- 'config' => null,
- 'force' => false,
- 'nodes' => null,
- 'lines' => null,
- 'tests' => null
- ])
- {
- $baseCmd = $this->getBaseCmd("generate:tests");
-
- $mftfArgNames = ['config', 'nodes', 'lines', 'tests'];
- // append arguments to the end of the command
- foreach ($opts as $argName => $argValue) {
- if (in_array($argName, $mftfArgNames) && $argValue !== null) {
- $baseCmd .= " --$argName $argValue";
- }
- }
-
- // use a separate conditional for the force flag (casting bool to string in php is hard)
- if ($opts['force']) {
- $baseCmd .= ' --force';
- }
-
- return $this->taskExec($baseCmd)->args($tests)->run();
- }
-
- /**
- * Generate a suite based on name(s) passed in as args.
- *
- * @param array $args
- * @throws Exception
- * @return \Robo\Result
- */
- function generateSuite(array $args)
- {
- if (empty($args)) {
- throw new Exception("Please provide suite name(s) after generate:suite command");
- }
- $baseCmd = $this->getBaseCmd("generate:suite");
- return $this->taskExec($baseCmd)->args($args)->run();
- }
-
- /**
- * Run all Tests with the specified @group tag'.
- *
- * @param array $args
- * @return \Robo\Result
- */
- function group(array $args)
- {
- $args = array_merge($args, ['-k']);
- $baseCmd = $this->getBaseCmd("run:group");
- return $this->taskExec($baseCmd)->args($args)->run();
- }
-
- /**
- * Generate the HTML for the Allure report based on the Test XML output - Allure v1.4.X
- *
- * @return \Robo\Result
- */
- function allure1Generate()
- {
- return $this->_exec('allure generate tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-results'. DIRECTORY_SEPARATOR .' -o tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .'');
- }
-
- /**
- * Generate the HTML for the Allure report based on the Test XML output - Allure v2.3.X
- *
- * @return \Robo\Result
- */
- function allure2Generate()
- {
- return $this->_exec('allure generate tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-results'. DIRECTORY_SEPARATOR .' --output tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .' --clean');
- }
-
- /**
- * Open the HTML Allure report - Allure v1.4.X
- *
- * @return \Robo\Result
- */
- function allure1Open()
- {
- return $this->_exec('allure report open --report-dir tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .'');
- }
-
- /**
- * Open the HTML Allure report - Allure v2.3.X
- *
- * @return \Robo\Result
- */
- function allure2Open()
- {
- return $this->_exec('allure open --port 0 tests'. DIRECTORY_SEPARATOR .'_output'. DIRECTORY_SEPARATOR .'allure-report'. DIRECTORY_SEPARATOR .'');
- }
-
- /**
- * Generate and open the HTML Allure report - Allure v1.4.X
- *
- * @return \Robo\Result
- */
- function allure1Report()
- {
- $result1 = $this->allure1Generate();
-
- if ($result1->wasSuccessful()) {
- return $this->allure1Open();
- } else {
- return $result1;
- }
- }
-
- /**
- * Generate and open the HTML Allure report - Allure v2.3.X
- *
- * @return \Robo\Result
- */
- function allure2Report()
- {
- $result1 = $this->allure2Generate();
-
- if ($result1->wasSuccessful()) {
- return $this->allure2Open();
- } else {
- return $result1;
- }
- }
-
- /**
- * Private function for returning the formatted command for the passthru to mftf bin execution.
- *
- * @param string $command
- * @return string
- */
- private function getBaseCmd($command)
- {
- $this->writeln("\033[01;31m Use of robo will be deprecated with next major release, please use /vendor/bin/mftf $command \033[0m");
- chdir(__DIR__);
- return realpath('../../../vendor/bin/mftf') . " $command";
- }
-}
\ No newline at end of file
diff --git a/dev/tests/acceptance/composer.json b/dev/tests/acceptance/composer.json
deleted file mode 100755
index 83cad123f8568..0000000000000
--- a/dev/tests/acceptance/composer.json
+++ /dev/null
@@ -1,25 +0,0 @@
-{
- "description": "Magento 2 (Open Source) Functional Tests",
- "type": "project",
- "version": "1.0.0-dev",
- "license": [
- "OSL-3.0",
- "AFL-3.0"
- ],
- "config": {
- "sort-packages": true
- },
- "require": {
- "php": "~7.1.3||~7.2.0",
- "codeception/codeception": "~2.3.4 || ~2.4.0",
- "consolidation/robo": "^1.0.0",
- "vlucas/phpdotenv": "^2.4"
- },
- "autoload": {
- "psr-4": {
- "Magento\\": "tests/functional/Magento"
- },
- "files": ["tests/_bootstrap.php"]
- },
- "prefer-stable": true
-}
diff --git a/dev/tests/acceptance/composer.lock b/dev/tests/acceptance/composer.lock
deleted file mode 100644
index 5d2c2c5eb1769..0000000000000
--- a/dev/tests/acceptance/composer.lock
+++ /dev/null
@@ -1,3534 +0,0 @@
-{
- "_readme": [
- "This file locks the dependencies of your project to a known state",
- "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
- "This file is @generated automatically"
- ],
- "content-hash": "b93d599d375af66b29edfd8a35875e69",
- "packages": [
- {
- "name": "behat/gherkin",
- "version": "v4.6.0",
- "source": {
- "type": "git",
- "url": "https://github.com/Behat/Gherkin.git",
- "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Behat/Gherkin/zipball/ab0a02ea14893860bca00f225f5621d351a3ad07",
- "reference": "ab0a02ea14893860bca00f225f5621d351a3ad07",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.1"
- },
- "require-dev": {
- "phpunit/phpunit": "~4.5|~5",
- "symfony/phpunit-bridge": "~2.7|~3|~4",
- "symfony/yaml": "~2.3|~3|~4"
- },
- "suggest": {
- "symfony/yaml": "If you want to parse features, represented in YAML files"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.4-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Behat\\Gherkin": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Konstantin Kudryashov",
- "email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
- }
- ],
- "description": "Gherkin DSL parser for PHP 5.3",
- "homepage": "http://behat.org/",
- "keywords": [
- "BDD",
- "Behat",
- "Cucumber",
- "DSL",
- "gherkin",
- "parser"
- ],
- "time": "2019-01-16T14:22:17+00:00"
- },
- {
- "name": "codeception/codeception",
- "version": "2.4.5",
- "source": {
- "type": "git",
- "url": "https://github.com/Codeception/Codeception.git",
- "reference": "5fee32d5c82791548931cbc34806b4de6aa1abfc"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Codeception/Codeception/zipball/5fee32d5c82791548931cbc34806b4de6aa1abfc",
- "reference": "5fee32d5c82791548931cbc34806b4de6aa1abfc",
- "shasum": ""
- },
- "require": {
- "behat/gherkin": "^4.4.0",
- "codeception/phpunit-wrapper": "^6.0.9|^7.0.6",
- "codeception/stub": "^2.0",
- "ext-json": "*",
- "ext-mbstring": "*",
- "facebook/webdriver": ">=1.1.3 <2.0",
- "guzzlehttp/guzzle": ">=4.1.4 <7.0",
- "guzzlehttp/psr7": "~1.0",
- "php": ">=5.6.0 <8.0",
- "symfony/browser-kit": ">=2.7 <5.0",
- "symfony/console": ">=2.7 <5.0",
- "symfony/css-selector": ">=2.7 <5.0",
- "symfony/dom-crawler": ">=2.7 <5.0",
- "symfony/event-dispatcher": ">=2.7 <5.0",
- "symfony/finder": ">=2.7 <5.0",
- "symfony/yaml": ">=2.7 <5.0"
- },
- "require-dev": {
- "codeception/specify": "~0.3",
- "facebook/graph-sdk": "~5.3",
- "flow/jsonpath": "~0.2",
- "monolog/monolog": "~1.8",
- "pda/pheanstalk": "~3.0",
- "php-amqplib/php-amqplib": "~2.4",
- "predis/predis": "^1.0",
- "squizlabs/php_codesniffer": "~2.0",
- "symfony/process": ">=2.7 <5.0",
- "vlucas/phpdotenv": "^2.4.0"
- },
- "suggest": {
- "aws/aws-sdk-php": "For using AWS Auth in REST module and Queue module",
- "codeception/phpbuiltinserver": "Start and stop PHP built-in web server for your tests",
- "codeception/specify": "BDD-style code blocks",
- "codeception/verify": "BDD-style assertions",
- "flow/jsonpath": "For using JSONPath in REST module",
- "league/factory-muffin": "For DataFactory module",
- "league/factory-muffin-faker": "For Faker support in DataFactory module",
- "phpseclib/phpseclib": "for SFTP option in FTP Module",
- "stecman/symfony-console-completion": "For BASH autocompletion",
- "symfony/phpunit-bridge": "For phpunit-bridge support"
- },
- "bin": [
- "codecept"
- ],
- "type": "library",
- "extra": {
- "branch-alias": []
- },
- "autoload": {
- "psr-4": {
- "Codeception\\": "src\\Codeception",
- "Codeception\\Extension\\": "ext"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Michael Bodnarchuk",
- "email": "davert@mail.ua",
- "homepage": "http://codegyre.com"
- }
- ],
- "description": "BDD-style testing framework",
- "homepage": "http://codeception.com/",
- "keywords": [
- "BDD",
- "TDD",
- "acceptance testing",
- "functional testing",
- "unit testing"
- ],
- "time": "2018-08-01T07:21:49+00:00"
- },
- {
- "name": "codeception/phpunit-wrapper",
- "version": "7.6.1",
- "source": {
- "type": "git",
- "url": "https://github.com/Codeception/phpunit-wrapper.git",
- "reference": "ed4b12beb167dc2ecea293b4f6df6c20ce8d280f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Codeception/phpunit-wrapper/zipball/ed4b12beb167dc2ecea293b4f6df6c20ce8d280f",
- "reference": "ed4b12beb167dc2ecea293b4f6df6c20ce8d280f",
- "shasum": ""
- },
- "require": {
- "phpunit/php-code-coverage": "^6.0",
- "phpunit/phpunit": ">=7.1 <7.6",
- "sebastian/comparator": "^3.0",
- "sebastian/diff": "^3.0"
- },
- "require-dev": {
- "codeception/specify": "*",
- "vlucas/phpdotenv": "^2.4"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Codeception\\PHPUnit\\": "src\\"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Davert",
- "email": "davert.php@resend.cc"
- }
- ],
- "description": "PHPUnit classes used by Codeception",
- "time": "2019-01-13T10:34:39+00:00"
- },
- {
- "name": "codeception/stub",
- "version": "2.0.4",
- "source": {
- "type": "git",
- "url": "https://github.com/Codeception/Stub.git",
- "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/Codeception/Stub/zipball/f50bc271f392a2836ff80690ce0c058efe1ae03e",
- "reference": "f50bc271f392a2836ff80690ce0c058efe1ae03e",
- "shasum": ""
- },
- "require": {
- "phpunit/phpunit": ">=4.8 <8.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Codeception\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "Flexible Stub wrapper for PHPUnit's Mock Builder",
- "time": "2018-07-26T11:55:37+00:00"
- },
- {
- "name": "consolidation/annotated-command",
- "version": "2.11.2",
- "source": {
- "type": "git",
- "url": "https://github.com/consolidation/annotated-command.git",
- "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/consolidation/annotated-command/zipball/004af26391cd7d1cd04b0ac736dc1324d1b4f572",
- "reference": "004af26391cd7d1cd04b0ac736dc1324d1b4f572",
- "shasum": ""
- },
- "require": {
- "consolidation/output-formatters": "^3.4",
- "php": ">=5.4.5",
- "psr/log": "^1",
- "symfony/console": "^2.8|^3|^4",
- "symfony/event-dispatcher": "^2.5|^3|^4",
- "symfony/finder": "^2.5|^3|^4"
- },
- "require-dev": {
- "g1a/composer-test-scenarios": "^3",
- "php-coveralls/php-coveralls": "^1",
- "phpunit/phpunit": "^6",
- "squizlabs/php_codesniffer": "^2.7"
- },
- "type": "library",
- "extra": {
- "scenarios": {
- "symfony4": {
- "require": {
- "symfony/console": "^4.0"
- },
- "config": {
- "platform": {
- "php": "7.1.3"
- }
- }
- },
- "symfony2": {
- "require": {
- "symfony/console": "^2.8"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.36"
- },
- "remove": [
- "php-coveralls/php-coveralls"
- ],
- "config": {
- "platform": {
- "php": "5.4.8"
- }
- },
- "scenario-options": {
- "create-lockfile": "false"
- }
- },
- "phpunit4": {
- "require-dev": {
- "phpunit/phpunit": "^4.8.36"
- },
- "remove": [
- "php-coveralls/php-coveralls"
- ],
- "config": {
- "platform": {
- "php": "5.4.8"
- }
- }
- }
- },
- "branch-alias": {
- "dev-master": "2.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Consolidation\\AnnotatedCommand\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Greg Anderson",
- "email": "greg.1.anderson@greenknowe.org"
- }
- ],
- "description": "Initialize Symfony Console commands from annotated command class methods.",
- "time": "2019-02-02T02:29:53+00:00"
- },
- {
- "name": "consolidation/config",
- "version": "1.1.1",
- "source": {
- "type": "git",
- "url": "https://github.com/consolidation/config.git",
- "reference": "925231dfff32f05b787e1fddb265e789b939cf4c"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/consolidation/config/zipball/925231dfff32f05b787e1fddb265e789b939cf4c",
- "reference": "925231dfff32f05b787e1fddb265e789b939cf4c",
- "shasum": ""
- },
- "require": {
- "dflydev/dot-access-data": "^1.1.0",
- "grasmash/expander": "^1",
- "php": ">=5.4.0"
- },
- "require-dev": {
- "g1a/composer-test-scenarios": "^1",
- "phpunit/phpunit": "^5",
- "satooshi/php-coveralls": "^1.0",
- "squizlabs/php_codesniffer": "2.*",
- "symfony/console": "^2.5|^3|^4",
- "symfony/yaml": "^2.8.11|^3|^4"
- },
- "suggest": {
- "symfony/yaml": "Required to use Consolidation\\Config\\Loader\\YamlConfigLoader"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Consolidation\\Config\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Greg Anderson",
- "email": "greg.1.anderson@greenknowe.org"
- }
- ],
- "description": "Provide configuration services for a commandline tool.",
- "time": "2018-10-24T17:55:35+00:00"
- },
- {
- "name": "consolidation/log",
- "version": "1.1.1",
- "source": {
- "type": "git",
- "url": "https://github.com/consolidation/log.git",
- "reference": "b2e887325ee90abc96b0a8b7b474cd9e7c896e3a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/consolidation/log/zipball/b2e887325ee90abc96b0a8b7b474cd9e7c896e3a",
- "reference": "b2e887325ee90abc96b0a8b7b474cd9e7c896e3a",
- "shasum": ""
- },
- "require": {
- "php": ">=5.4.5",
- "psr/log": "^1.0",
- "symfony/console": "^2.8|^3|^4"
- },
- "require-dev": {
- "g1a/composer-test-scenarios": "^3",
- "php-coveralls/php-coveralls": "^1",
- "phpunit/phpunit": "^6",
- "squizlabs/php_codesniffer": "^2"
- },
- "type": "library",
- "extra": {
- "scenarios": {
- "symfony4": {
- "require": {
- "symfony/console": "^4.0"
- },
- "config": {
- "platform": {
- "php": "7.1.3"
- }
- }
- },
- "symfony2": {
- "require": {
- "symfony/console": "^2.8"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.36"
- },
- "remove": [
- "php-coveralls/php-coveralls"
- ],
- "config": {
- "platform": {
- "php": "5.4.8"
- }
- }
- },
- "phpunit4": {
- "require-dev": {
- "phpunit/phpunit": "^4.8.36"
- },
- "remove": [
- "php-coveralls/php-coveralls"
- ],
- "config": {
- "platform": {
- "php": "5.4.8"
- }
- }
- }
- },
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Consolidation\\Log\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Greg Anderson",
- "email": "greg.1.anderson@greenknowe.org"
- }
- ],
- "description": "Improved Psr-3 / Psr\\Log logger based on Symfony Console components.",
- "time": "2019-01-01T17:30:51+00:00"
- },
- {
- "name": "consolidation/output-formatters",
- "version": "3.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/consolidation/output-formatters.git",
- "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/consolidation/output-formatters/zipball/a942680232094c4a5b21c0b7e54c20cce623ae19",
- "reference": "a942680232094c4a5b21c0b7e54c20cce623ae19",
- "shasum": ""
- },
- "require": {
- "dflydev/dot-access-data": "^1.1.0",
- "php": ">=5.4.0",
- "symfony/console": "^2.8|^3|^4",
- "symfony/finder": "^2.5|^3|^4"
- },
- "require-dev": {
- "g1a/composer-test-scenarios": "^2",
- "phpunit/phpunit": "^5.7.27",
- "satooshi/php-coveralls": "^2",
- "squizlabs/php_codesniffer": "^2.7",
- "symfony/console": "3.2.3",
- "symfony/var-dumper": "^2.8|^3|^4",
- "victorjonsson/markdowndocs": "^1.3"
- },
- "suggest": {
- "symfony/var-dumper": "For using the var_dump formatter"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Consolidation\\OutputFormatters\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Greg Anderson",
- "email": "greg.1.anderson@greenknowe.org"
- }
- ],
- "description": "Format text by applying transformations provided by plug-in formatters.",
- "time": "2018-10-19T22:35:38+00:00"
- },
- {
- "name": "consolidation/robo",
- "version": "1.4.4",
- "source": {
- "type": "git",
- "url": "https://github.com/consolidation/Robo.git",
- "reference": "8bec6a6ea54a7d03d56552a4250c49dec3b3083d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/consolidation/Robo/zipball/8bec6a6ea54a7d03d56552a4250c49dec3b3083d",
- "reference": "8bec6a6ea54a7d03d56552a4250c49dec3b3083d",
- "shasum": ""
- },
- "require": {
- "consolidation/annotated-command": "^2.10.2",
- "consolidation/config": "^1.0.10",
- "consolidation/log": "~1",
- "consolidation/output-formatters": "^3.1.13",
- "consolidation/self-update": "^1",
- "grasmash/yaml-expander": "^1.3",
- "league/container": "^2.2",
- "php": ">=5.5.0",
- "symfony/console": "^2.8|^3|^4",
- "symfony/event-dispatcher": "^2.5|^3|^4",
- "symfony/filesystem": "^2.5|^3|^4",
- "symfony/finder": "^2.5|^3|^4",
- "symfony/process": "^2.5|^3|^4"
- },
- "replace": {
- "codegyre/robo": "< 1.0"
- },
- "require-dev": {
- "codeception/aspect-mock": "^1|^2.1.1",
- "codeception/base": "^2.3.7",
- "codeception/verify": "^0.3.2",
- "g1a/composer-test-scenarios": "^3",
- "goaop/framework": "~2.1.2",
- "goaop/parser-reflection": "^1.1.0",
- "natxet/cssmin": "3.0.4",
- "nikic/php-parser": "^3.1.5",
- "patchwork/jsqueeze": "~2",
- "pear/archive_tar": "^1.4.4",
- "php-coveralls/php-coveralls": "^1",
- "phpunit/php-code-coverage": "~2|~4",
- "squizlabs/php_codesniffer": "^2.8"
- },
- "suggest": {
- "henrikbjorn/lurker": "For monitoring filesystem changes in taskWatch",
- "natxet/CssMin": "For minifying CSS files in taskMinify",
- "patchwork/jsqueeze": "For minifying JS files in taskMinify",
- "pear/archive_tar": "Allows tar archives to be created and extracted in taskPack and taskExtract, respectively."
- },
- "bin": [
- "robo"
- ],
- "type": "library",
- "extra": {
- "scenarios": {
- "symfony4": {
- "require": {
- "symfony/console": "^4"
- },
- "config": {
- "platform": {
- "php": "7.1.3"
- }
- }
- },
- "symfony2": {
- "require": {
- "symfony/console": "^2.8"
- },
- "remove": [
- "goaop/framework"
- ],
- "config": {
- "platform": {
- "php": "5.5.9"
- }
- },
- "scenario-options": {
- "create-lockfile": "false"
- }
- }
- },
- "branch-alias": {
- "dev-master": "2.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Robo\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Davert",
- "email": "davert.php@resend.cc"
- }
- ],
- "description": "Modern task runner",
- "time": "2019-02-08T20:59:23+00:00"
- },
- {
- "name": "consolidation/self-update",
- "version": "1.1.5",
- "source": {
- "type": "git",
- "url": "https://github.com/consolidation/self-update.git",
- "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/consolidation/self-update/zipball/a1c273b14ce334789825a09d06d4c87c0a02ad54",
- "reference": "a1c273b14ce334789825a09d06d4c87c0a02ad54",
- "shasum": ""
- },
- "require": {
- "php": ">=5.5.0",
- "symfony/console": "^2.8|^3|^4",
- "symfony/filesystem": "^2.5|^3|^4"
- },
- "bin": [
- "scripts/release"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "SelfUpdate\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Greg Anderson",
- "email": "greg.1.anderson@greenknowe.org"
- },
- {
- "name": "Alexander Menk",
- "email": "menk@mestrona.net"
- }
- ],
- "description": "Provides a self:update command for Symfony Console applications.",
- "time": "2018-10-28T01:52:03+00:00"
- },
- {
- "name": "container-interop/container-interop",
- "version": "1.2.0",
- "source": {
- "type": "git",
- "url": "https://github.com/container-interop/container-interop.git",
- "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/container-interop/container-interop/zipball/79cbf1341c22ec75643d841642dd5d6acd83bdb8",
- "reference": "79cbf1341c22ec75643d841642dd5d6acd83bdb8",
- "shasum": ""
- },
- "require": {
- "psr/container": "^1.0"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "Interop\\Container\\": "src/Interop/Container/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "Promoting the interoperability of container objects (DIC, SL, etc.)",
- "homepage": "https://github.com/container-interop/container-interop",
- "time": "2017-02-14T19:40:03+00:00"
- },
- {
- "name": "dflydev/dot-access-data",
- "version": "v1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/dflydev/dflydev-dot-access-data.git",
- "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/dflydev/dflydev-dot-access-data/zipball/3fbd874921ab2c041e899d044585a2ab9795df8a",
- "reference": "3fbd874921ab2c041e899d044585a2ab9795df8a",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.2"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Dflydev\\DotAccessData": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Dragonfly Development Inc.",
- "email": "info@dflydev.com",
- "homepage": "http://dflydev.com"
- },
- {
- "name": "Beau Simensen",
- "email": "beau@dflydev.com",
- "homepage": "http://beausimensen.com"
- },
- {
- "name": "Carlos Frutos",
- "email": "carlos@kiwing.it",
- "homepage": "https://github.com/cfrutos"
- }
- ],
- "description": "Given a deep data structure, access data by dot notation.",
- "homepage": "https://github.com/dflydev/dflydev-dot-access-data",
- "keywords": [
- "access",
- "data",
- "dot",
- "notation"
- ],
- "time": "2017-01-20T21:14:22+00:00"
- },
- {
- "name": "doctrine/instantiator",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/doctrine/instantiator.git",
- "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
- "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda",
- "shasum": ""
- },
- "require": {
- "php": "^7.1"
- },
- "require-dev": {
- "athletic/athletic": "~0.1.8",
- "ext-pdo": "*",
- "ext-phar": "*",
- "phpunit/phpunit": "^6.2.3",
- "squizlabs/php_codesniffer": "^3.0.2"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.2.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Marco Pivetta",
- "email": "ocramius@gmail.com",
- "homepage": "http://ocramius.github.com/"
- }
- ],
- "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors",
- "homepage": "https://github.com/doctrine/instantiator",
- "keywords": [
- "constructor",
- "instantiate"
- ],
- "time": "2017-07-22T11:58:36+00:00"
- },
- {
- "name": "facebook/webdriver",
- "version": "1.6.0",
- "source": {
- "type": "git",
- "url": "https://github.com/facebook/php-webdriver.git",
- "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/facebook/php-webdriver/zipball/bd8c740097eb9f2fc3735250fc1912bc811a954e",
- "reference": "bd8c740097eb9f2fc3735250fc1912bc811a954e",
- "shasum": ""
- },
- "require": {
- "ext-curl": "*",
- "ext-json": "*",
- "ext-mbstring": "*",
- "ext-zip": "*",
- "php": "^5.6 || ~7.0",
- "symfony/process": "^2.8 || ^3.1 || ^4.0"
- },
- "require-dev": {
- "friendsofphp/php-cs-fixer": "^2.0",
- "jakub-onderka/php-parallel-lint": "^0.9.2",
- "php-coveralls/php-coveralls": "^2.0",
- "php-mock/php-mock-phpunit": "^1.1",
- "phpunit/phpunit": "^5.7",
- "sebastian/environment": "^1.3.4 || ^2.0 || ^3.0",
- "squizlabs/php_codesniffer": "^2.6",
- "symfony/var-dumper": "^3.3 || ^4.0"
- },
- "suggest": {
- "ext-SimpleXML": "For Firefox profile creation"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-community": "1.5-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Facebook\\WebDriver\\": "lib/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "Apache-2.0"
- ],
- "description": "A PHP client for Selenium WebDriver",
- "homepage": "https://github.com/facebook/php-webdriver",
- "keywords": [
- "facebook",
- "php",
- "selenium",
- "webdriver"
- ],
- "time": "2018-05-16T17:37:13+00:00"
- },
- {
- "name": "grasmash/expander",
- "version": "1.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/grasmash/expander.git",
- "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/grasmash/expander/zipball/95d6037344a4be1dd5f8e0b0b2571a28c397578f",
- "reference": "95d6037344a4be1dd5f8e0b0b2571a28c397578f",
- "shasum": ""
- },
- "require": {
- "dflydev/dot-access-data": "^1.1.0",
- "php": ">=5.4"
- },
- "require-dev": {
- "greg-1-anderson/composer-test-scenarios": "^1",
- "phpunit/phpunit": "^4|^5.5.4",
- "satooshi/php-coveralls": "^1.0.2|dev-master",
- "squizlabs/php_codesniffer": "^2.7"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Grasmash\\Expander\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Matthew Grasmick"
- }
- ],
- "description": "Expands internal property references in PHP arrays file.",
- "time": "2017-12-21T22:14:55+00:00"
- },
- {
- "name": "grasmash/yaml-expander",
- "version": "1.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/grasmash/yaml-expander.git",
- "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/grasmash/yaml-expander/zipball/3f0f6001ae707a24f4d9733958d77d92bf9693b1",
- "reference": "3f0f6001ae707a24f4d9733958d77d92bf9693b1",
- "shasum": ""
- },
- "require": {
- "dflydev/dot-access-data": "^1.1.0",
- "php": ">=5.4",
- "symfony/yaml": "^2.8.11|^3|^4"
- },
- "require-dev": {
- "greg-1-anderson/composer-test-scenarios": "^1",
- "phpunit/phpunit": "^4.8|^5.5.4",
- "satooshi/php-coveralls": "^1.0.2|dev-master",
- "squizlabs/php_codesniffer": "^2.7"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Grasmash\\YamlExpander\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Matthew Grasmick"
- }
- ],
- "description": "Expands internal property references in a yaml file.",
- "time": "2017-12-16T16:06:03+00:00"
- },
- {
- "name": "guzzlehttp/guzzle",
- "version": "6.3.3",
- "source": {
- "type": "git",
- "url": "https://github.com/guzzle/guzzle.git",
- "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
- "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
- "shasum": ""
- },
- "require": {
- "guzzlehttp/promises": "^1.0",
- "guzzlehttp/psr7": "^1.4",
- "php": ">=5.5"
- },
- "require-dev": {
- "ext-curl": "*",
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
- "psr/log": "^1.0"
- },
- "suggest": {
- "psr/log": "Required for using the Log middleware"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "6.3-dev"
- }
- },
- "autoload": {
- "files": [
- "src/functions_include.php"
- ],
- "psr-4": {
- "GuzzleHttp\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Michael Dowling",
- "email": "mtdowling@gmail.com",
- "homepage": "https://github.com/mtdowling"
- }
- ],
- "description": "Guzzle is a PHP HTTP client library",
- "homepage": "http://guzzlephp.org/",
- "keywords": [
- "client",
- "curl",
- "framework",
- "http",
- "http client",
- "rest",
- "web service"
- ],
- "time": "2018-04-22T15:46:56+00:00"
- },
- {
- "name": "guzzlehttp/promises",
- "version": "v1.3.1",
- "source": {
- "type": "git",
- "url": "https://github.com/guzzle/promises.git",
- "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
- "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
- "shasum": ""
- },
- "require": {
- "php": ">=5.5.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.4-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "GuzzleHttp\\Promise\\": "src/"
- },
- "files": [
- "src/functions_include.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Michael Dowling",
- "email": "mtdowling@gmail.com",
- "homepage": "https://github.com/mtdowling"
- }
- ],
- "description": "Guzzle promises library",
- "keywords": [
- "promise"
- ],
- "time": "2016-12-20T10:07:11+00:00"
- },
- {
- "name": "guzzlehttp/psr7",
- "version": "1.5.2",
- "source": {
- "type": "git",
- "url": "https://github.com/guzzle/psr7.git",
- "reference": "9f83dded91781a01c63574e387eaa769be769115"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115",
- "reference": "9f83dded91781a01c63574e387eaa769be769115",
- "shasum": ""
- },
- "require": {
- "php": ">=5.4.0",
- "psr/http-message": "~1.0",
- "ralouphie/getallheaders": "^2.0.5"
- },
- "provide": {
- "psr/http-message-implementation": "1.0"
- },
- "require-dev": {
- "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.5-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "GuzzleHttp\\Psr7\\": "src/"
- },
- "files": [
- "src/functions_include.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Michael Dowling",
- "email": "mtdowling@gmail.com",
- "homepage": "https://github.com/mtdowling"
- },
- {
- "name": "Tobias Schultze",
- "homepage": "https://github.com/Tobion"
- }
- ],
- "description": "PSR-7 message implementation that also provides common utility methods",
- "keywords": [
- "http",
- "message",
- "psr-7",
- "request",
- "response",
- "stream",
- "uri",
- "url"
- ],
- "time": "2018-12-04T20:46:45+00:00"
- },
- {
- "name": "league/container",
- "version": "2.4.1",
- "source": {
- "type": "git",
- "url": "https://github.com/thephpleague/container.git",
- "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/thephpleague/container/zipball/43f35abd03a12977a60ffd7095efd6a7808488c0",
- "reference": "43f35abd03a12977a60ffd7095efd6a7808488c0",
- "shasum": ""
- },
- "require": {
- "container-interop/container-interop": "^1.2",
- "php": "^5.4.0 || ^7.0"
- },
- "provide": {
- "container-interop/container-interop-implementation": "^1.2",
- "psr/container-implementation": "^1.0"
- },
- "replace": {
- "orno/di": "~2.0"
- },
- "require-dev": {
- "phpunit/phpunit": "4.*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-2.x": "2.x-dev",
- "dev-1.x": "1.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "League\\Container\\": "src"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Phil Bennett",
- "email": "philipobenito@gmail.com",
- "homepage": "http://www.philipobenito.com",
- "role": "Developer"
- }
- ],
- "description": "A fast and intuitive dependency injection container.",
- "homepage": "https://github.com/thephpleague/container",
- "keywords": [
- "container",
- "dependency",
- "di",
- "injection",
- "league",
- "provider",
- "service"
- ],
- "time": "2017-05-10T09:20:27+00:00"
- },
- {
- "name": "myclabs/deep-copy",
- "version": "1.8.1",
- "source": {
- "type": "git",
- "url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8",
- "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8",
- "shasum": ""
- },
- "require": {
- "php": "^7.1"
- },
- "replace": {
- "myclabs/deep-copy": "self.version"
- },
- "require-dev": {
- "doctrine/collections": "^1.0",
- "doctrine/common": "^2.6",
- "phpunit/phpunit": "^7.1"
- },
- "type": "library",
- "autoload": {
- "psr-4": {
- "DeepCopy\\": "src/DeepCopy/"
- },
- "files": [
- "src/DeepCopy/deep_copy.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "description": "Create deep copies (clones) of your objects",
- "keywords": [
- "clone",
- "copy",
- "duplicate",
- "object",
- "object graph"
- ],
- "time": "2018-06-11T23:09:50+00:00"
- },
- {
- "name": "phar-io/manifest",
- "version": "1.0.3",
- "source": {
- "type": "git",
- "url": "https://github.com/phar-io/manifest.git",
- "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phar-io/manifest/zipball/7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
- "reference": "7761fcacf03b4d4f16e7ccb606d4879ca431fcf4",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-phar": "*",
- "phar-io/version": "^2.0",
- "php": "^5.6 || ^7.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Arne Blankerts",
- "email": "arne@blankerts.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Heuer",
- "email": "sebastian@phpeople.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "Developer"
- }
- ],
- "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
- "time": "2018-07-08T19:23:20+00:00"
- },
- {
- "name": "phar-io/version",
- "version": "2.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phar-io/version.git",
- "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phar-io/version/zipball/45a2ec53a73c70ce41d55cedef9063630abaf1b6",
- "reference": "45a2ec53a73c70ce41d55cedef9063630abaf1b6",
- "shasum": ""
- },
- "require": {
- "php": "^5.6 || ^7.0"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Arne Blankerts",
- "email": "arne@blankerts.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Heuer",
- "email": "sebastian@phpeople.de",
- "role": "Developer"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "Developer"
- }
- ],
- "description": "Library for handling version information and constraints",
- "time": "2018-07-08T19:19:57+00:00"
- },
- {
- "name": "phpdocumentor/reflection-common",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionCommon.git",
- "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionCommon/zipball/21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
- "reference": "21bdeb5f65d7ebf9f43b1b25d404f87deab5bfb6",
- "shasum": ""
- },
- "require": {
- "php": ">=5.5"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.6"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jaap van Otterdijk",
- "email": "opensource@ijaap.nl"
- }
- ],
- "description": "Common reflection classes used by phpdocumentor to reflect the code structure",
- "homepage": "http://www.phpdoc.org",
- "keywords": [
- "FQSEN",
- "phpDocumentor",
- "phpdoc",
- "reflection",
- "static analysis"
- ],
- "time": "2017-09-11T18:02:19+00:00"
- },
- {
- "name": "phpdocumentor/reflection-docblock",
- "version": "4.3.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git",
- "reference": "94fd0001232e47129dd3504189fa1c7225010d08"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08",
- "reference": "94fd0001232e47129dd3504189fa1c7225010d08",
- "shasum": ""
- },
- "require": {
- "php": "^7.0",
- "phpdocumentor/reflection-common": "^1.0.0",
- "phpdocumentor/type-resolver": "^0.4.0",
- "webmozart/assert": "^1.0"
- },
- "require-dev": {
- "doctrine/instantiator": "~1.0.5",
- "mockery/mockery": "^1.0",
- "phpunit/phpunit": "^6.4"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.",
- "time": "2017-11-30T07:14:17+00:00"
- },
- {
- "name": "phpdocumentor/type-resolver",
- "version": "0.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpDocumentor/TypeResolver.git",
- "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/9c977708995954784726e25d0cd1dddf4e65b0f7",
- "reference": "9c977708995954784726e25d0cd1dddf4e65b0f7",
- "shasum": ""
- },
- "require": {
- "php": "^5.5 || ^7.0",
- "phpdocumentor/reflection-common": "^1.0"
- },
- "require-dev": {
- "mockery/mockery": "^0.9.4",
- "phpunit/phpunit": "^5.2||^4.8.24"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "phpDocumentor\\Reflection\\": [
- "src/"
- ]
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Mike van Riel",
- "email": "me@mikevanriel.com"
- }
- ],
- "time": "2017-07-14T14:27:02+00:00"
- },
- {
- "name": "phpspec/prophecy",
- "version": "1.8.0",
- "source": {
- "type": "git",
- "url": "https://github.com/phpspec/prophecy.git",
- "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
- "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06",
- "shasum": ""
- },
- "require": {
- "doctrine/instantiator": "^1.0.2",
- "php": "^5.3|^7.0",
- "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
- "sebastian/comparator": "^1.1|^2.0|^3.0",
- "sebastian/recursion-context": "^1.0|^2.0|^3.0"
- },
- "require-dev": {
- "phpspec/phpspec": "^2.5|^3.2",
- "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.8.x-dev"
- }
- },
- "autoload": {
- "psr-0": {
- "Prophecy\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Konstantin Kudryashov",
- "email": "ever.zet@gmail.com",
- "homepage": "http://everzet.com"
- },
- {
- "name": "Marcello Duarte",
- "email": "marcello.duarte@gmail.com"
- }
- ],
- "description": "Highly opinionated mocking framework for PHP 5.3+",
- "homepage": "https://github.com/phpspec/prophecy",
- "keywords": [
- "Double",
- "Dummy",
- "fake",
- "mock",
- "spy",
- "stub"
- ],
- "time": "2018-08-05T17:53:17+00:00"
- },
- {
- "name": "phpunit/php-code-coverage",
- "version": "6.1.4",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
- "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
- "reference": "807e6013b00af69b6c5d9ceb4282d0393dbb9d8d",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-xmlwriter": "*",
- "php": "^7.1",
- "phpunit/php-file-iterator": "^2.0",
- "phpunit/php-text-template": "^1.2.1",
- "phpunit/php-token-stream": "^3.0",
- "sebastian/code-unit-reverse-lookup": "^1.0.1",
- "sebastian/environment": "^3.1 || ^4.0",
- "sebastian/version": "^2.0.1",
- "theseer/tokenizer": "^1.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^7.0"
- },
- "suggest": {
- "ext-xdebug": "^2.6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "6.1-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
- "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
- "keywords": [
- "coverage",
- "testing",
- "xunit"
- ],
- "time": "2018-10-31T16:06:48+00:00"
- },
- {
- "name": "phpunit/php-file-iterator",
- "version": "2.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
- "reference": "050bedf145a257b1ff02746c31894800e5122946"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/050bedf145a257b1ff02746c31894800e5122946",
- "reference": "050bedf145a257b1ff02746c31894800e5122946",
- "shasum": ""
- },
- "require": {
- "php": "^7.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^7.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "FilterIterator implementation that filters files based on a list of suffixes.",
- "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
- "keywords": [
- "filesystem",
- "iterator"
- ],
- "time": "2018-09-13T20:33:42+00:00"
- },
- {
- "name": "phpunit/php-text-template",
- "version": "1.2.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-text-template.git",
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
- "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Simple template engine.",
- "homepage": "https://github.com/sebastianbergmann/php-text-template/",
- "keywords": [
- "template"
- ],
- "time": "2015-06-21T13:50:34+00:00"
- },
- {
- "name": "phpunit/php-timer",
- "version": "2.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-timer.git",
- "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f",
- "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f",
- "shasum": ""
- },
- "require": {
- "php": "^7.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^7.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Utility class for timing",
- "homepage": "https://github.com/sebastianbergmann/php-timer/",
- "keywords": [
- "timer"
- ],
- "time": "2018-02-01T13:07:23+00:00"
- },
- {
- "name": "phpunit/php-token-stream",
- "version": "3.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/php-token-stream.git",
- "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18",
- "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18",
- "shasum": ""
- },
- "require": {
- "ext-tokenizer": "*",
- "php": "^7.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^7.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Wrapper around PHP's tokenizer extension.",
- "homepage": "https://github.com/sebastianbergmann/php-token-stream/",
- "keywords": [
- "tokenizer"
- ],
- "time": "2018-10-30T05:52:18+00:00"
- },
- {
- "name": "phpunit/phpunit",
- "version": "7.5.4",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "2896657da5fb237bc316bdfc18c2650efeee0dc0"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2896657da5fb237bc316bdfc18c2650efeee0dc0",
- "reference": "2896657da5fb237bc316bdfc18c2650efeee0dc0",
- "shasum": ""
- },
- "require": {
- "doctrine/instantiator": "^1.1",
- "ext-dom": "*",
- "ext-json": "*",
- "ext-libxml": "*",
- "ext-mbstring": "*",
- "ext-xml": "*",
- "myclabs/deep-copy": "^1.7",
- "phar-io/manifest": "^1.0.2",
- "phar-io/version": "^2.0",
- "php": "^7.1",
- "phpspec/prophecy": "^1.7",
- "phpunit/php-code-coverage": "^6.0.7",
- "phpunit/php-file-iterator": "^2.0.1",
- "phpunit/php-text-template": "^1.2.1",
- "phpunit/php-timer": "^2.0",
- "sebastian/comparator": "^3.0",
- "sebastian/diff": "^3.0",
- "sebastian/environment": "^4.0",
- "sebastian/exporter": "^3.1",
- "sebastian/global-state": "^2.0",
- "sebastian/object-enumerator": "^3.0.3",
- "sebastian/resource-operations": "^2.0",
- "sebastian/version": "^2.0.1"
- },
- "conflict": {
- "phpunit/phpunit-mock-objects": "*"
- },
- "require-dev": {
- "ext-pdo": "*"
- },
- "suggest": {
- "ext-soap": "*",
- "ext-xdebug": "*",
- "phpunit/php-invoker": "^2.0"
- },
- "bin": [
- "phpunit"
- ],
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "7.5-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "The PHP Unit Testing framework.",
- "homepage": "https://phpunit.de/",
- "keywords": [
- "phpunit",
- "testing",
- "xunit"
- ],
- "time": "2019-02-07T14:15:04+00:00"
- },
- {
- "name": "psr/container",
- "version": "1.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/container.git",
- "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
- "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Container\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "Common Container Interface (PHP FIG PSR-11)",
- "homepage": "https://github.com/php-fig/container",
- "keywords": [
- "PSR-11",
- "container",
- "container-interface",
- "container-interop",
- "psr"
- ],
- "time": "2017-02-14T16:28:37+00:00"
- },
- {
- "name": "psr/http-message",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/http-message.git",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
- "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Http\\Message\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "Common interface for HTTP messages",
- "homepage": "https://github.com/php-fig/http-message",
- "keywords": [
- "http",
- "http-message",
- "psr",
- "psr-7",
- "request",
- "response"
- ],
- "time": "2016-08-06T14:39:51+00:00"
- },
- {
- "name": "psr/log",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/php-fig/log.git",
- "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
- "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Psr\\Log\\": "Psr/Log/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "PHP-FIG",
- "homepage": "http://www.php-fig.org/"
- }
- ],
- "description": "Common interface for logging libraries",
- "homepage": "https://github.com/php-fig/log",
- "keywords": [
- "log",
- "psr",
- "psr-3"
- ],
- "time": "2018-11-20T15:27:04+00:00"
- },
- {
- "name": "ralouphie/getallheaders",
- "version": "2.0.5",
- "source": {
- "type": "git",
- "url": "https://github.com/ralouphie/getallheaders.git",
- "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
- "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3"
- },
- "require-dev": {
- "phpunit/phpunit": "~3.7.0",
- "satooshi/php-coveralls": ">=1.0"
- },
- "type": "library",
- "autoload": {
- "files": [
- "src/getallheaders.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Ralph Khattar",
- "email": "ralph.khattar@gmail.com"
- }
- ],
- "description": "A polyfill for getallheaders.",
- "time": "2016-02-11T07:05:27+00:00"
- },
- {
- "name": "sebastian/code-unit-reverse-lookup",
- "version": "1.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
- "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
- "reference": "4419fcdb5eabb9caa61a27c7a1db532a6b55dd18",
- "shasum": ""
- },
- "require": {
- "php": "^5.6 || ^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^5.7 || ^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Looks up which function or method a line of code belongs to",
- "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
- "time": "2017-03-04T06:30:41+00:00"
- },
- {
- "name": "sebastian/comparator",
- "version": "3.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/comparator.git",
- "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
- "reference": "5de4fc177adf9bce8df98d8d141a7559d7ccf6da",
- "shasum": ""
- },
- "require": {
- "php": "^7.1",
- "sebastian/diff": "^3.0",
- "sebastian/exporter": "^3.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^7.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Jeff Welch",
- "email": "whatthejeff@gmail.com"
- },
- {
- "name": "Volker Dusch",
- "email": "github@wallbash.com"
- },
- {
- "name": "Bernhard Schussek",
- "email": "bschussek@2bepublished.at"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Provides the functionality to compare PHP values for equality",
- "homepage": "https://github.com/sebastianbergmann/comparator",
- "keywords": [
- "comparator",
- "compare",
- "equality"
- ],
- "time": "2018-07-12T15:12:46+00:00"
- },
- {
- "name": "sebastian/diff",
- "version": "3.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/diff.git",
- "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
- "reference": "720fcc7e9b5cf384ea68d9d930d480907a0c1a29",
- "shasum": ""
- },
- "require": {
- "php": "^7.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^7.5 || ^8.0",
- "symfony/process": "^2 || ^3.3 || ^4"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Kore Nordmann",
- "email": "mail@kore-nordmann.de"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Diff implementation",
- "homepage": "https://github.com/sebastianbergmann/diff",
- "keywords": [
- "diff",
- "udiff",
- "unidiff",
- "unified diff"
- ],
- "time": "2019-02-04T06:01:07+00:00"
- },
- {
- "name": "sebastian/environment",
- "version": "4.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/environment.git",
- "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656",
- "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656",
- "shasum": ""
- },
- "require": {
- "php": "^7.1"
- },
- "require-dev": {
- "phpunit/phpunit": "^7.5"
- },
- "suggest": {
- "ext-posix": "*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.1-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Provides functionality to handle HHVM/PHP environments",
- "homepage": "http://www.github.com/sebastianbergmann/environment",
- "keywords": [
- "Xdebug",
- "environment",
- "hhvm"
- ],
- "time": "2019-02-01T05:27:49+00:00"
- },
- {
- "name": "sebastian/exporter",
- "version": "3.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/exporter.git",
- "reference": "234199f4528de6d12aaa58b612e98f7d36adb937"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/234199f4528de6d12aaa58b612e98f7d36adb937",
- "reference": "234199f4528de6d12aaa58b612e98f7d36adb937",
- "shasum": ""
- },
- "require": {
- "php": "^7.0",
- "sebastian/recursion-context": "^3.0"
- },
- "require-dev": {
- "ext-mbstring": "*",
- "phpunit/phpunit": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.1.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Jeff Welch",
- "email": "whatthejeff@gmail.com"
- },
- {
- "name": "Volker Dusch",
- "email": "github@wallbash.com"
- },
- {
- "name": "Bernhard Schussek",
- "email": "bschussek@2bepublished.at"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- },
- {
- "name": "Adam Harvey",
- "email": "aharvey@php.net"
- }
- ],
- "description": "Provides the functionality to export PHP variables for visualization",
- "homepage": "http://www.github.com/sebastianbergmann/exporter",
- "keywords": [
- "export",
- "exporter"
- ],
- "time": "2017-04-03T13:19:02+00:00"
- },
- {
- "name": "sebastian/global-state",
- "version": "2.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/global-state.git",
- "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
- "reference": "e8ba02eed7bbbb9e59e43dedd3dddeff4a56b0c4",
- "shasum": ""
- },
- "require": {
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "suggest": {
- "ext-uopz": "*"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Snapshotting of global state",
- "homepage": "http://www.github.com/sebastianbergmann/global-state",
- "keywords": [
- "global state"
- ],
- "time": "2017-04-27T15:39:26+00:00"
- },
- {
- "name": "sebastian/object-enumerator",
- "version": "3.0.3",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/object-enumerator.git",
- "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/7cfd9e65d11ffb5af41198476395774d4c8a84c5",
- "reference": "7cfd9e65d11ffb5af41198476395774d4c8a84c5",
- "shasum": ""
- },
- "require": {
- "php": "^7.0",
- "sebastian/object-reflector": "^1.1.1",
- "sebastian/recursion-context": "^3.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Traverses array structures and object graphs to enumerate all referenced objects",
- "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
- "time": "2017-08-03T12:35:26+00:00"
- },
- {
- "name": "sebastian/object-reflector",
- "version": "1.1.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/object-reflector.git",
- "reference": "773f97c67f28de00d397be301821b06708fca0be"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/773f97c67f28de00d397be301821b06708fca0be",
- "reference": "773f97c67f28de00d397be301821b06708fca0be",
- "shasum": ""
- },
- "require": {
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.1-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Allows reflection of object attributes, including inherited and non-public ones",
- "homepage": "https://github.com/sebastianbergmann/object-reflector/",
- "time": "2017-03-29T09:07:27+00:00"
- },
- {
- "name": "sebastian/recursion-context",
- "version": "3.0.0",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/recursion-context.git",
- "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
- "reference": "5b0cd723502bac3b006cbf3dbf7a1e3fcefe4fa8",
- "shasum": ""
- },
- "require": {
- "php": "^7.0"
- },
- "require-dev": {
- "phpunit/phpunit": "^6.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "3.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Jeff Welch",
- "email": "whatthejeff@gmail.com"
- },
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- },
- {
- "name": "Adam Harvey",
- "email": "aharvey@php.net"
- }
- ],
- "description": "Provides functionality to recursively process PHP variables",
- "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
- "time": "2017-03-03T06:23:57+00:00"
- },
- {
- "name": "sebastian/resource-operations",
- "version": "2.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/resource-operations.git",
- "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
- "reference": "4d7a795d35b889bf80a0cc04e08d77cedfa917a9",
- "shasum": ""
- },
- "require": {
- "php": "^7.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de"
- }
- ],
- "description": "Provides a list of PHP built-in functions that operate on resources",
- "homepage": "https://www.github.com/sebastianbergmann/resource-operations",
- "time": "2018-10-04T04:07:39+00:00"
- },
- {
- "name": "sebastian/version",
- "version": "2.0.1",
- "source": {
- "type": "git",
- "url": "https://github.com/sebastianbergmann/version.git",
- "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/99732be0ddb3361e16ad77b68ba41efc8e979019",
- "reference": "99732be0ddb3361e16ad77b68ba41efc8e979019",
- "shasum": ""
- },
- "require": {
- "php": ">=5.6"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Sebastian Bergmann",
- "email": "sebastian@phpunit.de",
- "role": "lead"
- }
- ],
- "description": "Library that helps with managing the version number of Git-hosted PHP projects",
- "homepage": "https://github.com/sebastianbergmann/version",
- "time": "2016-10-03T07:35:21+00:00"
- },
- {
- "name": "symfony/browser-kit",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/browser-kit.git",
- "reference": "ee4462581eb54bf34b746e4a5d522a4f21620160"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/browser-kit/zipball/ee4462581eb54bf34b746e4a5d522a4f21620160",
- "reference": "ee4462581eb54bf34b746e4a5d522a4f21620160",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3",
- "symfony/dom-crawler": "~3.4|~4.0"
- },
- "require-dev": {
- "symfony/css-selector": "~3.4|~4.0",
- "symfony/process": "~3.4|~4.0"
- },
- "suggest": {
- "symfony/process": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\BrowserKit\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony BrowserKit Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-16T21:31:25+00:00"
- },
- {
- "name": "symfony/console",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/console.git",
- "reference": "1f0ad51dfde4da8a6070f06adc58b4e37cbb37a4"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/1f0ad51dfde4da8a6070f06adc58b4e37cbb37a4",
- "reference": "1f0ad51dfde4da8a6070f06adc58b4e37cbb37a4",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3",
- "symfony/contracts": "^1.0",
- "symfony/polyfill-mbstring": "~1.0"
- },
- "conflict": {
- "symfony/dependency-injection": "<3.4",
- "symfony/process": "<3.3"
- },
- "provide": {
- "psr/log-implementation": "1.0"
- },
- "require-dev": {
- "psr/log": "~1.0",
- "symfony/config": "~3.4|~4.0",
- "symfony/dependency-injection": "~3.4|~4.0",
- "symfony/event-dispatcher": "~3.4|~4.0",
- "symfony/lock": "~3.4|~4.0",
- "symfony/process": "~3.4|~4.0"
- },
- "suggest": {
- "psr/log": "For using the console logger",
- "symfony/event-dispatcher": "",
- "symfony/lock": "",
- "symfony/process": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Console\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Console Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-25T14:35:16+00:00"
- },
- {
- "name": "symfony/contracts",
- "version": "v1.0.2",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/contracts.git",
- "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf",
- "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3"
- },
- "require-dev": {
- "psr/cache": "^1.0",
- "psr/container": "^1.0"
- },
- "suggest": {
- "psr/cache": "When using the Cache contracts",
- "psr/container": "When using the Service contracts",
- "symfony/cache-contracts-implementation": "",
- "symfony/service-contracts-implementation": "",
- "symfony/translation-contracts-implementation": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.0-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Contracts\\": ""
- },
- "exclude-from-classmap": [
- "**/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "A set of abstractions extracted out of the Symfony components",
- "homepage": "https://symfony.com",
- "keywords": [
- "abstractions",
- "contracts",
- "decoupling",
- "interfaces",
- "interoperability",
- "standards"
- ],
- "time": "2018-12-05T08:06:11+00:00"
- },
- {
- "name": "symfony/css-selector",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/css-selector.git",
- "reference": "48eddf66950fa57996e1be4a55916d65c10c604a"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/css-selector/zipball/48eddf66950fa57996e1be4a55916d65c10c604a",
- "reference": "48eddf66950fa57996e1be4a55916d65c10c604a",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\CssSelector\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Jean-François Simon",
- "email": "jeanfrancois.simon@sensiolabs.com"
- },
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony CssSelector Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-16T20:31:39+00:00"
- },
- {
- "name": "symfony/dom-crawler",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/dom-crawler.git",
- "reference": "d8476760b04cdf7b499c8718aa437c20a9155103"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/dom-crawler/zipball/d8476760b04cdf7b499c8718aa437c20a9155103",
- "reference": "d8476760b04cdf7b499c8718aa437c20a9155103",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3",
- "symfony/polyfill-ctype": "~1.8",
- "symfony/polyfill-mbstring": "~1.0"
- },
- "require-dev": {
- "symfony/css-selector": "~3.4|~4.0"
- },
- "suggest": {
- "symfony/css-selector": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\DomCrawler\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony DomCrawler Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-16T20:35:37+00:00"
- },
- {
- "name": "symfony/event-dispatcher",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/event-dispatcher.git",
- "reference": "bd09ad265cd50b2b9d09d65ce6aba2d29bc81fe1"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/bd09ad265cd50b2b9d09d65ce6aba2d29bc81fe1",
- "reference": "bd09ad265cd50b2b9d09d65ce6aba2d29bc81fe1",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3",
- "symfony/contracts": "^1.0"
- },
- "conflict": {
- "symfony/dependency-injection": "<3.4"
- },
- "require-dev": {
- "psr/log": "~1.0",
- "symfony/config": "~3.4|~4.0",
- "symfony/dependency-injection": "~3.4|~4.0",
- "symfony/expression-language": "~3.4|~4.0",
- "symfony/stopwatch": "~3.4|~4.0"
- },
- "suggest": {
- "symfony/dependency-injection": "",
- "symfony/http-kernel": ""
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\EventDispatcher\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony EventDispatcher Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-16T20:35:37+00:00"
- },
- {
- "name": "symfony/filesystem",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/filesystem.git",
- "reference": "7c16ebc2629827d4ec915a52ac809768d060a4ee"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/filesystem/zipball/7c16ebc2629827d4ec915a52ac809768d060a4ee",
- "reference": "7c16ebc2629827d4ec915a52ac809768d060a4ee",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3",
- "symfony/polyfill-ctype": "~1.8"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Filesystem\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Filesystem Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-16T20:35:37+00:00"
- },
- {
- "name": "symfony/finder",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/finder.git",
- "reference": "ef71816cbb264988bb57fe6a73f610888b9aa70c"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/finder/zipball/ef71816cbb264988bb57fe6a73f610888b9aa70c",
- "reference": "ef71816cbb264988bb57fe6a73f610888b9aa70c",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Finder\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Finder Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-16T20:35:37+00:00"
- },
- {
- "name": "symfony/polyfill-ctype",
- "version": "v1.10.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-ctype.git",
- "reference": "e3d826245268269cd66f8326bd8bc066687b4a19"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19",
- "reference": "e3d826245268269cd66f8326bd8bc066687b4a19",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "suggest": {
- "ext-ctype": "For best performance"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.9-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Ctype\\": ""
- },
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- },
- {
- "name": "Gert de Pagter",
- "email": "BackEndTea@gmail.com"
- }
- ],
- "description": "Symfony polyfill for ctype functions",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "ctype",
- "polyfill",
- "portable"
- ],
- "time": "2018-08-06T14:22:27+00:00"
- },
- {
- "name": "symfony/polyfill-mbstring",
- "version": "v1.10.0",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/polyfill-mbstring.git",
- "reference": "c79c051f5b3a46be09205c73b80b346e4153e494"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494",
- "reference": "c79c051f5b3a46be09205c73b80b346e4153e494",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.3"
- },
- "suggest": {
- "ext-mbstring": "For best performance"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.9-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Polyfill\\Mbstring\\": ""
- },
- "files": [
- "bootstrap.php"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Nicolas Grekas",
- "email": "p@tchwork.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony polyfill for the Mbstring extension",
- "homepage": "https://symfony.com",
- "keywords": [
- "compatibility",
- "mbstring",
- "polyfill",
- "portable",
- "shim"
- ],
- "time": "2018-09-21T13:07:52+00:00"
- },
- {
- "name": "symfony/process",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/process.git",
- "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/process/zipball/6c05edb11fbeff9e2b324b4270ecb17911a8b7ad",
- "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Process\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Process Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-24T22:05:03+00:00"
- },
- {
- "name": "symfony/yaml",
- "version": "v4.2.3",
- "source": {
- "type": "git",
- "url": "https://github.com/symfony/yaml.git",
- "reference": "d461670ee145092b7e2a56c1da7118f19cadadb0"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/symfony/yaml/zipball/d461670ee145092b7e2a56c1da7118f19cadadb0",
- "reference": "d461670ee145092b7e2a56c1da7118f19cadadb0",
- "shasum": ""
- },
- "require": {
- "php": "^7.1.3",
- "symfony/polyfill-ctype": "~1.8"
- },
- "conflict": {
- "symfony/console": "<3.4"
- },
- "require-dev": {
- "symfony/console": "~3.4|~4.0"
- },
- "suggest": {
- "symfony/console": "For validating YAML files using the lint command"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "4.2-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Symfony\\Component\\Yaml\\": ""
- },
- "exclude-from-classmap": [
- "/Tests/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Fabien Potencier",
- "email": "fabien@symfony.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "description": "Symfony Yaml Component",
- "homepage": "https://symfony.com",
- "time": "2019-01-16T20:35:37+00:00"
- },
- {
- "name": "theseer/tokenizer",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/theseer/tokenizer.git",
- "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b",
- "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b",
- "shasum": ""
- },
- "require": {
- "ext-dom": "*",
- "ext-tokenizer": "*",
- "ext-xmlwriter": "*",
- "php": "^7.0"
- },
- "type": "library",
- "autoload": {
- "classmap": [
- "src/"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Arne Blankerts",
- "email": "arne@blankerts.de",
- "role": "Developer"
- }
- ],
- "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
- "time": "2017-04-07T12:08:54+00:00"
- },
- {
- "name": "vlucas/phpdotenv",
- "version": "v2.6.1",
- "source": {
- "type": "git",
- "url": "https://github.com/vlucas/phpdotenv.git",
- "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/2a7dcf7e3e02dc5e701004e51a6f304b713107d5",
- "reference": "2a7dcf7e3e02dc5e701004e51a6f304b713107d5",
- "shasum": ""
- },
- "require": {
- "php": ">=5.3.9",
- "symfony/polyfill-ctype": "^1.9"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.8.35 || ^5.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.6-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Dotenv\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD-3-Clause"
- ],
- "authors": [
- {
- "name": "Vance Lucas",
- "email": "vance@vancelucas.com",
- "homepage": "http://www.vancelucas.com"
- }
- ],
- "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
- "keywords": [
- "dotenv",
- "env",
- "environment"
- ],
- "time": "2019-01-29T11:11:52+00:00"
- },
- {
- "name": "webmozart/assert",
- "version": "1.4.0",
- "source": {
- "type": "git",
- "url": "https://github.com/webmozart/assert.git",
- "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/webmozart/assert/zipball/83e253c8e0be5b0257b881e1827274667c5c17a9",
- "reference": "83e253c8e0be5b0257b881e1827274667c5c17a9",
- "shasum": ""
- },
- "require": {
- "php": "^5.3.3 || ^7.0",
- "symfony/polyfill-ctype": "^1.8"
- },
- "require-dev": {
- "phpunit/phpunit": "^4.6",
- "sebastian/version": "^1.0.1"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "1.3-dev"
- }
- },
- "autoload": {
- "psr-4": {
- "Webmozart\\Assert\\": "src/"
- }
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "MIT"
- ],
- "authors": [
- {
- "name": "Bernhard Schussek",
- "email": "bschussek@gmail.com"
- }
- ],
- "description": "Assertions to validate method input/output with nice error messages.",
- "keywords": [
- "assert",
- "check",
- "validate"
- ],
- "time": "2018-12-25T11:19:39+00:00"
- }
- ],
- "packages-dev": [],
- "aliases": [],
- "minimum-stability": "stable",
- "stability-flags": [],
- "prefer-stable": true,
- "prefer-lowest": false,
- "platform": {
- "php": "~7.1.3||~7.2.0"
- },
- "platform-dev": []
-}
diff --git a/dev/tests/acceptance/tests/_data/catalog_import_products.csv b/dev/tests/acceptance/tests/_data/catalog_import_products.csv
new file mode 100644
index 0000000000000..7732f15d4ce3a
--- /dev/null
+++ b/dev/tests/acceptance/tests/_data/catalog_import_products.csv
@@ -0,0 +1,4 @@
+sku,store_view_code,attribute_set_code,product_type,categories,product_websites,name,description,short_description,weight,product_online,tax_class_name,visibility,price,special_price,special_price_from_date,special_price_to_date,url_key,meta_title,meta_keywords,meta_description,base_image,base_image_label,small_image,small_image_label,thumbnail_image,thumbnail_image_label,swatch_image,swatch_image_label,created_at,updated_at,new_from_date,new_to_date,display_product_options_in,map_price,msrp_price,map_enabled,gift_message_available,custom_design,custom_design_from,custom_design_to,custom_layout_update,page_layout,product_options_container,msrp_display_actual_price_type,country_of_manufacture,additional_attributes,qty,out_of_stock_qty,use_config_min_qty,is_qty_decimal,allow_backorders,use_config_backorders,min_cart_qty,use_config_min_sale_qty,max_cart_qty,use_config_max_sale_qty,is_in_stock,notify_on_stock_below,use_config_notify_stock_qty,manage_stock,use_config_manage_stock,use_config_qty_increments,qty_increments,use_config_enable_qty_inc,enable_qty_increments,is_decimal_divided,website_id,related_skus,related_position,crosssell_skus,crosssell_position,upsell_skus,upsell_position,additional_images,additional_image_labels,hide_from_product_page,custom_options,bundle_price_type,bundle_sku_type,bundle_price_view,bundle_weight_type,bundle_values,bundle_shipment_type,configurable_variations,configurable_variation_labels,associated_skus
+SimpleProductForTest1,,Default,simple,"Default","base,second_website",SimpleProductAfterImport1,,,1.0000,1,"Taxable Goods","Catalog, Search",250.0000,,,,simple-product-for-test-1,,,,,,,,,,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,"Block after Info Column",,,,,,,,,,,"Use config",,,100.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,,,,,,,,,,,,,,,,,,,
+SimpleProductForTest2,,Default,simple,"Default",base,SimpleProductAfterImport2,,,1.0000,1,"Taxable Goods","Catalog, Search",300.0000,,,,simple-product-for-test-2,,,,,,,,,,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,"Block after Info Column",,,,,,,,,,,"Use config",,,100.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,,,,,,,,,,,,,,,,,,,
+SimpleProductForTest3,,Default,simple,"Default","base,second_website",SimpleProductAfterImport3,,,1.0000,1,"Taxable Goods","Catalog, Search",350.0000,,,,simple-product-for-test-3,,,,,,,,,,,,"3/4/19, 5:53 AM","3/4/19, 4:47 PM",,,"Block after Info Column",,,,,,,,,,,"Use config",,,100.0000,0.0000,1,0,0,1,1.0000,1,0.0000,1,1,,1,0,1,1,0.0000,1,0,0,0,,,,,,,,,,,,,,,,,,,
\ No newline at end of file
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php
index 1419aff867d2d..f62be7328481c 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/CategoryProductsVariantsTest.php
@@ -18,14 +18,11 @@
class CategoryProductsVariantsTest extends GraphQlAbstract
{
/**
- *
* @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
* @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function testGetSimpleProductsFromCategory()
{
- $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/360');
-
$query
= <<objectManager->create(
- \Magento\Integration\Api\CustomerTokenServiceInterface::class
- );
- $customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password');
- $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
- $response = $this->graphQlQuery($query, [], '', $headerMap);
+ $response = $this->graphQlQuery($query);
$responseDataObject = new DataObject($response);
//Some sort of smoke testing
self::assertEquals(
@@ -111,39 +104,37 @@ public function testCategoriesTree()
}
/**
- * @magentoApiDataFixture Magento/Customer/_files/customer.php
* @magentoApiDataFixture Magento/Catalog/_files/categories.php
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function testGetCategoryById()
{
- $rootCategoryId = 13;
+ $categoryId = 13;
$query = <<objectManager->create(
- \Magento\Integration\Api\CustomerTokenServiceInterface::class
- );
- $customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password');
- $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
- $response = $this->graphQlQuery($query, [], '', $headerMap);
- $responseDataObject = new DataObject($response);
- //Some sort of smoke testing
- self::assertEquals(
- 'Category 1.2',
- $responseDataObject->getData('category/name')
- );
- self::assertEquals(
- 13,
- $responseDataObject->getData('category/id')
- );
+ $response = $this->graphQlQuery($query);
+ self::assertEquals('Category 1.2', $response['category']['name']);
+ self::assertEquals(13, $response['category']['id']);
+ }
+
+ public function testNonExistentCategoryWithProductCount()
+ {
+ $query = <<expectException(ResponseContainsErrorsException::class);
+ $this->expectExceptionMessage('GraphQL response contains errors: Category doesn\'t exist');
+ $this->graphQlQuery($query);
}
/**
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductImageTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductImageTest.php
index b55c6c1d91460..b957292a3ac28 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductImageTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductImageTest.php
@@ -51,7 +51,6 @@ public function testProductWithBaseImage()
*/
public function testProductWithoutBaseImage()
{
- $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/239');
$productSku = 'simple';
$query = <<graphQlQuery($query);
self::assertEquals('Simple Product', $response['products']['items'][0]['image']['label']);
+ self::assertStringEndsWith(
+ 'images/product/placeholder/image.jpg',
+ $response['products']['items'][0]['image']['url']
+ );
+ self::assertEquals('Simple Product', $response['products']['items'][0]['small_image']['label']);
+ self::assertStringEndsWith(
+ 'images/product/placeholder/small_image.jpg',
+ $response['products']['items'][0]['small_image']['url']
+ );
}
/**
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php
index a7c83aba89f0a..e517b22ad09de 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Catalog/ProductViewTest.php
@@ -137,6 +137,26 @@ public function testQueryAllFieldsSimpleProduct()
sort_order
}
}
+ ... on CustomizableCheckboxOption {
+ checkbox_option: value {
+ option_type_id
+ sku
+ price
+ price_type
+ title
+ sort_order
+ }
+ }
+ ... on CustomizableMultipleOption {
+ multiple_option: value {
+ option_type_id
+ sku
+ price
+ price_type
+ title
+ sort_order
+ }
+ }
...on CustomizableFileOption {
product_sku
file_option: value {
@@ -736,7 +756,7 @@ private function assertOptions($product, $actualResponse)
$values = $option->getValues();
/** @var \Magento\Catalog\Model\Product\Option\Value $value */
$value = current($values);
- $findValueKeyName = $option->getType() === 'radio' ? 'radio_option' : 'drop_down_option';
+ $findValueKeyName = $option->getType() . '_option';
if ($value->getTitle() === $optionsArray[$findValueKeyName][0]['title']) {
$match = true;
}
@@ -756,7 +776,7 @@ private function assertOptions($product, $actualResponse)
];
if (!empty($option->getValues())) {
- $valueKeyName = $option->getType() === 'radio' ? 'radio_option' : 'drop_down_option';
+ $valueKeyName = $option->getType() . '_option';
$value = current($optionsArray[$valueKeyName]);
/** @var \Magento\Catalog\Model\Product\Option\Value $productValue */
$productValue = current($option->getValues());
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php
new file mode 100644
index 0000000000000..17c2af8dc59d0
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CatalogInventory/AddProductToCartTest.php
@@ -0,0 +1,124 @@
+quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/products.php
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ * @expectedException \Exception
+ * @expectedExceptionMessage The requested qty is not available
+ */
+ public function testAddProductIfQuantityIsNotAvailable()
+ {
+ $sku = 'simple';
+ $qty = 200;
+
+ $maskedQuoteId = $this->getMaskedQuoteId();
+ $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty);
+ $this->graphQlQuery($query);
+ self::fail('Should be "The requested qty is not available" error message.');
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/products.php
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ * @magentoConfigFixture default cataloginventory/item_options/max_sale_qty 5
+ * @expectedException \Exception
+ * @expectedExceptionMessage The most you may purchase is 5.
+ */
+ public function testAddMoreProductsThatAllowed()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/167');
+
+ $sku = 'custom-design-simple-product';
+ $qty = 7;
+
+ $maskedQuoteId = $this->getMaskedQuoteId();
+ $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty);
+ $this->graphQlQuery($query);
+ self::fail('Should be "The most you may purchase is 5." error message.');
+ }
+
+ /**
+ * @return string
+ */
+ public function getMaskedQuoteId() : string
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+
+ return $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param string $sku
+ * @param int $qty
+ * @return string
+ */
+ public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int $qty) : string
+ {
+ return <<markTestIncomplete('https://github.com/magento/graphql-ce/issues/361');
-
$productSku = 'configurable';
$query
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php
index f4e96e49a58e6..84c111bd25fd4 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/ChangeCustomerPasswordTest.php
@@ -77,7 +77,6 @@ public function testChangePasswordIfUserIsNotAuthorizedTest()
*/
public function testChangeWeakPassword()
{
- $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/190');
$customerEmail = 'customer@example.com';
$oldCustomerPassword = 'password';
$newCustomerPassword = 'weakpass';
@@ -94,7 +93,7 @@ public function testChangeWeakPassword()
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
* @expectedException \Exception
- * @expectedExceptionMessage The password doesn't match this account. Verify the password and try again.
+ * @expectedExceptionMessage Invalid login or password.
*/
public function testChangePasswordIfPasswordIsInvalid()
{
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php
index ba0232020298f..1153b9662b41a 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/DeleteCustomerAddressTest.php
@@ -132,7 +132,7 @@ public function testDeleteDefaultBillingCustomerAddress()
* @magentoApiDataFixture Magento/Customer/_files/customer.php
*
* @expectedException \Exception
- * @expectedExceptionMessage Address id 9999 does not exist.
+ * @expectedExceptionMessage Could not find a address with ID "9999"
*/
public function testDeleteNonExistCustomerAddress()
{
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/IsEmailAvailableTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/IsEmailAvailableTest.php
new file mode 100644
index 0000000000000..37693fbba7fef
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/IsEmailAvailableTest.php
@@ -0,0 +1,50 @@
+graphQlQuery($query);
+
+ self::assertArrayHasKey('isEmailAvailable', $response);
+ self::assertArrayHasKey('is_email_available', $response['isEmailAvailable']);
+ self::assertFalse($response['isEmailAvailable']['is_email_available']);
+ }
+
+ public function testEmailAvailable()
+ {
+ $query =
+ <<graphQlQuery($query);
+
+ self::assertArrayHasKey('isEmailAvailable', $response);
+ self::assertArrayHasKey('is_email_available', $response['isEmailAvailable']);
+ self::assertTrue($response['isEmailAvailable']['is_email_available']);
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php
index c11c1385f7412..df45e1de771d9 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Customer/UpdateCustomerTest.php
@@ -47,33 +47,57 @@ public function testUpdateCustomer()
$currentEmail = 'customer@example.com';
$currentPassword = 'password';
+ $newPrefix = 'Dr';
$newFirstname = 'Richard';
+ $newMiddlename = 'Riley';
$newLastname = 'Rowe';
+ $newSuffix = 'III';
+ $newDob = '3/11/1972';
+ $newTaxVat = 'GQL1234567';
+ $newGender = 2;
$newEmail = 'customer_updated@example.com';
$query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword));
+ $this->assertEquals($newPrefix, $response['updateCustomer']['customer']['prefix']);
$this->assertEquals($newFirstname, $response['updateCustomer']['customer']['firstname']);
+ $this->assertEquals($newMiddlename, $response['updateCustomer']['customer']['middlename']);
$this->assertEquals($newLastname, $response['updateCustomer']['customer']['lastname']);
+ $this->assertEquals($newSuffix, $response['updateCustomer']['customer']['suffix']);
+ $this->assertEquals($newDob, $response['updateCustomer']['customer']['dob']);
+ $this->assertEquals($newTaxVat, $response['updateCustomer']['customer']['taxvat']);
$this->assertEquals($newEmail, $response['updateCustomer']['customer']['email']);
+ $this->assertEquals($newGender, $response['updateCustomer']['customer']['gender']);
}
/**
@@ -185,7 +209,7 @@ public function testUpdateEmailIfPasswordIsMissed()
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
* @expectedException \Exception
- * @expectedExceptionMessage The password doesn't match this account. Verify the password and try again.
+ * @expectedExceptionMessage Invalid login or password.
*/
public function testUpdateEmailIfPasswordIsInvalid()
{
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/AddConfigurableProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php
new file mode 100644
index 0000000000000..b3f16c8734203
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/AddConfigurableProductToCartTest.php
@@ -0,0 +1,155 @@
+quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quote = $objectManager->create(Quote::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products.php
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ */
+ public function testAddConfigurableProductToCart()
+ {
+ $variantSku = 'simple_41';
+ $qty = 2;
+
+ $maskedQuoteId = $this->getMaskedQuoteId();
+
+ $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty);
+
+ $response = $this->graphQlQuery($query);
+ $cartItems = $response['addConfigurableProductsToCart']['cart']['items'];
+ self::assertEquals($qty, $cartItems[0]['qty']);
+ self::assertEquals($variantSku, $cartItems[0]['product']['sku']);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/multiple_mixed_products.php
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ * @expectedException \Exception
+ * @expectedExceptionMessage The requested qty is not available
+ */
+ public function testAddProductIfQuantityIsNotAvailable()
+ {
+ $variantSku = 'simple_41';
+ $qty = 200;
+
+ $maskedQuoteId = $this->getMaskedQuoteId();
+ $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty);
+
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Framework/Search/_files/product_configurable.php
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ * @expectedException \Exception
+ * @expectedExceptionMessage Product that you are trying to add is not available.
+ */
+ public function testAddOutOfStockProduct()
+ {
+ $variantSku = 'simple_1010';
+ $qty = 1;
+ $maskedQuoteId = $this->getMaskedQuoteId();
+ $query = $this->getAddConfigurableProductMutationQuery($maskedQuoteId, $variantSku, $qty);
+
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ * @return string
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ */
+ private function getMaskedQuoteId()
+ {
+ $this->quoteResource->load(
+ $this->quote,
+ 'test_order_1',
+ 'reserved_order_id'
+ );
+ return $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param string $sku
+ * @param int $qty
+ *
+ * @return string
+ */
+ private function getAddConfigurableProductMutationQuery(string $maskedQuoteId, string $variantSku, int $qty): string
+ {
+ return <<quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/products.php
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ */
+ public function testAddSimpleProductToCart()
+ {
+ $sku = 'simple';
+ $qty = 2;
+ $maskedQuoteId = $this->getMaskedQuoteId();
+
+ $query = $this->getAddSimpleProductQuery($maskedQuoteId, $sku, $qty);
+ $response = $this->graphQlQuery($query);
+ self::assertArrayHasKey('cart', $response['addSimpleProductsToCart']);
+
+ self::assertEquals($qty, $response['addSimpleProductsToCart']['cart']['items'][0]['qty']);
+ 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
+ */
+ public function getMaskedQuoteId() : string
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+
+ return $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param string $sku
+ * @param int $qty
+ * @return string
+ */
+ public function getAddSimpleProductQuery(string $maskedQuoteId, string $sku, int $qty): string
+ {
+ return <<graphQlQuery($query);
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php
index 1f8ad06a9f8ed..54fdc3ac0f360 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CouponTest.php
@@ -58,7 +58,7 @@ public function testApplyCouponToGuestCartWithItems()
$query = $this->prepareAddCouponRequestQuery($maskedQuoteId, $couponCode);
$response = $this->graphQlQuery($query);
- self::assertArrayHasKey("applyCouponToCart", $response);
+ self::assertArrayHasKey('applyCouponToCart', $response);
self::assertEquals($couponCode, $response['applyCouponToCart']['cart']['applied_coupon']['code']);
}
@@ -154,7 +154,7 @@ public function testRemoveCoupon()
$response = $this->graphQlQuery($query);
self::assertArrayHasKey('removeCouponFromCart', $response);
- self::assertSame('', $response['removeCouponFromCart']['cart']['applied_coupon']['code']);
+ self::assertNull($response['removeCouponFromCart']['cart']['applied_coupon']['code']);
}
/**
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CreateEmptyCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CreateEmptyCartTest.php
deleted file mode 100644
index 6e819b523ec82..0000000000000
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/CreateEmptyCartTest.php
+++ /dev/null
@@ -1,91 +0,0 @@
-objectManager = \Magento\TestFramework\Helper\Bootstrap::getObjectManager();
- $this->quoteIdMask = $this->objectManager->create(QuoteIdMask::class);
- $this->guestCartRepository = $this->objectManager->create(GuestCartRepositoryInterface::class);
- }
-
- public function testCreateEmptyCartForGuest()
- {
- $query = <<graphQlQuery($query);
-
- self::assertArrayHasKey('createEmptyCart', $response);
-
- $maskedCartId = $response['createEmptyCart'];
- /** @var CartInterface $guestCart */
- $guestCart = $this->guestCartRepository->get($maskedCartId);
-
- self::assertNotNull($guestCart->getId());
- self::assertNull($guestCart->getCustomer()->getId());
- }
-
- /**
- * @magentoApiDataFixture Magento/Customer/_files/customer.php
- */
- public function testCreateEmptyCartForRegisteredCustomer()
- {
- $query = <<objectManager->create(
- \Magento\Integration\Api\CustomerTokenServiceInterface::class
- );
- $customerToken = $customerTokenService->createCustomerAccessToken('customer@example.com', 'password');
- $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
-
- $response = $this->graphQlQuery($query, [], '', $headerMap);
-
- self::assertArrayHasKey('createEmptyCart', $response);
-
- $maskedCartId = $response['createEmptyCart'];
- /* guestCartRepository is used for registered customer to get the cart hash */
- $guestCart = $this->guestCartRepository->get($maskedCartId);
-
- self::assertNotNull($guestCart->getId());
- self::assertEquals(1, $guestCart->getCustomer()->getId());
- }
-}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php
new file mode 100644
index 0000000000000..0cb8a38b0cb5e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/CreateEmptyCartTest.php
@@ -0,0 +1,61 @@
+guestCartRepository = $objectManager->get(GuestCartRepositoryInterface::class);
+ $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ */
+ public function testCreateEmptyCart()
+ {
+ $query = <<customerTokenService->createCustomerAccessToken('customer@example.com', 'password');
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+
+ $response = $this->graphQlQuery($query, [], '', $headerMap);
+
+ self::assertArrayHasKey('createEmptyCart', $response);
+
+ $maskedCartId = $response['createEmptyCart'];
+ $guestCart = $this->guestCartRepository->get($maskedCartId);
+
+ self::assertNotNull($guestCart->getId());
+ self::assertEquals(1, $guestCart->getCustomer()->getId());
+ }
+}
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
new file mode 100644
index 0000000000000..ba640bc3402ba
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetAvailablePaymentMethodsTest.php
@@ -0,0 +1,160 @@
+getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ }
+
+ /**
+ * @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 testGetAvailablePaymentMethods()
+ {
+ $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']);
+ }
+
+ /**
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/customer.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 testGetAvailablePaymentMethodsFromGuestCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $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/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 testGetAvailablePaymentMethodsFromAnotherCustomerCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $this->expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$maskedQuoteId\""
+ );
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer3@search.example.com'));
+ }
+
+ /**
+ * @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 testGetAvailablePaymentMethodsIfPaymentsAreNotPresent()
+ {
+ $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::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 testGetAvailablePaymentMethodsOfNonExistentCart()
+ {
+ $maskedQuoteId = 'non_existent_masked_id';
+ $query = $this->getQuery($maskedQuoteId);
+
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @return string
+ */
+ private function getQuery(string $maskedQuoteId): string
+ {
+ return <<customerTokenService->createCustomerAccessToken($username, $password);
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ return $headerMap;
+ }
+}
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
new file mode 100644
index 0000000000000..eb62b8c92f310
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/GetCartTest.php
@@ -0,0 +1,159 @@
+getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ }
+
+ /**
+ * @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->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ self::assertArrayHasKey('cart', $response);
+ self::assertArrayHasKey('items', $response['cart']);
+ self::assertCount(2, $response['cart']['items']);
+
+ self::assertNotEmpty($response['cart']['items'][0]['id']);
+ 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(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/GraphQl/Quote/_files/guest/create_empty_cart.php
+ */
+ public function testGetGuestCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $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/GraphQl/Quote/_files/customer/create_empty_cart.php
+ */
+ public function testGetAnotherCustomerCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $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"
+ */
+ public function testGetNonExistentCart()
+ {
+ $maskedQuoteId = 'non_existent_masked_id';
+ $query = $this->getQuery($maskedQuoteId);
+
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @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()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @return string
+ */
+ private function getQuery(string $maskedQuoteId): string
+ {
+ return <<customerTokenService->createCustomerAccessToken($username, $password);
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ return $headerMap;
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php
new file mode 100644
index 0000000000000..e80a2127ad420
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/RemoveItemFromCartTest.php
@@ -0,0 +1,265 @@
+quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testRemoveItemFromCart()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId();
+
+ $query = $this->prepareMutationQuery($maskedQuoteId, $itemId);
+ $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ $this->assertArrayHasKey('removeItemFromCart', $response);
+ $this->assertArrayHasKey('cart', $response['removeItemFromCart']);
+ $this->assertCount(0, $response['removeItemFromCart']['cart']['items']);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testRemoveItemFromNonExistentCart()
+ {
+ $query = $this->prepareMutationQuery('non_existent_masked_id', 1);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testRemoveNonExistentItem()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $notExistentItemId = 999;
+
+ $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item.");
+
+ $query = $this->prepareMutationQuery($maskedQuoteId, $notExistentItemId);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ */
+ public function testRemoveItemIfItemIsNotBelongToCart()
+ {
+ $firstQuote = $this->quoteFactory->create();
+ $this->quoteResource->load($firstQuote, 'test_order_1', 'reserved_order_id');
+ $firstQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$firstQuote->getId());
+
+ $secondQuote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $secondQuote,
+ 'test_order_with_virtual_product_without_address',
+ 'reserved_order_id'
+ );
+ $secondQuote->setCustomerId(1);
+ $this->quoteResource->save($secondQuote);
+ $secondQuoteItemId = (int)$secondQuote
+ ->getItemByProduct($this->productRepository->get('virtual-product'))
+ ->getId();
+
+ $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item.");
+
+ $query = $this->prepareMutationQuery($firstQuoteMaskedId, $secondQuoteItemId);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ */
+ public function testRemoveItemFromGuestCart()
+ {
+ $guestQuote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $guestQuote,
+ 'test_order_with_virtual_product_without_address',
+ 'reserved_order_id'
+ );
+ $guestQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$guestQuote->getId());
+ $guestQuoteItemId = (int)$guestQuote
+ ->getItemByProduct($this->productRepository->get('virtual-product'))
+ ->getId();
+
+ $this->expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$guestQuoteMaskedId\""
+ );
+
+ $query = $this->prepareMutationQuery($guestQuoteMaskedId, $guestQuoteItemId);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/three_customers.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ */
+ public function testRemoveItemFromAnotherCustomerCart()
+ {
+ $anotherCustomerQuote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $anotherCustomerQuote,
+ 'test_order_with_virtual_product_without_address',
+ 'reserved_order_id'
+ );
+ $anotherCustomerQuote->setCustomerId(2);
+ $this->quoteResource->save($anotherCustomerQuote);
+
+ $anotherCustomerQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$anotherCustomerQuote->getId());
+ $anotherCustomerQuoteItemId = (int)$anotherCustomerQuote
+ ->getItemByProduct($this->productRepository->get('virtual-product'))
+ ->getId();
+
+ $this->expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$anotherCustomerQuoteMaskedId\""
+ );
+
+ $query = $this->prepareMutationQuery($anotherCustomerQuoteMaskedId, $anotherCustomerQuoteItemId);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @param string $input
+ * @param string $message
+ * @dataProvider dataProviderUpdateWithMissedRequiredParameters
+ */
+ public function testUpdateWithMissedItemRequiredParameters(string $input, string $message)
+ {
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderUpdateWithMissedRequiredParameters(): array
+ {
+ return [
+ 'missed_cart_id' => [
+ 'cart_item_id: 1',
+ 'Required parameter "cart_id" is missing.'
+ ],
+ 'missed_cart_item_id' => [
+ 'cart_id: "test"',
+ 'Required parameter "cart_item_id" is missing.'
+ ],
+ ];
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param int $itemId
+ * @return string
+ */
+ private function prepareMutationQuery(string $maskedQuoteId, int $itemId): string
+ {
+ return <<customerTokenService->createCustomerAccessToken($username, $password);
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ return $headerMap;
+ }
+}
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
new file mode 100644
index 0000000000000..129375debe068
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php
@@ -0,0 +1,575 @@
+getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ $this->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/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->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
+ $cartResponse = $response['setBillingAddressOnCart']['cart'];
+ self::assertArrayHasKey('billing_address', $cartResponse);
+ $billingAddressResponse = $cartResponse['billing_address'];
+ $this->assertNewAddressFields($billingAddressResponse);
+ }
+
+ /**
+ * @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 testSetNewBillingAddressWithUseForShippingParameter()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
+ $cartResponse = $response['setBillingAddressOnCart']['cart'];
+ self::assertArrayHasKey('billing_address', $cartResponse);
+ $billingAddressResponse = $cartResponse['billing_address'];
+ self::assertArrayHasKey('shipping_addresses', $cartResponse);
+ $shippingAddressResponse = current($cartResponse['shipping_addresses']);
+ $this->assertNewAddressFields($billingAddressResponse);
+ $this->assertNewAddressFields($shippingAddressResponse, 'SHIPPING');
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.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 testSetBillingAddressFromAddressBook()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
+ $cartResponse = $response['setBillingAddressOnCart']['cart'];
+ self::assertArrayHasKey('billing_address', $cartResponse);
+ $billingAddressResponse = $cartResponse['billing_address'];
+ $this->assertSavedBillingAddressFields($billingAddressResponse);
+ }
+
+ /**
+ * @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 Could not find a address with ID "100"
+ */
+ public function testSetNotExistedBillingAddressFromAddressBook()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.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 testSetNewBillingAddressAndFromAddressBookAtSameTime()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_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
+ */
+ public function testSetBillingAddressToGuestCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<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/Customer/_files/customer_address.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ */
+ public function testSetBillingAddressToAnotherCustomerCart()
+ {
+ $maskedQuoteId = $this->assignQuoteToCustomer('test_order_with_simple_product_without_address', 2);
+
+ $query = <<expectExceptionMessage(
+ "The current user cannot perform operations on cart \"{$maskedQuoteId}\""
+ );
+
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap('customer@search.example.com'));
+ }
+
+ /**
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/three_customers.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_address.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage Current customer does not have permission to address with ID "1"
+ */
+ public function testSetBillingAddressIfCustomerIsNotOwnerOfAddress()
+ {
+ $maskedQuoteId = $this->assignQuoteToCustomer('test_order_with_simple_product_without_address', 2);
+
+ $query = <<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"
+ */
+ public function testSetBillingAddressOnNonExistentCart()
+ {
+ $maskedQuoteId = 'non_existent_masked_id';
+ $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
+ *
+ * @param array $addressResponse
+ * @param string $addressType
+ */
+ private function assertNewAddressFields(array $addressResponse, string $addressType = 'BILLING'): void
+ {
+ $assertionMap = [
+ ['response_field' => 'firstname', 'expected_value' => 'test firstname'],
+ ['response_field' => 'lastname', 'expected_value' => 'test lastname'],
+ ['response_field' => 'company', 'expected_value' => 'test company'],
+ ['response_field' => 'street', 'expected_value' => [0 => 'test street 1', 1 => 'test street 2']],
+ ['response_field' => 'city', 'expected_value' => 'test city'],
+ ['response_field' => 'postcode', 'expected_value' => '887766'],
+ ['response_field' => 'telephone', 'expected_value' => '88776655'],
+ ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']],
+ ['response_field' => 'address_type', 'expected_value' => $addressType]
+ ];
+
+ $this->assertResponseFields($addressResponse, $assertionMap);
+ }
+
+ /**
+ * Verify the all the whitelisted fields for a Address Object
+ *
+ * @param array $billingAddressResponse
+ */
+ private function assertSavedBillingAddressFields(array $billingAddressResponse): void
+ {
+ $assertionMap = [
+ ['response_field' => 'firstname', 'expected_value' => 'John'],
+ ['response_field' => 'lastname', 'expected_value' => 'Smith'],
+ ['response_field' => 'company', 'expected_value' => 'CompanyName'],
+ ['response_field' => 'street', 'expected_value' => [0 => 'Green str, 67']],
+ ['response_field' => 'city', 'expected_value' => 'CityM'],
+ ['response_field' => 'postcode', 'expected_value' => '75477'],
+ ['response_field' => 'telephone', 'expected_value' => '3468676'],
+ ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']],
+ ];
+
+ $this->assertResponseFields($billingAddressResponse, $assertionMap);
+ }
+
+ /**
+ * @param string $username
+ * @param string $password
+ * @return array
+ */
+ private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array
+ {
+ $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password);
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ return $headerMap;
+ }
+
+ /**
+ * @param string $reversedOrderId
+ * @param int $customerId
+ * @return string
+ */
+ private function assignQuoteToCustomer(
+ string $reversedOrderId = 'test_order_with_simple_product_without_address',
+ int $customerId = 1
+ ): string {
+ $quote = $this->quoteFactory->create();
+ $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
new file mode 100644
index 0000000000000..450a22dd6e9c7
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetPaymentMethodOnCartTest.php
@@ -0,0 +1,296 @@
+getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ }
+
+ /**
+ * @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 testSetPaymentOnCartWithSimpleProduct()
+ {
+ $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $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::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']);
+ }
+
+ /**
+ * @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 testSetPaymentOnCartWithVirtualProduct()
+ {
+ $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $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::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']);
+ }
+
+ /**
+ * @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
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage The requested Payment Method is not available.
+ */
+ public function testSetNonExistentPaymentMethod()
+ {
+ $methodCode = 'noway';
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testSetPaymentOnNonExistentCart()
+ {
+ $maskedQuoteId = 'non_existent_masked_id';
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/customer.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->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $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/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->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $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
+ * @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 testSetPaymentMethodWithoutRequiredParameters(string $input, string $message)
+ {
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array
+ {
+ return [
+ 'missed_cart_id' => [
+ 'payment_method: {code: "' . Checkmo::PAYMENT_METHOD_CHECKMO_CODE . '"}',
+ 'Required parameter "cart_id" is missing.'
+ ],
+ 'missed_payment_method' => [
+ 'cart_id: "test"',
+ 'Required parameter "code" for "payment_method" is missing.'
+ ],
+ 'missed_payment_method_code' => [
+ 'cart_id: "test", payment_method: {code: ""}',
+ 'Required parameter "code" for "payment_method" is missing.'
+ ],
+ ];
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/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()
+ {
+ $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']);
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param string $methodCode
+ * @return string
+ */
+ private function getQuery(
+ string $maskedQuoteId,
+ string $methodCode
+ ) : string {
+ return <<customerTokenService->createCustomerAccessToken($username, $password);
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ return $headerMap;
+ }
+}
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
new file mode 100644
index 0000000000000..5ff29d20b34d7
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingAddressOnCartTest.php
@@ -0,0 +1,558 @@
+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/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 testSetNewShippingAddressOnCartWithSimpleProduct()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']);
+ $cartResponse = $response['setShippingAddressesOnCart']['cart'];
+ self::assertArrayHasKey('shipping_addresses', $cartResponse);
+ $shippingAddressResponse = current($cartResponse['shipping_addresses']);
+ $this->assertNewShippingAddressFields($shippingAddressResponse);
+ }
+
+ /**
+ * @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
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage The Cart includes virtual product(s) only, so a shipping address is not used.
+ */
+ public function testSetNewShippingAddressOnCartWithVirtualProduct()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.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 testSetShippingAddressFromAddressBook()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']);
+ $cartResponse = $response['setShippingAddressesOnCart']['cart'];
+ self::assertArrayHasKey('shipping_addresses', $cartResponse);
+ $shippingAddressResponse = current($cartResponse['shipping_addresses']);
+ $this->assertSavedShippingAddressFields($shippingAddressResponse);
+ }
+
+ /**
+ * @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 Could not find a address with ID "100"
+ */
+ public function testSetNonExistentShippingAddressFromAddressBook()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.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 testSetNewShippingAddressAndFromAddressBookAtSameTime()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/three_customers.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_address.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage Current customer does not have permission to address with ID "1"
+ */
+ public function testSetShippingAddressIfCustomerIsNotOwnerOfAddress()
+ {
+ $maskedQuoteId = $this->assignQuoteToCustomer('test_order_with_simple_product_without_address', 2);
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap('customer2@search.example.com'));
+ }
+
+ /**
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/three_customers.php
+ * @magentoApiDataFixture Magento/Customer/_files/customer_address.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ *
+ * @expectedException \Exception
+ */
+ public function testSetShippingAddressToAnotherCustomerCart()
+ {
+ $maskedQuoteId = $this->assignQuoteToCustomer('test_order_with_simple_product_without_address', 1);
+
+ $query = <<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
+ * @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
+ * @throws \Exception
+ */
+ public function testSetNewShippingAddressWithMissedRequiredParameters(string $input, string $message)
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderUpdateWithMissedRequiredParameters(): array
+ {
+ return [
+ 'shipping_addresses' => [
+ '',
+ 'The shipping address must contain either "customer_address_id" or "address".',
+ ],
+ 'missed_city' => [
+ 'address: { save_in_address_book: false }',
+ 'Field CartAddressInput.city of required type String! was not provided'
+ ]
+ ];
+ }
+
+ /**
+ * @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 You cannot specify multiple shipping addresses.
+ */
+ public function testSetMultipleNewShippingAddresses()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * Verify the all the whitelisted fields for a New Address Object
+ *
+ * @param array $shippingAddressResponse
+ */
+ private function assertNewShippingAddressFields(array $shippingAddressResponse): void
+ {
+ $assertionMap = [
+ ['response_field' => 'firstname', 'expected_value' => 'test firstname'],
+ ['response_field' => 'lastname', 'expected_value' => 'test lastname'],
+ ['response_field' => 'company', 'expected_value' => 'test company'],
+ ['response_field' => 'street', 'expected_value' => [0 => 'test street 1', 1 => 'test street 2']],
+ ['response_field' => 'city', 'expected_value' => 'test city'],
+ ['response_field' => 'postcode', 'expected_value' => '887766'],
+ ['response_field' => 'telephone', 'expected_value' => '88776655'],
+ ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']],
+ ['response_field' => 'address_type', 'expected_value' => 'SHIPPING']
+ ];
+
+ $this->assertResponseFields($shippingAddressResponse, $assertionMap);
+ }
+
+ /**
+ * Verify the all the whitelisted fields for a Address Object
+ *
+ * @param array $shippingAddressResponse
+ */
+ private function assertSavedShippingAddressFields(array $shippingAddressResponse): void
+ {
+ $assertionMap = [
+ ['response_field' => 'firstname', 'expected_value' => 'John'],
+ ['response_field' => 'lastname', 'expected_value' => 'Smith'],
+ ['response_field' => 'company', 'expected_value' => 'CompanyName'],
+ ['response_field' => 'street', 'expected_value' => [0 => 'Green str, 67']],
+ ['response_field' => 'city', 'expected_value' => 'CityM'],
+ ['response_field' => 'postcode', 'expected_value' => '75477'],
+ ['response_field' => 'telephone', 'expected_value' => '3468676']
+ ];
+
+ $this->assertResponseFields($shippingAddressResponse, $assertionMap);
+ }
+
+ /**
+ * @param string $username
+ * @param string $password
+ * @return array
+ */
+ private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array
+ {
+ $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password);
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ return $headerMap;
+ }
+
+ /**
+ * @param string $reversedOrderId
+ * @param int $customerId
+ * @return string
+ */
+ private function assignQuoteToCustomer(
+ string $reversedOrderId = 'test_order_with_simple_product_without_address',
+ int $customerId = 1
+ ): string {
+ $quote = $this->quoteFactory->create();
+ $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
new file mode 100644
index 0000000000000..b5634f51cb366
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetShippingMethodsOnCartTest.php
@@ -0,0 +1,206 @@
+quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ }
+
+ public function testShippingMethodWithVirtualProduct()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testShippingMethodWithSimpleProduct()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testShippingMethodWithSimpleProductWithoutAddress()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetShippingMethodWithMissedRequiredParameters()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetNonExistentShippingMethod()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetShippingMethodIfAddressIsNotBelongToCart()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetShippingMethodToNonExistentCart()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetShippingMethodToGuestCart()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetShippingMethodToAnotherCustomerCart()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetShippingMethodToNonExistentCartAddress()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetShippingMethodToGuestCartAddress()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetShippingMethodToAnotherCustomerCartAddress()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ public function testSetMultipleShippingMethods()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/423');
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param string $shippingMethodCode
+ * @param string $shippingCarrierCode
+ * @param string $shippingAddressId
+ * @return string
+ * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
+ */
+ private function prepareMutationQuery(
+ string $maskedQuoteId,
+ string $shippingMethodCode,
+ string $shippingCarrierCode,
+ string $shippingAddressId
+ ) : string {
+ return <<quoteFactory->create();
+ $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id');
+
+ return $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ }
+
+ /**
+ * @param string $reversedOrderId
+ * @param int $customerId
+ * @return string
+ * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
+ */
+ private function assignQuoteToCustomer(
+ string $reversedOrderId,
+ int $customerId
+ ): string {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, $reversedOrderId, 'reserved_order_id');
+ $quote->setCustomerId($customerId);
+ $this->quoteResource->save($quote);
+ return $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ }
+
+ /**
+ * @param string $username
+ * @param string $password
+ * @return array
+ * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
+ */
+ private function getHeaderMap(string $username = 'customer@example.com', string $password = 'password'): array
+ {
+ $customerToken = $this->customerTokenService->createCustomerAccessToken($username, $password);
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ return $headerMap;
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php
new file mode 100644
index 0000000000000..74e7aa8b5d0a4
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/UpdateCartItemsTest.php
@@ -0,0 +1,334 @@
+quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testUpdateCartItemQty()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId();
+ $qty = 2;
+
+ $query = $this->getQuery($maskedQuoteId, $itemId, $qty);
+ $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ $this->assertArrayHasKey('updateCartItems', $response);
+ $this->assertArrayHasKey('cart', $response['updateCartItems']);
+
+ $responseCart = $response['updateCartItems']['cart'];
+ $item = current($responseCart['items']);
+
+ $this->assertEquals($itemId, $item['id']);
+ $this->assertEquals($qty, $item['qty']);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testRemoveCartItemIfQuantityIsZero()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId();
+ $qty = 0;
+
+ $query = $this->getQuery($maskedQuoteId, $itemId, $qty);
+ $response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+
+ $this->assertArrayHasKey('updateCartItems', $response);
+ $this->assertArrayHasKey('cart', $response['updateCartItems']);
+
+ $responseCart = $response['updateCartItems']['cart'];
+ $this->assertCount(0, $responseCart['items']);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testUpdateItemInNonExistentCart()
+ {
+ $query = $this->getQuery('non_existent_masked_id', 1, 2);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testUpdateNonExistentItem()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $notExistentItemId = 999;
+
+ $this->expectExceptionMessage("Could not find cart item with id: {$notExistentItemId}.");
+
+ $query = $this->getQuery($maskedQuoteId, $notExistentItemId, 2);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ */
+ public function testUpdateItemIfItemIsNotBelongToCart()
+ {
+ $firstQuote = $this->quoteFactory->create();
+ $this->quoteResource->load($firstQuote, 'test_order_1', 'reserved_order_id');
+ $firstQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$firstQuote->getId());
+
+ $secondQuote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $secondQuote,
+ 'test_order_with_virtual_product_without_address',
+ 'reserved_order_id'
+ );
+ $secondQuote->setCustomerId(1);
+ $this->quoteResource->save($secondQuote);
+ $secondQuoteItemId = (int)$secondQuote
+ ->getItemByProduct($this->productRepository->get('virtual-product'))
+ ->getId();
+
+ $this->expectExceptionMessage("Could not find cart item with id: {$secondQuoteItemId}.");
+
+ $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId, 2);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ */
+ public function testUpdateItemInGuestCart()
+ {
+ $guestQuote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $guestQuote,
+ 'test_order_with_virtual_product_without_address',
+ 'reserved_order_id'
+ );
+ $guestQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$guestQuote->getId());
+ $guestQuoteItemId = (int)$guestQuote
+ ->getItemByProduct($this->productRepository->get('virtual-product'))
+ ->getId();
+
+ $this->expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$guestQuoteMaskedId\""
+ );
+
+ $query = $this->getQuery($guestQuoteMaskedId, $guestQuoteItemId, 2);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/three_customers.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ */
+ public function testUpdateItemInAnotherCustomerCart()
+ {
+ $anotherCustomerQuote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $anotherCustomerQuote,
+ 'test_order_with_virtual_product_without_address',
+ 'reserved_order_id'
+ );
+ $anotherCustomerQuote->setCustomerId(2);
+ $this->quoteResource->save($anotherCustomerQuote);
+
+ $anotherCustomerQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$anotherCustomerQuote->getId());
+ $anotherCustomerQuoteItemId = (int)$anotherCustomerQuote
+ ->getItemByProduct($this->productRepository->get('virtual-product'))
+ ->getId();
+
+ $this->expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$anotherCustomerQuoteMaskedId\""
+ );
+
+ $query = $this->getQuery($anotherCustomerQuoteMaskedId, $anotherCustomerQuoteItemId, 2);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @expectedException \Exception
+ * @expectedExceptionMessage Required parameter "cart_id" is missing.
+ */
+ public function testUpdateWithMissedCartItemId()
+ {
+ $query = <<graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @param string $input
+ * @param string $message
+ * @dataProvider dataProviderUpdateWithMissedRequiredParameters
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testUpdateWithMissedItemRequiredParameters(string $input, string $message)
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_1', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query, [], '', $this->getHeaderMap());
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderUpdateWithMissedRequiredParameters(): array
+ {
+ return [
+ 'missed_cart_items' => [
+ '',
+ 'Required parameter "cart_items" is missing.'
+ ],
+ 'missed_cart_item_id' => [
+ 'cart_items: [{ quantity: 2 }]',
+ 'Required parameter "cart_item_id" for "cart_items" is missing.'
+ ],
+ 'missed_cart_item_qty' => [
+ 'cart_items: [{ cart_item_id: 1 }]',
+ 'Required parameter "quantity" for "cart_items" is missing.'
+ ],
+ ];
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param int $itemId
+ * @param float $qty
+ * @return string
+ */
+ private function getQuery(string $maskedQuoteId, int $itemId, float $qty): string
+ {
+ return <<customerTokenService->createCustomerAccessToken($username, $password);
+ $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
+ return $headerMap;
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetAvailableShippingMethodsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetAvailableShippingMethodsTest.php
new file mode 100644
index 0000000000000..1a0ccbd198c37
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetAvailableShippingMethodsTest.php
@@ -0,0 +1,121 @@
+customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
+ $this->quoteResource = $objectManager->create(QuoteResource::class);
+ $this->quote = $objectManager->create(Quote::class);
+ $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testGetAvailableShippingMethods()
+ {
+ $this->quoteResource->load(
+ $this->quote,
+ 'test_order_1',
+ 'reserved_order_id'
+ );
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
+
+ $query = <<graphQlQuery(
+ $query,
+ [],
+ '',
+ $this->getCustomerAuthHeaders('customer@example.com', 'password')
+ );
+ self::assertArrayHasKey('cart', $response);
+ self::assertArrayHasKey('shipping_addresses', $response['cart']);
+ self::assertCount(1, $response['cart']['shipping_addresses']);
+ self::assertArrayHasKey('available_shipping_methods', $response['cart']['shipping_addresses'][0]);
+ self::assertCount(1, $response['cart']['shipping_addresses'][0]['available_shipping_methods']);
+
+ $expectedAddressData = [
+ 'amount' => 10,
+ 'base_amount' => 10,
+ 'carrier_code' => 'flatrate',
+ 'carrier_title' => 'Flat Rate',
+ 'error_message' => '',
+ 'method_code' => 'flatrate',
+ 'method_title' => 'Fixed',
+ 'price_incl_tax' => 10,
+ 'price_excl_tax' => 10,
+ ];
+ self::assertEquals(
+ $expectedAddressData,
+ $response['cart']['shipping_addresses'][0]['available_shipping_methods'][0]
+ );
+ }
+
+ /**
+ * @param string $email
+ * @param string $password
+ * @return array
+ */
+ private function getCustomerAuthHeaders(string $email, string $password): array
+ {
+ $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password);
+ return ['Authorization' => 'Bearer ' . $customerToken];
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php
deleted file mode 100644
index f0038351bcdcf..0000000000000
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTest.php
+++ /dev/null
@@ -1,169 +0,0 @@
-quoteResource = $objectManager->create(QuoteResource::class);
- $this->quote = $objectManager->create(Quote::class);
- $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class);
- $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
- */
- public function testGetOwnCartForRegisteredCustomer()
- {
- $reservedOrderId = 'test_order_item_with_items';
- $this->quoteResource->load(
- $this->quote,
- $reservedOrderId,
- 'reserved_order_id'
- );
-
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
- $query = $this->prepareGetCartQuery($maskedQuoteId);
-
- $response = $this->sendRequestWithToken($query);
-
- self::assertArrayHasKey('Cart', $response);
- self::assertNotEmpty($response['Cart']['items']);
- self::assertNotEmpty($response['Cart']['shipping_addresses']);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_items_saved.php
- */
- public function testGetCartFromAnotherCustomer()
- {
- $reservedOrderId = 'test_order_item_with_items';
- $this->quoteResource->load(
- $this->quote,
- $reservedOrderId,
- 'reserved_order_id'
- );
-
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
- $query = $this->prepareGetCartQuery($maskedQuoteId);
-
- self::expectExceptionMessage("The current user cannot perform operations on cart \"$maskedQuoteId\"");
-
- $this->graphQlQuery($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
- */
- public function testGetCartForGuest()
- {
- $reservedOrderId = 'test_order_1';
- $this->quoteResource->load(
- $this->quote,
- $reservedOrderId,
- 'reserved_order_id'
- );
-
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
- $query = $this->prepareGetCartQuery($maskedQuoteId);
-
- $response = $this->graphQlQuery($query);
-
- self::assertArrayHasKey('Cart', $response);
- }
-
- public function testGetNonExistentCart()
- {
- $maskedQuoteId = 'non_existent_masked_id';
- $query = $this->prepareGetCartQuery($maskedQuoteId);
-
- self::expectExceptionMessage("Could not find a cart with ID \"$maskedQuoteId\"");
-
- $this->graphQlQuery($query);
- }
-
- /**
- * Generates query for setting the specified shipping method on cart
- *
- * @param string $maskedQuoteId
- * @return string
- */
- private function prepareGetCartQuery(
- string $maskedQuoteId
- ) : 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/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/CreateEmptyCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php
new file mode 100644
index 0000000000000..4fd398439913e
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/CreateEmptyCartTest.php
@@ -0,0 +1,47 @@
+guestCartRepository = $objectManager->get(GuestCartRepositoryInterface::class);
+ }
+
+ public function testCreateEmptyCart()
+ {
+ $query = <<graphQlQuery($query);
+
+ self::assertArrayHasKey('createEmptyCart', $response);
+
+ $maskedCartId = $response['createEmptyCart'];
+ $guestCart = $this->guestCartRepository->get($maskedCartId);
+
+ self::assertNotNull($guestCart->getId());
+ self::assertNull($guestCart->getCustomer()->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
new file mode 100644
index 0000000000000..8271a76d88f12
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/GetAvailablePaymentMethodsTest.php
@@ -0,0 +1,117 @@
+getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ }
+
+ /**
+ * @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 testGetAvailablePaymentMethods()
+ {
+ $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']);
+ }
+
+ /**
+ * _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 testGetAvailablePaymentMethodsFromCustomerCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $this->expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$maskedQuoteId\""
+ );
+ $this->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
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/disable_all_active_payment_methods.php
+ */
+ public function testGetAvailablePaymentMethodsIfPaymentsAreNotPresent()
+ {
+ $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::assertEmpty($response['cart']['available_payment_methods']);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testGetAvailablePaymentMethodsOfNonExistentCart()
+ {
+ $maskedQuoteId = 'non_existent_masked_id';
+ $query = $this->getQuery($maskedQuoteId);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @return string
+ */
+ private function getQuery(string $maskedQuoteId): string
+ {
+ return <<getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ }
+
+ /**
+ * @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->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $response = $this->graphQlQuery($query);
+
+ self::assertArrayHasKey('cart', $response);
+ self::assertArrayHasKey('items', $response['cart']);
+ self::assertCount(2, $response['cart']['items']);
+
+ self::assertNotEmpty($response['cart']['items'][0]['id']);
+ 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(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/GraphQl/Quote/_files/customer/create_empty_cart.php
+ */
+ public function testGetCustomerCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $this->expectExceptionMessage(
+ "The current user cannot perform operations on cart \"{$maskedQuoteId}\""
+ );
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testGetNonExistentCart()
+ {
+ $maskedQuoteId = 'non_existent_masked_id';
+ $query = $this->getQuery($maskedQuoteId);
+
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @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()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+ $query = $this->getQuery($maskedQuoteId);
+
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @return string
+ */
+ private function getQuery(string $maskedQuoteId): string
+ {
+ return <<quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ */
+ public function testRemoveItemFromCart()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId();
+
+ $query = $this->prepareMutationQuery($maskedQuoteId, $itemId);
+ $response = $this->graphQlQuery($query);
+
+ $this->assertArrayHasKey('removeItemFromCart', $response);
+ $this->assertArrayHasKey('cart', $response['removeItemFromCart']);
+ $this->assertCount(0, $response['removeItemFromCart']['cart']['items']);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testRemoveItemFromNonExistentCart()
+ {
+ $query = $this->prepareMutationQuery('non_existent_masked_id', 1);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ */
+ public function testRemoveNonExistentItem()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $notExistentItemId = 999;
+
+ $this->expectExceptionMessage("Cart doesn't contain the {$notExistentItemId} item.");
+
+ $query = $this->prepareMutationQuery($maskedQuoteId, $notExistentItemId);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * Test mutation is only able to remove quote item belonging to the requested cart
+ *
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ */
+ public function testRemoveItemIfItemIsNotBelongToCart()
+ {
+ $firstQuote = $this->quoteFactory->create();
+ $this->quoteResource->load($firstQuote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
+ $firstQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$firstQuote->getId());
+
+ $secondQuote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $secondQuote,
+ 'test_order_with_virtual_product_without_address',
+ 'reserved_order_id'
+ );
+ $secondQuoteItemId = (int)$secondQuote
+ ->getItemByProduct($this->productRepository->get('virtual-product'))
+ ->getId();
+
+ $this->expectExceptionMessage("Cart doesn't contain the {$secondQuoteItemId} item.");
+
+ $query = $this->prepareMutationQuery($firstQuoteMaskedId, $secondQuoteItemId);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * Test mutation is only able to remove quote item belonging to the requested cart
+ *
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testRemoveItemFromCustomerCart()
+ {
+ $customerQuote = $this->quoteFactory->create();
+ $this->quoteResource->load($customerQuote, 'test_order_1', 'reserved_order_id');
+ $customerQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$customerQuote->getId());
+ $customerQuoteItemId = (int)$customerQuote->getItemByProduct($this->productRepository->get('simple'))->getId();
+
+ $this->expectExceptionMessage("The current user cannot perform operations on cart \"$customerQuoteMaskedId\"");
+
+ $query = $this->prepareMutationQuery($customerQuoteMaskedId, $customerQuoteItemId);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @param string $input
+ * @param string $message
+ * @dataProvider dataProviderUpdateWithMissedRequiredParameters
+ */
+ public function testUpdateWithMissedItemRequiredParameters(string $input, string $message)
+ {
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderUpdateWithMissedRequiredParameters(): array
+ {
+ return [
+ 'missed_cart_id' => [
+ 'cart_item_id: 1',
+ 'Required parameter "cart_id" is missing.'
+ ],
+ 'missed_cart_item_id' => [
+ 'cart_id: "test"',
+ 'Required parameter "cart_item_id" is missing.'
+ ],
+ ];
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param int $itemId
+ * @return string
+ */
+ private function prepareMutationQuery(string $maskedQuoteId, int $itemId): string
+ {
+ return <<getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ }
+
+ /**
+ * @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->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query);
+
+ self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
+ $cartResponse = $response['setBillingAddressOnCart']['cart'];
+ self::assertArrayHasKey('billing_address', $cartResponse);
+ $billingAddressResponse = $cartResponse['billing_address'];
+ $this->assertNewAddressFields($billingAddressResponse);
+ }
+
+ /**
+ * @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 testSetNewBillingAddressWithUseForShippingParameter()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query);
+
+ self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
+ $cartResponse = $response['setBillingAddressOnCart']['cart'];
+ self::assertArrayHasKey('billing_address', $cartResponse);
+ $billingAddressResponse = $cartResponse['billing_address'];
+ self::assertArrayHasKey('shipping_addresses', $cartResponse);
+ $shippingAddressResponse = current($cartResponse['shipping_addresses']);
+ $this->assertNewAddressFields($billingAddressResponse);
+ $this->assertNewAddressFields($shippingAddressResponse, 'SHIPPING');
+ }
+
+ /**
+ * _security
+ * @magentoApiDataFixture Magento/Customer/_files/customer.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php
+ */
+ public function testSetBillingAddressToCustomerCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$maskedQuoteId\""
+ );
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * _security
+ * @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 current customer isn't authorized.
+ */
+ public function testSetBillingAddressFromAddressBook()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testSetBillingAddressOnNonExistentCart()
+ {
+ $maskedQuoteId = 'non_existent_masked_id';
+ $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
+ *
+ * @param array $addressResponse
+ * @param string $addressType
+ */
+ private function assertNewAddressFields(array $addressResponse, string $addressType = 'BILLING'): void
+ {
+ $assertionMap = [
+ ['response_field' => 'firstname', 'expected_value' => 'test firstname'],
+ ['response_field' => 'lastname', 'expected_value' => 'test lastname'],
+ ['response_field' => 'company', 'expected_value' => 'test company'],
+ ['response_field' => 'street', 'expected_value' => [0 => 'test street 1', 1 => 'test street 2']],
+ ['response_field' => 'city', 'expected_value' => 'test city'],
+ ['response_field' => 'postcode', 'expected_value' => '887766'],
+ ['response_field' => 'telephone', 'expected_value' => '88776655'],
+ ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']],
+ ['response_field' => 'address_type', 'expected_value' => $addressType]
+ ];
+
+ $this->assertResponseFields($addressResponse, $assertionMap);
+ }
+}
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
new file mode 100644
index 0000000000000..8f37f00c3db7f
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetPaymentMethodOnCartTest.php
@@ -0,0 +1,251 @@
+getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ }
+
+ /**
+ * @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 testSetPaymentOnCartWithSimpleProduct()
+ {
+ $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);
+ self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']);
+ self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']);
+ self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']);
+ }
+
+ /**
+ * @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 testSetPaymentOnCartWithSimpleProductAndWithoutAddress()
+ {
+ $methodCode = Checkmo::PAYMENT_METHOD_CHECKMO_CODE;
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $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);
+ self::assertArrayHasKey('cart', $response['setPaymentMethodOnCart']);
+ self::assertArrayHasKey('selected_payment_method', $response['setPaymentMethodOnCart']['cart']);
+ self::assertEquals($methodCode, $response['setPaymentMethodOnCart']['cart']['selected_payment_method']['code']);
+ }
+
+ /**
+ * @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 requested Payment Method is not available.
+ */
+ public function testSetNonExistentPaymentMethod()
+ {
+ $methodCode = 'noway';
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testSetPaymentOnNonExistentCart()
+ {
+ $maskedQuoteId = 'non_existent_masked_id';
+ $query = <<graphQlQuery($query);
+ }
+
+ /**
+ * _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->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = $this->getQuery($maskedQuoteId, $methodCode);
+
+ $this->expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$maskedQuoteId\""
+ );
+ $this->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
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php
+ *
+ * @param string $input
+ * @param string $message
+ * @dataProvider dataProviderSetPaymentMethodWithoutRequiredParameters
+ * @throws \Exception
+ */
+ public function testSetPaymentMethodWithoutRequiredParameters(string $input, string $message)
+ {
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderSetPaymentMethodWithoutRequiredParameters(): array
+ {
+ return [
+ 'missed_cart_id' => [
+ 'payment_method: {code: "' . Checkmo::PAYMENT_METHOD_CHECKMO_CODE . '"}',
+ 'Required parameter "cart_id" is missing.'
+ ],
+ 'missed_payment_method' => [
+ 'cart_id: "test"',
+ 'Required parameter "code" for "payment_method" is missing.'
+ ],
+ 'missed_payment_method_code' => [
+ 'cart_id: "test", payment_method: {code: ""}',
+ 'Required parameter "code" for "payment_method" is missing.'
+ ],
+ ];
+ }
+
+ /**
+ * @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()
+ {
+ $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']);
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param string $methodCode
+ * @return string
+ */
+ private function getQuery(
+ string $maskedQuoteId,
+ string $methodCode
+ ) : string {
+ return <<getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ }
+
+ /**
+ * @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 testSetNewShippingAddressOnCartWithSimpleProduct()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query);
+
+ self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']);
+ $cartResponse = $response['setShippingAddressesOnCart']['cart'];
+ self::assertArrayHasKey('shipping_addresses', $cartResponse);
+ $shippingAddressResponse = current($cartResponse['shipping_addresses']);
+ $this->assertNewShippingAddressFields($shippingAddressResponse);
+ }
+
+ /**
+ * @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
+ *
+ * @expectedException \Exception
+ * @expectedExceptionMessage The Cart includes virtual product(s) only, so a shipping address is not used.
+ */
+ public function testSetNewShippingAddressOnCartWithVirtualProduct()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query);
+ }
+
+ /**
+ * _security
+ * @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 current customer isn't authorized.
+ */
+ public function testSetShippingAddressFromAddressBook()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query);
+ }
+
+ /**
+ * _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
+ *
+ * @expectedException \Exception
+ */
+ public function testSetShippingAddressToCustomerCart()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<expectExceptionMessage(
+ "The current user cannot perform operations on cart \"$maskedQuoteId\""
+ );
+ $this->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 dataProviderUpdateWithMissedRequiredParameters
+ * @param string $input
+ * @param string $message
+ * @throws \Exception
+ */
+ public function testSetNewShippingAddressWithMissedRequiredParameters(string $input, string $message)
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderUpdateWithMissedRequiredParameters(): array
+ {
+ return [
+ 'shipping_addresses' => [
+ '',
+ 'The shipping address must contain either "customer_address_id" or "address".',
+ ],
+ 'missed_city' => [
+ 'address: { save_in_address_book: false }',
+ 'Field CartAddressInput.city of required type String! was not provided'
+ ]
+ ];
+ }
+
+ /**
+ * @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->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = <<graphQlQuery($query);
+ }
+
+ /**
+ * Verify the all the whitelisted fields for a New Address Object
+ *
+ * @param array $shippingAddressResponse
+ */
+ private function assertNewShippingAddressFields(array $shippingAddressResponse): void
+ {
+ $assertionMap = [
+ ['response_field' => 'firstname', 'expected_value' => 'test firstname'],
+ ['response_field' => 'lastname', 'expected_value' => 'test lastname'],
+ ['response_field' => 'company', 'expected_value' => 'test company'],
+ ['response_field' => 'street', 'expected_value' => [0 => 'test street 1', 1 => 'test street 2']],
+ ['response_field' => 'city', 'expected_value' => 'test city'],
+ ['response_field' => 'postcode', 'expected_value' => '887766'],
+ ['response_field' => 'telephone', 'expected_value' => '88776655'],
+ ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']],
+ ['response_field' => 'address_type', 'expected_value' => 'SHIPPING']
+ ];
+
+ $this->assertResponseFields($shippingAddressResponse, $assertionMap);
+ }
+}
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
new file mode 100644
index 0000000000000..bf08ce8adce6c
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetShippingMethodsOnCartTest.php
@@ -0,0 +1,167 @@
+quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ }
+
+ public function testShippingMethodWithVirtualProduct()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testShippingMethodWithSimpleProduct()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testShippingMethodWithSimpleProductWithoutAddress()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetShippingMethodWithMissedRequiredParameters()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetNonExistentShippingMethod()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetShippingMethodIfAddressIsNotBelongToCart()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetShippingMethodToNonExistentCart()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetShippingMethodToGuestCart()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetShippingMethodToAnotherCustomerCart()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetShippingMethodToNonExistentCartAddress()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetShippingMethodToGuestCartAddress()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetShippingMethodToAnotherCustomerCartAddress()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ public function testSetMultipleShippingMethods()
+ {
+ $this->markTestIncomplete('https://github.com/magento/graphql-ce/issues/422');
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param string $shippingMethodCode
+ * @param string $shippingCarrierCode
+ * @param string $shippingAddressId
+ * @return string
+ * @SuppressWarnings(PHPMD.UnusedPrivateMethod)
+ */
+ private function prepareMutationQuery(
+ string $maskedQuoteId,
+ string $shippingMethodCode,
+ string $shippingCarrierCode,
+ string $shippingAddressId
+ ) : string {
+ return <<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/UpdateCartItemsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php
new file mode 100644
index 0000000000000..fca7a4287620b
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/UpdateCartItemsTest.php
@@ -0,0 +1,273 @@
+quoteResource = $objectManager->get(QuoteResource::class);
+ $this->quoteFactory = $objectManager->get(QuoteFactory::class);
+ $this->quoteIdToMaskedId = $objectManager->get(QuoteIdToMaskedQuoteIdInterface::class);
+ $this->productRepository = $objectManager->get(ProductRepositoryInterface::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ */
+ public function testUpdateCartItemQty()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId();
+ $qty = 2;
+
+ $query = $this->getQuery($maskedQuoteId, $itemId, $qty);
+ $response = $this->graphQlQuery($query);
+
+ $this->assertArrayHasKey('updateCartItems', $response);
+ $this->assertArrayHasKey('cart', $response['updateCartItems']);
+
+ $responseCart = $response['updateCartItems']['cart'];
+ $item = current($responseCart['items']);
+
+ $this->assertEquals($itemId, $item['id']);
+ $this->assertEquals($qty, $item['qty']);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ */
+ public function testRemoveCartItemIfQuantityIsZero()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $itemId = (int)$quote->getItemByProduct($this->productRepository->get('simple'))->getId();
+ $qty = 0;
+
+ $query = $this->getQuery($maskedQuoteId, $itemId, $qty);
+ $response = $this->graphQlQuery($query);
+
+ $this->assertArrayHasKey('updateCartItems', $response);
+ $this->assertArrayHasKey('cart', $response['updateCartItems']);
+
+ $responseCart = $response['updateCartItems']['cart'];
+ $this->assertCount(0, $responseCart['items']);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Could not find a cart with ID "non_existent_masked_id"
+ */
+ public function testUpdateItemInNonExistentCart()
+ {
+ $query = $this->getQuery('non_existent_masked_id', 1, 2);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ */
+ public function testUpdateNonExistentItem()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+ $notExistentItemId = 999;
+
+ $this->expectExceptionMessage("Could not find cart item with id: {$notExistentItemId}.");
+
+ $query = $this->getQuery($maskedQuoteId, $notExistentItemId, 2);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_virtual_product_saved.php
+ */
+ public function testUpdateItemIfItemIsNotBelongToCart()
+ {
+ $firstQuote = $this->quoteFactory->create();
+ $this->quoteResource->load($firstQuote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
+ $firstQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$firstQuote->getId());
+
+ $secondQuote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $secondQuote,
+ 'test_order_with_virtual_product_without_address',
+ 'reserved_order_id'
+ );
+ $secondQuoteItemId = (int)$secondQuote
+ ->getItemByProduct($this->productRepository->get('virtual-product'))
+ ->getId();
+
+ $this->expectExceptionMessage("Could not find cart item with id: {$secondQuoteItemId}.");
+
+ $query = $this->getQuery($firstQuoteMaskedId, $secondQuoteItemId, 2);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ */
+ public function testUpdateItemFromCustomerCart()
+ {
+ $customerQuote = $this->quoteFactory->create();
+ $this->quoteResource->load($customerQuote, 'test_order_1', 'reserved_order_id');
+ $customerQuoteMaskedId = $this->quoteIdToMaskedId->execute((int)$customerQuote->getId());
+ $customerQuoteItemId = (int)$customerQuote->getItemByProduct($this->productRepository->get('simple'))->getId();
+
+ $this->expectExceptionMessage("The current user cannot perform operations on cart \"$customerQuoteMaskedId\"");
+
+ $query = $this->getQuery($customerQuoteMaskedId, $customerQuoteItemId, 2);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage Required parameter "cart_id" is missing.
+ */
+ public function testUpdateWithMissedCartItemId()
+ {
+ $query = <<graphQlQuery($query);
+ }
+
+ /**
+ * @param string $input
+ * @param string $message
+ * @dataProvider dataProviderUpdateWithMissedRequiredParameters
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
+ */
+ public function testUpdateWithMissedItemRequiredParameters(string $input, string $message)
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load($quote, 'test_order_with_simple_product_without_address', 'reserved_order_id');
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+
+ $query = <<expectExceptionMessage($message);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @return array
+ */
+ public function dataProviderUpdateWithMissedRequiredParameters(): array
+ {
+ return [
+ 'missed_cart_items' => [
+ '',
+ 'Required parameter "cart_items" is missing.'
+ ],
+ 'missed_cart_item_id' => [
+ 'cart_items: [{ quantity: 2 }]',
+ 'Required parameter "cart_item_id" for "cart_items" is missing.'
+ ],
+ 'missed_cart_item_qty' => [
+ 'cart_items: [{ cart_item_id: 1 }]',
+ 'Required parameter "quantity" for "cart_items" is missing.'
+ ],
+ ];
+ }
+
+ /**
+ * @param string $maskedQuoteId
+ * @param int $itemId
+ * @param float $qty
+ * @return string
+ */
+ private function getQuery(string $maskedQuoteId, int $itemId, float $qty): string
+ {
+ return <<quoteResource = $objectManager->create(QuoteResource::class);
- $this->quote = $objectManager->create(Quote::class);
- $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- */
- public function testSetNewGuestBillingAddressOnCart()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = <<graphQlQuery($query);
-
- self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
- $cartResponse = $response['setBillingAddressOnCart']['cart'];
- self::assertArrayHasKey('billing_address', $cartResponse);
- $billingAddressResponse = $cartResponse['billing_address'];
- $this->assertNewAddressFields($billingAddressResponse);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- */
- public function testSetNewGuestBillingAddressOnUseForShippingCart()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = <<graphQlQuery($query);
-
- self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
- $cartResponse = $response['setBillingAddressOnCart']['cart'];
- self::assertArrayHasKey('billing_address', $cartResponse);
- $billingAddressResponse = $cartResponse['billing_address'];
- self::assertArrayHasKey('shipping_addresses', $cartResponse);
- $shippingAddressResponse = current($cartResponse['shipping_addresses']);
- $this->assertNewAddressFields($billingAddressResponse);
- $this->assertNewAddressFields($shippingAddressResponse);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- */
- public function testSetSavedBillingAddressOnCartByGuest()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = <<graphQlQuery($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- * @magentoApiDataFixture Magento/Customer/_files/customer.php
- */
- public function testSetNewRegisteredCustomerBillingAddressOnCart()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $this->quote->setCustomerId(1);
- $this->quoteResource->save($this->quote);
-
- $headerMap = $this->getHeaderMap();
-
- $query = <<graphQlQuery($query, [], '', $headerMap);
-
- self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
- $cartResponse = $response['setBillingAddressOnCart']['cart'];
- self::assertArrayHasKey('billing_address', $cartResponse);
- $billingAddressResponse = $cartResponse['billing_address'];
- $this->assertNewAddressFields($billingAddressResponse);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- * @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php
- */
- public function testSetSavedRegisteredCustomerBillingAddressOnCart()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $this->quote->setCustomerId(1);
- $this->quoteResource->save($this->quote);
-
- $headerMap = $this->getHeaderMap();
-
- $query = <<graphQlQuery($query, [], '', $headerMap);
-
- self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']);
- $cartResponse = $response['setBillingAddressOnCart']['cart'];
- self::assertArrayHasKey('billing_address', $cartResponse);
- $billingAddressResponse = $cartResponse['billing_address'];
- $this->assertSavedBillingAddressFields($billingAddressResponse);
- }
-
- /**
- * Verify the all the whitelisted fields for a New Address Object
- *
- * @param array $billingAddressResponse
- */
- private function assertNewAddressFields(array $billingAddressResponse): void
- {
- $assertionMap = [
- ['response_field' => 'firstname', 'expected_value' => 'test firstname'],
- ['response_field' => 'lastname', 'expected_value' => 'test lastname'],
- ['response_field' => 'company', 'expected_value' => 'test company'],
- ['response_field' => 'street', 'expected_value' => [0 => 'test street 1', 1 => 'test street 2']],
- ['response_field' => 'city', 'expected_value' => 'test city'],
- ['response_field' => 'postcode', 'expected_value' => '887766'],
- ['response_field' => 'telephone', 'expected_value' => '88776655']
- ];
-
- $this->assertResponseFields($billingAddressResponse, $assertionMap);
- }
-
- /**
- * Verify the all the whitelisted fields for a Address Object
- *
- * @param array $billingAddressResponse
- */
- private function assertSavedBillingAddressFields(array $billingAddressResponse): void
- {
- $assertionMap = [
- ['response_field' => 'firstname', 'expected_value' => 'John'],
- ['response_field' => 'lastname', 'expected_value' => 'Smith'],
- ['response_field' => 'company', 'expected_value' => 'CompanyName'],
- ['response_field' => 'street', 'expected_value' => [0 => 'Green str, 67']],
- ['response_field' => 'city', 'expected_value' => 'CityM'],
- ['response_field' => 'postcode', 'expected_value' => '75477'],
- ['response_field' => 'telephone', 'expected_value' => '3468676']
- ];
-
- $this->assertResponseFields($billingAddressResponse, $assertionMap);
- }
-
- /**
- * @param string $username
- * @return array
- */
- private function getHeaderMap(string $username = 'customer@example.com'): array
- {
- $password = 'password';
- /** @var CustomerTokenServiceInterface $customerTokenService */
- $customerTokenService = ObjectManager::getInstance()
- ->get(CustomerTokenServiceInterface::class);
- $customerToken = $customerTokenService->createCustomerAccessToken($username, $password);
- $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
- return $headerMap;
- }
-
- public function tearDown()
- {
- /** @var \Magento\Config\Model\ResourceModel\Config $config */
- $config = ObjectManager::getInstance()->get(\Magento\Config\Model\ResourceModel\Config::class);
-
- //default state of multishipping config
- $config->saveConfig(
- Data::XML_PATH_CHECKOUT_MULTIPLE_AVAILABLE,
- 1,
- ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
- 0
- );
-
- /** @var \Magento\Framework\App\Config\ReinitableConfigInterface $config */
- $config = ObjectManager::getInstance()->get(\Magento\Framework\App\Config\ReinitableConfigInterface::class);
- $config->reinit();
- }
-}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php
new file mode 100644
index 0000000000000..cf32003340a66
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetOfflineShippingMethodsOnCartTest.php
@@ -0,0 +1,210 @@
+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/GraphQl/Quote/_files/enable_offline_shipping_methods.php
+ * @magentoApiDataFixture Magento/OfflineShipping/_files/tablerates_weight.php
+ *
+ * @param string $carrierCode
+ * @param string $methodCode
+ * @param float $amount
+ * @param string $label
+ * @dataProvider offlineShippingMethodDataProvider
+ */
+ public function testSetOfflineShippingMethod(string $carrierCode, string $methodCode, float $amount, string $label)
+ {
+ $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->getQuery(
+ $maskedQuoteId,
+ $shippingAddressId,
+ $carrierCode,
+ $methodCode
+ );
+
+ $response = $this->sendRequestWithToken($query);
+
+ $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses'];
+ self::assertEquals($addressesInformation[0]['selected_shipping_method']['carrier_code'], $carrierCode);
+ self::assertEquals($addressesInformation[0]['selected_shipping_method']['method_code'], $methodCode);
+ self::assertEquals($addressesInformation[0]['selected_shipping_method']['amount'], $amount);
+ self::assertEquals($addressesInformation[0]['selected_shipping_method']['label'], $label);
+ }
+
+ /**
+ * @return array
+ */
+ public function offlineShippingMethodDataProvider()
+ {
+ return [
+ 'flatrate_flatrate' => ['flatrate', 'flatrate', 10, 'Flat Rate - Fixed'],
+ 'tablerate_bestway' => ['tablerate', 'bestway', 10, 'Best Way - Table Rate'],
+ 'freeshipping_freeshipping' => ['freeshipping', 'freeshipping', 0, 'Free Shipping - Free'],
+ ];
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/enable_offline_shipping_methods.php
+ */
+ public function testSetShippingMethodTwiceInOneRequest()
+ {
+ $quote = $this->quoteFactory->create();
+ $this->quoteResource->load(
+ $quote,
+ 'test_order_1',
+ 'reserved_order_id'
+ );
+ $shippingAddress = $quote->getShippingAddress();
+ $shippingAddressId = $shippingAddress->getId();
+ $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$quote->getId());
+
+ $query = <<sendRequestWithToken($query);
+ }
+
+ /**
+ * 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 getQuery(
+ 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/Quote/SetShippingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php
deleted file mode 100644
index d60876e7c0be4..0000000000000
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingAddressOnCartTest.php
+++ /dev/null
@@ -1,501 +0,0 @@
-quoteResource = $objectManager->create(QuoteResource::class);
- $this->quote = $objectManager->create(Quote::class);
- $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- */
- public function testSetNewGuestShippingAddressOnCart()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = <<graphQlQuery($query);
-
- self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']);
- $cartResponse = $response['setShippingAddressesOnCart']['cart'];
- self::assertArrayHasKey('shipping_addresses', $cartResponse);
- $shippingAddressResponse = current($cartResponse['shipping_addresses']);
- $this->assertNewShippingAddressFields($shippingAddressResponse);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- */
- public function testSetSavedShippingAddressOnCartByGuest()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = <<graphQlQuery($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- */
- public function testSetMultipleShippingAddressesOnCartByGuest()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = <<get(\Magento\Config\Model\ResourceModel\Config::class);
- $config->saveConfig(
- Data::XML_PATH_CHECKOUT_MULTIPLE_AVAILABLE,
- null,
- ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
- 0
- );
- /** @var \Magento\Framework\App\Config\ReinitableConfigInterface $config */
- $config = ObjectManager::getInstance()->get(\Magento\Framework\App\Config\ReinitableConfigInterface::class);
- $config->reinit();
-
- self::expectExceptionMessage('You cannot specify multiple shipping addresses.');
- $this->graphQlQuery($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- */
- public function testSetSavedAndNewShippingAddressOnCartAtTheSameTime()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = <<graphQlQuery($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- */
- public function testSetShippingAddressOnCartWithNoAddresses()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = <<graphQlQuery($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- * @magentoApiDataFixture Magento/Customer/_files/customer.php
- */
- public function testSetNewRegisteredCustomerShippingAddressOnCart()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $this->quote->setCustomerId(1);
- $this->quoteResource->save($this->quote);
-
- $headerMap = $this->getHeaderMap();
-
- $query = <<graphQlQuery($query, [], '', $headerMap);
-
- self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']);
- $cartResponse = $response['setShippingAddressesOnCart']['cart'];
- self::assertArrayHasKey('shipping_addresses', $cartResponse);
- $shippingAddressResponse = current($cartResponse['shipping_addresses']);
- $this->assertNewShippingAddressFields($shippingAddressResponse);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_simple_product_saved.php
- * @magentoApiDataFixture Magento/Customer/_files/customer.php
- * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php
- */
- public function testSetSavedRegisteredCustomerShippingAddressOnCart()
- {
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
- $this->quoteResource->load(
- $this->quote,
- 'test_order_with_simple_product_without_address',
- 'reserved_order_id'
- );
- $this->quote->setCustomerId(1);
- $this->quoteResource->save($this->quote);
-
- $headerMap = $this->getHeaderMap();
-
- $query = <<graphQlQuery($query, [], '', $headerMap);
-
- self::assertArrayHasKey('cart', $response['setShippingAddressesOnCart']);
- $cartResponse = $response['setShippingAddressesOnCart']['cart'];
- self::assertArrayHasKey('shipping_addresses', $cartResponse);
- $shippingAddressResponse = current($cartResponse['shipping_addresses']);
- $this->assertSavedShippingAddressFields($shippingAddressResponse);
- }
-
- /**
- * Verify the all the whitelisted fields for a New Address Object
- *
- * @param array $shippingAddressResponse
- */
- private function assertNewShippingAddressFields(array $shippingAddressResponse): void
- {
- $assertionMap = [
- ['response_field' => 'firstname', 'expected_value' => 'test firstname'],
- ['response_field' => 'lastname', 'expected_value' => 'test lastname'],
- ['response_field' => 'company', 'expected_value' => 'test company'],
- ['response_field' => 'street', 'expected_value' => [0 => 'test street 1', 1 => 'test street 2']],
- ['response_field' => 'city', 'expected_value' => 'test city'],
- ['response_field' => 'postcode', 'expected_value' => '887766'],
- ['response_field' => 'telephone', 'expected_value' => '88776655']
- ];
-
- $this->assertResponseFields($shippingAddressResponse, $assertionMap);
- }
-
- /**
- * Verify the all the whitelisted fields for a Address Object
- *
- * @param array $shippingAddressResponse
- */
- private function assertSavedShippingAddressFields(array $shippingAddressResponse): void
- {
- $assertionMap = [
- ['response_field' => 'firstname', 'expected_value' => 'John'],
- ['response_field' => 'lastname', 'expected_value' => 'Smith'],
- ['response_field' => 'company', 'expected_value' => 'CompanyName'],
- ['response_field' => 'street', 'expected_value' => [0 => 'Green str, 67']],
- ['response_field' => 'city', 'expected_value' => 'CityM'],
- ['response_field' => 'postcode', 'expected_value' => '75477'],
- ['response_field' => 'telephone', 'expected_value' => '3468676']
- ];
-
- $this->assertResponseFields($shippingAddressResponse, $assertionMap);
- }
-
- /**
- * @param string $username
- * @return array
- */
- private function getHeaderMap(string $username = 'customer@example.com'): array
- {
- $password = 'password';
- /** @var CustomerTokenServiceInterface $customerTokenService */
- $customerTokenService = ObjectManager::getInstance()
- ->get(CustomerTokenServiceInterface::class);
- $customerToken = $customerTokenService->createCustomerAccessToken($username, $password);
- $headerMap = ['Authorization' => 'Bearer ' . $customerToken];
- return $headerMap;
- }
-
- public function tearDown()
- {
- /** @var \Magento\Config\Model\ResourceModel\Config $config */
- $config = ObjectManager::getInstance()->get(\Magento\Config\Model\ResourceModel\Config::class);
-
- //default state of multishipping config
- $config->saveConfig(
- Data::XML_PATH_CHECKOUT_MULTIPLE_AVAILABLE,
- 1,
- ScopeConfigInterface::SCOPE_TYPE_DEFAULT,
- 0
- );
-
- /** @var \Magento\Framework\App\Config\ReinitableConfigInterface $config */
- $config = ObjectManager::getInstance()->get(\Magento\Framework\App\Config\ReinitableConfigInterface::class);
- $config->reinit();
- }
-}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodOnCartTest.php
deleted file mode 100644
index 1c6679ee30f29..0000000000000
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/SetShippingMethodOnCartTest.php
+++ /dev/null
@@ -1,252 +0,0 @@
-quoteResource = $objectManager->create(QuoteResource::class);
- $this->quote = $objectManager->create(Quote::class);
- $this->quoteIdToMaskedId = $objectManager->create(QuoteIdToMaskedQuoteIdInterface::class);
- $this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
- */
- public function testSetShippingMethodOnCart()
- {
- $shippingCarrierCode = 'flatrate';
- $shippingMethodCode = 'flatrate';
- $this->quoteResource->load(
- $this->quote,
- 'test_order_1',
- 'reserved_order_id'
- );
- $shippingAddress = $this->quote->getShippingAddress();
- $shippingAddressId = $shippingAddress->getId();
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = $this->prepareMutationQuery(
- $maskedQuoteId,
- $shippingMethodCode,
- $shippingCarrierCode,
- $shippingAddressId
- );
-
- $response = $this->sendRequestWithToken($query);
-
- self::assertArrayHasKey('setShippingMethodsOnCart', $response);
- self::assertArrayHasKey('cart', $response['setShippingMethodsOnCart']);
- self::assertEquals($maskedQuoteId, $response['setShippingMethodsOnCart']['cart']['cart_id']);
- $addressesInformation = $response['setShippingMethodsOnCart']['cart']['shipping_addresses'];
- self::assertCount(1, $addressesInformation);
- self::assertEquals(
- $addressesInformation[0]['selected_shipping_method']['code'],
- $shippingCarrierCode . '_' . $shippingMethodCode
- );
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
- */
- public function testSetShippingMethodWithWrongCartId()
- {
- $shippingCarrierCode = 'flatrate';
- $shippingMethodCode = 'flatrate';
- $shippingAddressId = '1';
- $maskedQuoteId = 'invalid';
-
- $query = $this->prepareMutationQuery(
- $maskedQuoteId,
- $shippingMethodCode,
- $shippingCarrierCode,
- $shippingAddressId
- );
-
- self::expectExceptionMessage("Could not find a cart with ID \"$maskedQuoteId\"");
- $this->sendRequestWithToken($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
- */
- public function testSetNonExistingShippingMethod()
- {
- $shippingCarrierCode = 'non';
- $shippingMethodCode = 'existing';
- $this->quoteResource->load(
- $this->quote,
- 'test_order_1',
- 'reserved_order_id'
- );
- $shippingAddress = $this->quote->getShippingAddress();
- $shippingAddressId = $shippingAddress->getId();
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = $this->prepareMutationQuery(
- $maskedQuoteId,
- $shippingMethodCode,
- $shippingCarrierCode,
- $shippingAddressId
- );
-
- self::expectExceptionMessage("Carrier with such method not found: $shippingCarrierCode, $shippingMethodCode");
- $this->sendRequestWithToken($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
- */
- public function testSetShippingMethodWithNonExistingAddress()
- {
- $shippingCarrierCode = 'flatrate';
- $shippingMethodCode = 'flatrate';
- $this->quoteResource->load(
- $this->quote,
- 'test_order_1',
- 'reserved_order_id'
- );
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
- $shippingAddressId = '-20';
-
- $query = $this->prepareMutationQuery(
- $maskedQuoteId,
- $shippingMethodCode,
- $shippingCarrierCode,
- $shippingAddressId
- );
-
- self::expectExceptionMessage('The shipping address is missing. Set the address and try again.');
- $this->sendRequestWithToken($query);
- }
-
- /**
- * @magentoApiDataFixture Magento/Checkout/_files/quote_with_address_saved.php
- */
- public function testSetShippingMethodByGuestToCustomerCart()
- {
- $shippingCarrierCode = 'flatrate';
- $shippingMethodCode = 'flatrate';
- $this->quoteResource->load(
- $this->quote,
- 'test_order_1',
- 'reserved_order_id'
- );
- $shippingAddress = $this->quote->getShippingAddress();
- $shippingAddressId = $shippingAddress->getId();
- $maskedQuoteId = $this->quoteIdToMaskedId->execute((int)$this->quote->getId());
-
- $query = $this->prepareMutationQuery(
- $maskedQuoteId,
- $shippingMethodCode,
- $shippingCarrierCode,
- $shippingAddressId
- );
-
- self::expectExceptionMessage(
- "The current user cannot perform operations on cart \"$maskedQuoteId\""
- );
-
- $this->graphQlQuery($query);
- }
-
- /**
- * Generates query for setting the specified shipping method on cart
- *
- * @param string $maskedQuoteId
- * @param string $shippingMethodCode
- * @param string $shippingCarrierCode
- * @param string $shippingAddressId
- * @return string
- */
- private function prepareMutationQuery(
- string $maskedQuoteId,
- string $shippingMethodCode,
- string $shippingCarrierCode,
- string $shippingAddressId
- ) : 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/SendFriend/SendFriendTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php
new file mode 100644
index 0000000000000..05e3e608c5e52
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/SendFriend/SendFriendTest.php
@@ -0,0 +1,357 @@
+sendFriendFactory = Bootstrap::getObjectManager()->get(SendFriendFactory::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testSendFriend()
+ {
+ $query =
+ <<graphQlQuery($query);
+ self::assertEquals('Name', $response['sendEmailToFriend']['sender']['name']);
+ self::assertEquals('e@mail.com', $response['sendEmailToFriend']['sender']['email']);
+ self::assertEquals('Lorem Ipsum', $response['sendEmailToFriend']['sender']['message']);
+ self::assertEquals('Recipient Name 1', $response['sendEmailToFriend']['recipients'][0]['name']);
+ self::assertEquals('recipient1@mail.com', $response['sendEmailToFriend']['recipients'][0]['email']);
+ self::assertEquals('Recipient Name 2', $response['sendEmailToFriend']['recipients'][1]['name']);
+ self::assertEquals('recipient2@mail.com', $response['sendEmailToFriend']['recipients'][1]['email']);
+ }
+
+ public function testSendWithoutExistProduct()
+ {
+ $query =
+ <<expectException(\Exception::class);
+ $this->expectExceptionMessage(
+ 'The product that was requested doesn\'t exist. Verify the product and try again.'
+ );
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ */
+ public function testMaxSendEmailToFriend()
+ {
+ /** @var SendFriend $sendFriend */
+ $sendFriend = $this->sendFriendFactory->create();
+
+ $query =
+ <<expectException(\Exception::class);
+ $this->expectExceptionMessage("No more than {$sendFriend->getMaxRecipients()} emails can be sent at a time.");
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * @dataProvider sendFriendsErrorsDataProvider
+ * @param string $input
+ * @param string $errorMessage
+ */
+ public function testErrors(string $input, string $errorMessage)
+ {
+ $query =
+ <<expectException(\Exception::class);
+ $this->expectExceptionMessage($errorMessage);
+ $this->graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Catalog/_files/product_simple.php
+ * TODO: use magentoApiConfigFixture (to be merged https://github.com/magento/graphql-ce/pull/351)
+ * @magentoApiDataFixture Magento/SendFriend/Fixtures/sendfriend_configuration.php
+ */
+ public function testLimitMessagesPerHour()
+ {
+
+ /** @var SendFriend $sendFriend */
+ $sendFriend = $this->sendFriendFactory->create();
+
+ $query =
+ <<expectException(\Exception::class);
+ $this->expectExceptionMessage(
+ "You can't send messages more than {$sendFriend->getMaxSendsToFriend()} times an hour."
+ );
+
+ for ($i = 0; $i <= $sendFriend->getMaxSendsToFriend() + 1; $i++) {
+ $this->graphQlQuery($query);
+ }
+ }
+
+ /**
+ * @return array
+ */
+ public function sendFriendsErrorsDataProvider()
+ {
+ return [
+ [
+ 'product_id: 1
+ sender: {
+ name: "Name"
+ email: "e@mail.com"
+ message: "Lorem Ipsum"
+ }
+ recipients: [
+ {
+ name: ""
+ email:"recipient1@mail.com"
+ },
+ {
+ name: ""
+ email:"recipient2@mail.com"
+ }
+ ]', 'Please provide Name for all of recipients.'
+ ],
+ [
+ 'product_id: 1
+ sender: {
+ name: "Name"
+ email: "e@mail.com"
+ message: "Lorem Ipsum"
+ }
+ recipients: [
+ {
+ name: "Recipient Name 1"
+ email:""
+ },
+ {
+ name: "Recipient Name 2"
+ email:""
+ }
+ ]', 'Please provide Email for all of recipients.'
+ ],
+ [
+ 'product_id: 1
+ sender: {
+ name: ""
+ email: "e@mail.com"
+ message: "Lorem Ipsum"
+ }
+ recipients: [
+ {
+ name: "Recipient Name 1"
+ email:"recipient1@mail.com"
+ },
+ {
+ name: "Recipient Name 2"
+ email:"recipient2@mail.com"
+ }
+ ]', 'Please provide Name of sender.'
+ ],
+ [
+ 'product_id: 1
+ sender: {
+ name: "Name"
+ email: "e@mail.com"
+ message: ""
+ }
+ recipients: [
+ {
+ name: "Recipient Name 1"
+ email:"recipient1@mail.com"
+ },
+ {
+ name: "Recipient Name 2"
+ email:"recipient2@mail.com"
+ }
+ ]', 'Please provide Message.'
+ ]
+ ];
+ }
+}
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/UrlRewrite/UrlResolverTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php
index c70b1631e85cd..370121a1dad78 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/UrlRewrite/UrlResolverTest.php
@@ -31,7 +31,7 @@ protected function setUp()
}
/**
- * Tests if target_path(canonical_url) is resolved for Product entity
+ * Tests if target_path(relative_url) is resolved for Product entity
*
* @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php
*/
@@ -60,7 +60,7 @@ public function testProductUrlResolver()
urlResolver(url:"{$urlPath}")
{
id
- canonical_url
+ relative_url
type
}
}
@@ -68,12 +68,12 @@ public function testProductUrlResolver()
$response = $this->graphQlQuery($query);
$this->assertArrayHasKey('urlResolver', $response);
$this->assertEquals($product->getEntityId(), $response['urlResolver']['id']);
- $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']);
+ $this->assertEquals($targetPath, $response['urlResolver']['relative_url']);
$this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']);
}
/**
- * Tests the use case where canonical_url is provided as resolver input in the Query
+ * Tests the use case where relative_url is provided as resolver input in the Query
*
* @magentoApiDataFixture Magento/CatalogUrlRewrite/_files/product_with_category.php
*/
@@ -104,7 +104,7 @@ public function testProductUrlWithCanonicalUrlInput()
urlResolver(url:"{$canonicalPath}")
{
id
- canonical_url
+ relative_url
type
}
}
@@ -112,7 +112,7 @@ public function testProductUrlWithCanonicalUrlInput()
$response = $this->graphQlQuery($query);
$this->assertArrayHasKey('urlResolver', $response);
$this->assertEquals($product->getEntityId(), $response['urlResolver']['id']);
- $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']);
+ $this->assertEquals($targetPath, $response['urlResolver']['relative_url']);
$this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']);
}
@@ -147,7 +147,7 @@ public function testCategoryUrlResolver()
urlResolver(url:"{$urlPath2}")
{
id
- canonical_url
+ relative_url
type
}
}
@@ -155,7 +155,7 @@ public function testCategoryUrlResolver()
$response = $this->graphQlQuery($query);
$this->assertArrayHasKey('urlResolver', $response);
$this->assertEquals($categoryId, $response['urlResolver']['id']);
- $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']);
+ $this->assertEquals($targetPath, $response['urlResolver']['relative_url']);
$this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']);
}
@@ -183,14 +183,14 @@ public function testCMSPageUrlResolver()
urlResolver(url:"{$requestPath}")
{
id
- canonical_url
+ relative_url
type
}
}
QUERY;
$response = $this->graphQlQuery($query);
$this->assertEquals($cmsPageId, $response['urlResolver']['id']);
- $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']);
+ $this->assertEquals($targetPath, $response['urlResolver']['relative_url']);
$this->assertEquals(strtoupper(str_replace('-', '_', $expectedEntityType)), $response['urlResolver']['type']);
}
@@ -226,7 +226,7 @@ public function testProductUrlRewriteResolver()
urlResolver(url:"{$urlPath}")
{
id
- canonical_url
+ relative_url
type
}
}
@@ -234,7 +234,7 @@ public function testProductUrlRewriteResolver()
$response = $this->graphQlQuery($query);
$this->assertArrayHasKey('urlResolver', $response);
$this->assertEquals($product->getEntityId(), $response['urlResolver']['id']);
- $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']);
+ $this->assertEquals($targetPath, $response['urlResolver']['relative_url']);
$this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']);
}
@@ -266,7 +266,7 @@ public function testInvalidUrlResolverInput()
urlResolver(url:"{$urlPath}")
{
id
- canonical_url
+ relative_url
type
}
}
@@ -307,7 +307,7 @@ public function testCategoryUrlWithLeadingSlash()
urlResolver(url:"/{$urlPath}")
{
id
- canonical_url
+ relative_url
type
}
}
@@ -315,7 +315,7 @@ public function testCategoryUrlWithLeadingSlash()
$response = $this->graphQlQuery($query);
$this->assertArrayHasKey('urlResolver', $response);
$this->assertEquals($categoryId, $response['urlResolver']['id']);
- $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']);
+ $this->assertEquals($targetPath, $response['urlResolver']['relative_url']);
$this->assertEquals(strtoupper($expectedType), $response['urlResolver']['type']);
}
@@ -344,7 +344,7 @@ public function testResolveSlash()
urlResolver(url:"/")
{
id
- canonical_url
+ relative_url
type
}
}
@@ -352,7 +352,7 @@ public function testResolveSlash()
$response = $this->graphQlQuery($query);
$this->assertArrayHasKey('urlResolver', $response);
$this->assertEquals($homePageId, $response['urlResolver']['id']);
- $this->assertEquals($targetPath, $response['urlResolver']['canonical_url']);
+ $this->assertEquals($targetPath, $response['urlResolver']['relative_url']);
$this->assertEquals('CMS_PAGE', $response['urlResolver']['type']);
}
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php
new file mode 100644
index 0000000000000..89fbbb9c49ed3
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Vault/CustomerPaymentTokensTest.php
@@ -0,0 +1,206 @@
+customerTokenService = Bootstrap::getObjectManager()->get(CustomerTokenServiceInterface::class);
+ $this->paymentTokenManagement = Bootstrap::getObjectManager()->get(PaymentTokenManagement::class);
+ $this->tokenResource = Bootstrap::getObjectManager()->get(TokenResource::class);
+ $this->tokenCollectionFactory = Bootstrap::getObjectManager()->get(CollectionFactory::class);
+ }
+
+ protected function tearDown()
+ {
+ parent::tearDown();
+
+ $collection = $this->tokenCollectionFactory->create();
+ $collection->addFieldToFilter('customer_id', ['eq' => 1]);
+
+ foreach ($collection->getItems() as $token) {
+ // Using the resource directly to delete. Deleting from the repository only makes token inactive
+ $this->tokenResource->delete($token);
+ }
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Vault/_files/payment_tokens.php
+ */
+ public function testGetCustomerPaymentTokens()
+ {
+ $currentEmail = 'customer@example.com';
+ $currentPassword = 'password';
+
+ $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword));
+
+ $this->assertEquals(2, count($response['customerPaymentTokens']['items']));
+ $this->assertArrayHasKey('public_hash', $response['customerPaymentTokens']['items'][0]);
+ $this->assertArrayHasKey('details', $response['customerPaymentTokens']['items'][0]);
+ $this->assertArrayHasKey('payment_method_code', $response['customerPaymentTokens']['items'][0]);
+ $this->assertArrayHasKey('type', $response['customerPaymentTokens']['items'][0]);
+ // Validate gateway token is NOT returned
+ $this->assertArrayNotHasKey('gateway_token', $response['customerPaymentTokens']['items'][0]);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage GraphQL response contains errors: The current customer isn't authorized.
+ */
+ public function testGetCustomerPaymentTokensIfUserIsNotAuthorized()
+ {
+ $query = <<graphQlQuery($query);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Vault/_files/payment_tokens.php
+ */
+ public function testDeletePaymentToken()
+ {
+ $currentEmail = 'customer@example.com';
+ $currentPassword = 'password';
+ $tokens = $this->paymentTokenManagement->getVisibleAvailableTokens(1);
+ $token = current($tokens);
+ $publicHash = $token->getPublicHash();
+
+ $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword));
+
+ $this->assertTrue($response['deletePaymentToken']['result']);
+ $this->assertEquals(1, count($response['deletePaymentToken']['customerPaymentTokens']['items']));
+
+ $token = $response['deletePaymentToken']['customerPaymentTokens']['items'][0];
+ $this->assertArrayHasKey('public_hash', $token);
+ $this->assertArrayHasKey('details', $token);
+ $this->assertArrayHasKey('payment_method_code', $token);
+ $this->assertArrayHasKey('type', $token);
+ // Validate gateway token is NOT returned
+ $this->assertArrayNotHasKey('gateway_token', $token);
+ }
+
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage GraphQL response contains errors: The current customer isn't authorized.
+ */
+ public function testDeletePaymentTokenIfUserIsNotAuthorized()
+ {
+ $query = <<graphQlQuery($query, [], '');
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/Vault/_files/payment_tokens.php
+ * @expectedException \Exception
+ * @expectedExceptionMessage GraphQL response contains errors: Could not find a token using public hash: ksdfk392ks
+ */
+ public function testDeletePaymentTokenInvalidPublicHash()
+ {
+ $currentEmail = 'customer@example.com';
+ $currentPassword = 'password';
+
+ $query = <<graphQlQuery($query, [], '', $this->getCustomerAuthHeaders($currentEmail, $currentPassword));
+ }
+
+ /**
+ * @param string $email
+ * @param string $password
+ * @return array
+ */
+ private function getCustomerAuthHeaders(string $email, string $password): array
+ {
+ $customerToken = $this->customerTokenService->createCustomerAccessToken($email, $password);
+ return ['Authorization' => 'Bearer ' . $customerToken];
+ }
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php
index d570fc09b7714..4aac5d9445934 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Wishlist/WishlistTest.php
@@ -93,6 +93,36 @@ public function testGetCustomerWishlist(): void
$this->assertEquals($wishlistItemProduct->getName(), $response['wishlist']['items'][0]['product']['name']);
}
+ /**
+ * @expectedException \Exception
+ * @expectedExceptionMessage The current user cannot perform operations on wishlist
+ */
+ public function testGetGuestWishlist()
+ {
+ $query =
+ <<graphQlQuery($query);
+ }
+
/**
* @param string $email
* @param string $password
diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php
index 6dbf2b1aa6a12..03492f7ae1a9e 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/ConditionsElement.php
@@ -195,6 +195,13 @@ class ConditionsElement extends SimpleElement
*/
protected $exception;
+ /**
+ * Condition option text selector.
+ *
+ * @var string
+ */
+ private $conditionOptionTextSelector = '//option[normalize-space(text())="%s"]';
+
/**
* @inheritdoc
*/
@@ -265,7 +272,7 @@ protected function addSingleCondition($condition, ElementInterface $context)
$this->addCondition($condition['type'], $context);
$createdCondition = $context->find($this->created, Locator::SELECTOR_XPATH);
$this->waitForCondition($createdCondition);
- $this->fillCondition($condition['rules'], $createdCondition);
+ $this->fillCondition($condition['rules'], $createdCondition, $condition['type']);
}
/**
@@ -282,10 +289,16 @@ protected function addCondition($type, ElementInterface $context)
$count = 0;
do {
- $newCondition->find($this->addNew, Locator::SELECTOR_XPATH)->click();
-
try {
- $newCondition->find($this->typeNew, Locator::SELECTOR_XPATH, 'select')->setValue($type);
+ $specificType = $newCondition->find(
+ sprintf($this->conditionOptionTextSelector, $type),
+ Locator::SELECTOR_XPATH
+ )->isPresent();
+ $newCondition->find($this->addNew, Locator::SELECTOR_XPATH)->click();
+ $condition = $specificType
+ ? $newCondition->find($this->typeNew, Locator::SELECTOR_XPATH, 'selectcondition')
+ : $newCondition->find($this->typeNew, Locator::SELECTOR_XPATH, 'select');
+ $condition->setValue($type);
$isSetType = true;
} catch (\PHPUnit_Extensions_Selenium2TestCase_WebDriverException $e) {
$isSetType = false;
@@ -306,13 +319,14 @@ protected function addCondition($type, ElementInterface $context)
*
* @param array $rules
* @param ElementInterface $element
+ * @param string|null $type
* @return void
* @throws \Exception
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
- protected function fillCondition(array $rules, ElementInterface $element)
+ protected function fillCondition(array $rules, ElementInterface $element, $type = null)
{
$this->resetKeyParam();
foreach ($rules as $rule) {
@@ -333,7 +347,7 @@ protected function fillCondition(array $rules, ElementInterface $element)
if ($this->fillGrid($rule, $param)) {
$isSet = true;
- } elseif ($this->fillSelect($rule, $param)) {
+ } elseif ($this->fillSelect($rule, $param, $type)) {
$isSet = true;
} elseif ($this->fillText($rule, $param)) {
$isSet = true;
@@ -390,11 +404,15 @@ protected function fillGrid($rule, ElementInterface $param)
*
* @param string $rule
* @param ElementInterface $param
+ * @param string|null $type
* @return bool
*/
- protected function fillSelect($rule, ElementInterface $param)
+ protected function fillSelect($rule, ElementInterface $param, $type = null)
{
- $value = $param->find('select', Locator::SELECTOR_TAG_NAME, 'select');
+ //Avoid confusion between regions like: "Baja California" and "California".
+ $value = strpos($type, 'State/Province') === false
+ ? $param->find('select', Locator::SELECTOR_TAG_NAME, 'select')
+ : $param->find('select', Locator::SELECTOR_TAG_NAME, 'selectstate');
if ($value->isVisible()) {
$value->setValue($rule);
$this->click();
diff --git a/dev/tests/functional/lib/Magento/Mtf/Client/Element/SelectconditionElement.php b/dev/tests/functional/lib/Magento/Mtf/Client/Element/SelectconditionElement.php
new file mode 100644
index 0000000000000..15a799eac5188
--- /dev/null
+++ b/dev/tests/functional/lib/Magento/Mtf/Client/Element/SelectconditionElement.php
@@ -0,0 +1,20 @@
+transport = $transport;
+ $this->webapiHandler = $webapiHandler;
}
/**
@@ -43,22 +53,31 @@ public function __construct(CurlTransport $transport)
*/
public function execute($command, $options = [])
{
- $curl = $this->transport;
- $curl->write($this->prepareUrl($command, $options), [], CurlInterface::GET);
- $curl->read();
- $curl->close();
+ $this->transport->write(
+ rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL,
+ $this->prepareParamArray($command, $options),
+ CurlInterface::POST,
+ []
+ );
+ $this->transport->read();
+ $this->transport->close();
}
/**
- * Prepare url.
+ * Prepare parameter array.
*
* @param string $command
* @param array $options [optional]
- * @return string
+ * @return array
*/
- private function prepareUrl($command, $options = [])
+ private function prepareParamArray($command, $options = [])
{
- $command .= ' ' . implode(' ', $options);
- return $_ENV['app_frontend_url'] . self::URL . '?command=' . urlencode($command);
+ if (!empty($options)) {
+ $command .= ' ' . implode(' ', $options);
+ }
+ return [
+ 'token' => urlencode($this->webapiHandler->getWebapiToken()),
+ 'command' => urlencode($command)
+ ];
}
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export.php
index f2ab1501dc2ba..1dac1f213920e 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export.php
@@ -9,6 +9,7 @@
use Magento\Mtf\ObjectManagerInterface;
use Magento\Mtf\Util\Command\File\Export\Data;
use Magento\Mtf\Util\Command\File\Export\ReaderInterface;
+use Magento\ImportExport\Test\Page\Adminhtml\AdminExportIndex;
/**
* Get Exporting file from the Magento.
@@ -36,13 +37,26 @@ class Export implements ExportInterface
*/
private $reader;
+ /**
+ * Admin export index page.
+ *
+ * @var AdminExportIndex
+ */
+ private $adminExportIndex;
+
/**
* @param ObjectManagerInterface $objectManager
- * @param string $type [optional]
+ * @param AdminExportIndex $adminExportIndex
+ * @param string $type
+ * @throws \ReflectionException
*/
- public function __construct(ObjectManagerInterface $objectManager, $type = 'product')
- {
+ public function __construct(
+ ObjectManagerInterface $objectManager,
+ AdminExportIndex $adminExportIndex,
+ $type = 'product'
+ ) {
$this->objectManager = $objectManager;
+ $this->adminExportIndex = $adminExportIndex;
$this->reader = $this->getReader($type);
}
@@ -68,9 +82,11 @@ private function getReader($type)
*
* @param string $name
* @return Data|null
+ * @throws \Exception
*/
public function getByName($name)
{
+ $this->downloadFile();
$this->reader->getData();
foreach ($this->reader->getData() as $file) {
if ($file->getName() === $name) {
@@ -85,9 +101,11 @@ public function getByName($name)
* Get latest created the export file.
*
* @return Data|null
+ * @throws \Exception
*/
public function getLatest()
{
+ $this->downloadFile();
$max = 0;
$latest = null;
foreach ($this->reader->getData() as $file) {
@@ -106,9 +124,11 @@ public function getLatest()
* @param string $start
* @param string $end
* @return Data[]
+ * @throws \Exception
*/
public function getByDateRange($start, $end)
{
+ $this->downloadFile();
$files = [];
foreach ($this->reader->getData() as $file) {
if ($file->getDate() > $start && $file->getDate() < $end) {
@@ -123,9 +143,25 @@ public function getByDateRange($start, $end)
* Get all export files.
*
* @return Data[]
+ * @throws \Exception
*/
public function getAll()
{
+ $this->downloadFile();
return $this->reader->getData();
}
+
+ /**
+ * Download exported file
+ *
+ * @return void
+ * @throws \Exception
+ */
+ private function downloadFile()
+ {
+ $this->adminExportIndex->open();
+ /** @var \Magento\ImportExport\Test\Block\Adminhtml\Export\ExportedGrid $exportedGrid */
+ $exportedGrid = $this->adminExportIndex->getExportedGrid();
+ $exportedGrid->downloadFirstFile();
+ }
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php
index 1c05fbaebf625..69df78a5cad64 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/Reader.php
@@ -3,12 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\Mtf\Util\Command\File\Export;
use Magento\Mtf\ObjectManagerInterface;
use Magento\Mtf\Util\Protocol\CurlTransport;
use Magento\Mtf\Util\Protocol\CurlInterface;
+use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator;
/**
* File reader for Magento export files.
@@ -36,16 +36,29 @@ class Reader implements ReaderInterface
*/
private $transport;
+ /**
+ * Webapi handler.
+ *
+ * @var WebapiDecorator
+ */
+ private $webapiHandler;
+
/**
* @param ObjectManagerInterface $objectManager
* @param CurlTransport $transport
+ * @param WebapiDecorator $webapiHandler
* @param string $template
*/
- public function __construct(ObjectManagerInterface $objectManager, CurlTransport $transport, $template)
- {
+ public function __construct(
+ ObjectManagerInterface $objectManager,
+ CurlTransport $transport,
+ WebapiDecorator $webapiHandler,
+ $template
+ ) {
$this->objectManager = $objectManager;
$this->template = $template;
$this->transport = $transport;
+ $this->webapiHandler = $webapiHandler;
}
/**
@@ -70,20 +83,27 @@ public function getData()
*/
private function getFiles()
{
- $this->transport->write($this->prepareUrl(), [], CurlInterface::GET);
+ $this->transport->write(
+ rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL,
+ $this->prepareParamArray(),
+ CurlInterface::POST,
+ []
+ );
$serializedFiles = $this->transport->read();
$this->transport->close();
-
return unserialize($serializedFiles);
}
/**
- * Prepare url.
+ * Prepare parameter array.
*
- * @return string
+ * @return array
*/
- private function prepareUrl()
+ private function prepareParamArray()
{
- return $_ENV['app_frontend_url'] . self::URL . '?template=' . urlencode($this->template);
+ return [
+ 'token' => urlencode($this->webapiHandler->getWebapiToken()),
+ 'template' => urlencode($this->template)
+ ];
}
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php
index 93f7cf1ce9764..3666e8643efa3 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Export/ReaderInterface.php
@@ -14,7 +14,7 @@ interface ReaderInterface
/**
* Url to export.php.
*/
- const URL = 'dev/tests/functional/utils/export.php';
+ const URL = '/dev/tests/functional/utils/export.php';
/**
* Exporting files as Data object from Magento.
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Log.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Log.php
index 8b41924fe0a90..820a5b0a82228 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Log.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/File/Log.php
@@ -7,6 +7,7 @@
namespace Magento\Mtf\Util\Command\File;
use Magento\Mtf\Util\Protocol\CurlTransport;
+use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator;
/**
* Get content of log file in var/log folder.
@@ -16,7 +17,7 @@ class Log
/**
* Url to log.php.
*/
- const URL = 'dev/tests/functional/utils/log.php';
+ const URL = '/dev/tests/functional/utils/log.php';
/**
* Curl transport protocol.
@@ -25,12 +26,21 @@ class Log
*/
private $transport;
+ /**
+ * Webapi handler.
+ *
+ * @var WebapiDecorator
+ */
+ private $webapiHandler;
+
/**
* @param CurlTransport $transport
+ * @param WebapiDecorator $webapiHandler
*/
- public function __construct(CurlTransport $transport)
+ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler)
{
$this->transport = $transport;
+ $this->webapiHandler = $webapiHandler;
}
/**
@@ -41,22 +51,28 @@ public function __construct(CurlTransport $transport)
*/
public function getFileContent($name)
{
- $curl = $this->transport;
- $curl->write($this->prepareUrl($name), [], CurlTransport::GET);
- $data = $curl->read();
- $curl->close();
-
+ $this->transport->write(
+ rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL,
+ $this->prepareParamArray($name),
+ CurlInterface::POST,
+ []
+ );
+ $data = $this->transport->read();
+ $this->transport->close();
return unserialize($data);
}
/**
- * Prepare url.
+ * Prepare parameter array.
*
* @param string $name
- * @return string
+ * @return array
*/
- private function prepareUrl($name)
+ private function prepareParamArray($name)
{
- return $_ENV['app_frontend_url'] . self::URL . '?name=' . urlencode($name);
+ return [
+ 'token' => urlencode($this->webapiHandler->getWebapiToken()),
+ 'name' => urlencode($name)
+ ];
}
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php
index dde3409ed1562..a9fefa25ffa24 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/GeneratedCode.php
@@ -7,6 +7,7 @@
use Magento\Mtf\Util\Protocol\CurlInterface;
use Magento\Mtf\Util\Protocol\CurlTransport;
+use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator;
/**
* GeneratedCode removes generated code of Magento (like generated/code and generated/metadata).
@@ -16,7 +17,7 @@ class GeneratedCode
/**
* Url to deleteMagentoGeneratedCode.php.
*/
- const URL = 'dev/tests/functional/utils/deleteMagentoGeneratedCode.php';
+ const URL = '/dev/tests/functional/utils/deleteMagentoGeneratedCode.php';
/**
* Curl transport protocol.
@@ -25,12 +26,21 @@ class GeneratedCode
*/
private $transport;
+ /**
+ * Webapi handler.
+ *
+ * @var WebapiDecorator
+ */
+ private $webapiHandler;
+
/**
* @param CurlTransport $transport
+ * @param WebapiDecorator $webapiHandler
*/
- public function __construct(CurlTransport $transport)
+ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler)
{
$this->transport = $transport;
+ $this->webapiHandler = $webapiHandler;
}
/**
@@ -40,10 +50,25 @@ public function __construct(CurlTransport $transport)
*/
public function delete()
{
- $url = $_ENV['app_frontend_url'] . self::URL;
- $curl = $this->transport;
- $curl->write($url, [], CurlInterface::GET);
- $curl->read();
- $curl->close();
+ $this->transport->write(
+ rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL,
+ $this->prepareParamArray(),
+ CurlInterface::POST,
+ []
+ );
+ $this->transport->read();
+ $this->transport->close();
+ }
+
+ /**
+ * Prepare parameter array.
+ *
+ * @return array
+ */
+ private function prepareParamArray()
+ {
+ return [
+ 'token' => urlencode($this->webapiHandler->getWebapiToken())
+ ];
}
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Locales.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Locales.php
index f669d91f2f2e5..a55d803f43087 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Locales.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Locales.php
@@ -7,6 +7,7 @@
use Magento\Mtf\Util\Protocol\CurlInterface;
use Magento\Mtf\Util\Protocol\CurlTransport;
+use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator;
/**
* Returns array of locales depends on fetching type.
@@ -26,7 +27,7 @@ class Locales
/**
* Url to locales.php.
*/
- const URL = 'dev/tests/functional/utils/locales.php';
+ const URL = '/dev/tests/functional/utils/locales.php';
/**
* Curl transport protocol.
@@ -35,12 +36,21 @@ class Locales
*/
private $transport;
+ /**
+ * Webapi handler.
+ *
+ * @var WebapiDecorator
+ */
+ private $webapiHandler;
+
/**
* @param CurlTransport $transport Curl transport protocol
+ * @param WebapiDecorator $webapiHandler
*/
- public function __construct(CurlTransport $transport)
+ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler)
{
$this->transport = $transport;
+ $this->webapiHandler = $webapiHandler;
}
/**
@@ -51,12 +61,28 @@ public function __construct(CurlTransport $transport)
*/
public function getList($type = self::TYPE_ALL)
{
- $url = $_ENV['app_frontend_url'] . self::URL . '?type=' . $type;
- $curl = $this->transport;
- $curl->write($url, [], CurlInterface::GET);
- $result = $curl->read();
- $curl->close();
-
+ $this->transport->write(
+ rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL,
+ $this->prepareParamArray($type),
+ CurlInterface::POST,
+ []
+ );
+ $result = $this->transport->read();
+ $this->transport->close();
return explode('|', $result);
}
+
+ /**
+ * Prepare parameter array.
+ *
+ * @param string $type
+ * @return array
+ */
+ private function prepareParamArray($type)
+ {
+ return [
+ 'token' => urlencode($this->webapiHandler->getWebapiToken()),
+ 'type' => urlencode($type)
+ ];
+ }
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php
index fd1f746a6f09c..4b12f6eec87aa 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/PathChecker.php
@@ -7,6 +7,7 @@
use Magento\Mtf\Util\Protocol\CurlInterface;
use Magento\Mtf\Util\Protocol\CurlTransport;
+use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator;
/**
* PathChecker checks that path to file or directory exists.
@@ -16,7 +17,7 @@ class PathChecker
/**
* Url to checkPath.php.
*/
- const URL = 'dev/tests/functional/utils/pathChecker.php';
+ const URL = '/dev/tests/functional/utils/pathChecker.php';
/**
* Curl transport protocol.
@@ -26,11 +27,21 @@ class PathChecker
private $transport;
/**
+ * Webapi handler.
+ *
+ * @var WebapiDecorator
+ */
+ private $webapiHandler;
+
+ /**
+ * @constructor
* @param CurlTransport $transport
+ * @param WebapiDecorator $webapiHandler
*/
- public function __construct(CurlTransport $transport)
+ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler)
{
$this->transport = $transport;
+ $this->webapiHandler = $webapiHandler;
}
/**
@@ -41,12 +52,28 @@ public function __construct(CurlTransport $transport)
*/
public function pathExists($path)
{
- $url = $_ENV['app_frontend_url'] . self::URL . '?path=' . urlencode($path);
- $curl = $this->transport;
- $curl->write($url, [], CurlInterface::GET);
- $result = $curl->read();
- $curl->close();
-
+ $this->transport->write(
+ rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL,
+ $this->prepareParamArray($path),
+ CurlInterface::POST,
+ []
+ );
+ $result = $this->transport->read();
+ $this->transport->close();
return strpos($result, 'path exists: true') !== false;
}
+
+ /**
+ * Prepare parameter array.
+ *
+ * @param string $path
+ * @return array
+ */
+ private function prepareParamArray($path)
+ {
+ return [
+ 'token' => urlencode($this->webapiHandler->getWebapiToken()),
+ 'path' => urlencode($path)
+ ];
+ }
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php
index 7d73634c0360d..fec20bb2a8715 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Command/Website.php
@@ -3,11 +3,11 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\Mtf\Util\Command;
use Magento\Mtf\Util\Protocol\CurlInterface;
use Magento\Mtf\Util\Protocol\CurlTransport;
+use Magento\Mtf\Util\Protocol\CurlTransport\WebapiDecorator;
/**
* Perform Website folder creation for functional tests executions.
@@ -17,7 +17,7 @@ class Website
/**
* Url to website.php.
*/
- const URL = 'dev/tests/functional/utils/website.php';
+ const URL = '/dev/tests/functional/utils/website.php';
/**
* Curl transport protocol.
@@ -26,13 +26,22 @@ class Website
*/
private $transport;
+ /**
+ * Webapi handler.
+ *
+ * @var WebapiDecorator
+ */
+ private $webapiHandler;
+
/**
* @constructor
* @param CurlTransport $transport
+ * @param WebapiDecorator $webapiHandler
*/
- public function __construct(CurlTransport $transport)
+ public function __construct(CurlTransport $transport, WebapiDecorator $webapiHandler)
{
$this->transport = $transport;
+ $this->webapiHandler = $webapiHandler;
}
/**
@@ -43,21 +52,28 @@ public function __construct(CurlTransport $transport)
*/
public function create($websiteCode)
{
- $curl = $this->transport;
- $curl->addOption(CURLOPT_HEADER, 1);
- $curl->write($this->prepareUrl($websiteCode), [], CurlInterface::GET);
- $curl->read();
- $curl->close();
+ $this->transport->addOption(CURLOPT_HEADER, 1);
+ $this->transport->write(
+ rtrim(str_replace('index.php', '', $_ENV['app_frontend_url']), '/') . self::URL,
+ $this->prepareParamArray($websiteCode),
+ CurlInterface::POST,
+ []
+ );
+ $this->transport->read();
+ $this->transport->close();
}
/**
- * Prepare url.
+ * Prepare parameter array.
*
* @param string $websiteCode
- * @return string
+ * @return array
*/
- private function prepareUrl($websiteCode)
+ private function prepareParamArray($websiteCode)
{
- return $_ENV['app_frontend_url'] . self::URL . '?website_code=' . urlencode($websiteCode);
+ return [
+ 'token' => urlencode($this->webapiHandler->getWebapiToken()),
+ 'website_code' => urlencode($websiteCode)
+ ];
}
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php
index bb82715e4b402..d7026e9b8efb3 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/BackendDecorator.php
@@ -63,23 +63,56 @@ public function __construct(CurlTransport $transport, DataInterface $configurati
*/
protected function authorize()
{
- // Perform GET to backend url so form_key is set
- $url = $_ENV['app_backend_url'];
- $this->transport->write($url, [], CurlInterface::GET);
- $this->read();
-
- $url = $_ENV['app_backend_url'] . $this->configuration->get('application/0/backendLoginUrl/0/value');
- $data = [
- 'login[username]' => $this->configuration->get('application/0/backendLogin/0/value'),
- 'login[password]' => $this->configuration->get('application/0/backendPassword/0/value'),
- 'form_key' => $this->formKey,
- ];
- $this->transport->write($url, $data, CurlInterface::POST);
- $response = $this->read();
- if (strpos($response, 'login-form') !== false) {
- throw new \Exception(
- 'Admin user cannot be logged in by curl handler!'
- );
+ // There are situations where magento application backend url could be slightly different from the environment
+ // variable we know. It could be intentionally (e.g. InstallTest) or unintentionally. We would still want tests
+ // to run in this case.
+ // When the original app_backend_url does not work, we will try 4 variants of the it. i.e. with and without
+ // url rewrite, http and https.
+ $urls = [];
+ $originalUrl = rtrim($_ENV['app_backend_url'], '/') . '/';
+ $urls[] = $originalUrl;
+ // It could be the case that the page needs a refresh, so we will try the original one twice.
+ $urls[] = $originalUrl;
+ if (strpos($originalUrl, '/index.php') !== false) {
+ $url2 = str_replace('/index.php', '', $originalUrl);
+ } else {
+ $url2 = $originalUrl . 'index.php/';
+ }
+ $urls[] = $url2;
+ if (strpos($originalUrl, 'https') !== false) {
+ $urls[] = str_replace('https', 'http', $originalUrl);
+ } else {
+ $urls[] = str_replace('http', 'https', $url2);
+ }
+
+ $isAuthorized = false;
+ foreach ($urls as $url) {
+ try {
+ // Perform GET to backend url so form_key is set
+ $this->transport->write($url, [], CurlInterface::GET);
+ $this->read();
+
+ $authUrl = $url . $this->configuration->get('application/0/backendLoginUrl/0/value');
+ $data = [
+ 'login[username]' => $this->configuration->get('application/0/backendLogin/0/value'),
+ 'login[password]' => $this->configuration->get('application/0/backendPassword/0/value'),
+ 'form_key' => $this->formKey,
+ ];
+
+ $this->transport->write($authUrl, $data, CurlInterface::POST);
+ $response = $this->read();
+ if (strpos($response, 'login-form') !== false) {
+ continue;
+ }
+ $isAuthorized = true;
+ $_ENV['app_backend_url'] = $url;
+ break;
+ } catch (\Exception $e) {
+ continue;
+ }
+ }
+ if ($isAuthorized == false) {
+ throw new \Exception('Admin user cannot be logged in by curl handler!');
}
}
diff --git a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/WebapiDecorator.php b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/WebapiDecorator.php
index 3aa756904ab00..df5ab45a3f96d 100644
--- a/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/WebapiDecorator.php
+++ b/dev/tests/functional/lib/Magento/Mtf/Util/Protocol/CurlTransport/WebapiDecorator.php
@@ -70,6 +70,13 @@ class WebapiDecorator implements CurlInterface
*/
protected $response;
+ /**
+ * Webapi token.
+ *
+ * @var string
+ */
+ protected $webapiToken;
+
/**
* @construct
* @param ObjectManager $objectManager
@@ -110,6 +117,9 @@ protected function init()
$integration->persist();
$this->setConfiguration($integration);
+ $this->webapiToken = $integration->getToken();
+ } else {
+ $this->webapiToken = $integrationToken;
}
}
@@ -161,7 +171,13 @@ protected function setConfiguration(Integration $integration)
*/
protected function isValidIntegration()
{
- $this->write($_ENV['app_frontend_url'] . 'rest/V1/modules', [], CurlInterface::GET);
+ $url = rtrim($_ENV['app_frontend_url'], '/');
+ if (strpos($url, 'index.php') === false) {
+ $url .= '/index.php/rest/V1/modules';
+ } else {
+ $url .= '/rest/V1/modules';
+ }
+ $this->write($url, [], CurlInterface::GET);
$response = json_decode($this->read(), true);
return (null !== $response) && !isset($response['message']);
@@ -219,4 +235,18 @@ public function close()
{
$this->transport->close();
}
+
+ /**
+ * Return webapiToken.
+ *
+ * @return string
+ */
+ public function getWebapiToken()
+ {
+ // Request token if integration is no longer valid
+ if (!$this->isValidIntegration()) {
+ $this->init();
+ }
+ return $this->webapiToken;
+ }
}
diff --git a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/Constraint/AssertExportAdvancedPricing.php b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/Constraint/AssertExportAdvancedPricing.php
index 565d0f432bdaf..c92563c1ca5bd 100644
--- a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/Constraint/AssertExportAdvancedPricing.php
+++ b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/Constraint/AssertExportAdvancedPricing.php
@@ -8,6 +8,7 @@
use Magento\Mtf\Constraint\AbstractConstraint;
use Magento\Mtf\Fixture\InjectableFixture;
use Magento\Mtf\Util\Command\File\ExportInterface;
+use Magento\ImportExport\Test\Page\Adminhtml\AdminExportIndex;
/**
* Assert that exported file with advanced pricing options contains product data.
@@ -21,19 +22,30 @@ class AssertExportAdvancedPricing extends AbstractConstraint
*/
private $exportData;
+ /**
+ * Admin export index page.
+ *
+ * @var AdminExportIndex
+ */
+ private $adminExportIndex;
+
/**
* Assert that exported file with advanced pricing options contains product data.
*
* @param ExportInterface $export
* @param array $products
* @param array $exportedFields
+ * @param AdminExportIndex $adminExportIndex
* @return void
*/
public function processAssert(
ExportInterface $export,
array $products,
- array $exportedFields
+ array $exportedFields,
+ AdminExportIndex $adminExportIndex
) {
+ $this->adminExportIndex = $adminExportIndex;
+ $this->adminExportIndex->open();
$this->exportData = $export->getLatest();
foreach ($products as $product) {
$regexps = $this->prepareRegexpsForCheck($exportedFields, $product);
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 df8cd6f354c2a..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
@@ -11,6 +11,7 @@
use Magento\Mtf\TestCase\Injectable;
use Magento\Mtf\TestStep\TestStepFactory;
use Magento\Store\Test\Fixture\Website;
+use Magento\Mtf\Util\Command\Cli\Cron;
/**
* Preconditions:
@@ -65,16 +66,23 @@ class ExportAdvancedPricingTest extends Injectable
private $catalogProductIndex;
/**
- * Prepare test data.
+ * Cron command
*
- * @param CatalogProductIndex $catalogProductIndex
+ * @var Cron
+ */
+ private $cron;
+
+ /**
+ * Run cron before tests running
+ *
+ * @param Cron $cron
* @return void
*/
public function __prepare(
- CatalogProductIndex $catalogProductIndex
+ Cron $cron
) {
- $catalogProductIndex->open();
- $catalogProductIndex->getProductGrid()->massaction([], 'Delete', true, 'Select All');
+ $cron->run();
+ $cron->run();
}
/**
@@ -84,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;
}
/**
@@ -129,10 +140,14 @@ 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(
'exportData',
[
@@ -191,6 +206,9 @@ private function setupCurrencyForCustomWebsite($website, $currencyDataset)
*/
public function prepareProducts(array $products, Website $website = null)
{
+ $this->catalogProductIndex->open();
+ $this->catalogProductIndex->getProductGrid()->massaction([], 'Delete', true, 'Select All');
+
if (empty($products)) {
return null;
}
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 9f19ff4cb00a8..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
@@ -9,6 +9,7 @@
csv_with_advanced_pricing
+
diff --git a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ImportDataNegativeTest.xml b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ImportDataNegativeTest.xml
index 65b4d6e973bb3..db992e662d817 100644
--- a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ImportDataNegativeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/TestCase/ImportDataNegativeTest.xml
@@ -19,7 +19,7 @@
- Advanced Pricing
- Add/Update
- Stop on Error
- - 10
+ - 1
- ,
- ,
-
diff --git a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/_files/template/pricing/advanced_incorrect.php b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/_files/template/pricing/advanced_incorrect.php
index 12203222534cd..e728a87616392 100644
--- a/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/_files/template/pricing/advanced_incorrect.php
+++ b/dev/tests/functional/tests/app/Magento/AdvancedPricingImportExport/Test/_files/template/pricing/advanced_incorrect.php
@@ -14,5 +14,13 @@
'tier_price' => 'text',
'tier_price_value_type' => 'Fixed',
],
+ 'data_1' => [
+ 'sku' => '%sku%',
+ 'tier_price_website' => "All Websites [USD]",
+ 'tier_price_customer_group' => 'ALL GROUPS',
+ 'tier_price_qty' => '3',
+ 'tier_price' => 'text',
+ 'tier_price_value_type' => 'Fixed',
+ ],
],
];
diff --git a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.xml b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.xml
index a975d19ef8879..89c9d9168921f 100644
--- a/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Analytics/Test/TestCase/AdvancedReportingButtonTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
https://advancedreporting.rjmetrics.com/report
diff --git a/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/Dashboard.xml b/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/Dashboard.xml
index adae65a1d06d6..799f9e30fd972 100644
--- a/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/Dashboard.xml
+++ b/dev/tests/functional/tests/app/Magento/Backend/Test/Page/Adminhtml/Dashboard.xml
@@ -17,5 +17,6 @@
+
diff --git a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/ValidateOrderOfProductTypeTest.xml b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/ValidateOrderOfProductTypeTest.xml
index 7077f367795ed..cb32742a0ce6b 100644
--- a/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/ValidateOrderOfProductTypeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Bundle/Test/TestCase/ValidateOrderOfProductTypeTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
Bundle Product
diff --git a/dev/tests/functional/tests/app/Magento/BundleImportExport/Test/TestCase/ExportProductsTest.xml b/dev/tests/functional/tests/app/Magento/BundleImportExport/Test/TestCase/ExportProductsTest.xml
index 3ad8cff31eaf8..bfbe233b9dc1b 100644
--- a/dev/tests/functional/tests/app/Magento/BundleImportExport/Test/TestCase/ExportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/BundleImportExport/Test/TestCase/ExportProductsTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
- bundleProduct
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Category.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Category.xml
index d529f74865985..014d685cfdb7c 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Category.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/Repository/Category.xml
@@ -179,5 +179,31 @@
- catalogProductSimple::default
+
+ DefaultSubcategory%isolation%
+ default-subcategory-%isolation%
+
+ - default_anchored_category_with_product
+
+ Yes
+ Yes
+ Yes
+
+ - catalogProductSimple::default
+
+
+
+ Category%isolation%
+ category%isolation%
+ Yes
+ Yes
+ Yes
+
+ - default_category
+
+
+ - catalogProductSimple::product_5_dollar
+
+
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/MoveCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/MoveCategoryEntityTest.xml
index a5758fe1d1346..b4fd843ca800f 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/MoveCategoryEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/MoveCategoryEntityTest.xml
@@ -17,8 +17,7 @@
mftf_migrated:yes
- MAGETWO-65147: Category is not present in Layered navigation block when anchor is on
- default_subcategory_with_anchored_parent
+ default_subcategory_with_anchored_parent_with_product
default
2
@@ -28,9 +27,8 @@
mftf_migrated:yes
- MAGETWO-65147: Category is not present in Layered navigation block when anchor is on
default_subcategory_with_anchored_parent
- default_anchor_subcategory_with_anchored_parent
+ default_subcategory_with_anchored_parent_with_product
default_category
2
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/SubcategoryNotIncludeInNavigationMenuTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/SubcategoryNotIncludeInNavigationMenuTest.xml
index 94d99dd6b7b24..53a7debffa438 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/SubcategoryNotIncludeInNavigationMenuTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Category/SubcategoryNotIncludeInNavigationMenuTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
two_nested_categories
2
Yes
@@ -16,6 +17,7 @@
+ mftf_migrated:yes
two_nested_categories
2
Yes
@@ -24,6 +26,7 @@
+ mftf_migrated:yes
two_nested_categories
2
No
@@ -32,6 +35,7 @@
+ mftf_migrated:yes
two_nested_categories
2
No
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml
index 874fc3f670362..b1f093162fa4b 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/AddToCartCrossSellTest.xml
@@ -8,7 +8,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test
+ test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes
simple1::catalogProductSimple::product_with_category,simple2::catalogProductSimple::product_with_category,config1::configurableProduct::two_options_with_fixed_price
simple1:simple2,config1;config1:simple2
simple1,config1,simple2
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ManageProductsStockTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ManageProductsStockTest.xml
index 33b578672d48b..d005c05a9cba2 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ManageProductsStockTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ManageProductsStockTest.xml
@@ -15,6 +15,7 @@
+ mftf_migrated:yes
product_with_category
Yes
5
@@ -33,6 +34,7 @@
+ mftf_migrated:yes
product_with_category
Yes
5
@@ -51,6 +53,7 @@
+ mftf_migrated:yes
product_with_category
Yes
5
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.xml
index 8fbb64f4579b1..a563d369f95cc 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ProductTypeSwitchingOnCreationTest.xml
@@ -8,7 +8,7 @@
- stable:no
+ stable:no, mftf_migrated:yes
simple
configurableProduct::default
@@ -23,16 +23,19 @@
simple
catalogProductVirtual::default
+ mftf_migrated:yes
configurable
catalogProductSimple::product_without_category
+ mftf_migrated:yes
+ mftf_migrated:yes
MSI-1624
configurable
catalogProductVirtual::required_fields
@@ -42,11 +45,12 @@
virtual
catalogProductSimple::default
+ mftf_migrated:yes
- stable:no
+ stable:no, mftf_migrated:yes
virtual
configurableProduct::not_virtual_for_type_switching
@@ -71,6 +75,7 @@
downloadable
catalogProductSimple::default
+ mftf_migrated:yes
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateSimpleProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateSimpleProductEntityTest.xml
index 2a46abdc2fd15..ce99a61c33bac 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateSimpleProductEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/UpdateSimpleProductEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
Update visibility to Catalog, Search
product_with_category
Test simple product %isolation%
@@ -24,6 +25,7 @@
+ mftf_migrated:yes
Update visibility to Not Visible Individually
product_with_category
Test simple product %isolation%
@@ -38,6 +40,7 @@
+ mftf_migrated:yes
Update visibility to Catalog
product_with_category
Test simple product %isolation%
@@ -55,6 +58,7 @@
+ mftf_migrated:yes
Update visibility to Search
product_with_category
Test simple product %isolation%
@@ -72,6 +76,7 @@
+ mftf_migrated:yes
Update stock to Out of Stock
product_with_category
Test simple product %isolation%
@@ -89,6 +94,7 @@
+ mftf_migrated:yes
Update product status to offline
product_with_category
Test simple product %isolation%
@@ -103,6 +109,7 @@
+ mftf_migrated:yes
Update category
product_with_category
default
@@ -118,6 +125,7 @@
+ mftf_migrated:yes
product_with_category
default
Test simple product %isolation%
@@ -128,13 +136,14 @@
- test_type:acceptance_test, test_type:extended_acceptance_test
+ test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes
product_with_category
-
+ mftf_migrated:yes
product_flat
simple_10_dollar
Simple Product %isolation%
@@ -146,6 +155,7 @@
+ mftf_migrated:yes
product_with_category
Test simple product %isolation%
test_simple_product_%isolation%
@@ -160,6 +170,7 @@
+ mftf_migrated:yes
product_with_category
custom
No
@@ -168,7 +179,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test
+ test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes
price_scope_website
product_with_category
custom
@@ -178,6 +189,7 @@
+ mftf_migrated:yes
simple_with_hight_tier_price
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ValidateOrderOfProductTypeTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ValidateOrderOfProductTypeTest.xml
index 0d1a34ab52f97..ec776128fdfe3 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ValidateOrderOfProductTypeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/Product/ValidateOrderOfProductTypeTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
Simple Product
Virtual Product
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateAttributeSetEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateAttributeSetEntityTest.xml
index 86bacd925ba05..13e05b1d122cb 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateAttributeSetEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateAttributeSetEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
AttributeSet%isolation%
default
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityFromProductPageTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityFromProductPageTest.xml
index 73fbf556d099c..e15eab57cca01 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityFromProductPageTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityFromProductPageTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
Text_Field_Admin_%isolation%
Text Field
No
@@ -33,6 +34,7 @@
+ mftf_migrated:yes
Dropdown_Admin_%isolation%
Dropdown
two_options
@@ -50,6 +52,7 @@
+ mftf_migrated:yes
Text_Field_Admin_%isolation%
Text Field
Yes
@@ -59,6 +62,7 @@
+ mftf_migrated:yes
Text_Field_Admin_%isolation%
Text Field
No
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml
index aae9ec6039f56..2287546aed102 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/CreateProductAttributeEntityTest.xml
@@ -20,6 +20,7 @@
+ mftf_migrated:yes
custom_attribute_set
Text_Field_Admin_%isolation%
Text Area
@@ -115,7 +116,7 @@
- test_type:extended_acceptance_test
+ test_type:extended_acceptance_test, mftf_migrated:yes
custom_attribute_set
Dropdown_Admin_%isolation%
Dropdown
@@ -154,6 +155,7 @@
+ mftf_migrated:yes
custom_attribute_set
Price_Admin_%isolation%
Price
@@ -209,6 +211,7 @@
+ mftf_migrated:yes
custom_attribute_set
Dropdown_Admin_%isolation%
Dropdown
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteAssignedToTemplateProductAttributeTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteAssignedToTemplateProductAttributeTest.xml
index b12f4f1ad7d94..d674535b54abb 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteAssignedToTemplateProductAttributeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteAssignedToTemplateProductAttributeTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
custom_attribute_set
attribute_type_dropdown
@@ -16,6 +17,7 @@
+ mftf_migrated:yes
default
attribute_type_text_field
Product name
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteSystemProductAttributeTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteSystemProductAttributeTest.xml
index 7763b0fb534f4..a9a85d2472073 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteSystemProductAttributeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteSystemProductAttributeTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
news_from_date
Yes
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteUsedInConfigurableProductAttributeTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteUsedInConfigurableProductAttributeTest.xml
index df396c22312b1..e46174de8818f 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteUsedInConfigurableProductAttributeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/DeleteUsedInConfigurableProductAttributeTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
one_variation
diff --git a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateProductAttributeEntityTest.xml b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateProductAttributeEntityTest.xml
index b051d50b4acb6..40cf8e40ae33f 100644
--- a/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateProductAttributeEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Catalog/Test/TestCase/ProductAttribute/UpdateProductAttributeEntityTest.xml
@@ -67,6 +67,7 @@
+ mftf_migrated:yes
custom_attribute_set
attribute_type_dropdown
Dropdown
@@ -76,6 +77,7 @@
+ mftf_migrated:yes
custom_attribute_set
attribute_type_multiple_select
Dropdown_%isolation%
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 9e246939595ca..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
@@ -11,6 +11,7 @@
use Magento\Mtf\Util\Command\File\Export;
use Magento\Mtf\Fixture\FixtureFactory;
use Magento\Mtf\TestCase\Injectable;
+use Magento\Mtf\Util\Command\Cli\Cron;
/**
* Preconditions:
@@ -50,22 +51,32 @@ class ExportProductsTest extends Injectable
*/
private $assertExportProduct;
+ /**
+ * Cron command
+ *
+ * @var Cron
+ */
+ private $cron;
+
/**
* Inject data.
*
* @param FixtureFactory $fixtureFactory
* @param AdminExportIndex $adminExportIndex
* @param AssertExportProduct $assertExportProduct
+ * @param Cron $cron
* @return void
*/
public function __inject(
FixtureFactory $fixtureFactory,
AdminExportIndex $adminExportIndex,
- AssertExportProduct $assertExportProduct
+ AssertExportProduct $assertExportProduct,
+ Cron $cron
) {
$this->fixtureFactory = $fixtureFactory;
$this->adminExportIndex = $adminExportIndex;
$this->assertExportProduct = $assertExportProduct;
+ $this->cron = $cron;
}
/**
@@ -83,14 +94,18 @@ public function test(
array $exportedFields,
array $products
) {
+ $this->cron->run();
+ $this->cron->run();
$products = $this->prepareProducts($products);
$this->adminExportIndex->open();
+ $this->adminExportIndex->getExportedGrid()->deleteAllExportedFiles();
$exportData = $this->fixtureFactory->createByCode('exportData', ['dataset' => $exportData]);
$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 40f535cd225a2..be22eab8ac717 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ExportProductsTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
- catalogProductSimple
@@ -27,6 +28,7 @@
+ mftf_migrated:yes
default
- catalogProductSimple
@@ -43,6 +45,7 @@
+ mftf_migrated:yes
default
- catalogProductSimple
@@ -58,6 +61,7 @@
+ mftf_migrated:yes
default
- catalogProductSimple
diff --git a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml
index edb0aad954fbb..77e5e2b91d93f 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogImportExport/Test/TestCase/ImportProductsTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
- Products
- Add/Update
@@ -38,6 +39,7 @@
+ mftf_migrated:yes
Products
Replace
Stop on Error
diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php
index e2193b799c3be..11a8693723f25 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php
+++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/Fixture/CatalogSearchQuery/QueryText.php
@@ -61,7 +61,7 @@ private function createProducts(FixtureFactory $fixtureFactory, $productsData)
$searchValue = isset($productData[2]) ? $productData[2] : $productData[1];
if ($this->data === null) {
if ($product->hasData($searchValue)) {
- $getProperty = 'get' . str_replace(' ', '', ucwords(str_replace('_', ' ', $searchValue)));
+ $getProperty = 'get' . str_replace('_', '', ucwords($searchValue, '_'));
$this->data = $product->$getProperty();
} else {
$this->data = $searchValue;
diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/CreateSearchTermEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/CreateSearchTermEntityTest.xml
index 0437e0a5e999b..8c465544a3283 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/CreateSearchTermEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/CreateSearchTermEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
catalogProductSimple::sku
Main Website/Main Website Store/Default Store View
http://example.com/
diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/DeleteSearchTermEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/DeleteSearchTermEntityTest.xml
index a9cc0dfd34f9f..8fdd7ef715521 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/DeleteSearchTermEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/DeleteSearchTermEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
diff --git a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/MassDeleteSearchTermEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/MassDeleteSearchTermEntityTest.xml
index 3bf4e521c4a04..3ef2b65c0224b 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/MassDeleteSearchTermEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogSearch/Test/TestCase/MassDeleteSearchTermEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
catalogSearchQuery::default,catalogSearchQuery::default,catalogSearchQuery::default
diff --git a/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlCategoryEntityTest.xml b/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlCategoryEntityTest.xml
index 398054f1f0ed3..8b15da5ecd2ef 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlCategoryEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlCategoryEntityTest.xml
@@ -14,7 +14,7 @@
Yes
Subcategory%isolation%
subcategory-%isolation%
- test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1
+ test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1, mftf_migrated:yes
diff --git a/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlProductEntity.xml b/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlProductEntity.xml
index 1116821f756a9..8110ed1ed00b1 100644
--- a/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlProductEntity.xml
+++ b/dev/tests/functional/tests/app/Magento/CatalogUrlRewrite/Test/TestCase/CreateDuplicateUrlProductEntity.xml
@@ -8,7 +8,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1
+ test_type:acceptance_test, test_type:extended_acceptance_test, severity:S1, mftf_migrated:yes
simple-product-%isolation%
Simple Product %isolation%
simple_sku_%isolation%
diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/EditShippingAddressOnePageCheckoutTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/EditShippingAddressOnePageCheckoutTest.xml
index 3c88d9193db28..64ed05904469e 100644
--- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/EditShippingAddressOnePageCheckoutTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/EditShippingAddressOnePageCheckoutTest.xml
@@ -8,7 +8,7 @@
- severity:S1
+ severity:S1, mftf_migrated:yes
johndoe_with_addresses
UK_address_without_email
empty_UK_address_without_email
diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml
index 8b2460718097c..b4c97a11b9145 100644
--- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateProductFromMiniShoppingCartEntityTest.xml
@@ -9,7 +9,7 @@
https://github.com/magento-engcom/msi/issues/1624
- test_type:extended_acceptance_test, severity:S0
+ test_type:extended_acceptance_test, severity:S0, mftf_migrated:yes
catalogProductSimple::default
simple_order_qty_2
true
diff --git a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.xml b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.xml
index e0ea721a51f1b..5caa3ba9b924e 100644
--- a/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Checkout/Test/TestCase/UpdateShoppingCartTest.xml
@@ -8,7 +8,7 @@
- severity:S0
+ severity:S0,mftf_migrated:yes
default
100
3
@@ -20,7 +20,7 @@
- severity:S0
+ severity:S0,mftf_migrated:yes
with_two_custom_option
50
11
diff --git a/dev/tests/functional/tests/app/Magento/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/Cms/Test/TestCase/CreateCustomUrlRewriteEntityTest.xml b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCustomUrlRewriteEntityTest.xml
index 3682fce4267c6..8adbc1ae46c7b 100644
--- a/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCustomUrlRewriteEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Cms/Test/TestCase/CreateCustomUrlRewriteEntityTest.xml
@@ -8,7 +8,7 @@
- severity:S1
+ severity:S1, mftf_migrated:yes
Custom
Main Website/Main Website Store/Default Store View
cms/page/view/page_id/%cmsPage::default%
@@ -19,7 +19,7 @@
- severity:S1
+ severity:S1, mftf_migrated:yes
Custom
Main Website/Main Website Store/Default Store View
cms/page/view/page_id/%cmsPage::default%
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 c157f5c58d408..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
@@ -7,7 +7,8 @@
-->
-
+
+ default
- configurableProduct
- product_with_size
@@ -18,19 +19,48 @@
+
+ - sku
+ - name
+ - weight
+ - visibility
+ - price
+ - url_key
+ - additional_images
+
-
+
+ mftf_migrated:yes
+ default
- configurableProduct
- first_product_with_custom_options_and_option_key_1
+
+ - sku
+ - name
+ - weight
+ - visibility
+ - price
+ - url_key
+
-
+
+ mftf_migrated:yes
+ default
- configurableProduct
- default
- custom_store
+
+ - sku
+ - name
+ - weight
+ - visibility
+ - price
+ - url_key
+
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml
index e1b6fcf47e562..f831173ba0ae0 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/CreateConfigurableProductEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
configurable-product-%isolation%
two_new_options
configurable_two_options
@@ -32,6 +33,7 @@
+ mftf_migrated:yes
configurable-product-%isolation%
two_options
configurable_two_options
@@ -97,6 +99,7 @@
+ mftf_migrated:yes
configurable-product-%isolation%
two_options_with_fixed_price
Configurable Product %isolation%
@@ -113,7 +116,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test
+ test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes
two_searchable_options
Configurable Product %isolation%
configurable_sku_%isolation%
@@ -129,6 +132,7 @@
+ mftf_migrated:yes
two_new_options_with_empty_sku
Configurable Product %isolation%
configurable_sku_%isolation%
@@ -137,6 +141,7 @@
+ mftf_migrated:yes
two_new_options_with_parent_sku
Configurable Product %isolation%
existing_sku
@@ -145,6 +150,7 @@
+ mftf_migrated:yes
configurable-product-%isolation%
two_options_with_assigned_product_special_price
configurable_two_new_options_with_special_price
@@ -156,6 +162,7 @@
+ mftf_migrated:yes
configurable-product-%isolation%
two_options_with_assigned_product_tier_price
configurable_two_new_options_with_special_price
@@ -170,7 +177,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test
+ test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes
with_out_of_stock_item
Configurable Product %isolation%
configurable_sku_%isolation%
@@ -189,7 +196,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test
+ test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes
with_disabled_item
Configurable Product %isolation%
configurable_sku_%isolation%
@@ -207,7 +214,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test
+ test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes
with_one_disabled_item_and_one_out_of_stock_item
Configurable Product %isolation%
configurable_sku_%isolation%
@@ -225,6 +232,7 @@
+ mftf_migrated:yes
color_and_size_with_images
Configurable Product %isolation%
configurable_sku_%isolation%
@@ -238,6 +246,7 @@
+ mftf_migrated:yes
configurable-product-%isolation%
three_new_options_with_out_of_stock_product
Configurable Product %isolation%
@@ -254,6 +263,7 @@
+ mftf_migrated:yes
configurable-product-%isolation%
three_new_options_with_out_of_stock_product
Configurable Product %isolation%
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteChildConfigurableProductTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteChildConfigurableProductTest.xml
index 435d5aad4635d..64f9141fba962 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteChildConfigurableProductTest.xml
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/DeleteChildConfigurableProductTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
configurable-product-%isolation%
two_new_options_with_empty_sku
Configurable Product %isolation%
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/ValidateOrderOfProductTypeTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/ValidateOrderOfProductTypeTest.xml
index a62bcf92ebf39..5af854cae5434 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/ValidateOrderOfProductTypeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/ValidateOrderOfProductTypeTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
Configurable Product
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/VerifyConfigurableProductEntityPriceTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/VerifyConfigurableProductEntityPriceTest.xml
index 6d22cea4689a8..d576e760179ed 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/VerifyConfigurableProductEntityPriceTest.xml
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/VerifyConfigurableProductEntityPriceTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
configurableProduct::product_with_color
-
@@ -19,6 +20,7 @@
+ mftf_migrated:yes
configurableProduct::product_with_color
-
diff --git a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/VerifyConfigurableProductLayeredNavigationTest.xml b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/VerifyConfigurableProductLayeredNavigationTest.xml
index 082b93ba62e03..9108b44a0e85b 100644
--- a/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/VerifyConfigurableProductLayeredNavigationTest.xml
+++ b/dev/tests/functional/tests/app/Magento/ConfigurableProduct/Test/TestCase/VerifyConfigurableProductLayeredNavigationTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
configurableProduct::product_with_3_sizes
-
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/ChangeCustomerPasswordTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/ChangeCustomerPasswordTest.xml
index 9765b9d9340a7..f94d99b07ec6e 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/ChangeCustomerPasswordTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/ChangeCustomerPasswordTest.xml
@@ -9,6 +9,7 @@
default
+ mftf_migrated:yes
123123^q
123123^a
123123^a
@@ -17,6 +18,7 @@
default
+ mftf_migrated:yes
123123
123123^a
123123^a
@@ -24,6 +26,7 @@
default
+ mftf_migrated:yes
123123^q
123123^a
123123^d
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml
index 5bb96dc13c739..ff87a1df60fe8 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/CreateCustomerBackendEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
save
Main Website
General
@@ -35,7 +36,7 @@
- test_type:extended_acceptance_test
+ test_type:extended_acceptance_test, mftf_migrated:yes
save
Main Website
General
@@ -55,6 +56,7 @@
+ mftf_migrated:yes
save
Main Website
Retailer
@@ -64,6 +66,7 @@
+ mftf_migrated:yes
save
Main Website
General
@@ -83,6 +86,7 @@
+ mftf_migrated:yes
saveAndContinue
Main Website
General
@@ -105,6 +109,7 @@
+ mftf_migrated:yes
save
Main Website
General
@@ -116,6 +121,7 @@
+ mftf_migrated:yes
Main Website
General
John%isolation%
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/DeleteCustomerBackendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/DeleteCustomerBackendEntityTest.xml
index 77b63fe39d4a1..3807360e89ec0 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/DeleteCustomerBackendEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/DeleteCustomerBackendEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/ForgotPasswordOnFrontendTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/ForgotPasswordOnFrontendTest.xml
index 0d168451c6d32..7bf916157c5dc 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/ForgotPasswordOnFrontendTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/ForgotPasswordOnFrontendTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
customer_US
captcha_storefront_disable
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/LoginOnFrontendFailTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/LoginOnFrontendFailTest.xml
index 2dea52c1b7fcc..bf84d2be43a11 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/LoginOnFrontendFailTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/LoginOnFrontendFailTest.xml
@@ -9,6 +9,7 @@
default
+ mftf_migrated:yes
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/MassAssignCustomerGroupTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/MassAssignCustomerGroupTest.xml
index 4086a8585c8a8..c868d3332a5a9 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/MassAssignCustomerGroupTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/MassAssignCustomerGroupTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
- default
@@ -16,6 +17,7 @@
+ mftf_migrated:yes
- default
- customer_US
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/PasswordAutocompleteOffTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/PasswordAutocompleteOffTest.xml
index b4188ebd98ae7..7f267c1f171f6 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/PasswordAutocompleteOffTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/PasswordAutocompleteOffTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
disable_guest_checkout,password_autocomplete_off
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.xml
index c1cf533583114..86c8e56d232d1 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/RegisterCustomerFrontendEntityTest.xml
@@ -20,6 +20,7 @@
+ mftf_migrated:yes
john
doe
johndoe%isolation%@example.com
@@ -32,7 +33,7 @@
- test_type:acceptance_test, test_type:extended_acceptance_test
+ test_type:acceptance_test, test_type:extended_acceptance_test, mftf_migrated:yes
john
doe
johndoe%isolation%@example.com
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerBackendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerBackendEntityTest.xml
index 24f68f686fdc1..095746cfdef57 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerBackendEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerBackendEntityTest.xml
@@ -25,6 +25,7 @@
+ mftf_migrated:yes
default
-
Prefix%isolation%_
@@ -75,6 +76,7 @@
+ mftf_migrated:yes
default
-
Yes
@@ -95,6 +97,7 @@
+ mftf_migrated:yes
johndoe_unique_TX
-
No
@@ -114,6 +117,7 @@
+ mftf_migrated:yes
johndoe_with_multiple_addresses
-
1
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerFrontendEntityTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerFrontendEntityTest.xml
index 2cbb9a0315e16..b96d688b0c4b6 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerFrontendEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/UpdateCustomerFrontendEntityTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
Patrick</title></head><svg/onload=alert('XSS')>
<script>alert('Last name')</script>
123123^q
@@ -25,6 +26,7 @@
+ mftf_migrated:yes
Jonny %isolation%
Doe %isolation%
jonny%isolation%@example.com
@@ -44,6 +46,7 @@
+ mftf_migrated:yes
Jean %isolation%
Reno %isolation%
jean%isolation%@example.com
diff --git a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/VerifyDisabledCustomerGroupFieldTest.xml b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/VerifyDisabledCustomerGroupFieldTest.xml
index 70a912a3b5ffe..e88e5161e474e 100644
--- a/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/VerifyDisabledCustomerGroupFieldTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Customer/Test/TestCase/VerifyDisabledCustomerGroupFieldTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
NOT_LOGGED_IN
- customer_group_code
diff --git a/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/TestCase/ExportCustomerAddressesTest.php b/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/TestCase/ExportCustomerAddressesTest.php
index 1f046f5111dfe..6b92891ada2b4 100644
--- a/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/TestCase/ExportCustomerAddressesTest.php
+++ b/dev/tests/functional/tests/app/Magento/CustomerImportExport/Test/TestCase/ExportCustomerAddressesTest.php
@@ -10,6 +10,7 @@
use Magento\ImportExport\Test\Page\Adminhtml\AdminExportIndex;
use Magento\Mtf\Fixture\FixtureFactory;
use Magento\Mtf\TestCase\Injectable;
+use Magento\Mtf\Util\Command\Cli\Cron;
/**
* Preconditions:
@@ -42,19 +43,29 @@ class ExportCustomerAddressesTest extends Injectable
*/
private $adminExportIndex;
+ /**
+ * Cron command
+ *
+ * @var Cron
+ */
+ private $cron;
+
/**
* Inject pages.
*
* @param FixtureFactory $fixtureFactory
* @param AdminExportIndex $adminExportIndex
+ * @param Cron $cron
* @return void
*/
public function __inject(
FixtureFactory $fixtureFactory,
- AdminExportIndex $adminExportIndex
+ AdminExportIndex $adminExportIndex,
+ Cron $cron
) {
$this->fixtureFactory = $fixtureFactory;
$this->adminExportIndex = $adminExportIndex;
+ $this->cron = $cron;
}
/**
@@ -68,8 +79,11 @@ public function test(
ExportData $exportData,
Customer $customer
) {
+ $this->cron->run();
+ $this->cron->run();
$customer->persist();
$this->adminExportIndex->open();
+ $this->adminExportIndex->getExportedGrid()->deleteAllExportedFiles();
$exportData->persist();
$this->adminExportIndex->getExportForm()->fill($exportData);
$this->adminExportIndex->getFilterExport()->clickContinue();
diff --git a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/ValidateOrderOfProductTypeTest.xml b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/ValidateOrderOfProductTypeTest.xml
index ccb74e1f79dc9..e807ec9134277 100644
--- a/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/ValidateOrderOfProductTypeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Downloadable/Test/TestCase/ValidateOrderOfProductTypeTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
Downloadable Product
diff --git a/dev/tests/functional/tests/app/Magento/GroupedImportExport/Test/TestCase/ExportProductsTest.xml b/dev/tests/functional/tests/app/Magento/GroupedImportExport/Test/TestCase/ExportProductsTest.xml
index cffcdbf45a6dc..a110dc6a89f8c 100644
--- a/dev/tests/functional/tests/app/Magento/GroupedImportExport/Test/TestCase/ExportProductsTest.xml
+++ b/dev/tests/functional/tests/app/Magento/GroupedImportExport/Test/TestCase/ExportProductsTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
default
- groupedProduct
diff --git a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/ValidateOrderOfProductTypeTest.xml b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/ValidateOrderOfProductTypeTest.xml
index 5a5cfdd9adc4c..ac57cdeb92053 100644
--- a/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/ValidateOrderOfProductTypeTest.xml
+++ b/dev/tests/functional/tests/app/Magento/GroupedProduct/Test/TestCase/ValidateOrderOfProductTypeTest.xml
@@ -8,6 +8,7 @@
+ mftf_migrated:yes
Grouped Product
diff --git a/dev/tests/functional/tests/app/Magento/ImportExport/Test/Block/Adminhtml/Export/ExportedGrid.php b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Block/Adminhtml/Export/ExportedGrid.php
new file mode 100644
index 0000000000000..60a313a9c01b2
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Block/Adminhtml/Export/ExportedGrid.php
@@ -0,0 +1,148 @@
+waifForGrid();
+ $firstGridRow = $this->getFirstRow();
+ while ($firstGridRow->isVisible()) {
+ $this->deleteFile($firstGridRow);
+ }
+ }
+
+ /**
+ * Delete exported file from the grid
+ *
+ * @param SimpleElement $rowItem
+ * @return void
+ */
+ private function deleteFile(SimpleElement $rowItem)
+ {
+ $rowItem->find($this->selectAction)->click();
+ $rowItem->find($this->deleteLink, Locator::SELECTOR_XPATH)->click();
+ $this->confirmDeleteModal();
+ $this->waitLoader();
+ }
+
+ /**
+ * Get first row from the grid
+ *
+ * @return SimpleElement
+ */
+ public function getFirstRow(): SimpleElement
+ {
+ return $this->_rootElement->find($this->firstRowSelector, \Magento\Mtf\Client\Locator::SELECTOR_XPATH);
+ }
+
+ /**
+ * Download first exported file
+ *
+ * @throws \Exception
+ */
+ public function downloadFirstFile()
+ {
+ $this->waifForGrid();
+ $firstRow = $this->getFirstRow();
+ $i = 0;
+ while (!$firstRow->isVisible()) {
+ if ($i === 10) {
+ throw new \Exception('There is no exported file in the grid');
+ }
+ $this->browser->refresh();
+ $this->waifForGrid();
+ ++$i;
+ }
+ $this->clickDownloadLink($firstRow);
+ }
+
+ /**
+ * Wait for the grid
+ *
+ * @return void
+ */
+ public function waifForGrid()
+ {
+ $this->waitForElementVisible($this->exportGrid);
+ $this->waitLoader();
+ }
+
+ /**
+ * Click on "Download" link.
+ *
+ * @param SimpleElement $rowItem
+ * @return void
+ */
+ private function clickDownloadLink(SimpleElement $rowItem)
+ {
+ $rowItem->find($this->selectAction)->click();
+ $rowItem->find($this->editLink, Locator::SELECTOR_XPATH)->click();
+ }
+
+ /**
+ * Confirm delete file modal
+ *
+ * @return void
+ */
+ private function confirmDeleteModal()
+ {
+ $modalElement = $this->browser->find($this->confirmModal);
+ /** @var \Magento\Ui\Test\Block\Adminhtml\Modal $modal */
+ $modal = $this->blockFactory->create(
+ \Magento\Ui\Test\Block\Adminhtml\Modal::class,
+ ['element' => $modalElement]
+ );
+ $modal->acceptAlert();
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/ImportExport/Test/Block/Adminhtml/Export/NotificationsArea.php b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Block/Adminhtml/Export/NotificationsArea.php
new file mode 100644
index 0000000000000..4a781c787eb0e
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Block/Adminhtml/Export/NotificationsArea.php
@@ -0,0 +1,52 @@
+browser->find($this->notificationsDropdown)->click();
+ }
+
+ /**
+ * Get latest notification message text
+ *
+ * @return string
+ */
+ public function getLatestMessage()
+ {
+ $this->waitForElementVisible($this->notificationDescription, Locator::SELECTOR_XPATH);
+ return $this->_rootElement->find($this->notificationDescription, Locator::SELECTOR_XPATH)->getText();
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/ImportExport/Test/Constraint/AssertExportNoDataErrorMessage.php b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Constraint/AssertExportNoDataErrorMessage.php
index 25e4d4b39174e..c52f8c6613fb7 100644
--- a/dev/tests/functional/tests/app/Magento/ImportExport/Test/Constraint/AssertExportNoDataErrorMessage.php
+++ b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Constraint/AssertExportNoDataErrorMessage.php
@@ -16,7 +16,7 @@ class AssertExportNoDataErrorMessage extends AbstractConstraint
/**
* Text value to be checked.
*/
- const ERROR_MESSAGE = 'There is no data for the export.';
+ const ERROR_MESSAGE = 'Error during export process occurred. Please check logs for detail';
/**
* Assert that error message is visible after exporting without entity attributes data.
@@ -26,7 +26,11 @@ class AssertExportNoDataErrorMessage extends AbstractConstraint
*/
public function processAssert(AdminExportIndex $adminExportIndex)
{
- $actualMessage = $adminExportIndex->getMessagesBlock()->getErrorMessage();
+ $adminExportIndex->open();
+ /** @var \Magento\ImportExport\Test\Block\Adminhtml\Export\NotificationsArea $notificationsArea */
+ $notificationsArea = $adminExportIndex->getNotificationsArea();
+ $notificationsArea->openNotificationsDropDown();
+ $actualMessage = $notificationsArea->getLatestMessage();
\PHPUnit\Framework\Assert::assertEquals(
self::ERROR_MESSAGE,
diff --git a/dev/tests/functional/tests/app/Magento/ImportExport/Test/Constraint/AssertExportSubmittedMessage.php b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Constraint/AssertExportSubmittedMessage.php
new file mode 100644
index 0000000000000..59b1c7570c3de
--- /dev/null
+++ b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Constraint/AssertExportSubmittedMessage.php
@@ -0,0 +1,49 @@
+getMessagesBlock()->getSuccessMessage();
+
+ \PHPUnit\Framework\Assert::assertEquals(
+ self::MESSAGE,
+ $actualMessage,
+ 'Wrong message is displayed.'
+ . "\nExpected: " . self::MESSAGE
+ . "\nActual: " . $actualMessage
+ );
+ }
+
+ /**
+ * Returns a string representation of the object.
+ *
+ * @return string
+ */
+ public function toString()
+ {
+ return 'Correct message is displayed.';
+ }
+}
diff --git a/dev/tests/functional/tests/app/Magento/ImportExport/Test/Page/Adminhtml/AdminExportIndex.xml b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Page/Adminhtml/AdminExportIndex.xml
index 51afed2087316..e70a5fc29820c 100644
--- a/dev/tests/functional/tests/app/Magento/ImportExport/Test/Page/Adminhtml/AdminExportIndex.xml
+++ b/dev/tests/functional/tests/app/Magento/ImportExport/Test/Page/Adminhtml/AdminExportIndex.xml
@@ -9,6 +9,9 @@
+
+
+
diff --git a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Block/Navigation.php b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Block/Navigation.php
index 3c36fe82b1307..97c43d7c4e2ce 100644
--- a/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Block/Navigation.php
+++ b/dev/tests/functional/tests/app/Magento/LayeredNavigation/Test/Block/Navigation.php
@@ -64,6 +64,13 @@ class Navigation extends Block
*/
private $productQty = '/following-sibling::span[contains(text(), "%s")]';
+ /**
+ * Selector for child element with product quantity.
+ *
+ * @var string
+ */
+ private $productQtyInCategory = '/span[contains(text(), "%s")]';
+
/**
* Remove all applied filters.
*
@@ -124,10 +131,20 @@ public function applyFilter($filter, $linkPattern)
*/
public function isCategoryVisible(Category $category, $qty)
{
- return $this->_rootElement->find(
- sprintf($this->categoryName, $category->getName()) . sprintf($this->productQty, $qty),
- Locator::SELECTOR_XPATH
- )->isVisible();
+ $link = sprintf($this->categoryName, $category->getName());
+
+ if (!$this->_rootElement->find($link, Locator::SELECTOR_XPATH)->isVisible()) {
+ $this->openFilterContainer('Category', $link);
+ return $this->_rootElement->find(
+ $link . sprintf($this->productQtyInCategory, $qty),
+ Locator::SELECTOR_XPATH
+ )->isVisible();
+ } else {
+ return $this->_rootElement->find(
+ $link . sprintf($this->productQty, $qty),
+ Locator::SELECTOR_XPATH
+ )->isVisible();
+ }
}
/**
diff --git a/dev/tests/functional/tests/app/Magento/PageCache/Test/TestCase/FlushStaticFilesCacheButtonVisibilityTest.xml b/dev/tests/functional/tests/app/Magento/PageCache/Test/TestCase/FlushStaticFilesCacheButtonVisibilityTest.xml
index cbdce59057195..bc529729f1217 100644
--- a/dev/tests/functional/tests/app/Magento/PageCache/Test/TestCase/FlushStaticFilesCacheButtonVisibilityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/PageCache/Test/TestCase/FlushStaticFilesCacheButtonVisibilityTest.xml
@@ -8,7 +8,7 @@
- severity:S3
+ severity:S3, mftf_migrated:yes
diff --git a/dev/tests/functional/tests/app/Magento/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/functional/tests/app/Magento/Sales/Test/TestCase/CreateCustomOrderStatusEntityTest.xml b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCustomOrderStatusEntityTest.xml
index 38ce04fa56d81..e05d0fea6b129 100644
--- a/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCustomOrderStatusEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Sales/Test/TestCase/CreateCustomOrderStatusEntityTest.xml
@@ -8,17 +8,20 @@
+ mftf_migrated:yes
order_status%isolation%
orderLabel%isolation%
+ mftf_migrated:yes
pending
orderLabel%isolation%
+ mftf_migrated:yes
order_status%isolation%
Suspected Fraud
diff --git a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml
index 586ad2acee203..4995c1feb048e 100644
--- a/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/SalesRule/Test/TestCase/CreateSalesRuleEntityTest.xml
@@ -75,7 +75,6 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit - %isolation%
Fixed amount discount for whole cart
60
- No
No
Coupon code+Fixed amount discount for whole cart
simple_for_salesrule_1
diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml
index 5a547f69280e1..e35ef853d1b68 100644
--- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/CreateWebsiteEntityTest.xml
@@ -8,7 +8,7 @@
- severity:S1
+ severity:S1, mftf_migrated:yes
website_%isolation%
code_%isolation%
diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml
index 306a9fd2024a4..cd37c555fdb1d 100644
--- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreEntityTest.xml
@@ -8,7 +8,7 @@
- severity:S2
+ severity:S2, mftf_migrated:yes
custom
Yes
diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.xml
index 66ae1d8179af5..865530862853a 100644
--- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/DeleteStoreGroupEntityTest.xml
@@ -8,7 +8,7 @@
- severity:S3
+ severity:S3, mftf_migrated:yes
custom
Yes
diff --git a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml
index ac857ad035f44..5db0e7f8baad4 100644
--- a/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml
+++ b/dev/tests/functional/tests/app/Magento/Store/Test/TestCase/UpdateWebsiteEntityTest.xml
@@ -8,7 +8,7 @@
-