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 @@
@@ -60,14 +69,14 @@
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
= $block->getColumnHtml($_item, 'price') ?>
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
= /* @escapeNotVerified */ __('Ordered') ?>
@@ -116,7 +125,7 @@
- canShowPriceInfo($_item)): ?>
+ canShowPriceInfo($_item) || $shipTogether): ?>
canEditQty()) : ?>
',
controlContainer: 'dd', // should be eliminated
priceFormat: {},
- isFixedPrice: false
+ isFixedPrice: false,
+ optionTierPricesBlocksSelector: '#option-tier-prices-{1} [data-role="selection-tier-prices"]'
};
$.widget('mage.priceBundle', {
@@ -91,6 +92,8 @@ define([
if (changes) {
priceBox.trigger('updatePrice', changes);
}
+
+ this._displayTierPriceBlock(bundleOption);
this.updateProductSummary();
},
@@ -207,6 +210,35 @@ define([
return this;
},
+ /**
+ * Show or hide option tier prices block
+ *
+ * @param {Object} optionElement
+ * @private
+ */
+ _displayTierPriceBlock: function (optionElement) {
+ var optionType = optionElement.prop('type'),
+ optionId,
+ optionValue,
+ optionTierPricesElements;
+
+ if (optionType === 'select-one') {
+ optionId = utils.findOptionId(optionElement[0]);
+ optionValue = optionElement.val() || null;
+ optionTierPricesElements = $(this.options.optionTierPricesBlocksSelector.replace('{1}', optionId));
+
+ _.each(optionTierPricesElements, function (tierPriceElement) {
+ var selectionId = $(tierPriceElement).data('selection-id') + '';
+
+ if (selectionId === optionValue) {
+ $(tierPriceElement).show();
+ } else {
+ $(tierPriceElement).hide();
+ }
+ });
+ }
+ },
+
/**
* Handler to update productSummary box
*/
@@ -374,8 +406,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/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml
index 5b8c050e5af54..d12f2e8f6a952 100644
--- a/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml
+++ b/app/code/Magento/Bundle/view/frontend/layout/catalog_product_view_type_bundle.xml
@@ -29,10 +29,22 @@
-
+
+
+ \Magento\Bundle\Block\DataProviders\OptionPriceRenderer
+
+
-
-
+
+
+ \Magento\Bundle\Block\DataProviders\OptionPriceRenderer
+
+
+
+
+ \Magento\Bundle\Block\DataProviders\OptionPriceRenderer
+
+
diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml
index bda649eb603e6..830d03c826f32 100644
--- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml
+++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/checkbox.phtml
@@ -19,6 +19,7 @@
showSingle()): ?>
= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selections[0]) ?>
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?>
= /* @escapeNotVerified */ $block->getSelectionQtyTitlePrice($_selection) ?>
+
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?>
diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml
index 7ea89e8609818..1f33d97227ea3 100644
--- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml
+++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/radio.phtml
@@ -21,6 +21,7 @@
showSingle()): ?>
= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?>
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?>
= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selection) ?>
+
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?>
diff --git a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml
index 977daa2b2a446..4ea00f62b2043 100644
--- a/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml
+++ b/app/code/Magento/Bundle/view/frontend/templates/catalog/product/view/type/bundle/option/select.phtml
@@ -20,6 +20,7 @@
showSingle()): ?>
= /* @escapeNotVerified */ $block->getSelectionTitlePrice($_selections[0]) ?>
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selections[0]) ?>
+
+
+
+ = /* @noEscape */ $block->getTierPriceRenderer()->renderTierPrice($_selection) ?>
+
+
+
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/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml
new file mode 100644
index 0000000000000..07329e2659876
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AdminLoginWithCaptchaActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml
new file mode 100644
index 0000000000000..a371f177e3552
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaNotVisibleOnCustomerLoginFormActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml
new file mode 100644
index 0000000000000..aa02588000d2b
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnAdminLoginFormActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml
new file mode 100644
index 0000000000000..d800c65cabb60
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnContactUsFormActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml
new file mode 100644
index 0000000000000..6c09d1d49381f
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountCreatePageActionGroup.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml
new file mode 100644
index 0000000000000..c68ffbfb5be4b
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerAccountInfoActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml
new file mode 100644
index 0000000000000..5616b099c026d
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/AssertCaptchaVisibleOnCustomerLoginFormActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
index beb2c2bffa135..f3b6eb1d9af84 100644
--- a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/CaptchaFormsDisplayingActionGroup.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:Test/etc/actionGroupSchema.xsd">
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml
new file mode 100644
index 0000000000000..8aff3d5482f2c
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailWithCaptchaActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml
new file mode 100644
index 0000000000000..3546fa2e57a33
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillContactUsFormWithCaptchaActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml
new file mode 100644
index 0000000000000..d67ebc1a00768
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormWithCaptchaActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml
new file mode 100644
index 0000000000000..5ad727a8fe99d
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormWithCaptchaActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml
new file mode 100644
index 0000000000000..90f48c320f2ac
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaConfigData.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+ customer/captcha/enable
+ 0
+ Yes
+ 1
+
+
+ customer/captcha/enable
+ 0
+ No
+ 0
+
+
+ customer/captcha/forms
+ 0
+ Create user
+ user_create
+
+
+ customer/captcha/forms
+ 0
+ Contact Us
+ contact_us
+
+
+
+ customer/captcha/forms
+ 0
+ Login
+ user_login
+
+
+ customer/captcha/forms
+ 0
+ Change password
+ user_edit
+
+
+
+ customer/captcha/forms
+ 0
+ Forgot password
+ user_forgotpassword
+
+
+ customer/captcha/mode
+ 0
+ Always
+ always
+
+
+
+ customer/captcha/mode
+ 0
+ After number of attempts to login
+ after_fail
+
+
+ customer/captcha/length
+ admin
+ 1
+ 3
+ 3
+
+
+ customer/captcha/symbols
+ admin
+ 1
+ 1
+ 1
+
+
+
+ customer/captcha/length
+ admin
+ 1
+ 4-5
+ 4-5
+
+
+
+ customer/captcha/symbols
+ admin
+ 1
+ ABCDEFGHJKMnpqrstuvwxyz23456789
+ ABCDEFGHJKMnpqrstuvwxyz23456789
+
+
+
+ admin/captcha/enable
+ 0
+ Yes
+ 1
+
+
+ admin/captcha/enable
+ 0
+ No
+ 0
+
+
+ admin/captcha/length
+ admin
+ 1
+ 3
+ 3
+
+
+ admin/captcha/symbols
+ admin
+ 1
+ 1
+ 1
+
+
+
+ admin/captcha/length
+ admin
+ 1
+ 4-5
+ 4-5
+
+
+
+ admin/captcha/symbols
+ admin
+ 1
+ ABCDEFGHJKMnpqrstuvwxyz23456789
+ ABCDEFGHJKMnpqrstuvwxyz23456789
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml
new file mode 100644
index 0000000000000..d8fb206b8111c
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaData.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ WrongCAPTCHA
+
+
+
+
+ 111
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml
index 9db8110c0f64b..57a09219fe4db 100644
--- a/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/Data/CaptchaFormsDisplayingData.xml
@@ -7,7 +7,7 @@
-->
+ xsi:noNamespaceSchemaLocation="urn:magento:mftf:DataGenerator/etc/dataProfileSchema.xsd">
Create user
Login
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml
new file mode 100644
index 0000000000000..2bcc6fc542d82
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/AdminLoginFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml
new file mode 100644
index 0000000000000..f587812576ff1
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsCaptchaSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml
new file mode 100644
index 0000000000000..60cf961ba7e8c
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontContactUsFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
new file mode 100644
index 0000000000000..a273c8d4abd28
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
new file mode 100644
index 0000000000000..f48e6124cb214
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerCreateFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
index 7a0557c4a2744..54aa36d1ca267 100644
--- a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInFormSection.xml
@@ -8,7 +8,7 @@
-
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml
new file mode 100644
index 0000000000000..7a0557c4a2744
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Section/StorefrontCustomerSignInPopupFormSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml
new file mode 100644
index 0000000000000..e5ee55910df65
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/AdminLoginWithCaptchaTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml
new file mode 100644
index 0000000000000..54237087227d8
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaEditCustomerEmailTest.xml
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml
new file mode 100644
index 0000000000000..0c6a3f31c1df2
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnContactUsTest.xml
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml
new file mode 100644
index 0000000000000..5a1be68d3f251
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaOnCustomerLoginTest.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml
new file mode 100644
index 0000000000000..2c331f958e467
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontCaptchaRegisterNewCustomerTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml
new file mode 100644
index 0000000000000..36d7989b9acc1
--- /dev/null
+++ b/app/code/Magento/Captcha/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
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/Category/AbstractCategory.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/AbstractCategory.php
index 331679874629b..ffb648cdf438a 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Category/AbstractCategory.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/AbstractCategory.php
@@ -67,6 +67,8 @@ public function getCategory()
}
/**
+ * Get category id
+ *
* @return int|string|null
*/
public function getCategoryId()
@@ -78,6 +80,8 @@ public function getCategoryId()
}
/**
+ * Get category name
+ *
* @return string
*/
public function getCategoryName()
@@ -86,6 +90,8 @@ public function getCategoryName()
}
/**
+ * Get category path
+ *
* @return mixed
*/
public function getCategoryPath()
@@ -97,6 +103,8 @@ public function getCategoryPath()
}
/**
+ * Check store root category
+ *
* @return bool
*/
public function hasStoreRootCategory()
@@ -109,6 +117,8 @@ public function hasStoreRootCategory()
}
/**
+ * Get store from request
+ *
* @return Store
*/
public function getStore()
@@ -118,6 +128,8 @@ public function getStore()
}
/**
+ * Get root category for tree
+ *
* @param mixed|null $parentNodeCategory
* @param int $recursionLevel
* @return Node|array|null
@@ -149,10 +161,11 @@ public function getRoot($parentNodeCategory = null, $recursionLevel = 3)
$root = $tree->getNodeById($rootId);
- if ($root && $rootId != \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
+ if ($root) {
$root->setIsVisible(true);
- } elseif ($root && $root->getId() == \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
- $root->setName(__('Root'));
+ if ($root->getId() == \Magento\Catalog\Model\Category::TREE_ROOT_ID) {
+ $root->setName(__('Root'));
+ }
}
$this->_coreRegistry->register('root', $root);
@@ -162,6 +175,8 @@ public function getRoot($parentNodeCategory = null, $recursionLevel = 3)
}
/**
+ * Get Default Store Id
+ *
* @return int
*/
protected function _getDefaultStoreId()
@@ -170,6 +185,8 @@ protected function _getDefaultStoreId()
}
/**
+ * Get category collection
+ *
* @return \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
*/
public function getCategoryCollection()
@@ -227,6 +244,8 @@ public function getRootByIds($ids)
}
/**
+ * Get category node for tree
+ *
* @param mixed $parentNodeCategory
* @param int $recursionLevel
* @return Node
@@ -249,6 +268,8 @@ public function getNode($parentNodeCategory, $recursionLevel = 2)
}
/**
+ * Get category save url
+ *
* @param array $args
* @return string
*/
@@ -260,6 +281,8 @@ public function getSaveUrl(array $args = [])
}
/**
+ * Get category edit url
+ *
* @return string
*/
public function getEditUrl()
diff --git a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php
index a67f55235b6df..83ec501592489 100644
--- a/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php
+++ b/app/code/Magento/Catalog/Block/Adminhtml/Category/Tree.php
@@ -71,7 +71,7 @@ public function __construct(
}
/**
- * @return void
+ * @inheritdoc
*/
protected function _construct()
{
@@ -80,7 +80,7 @@ protected function _construct()
}
/**
- * @return $this
+ * @inheritdoc
*/
protected function _prepareLayout()
{
@@ -182,6 +182,8 @@ public function getSuggestedCategoriesJson($namePart)
}
/**
+ * Get add root button html
+ *
* @return string
*/
public function getAddRootButtonHtml()
@@ -190,6 +192,8 @@ public function getAddRootButtonHtml()
}
/**
+ * Get add sub button html
+ *
* @return string
*/
public function getAddSubButtonHtml()
@@ -198,6 +202,8 @@ public function getAddSubButtonHtml()
}
/**
+ * Get expand button html
+ *
* @return string
*/
public function getExpandButtonHtml()
@@ -206,6 +212,8 @@ public function getExpandButtonHtml()
}
/**
+ * Get collapse button html
+ *
* @return string
*/
public function getCollapseButtonHtml()
@@ -214,6 +222,8 @@ public function getCollapseButtonHtml()
}
/**
+ * Get store switcher
+ *
* @return string
*/
public function getStoreSwitcherHtml()
@@ -222,6 +232,8 @@ public function getStoreSwitcherHtml()
}
/**
+ * Get loader tree url
+ *
* @param bool|null $expanded
* @return string
*/
@@ -235,6 +247,8 @@ public function getLoadTreeUrl($expanded = null)
}
/**
+ * Get nodes url
+ *
* @return string
*/
public function getNodesUrl()
@@ -243,6 +257,8 @@ public function getNodesUrl()
}
/**
+ * Get switcher tree url
+ *
* @return string
*/
public function getSwitchTreeUrl()
@@ -254,6 +270,8 @@ public function getSwitchTreeUrl()
}
/**
+ * Get is was expanded
+ *
* @return bool
* @SuppressWarnings(PHPMD.BooleanGetMethodName)
*/
@@ -263,6 +281,8 @@ public function getIsWasExpanded()
}
/**
+ * Get move url
+ *
* @return string
*/
public function getMoveUrl()
@@ -271,6 +291,8 @@ public function getMoveUrl()
}
/**
+ * Get tree
+ *
* @param mixed|null $parenNodeCategory
* @return array
*/
@@ -282,6 +304,8 @@ public function getTree($parenNodeCategory = null)
}
/**
+ * Get tree json
+ *
* @param mixed|null $parenNodeCategory
* @return string
*/
@@ -367,7 +391,7 @@ protected function _getNodeJson($node, $level = 0)
}
}
- if ($isParent || $node->getLevel() < 2) {
+ if ($isParent || $node->getLevel() < 1) {
$item['expanded'] = true;
}
@@ -390,6 +414,8 @@ public function buildNodeName($node)
}
/**
+ * Is category movable
+ *
* @param Node|array $node
* @return bool
*/
@@ -403,6 +429,8 @@ protected function _isCategoryMoveable($node)
}
/**
+ * Is parent selected category
+ *
* @param Node|array $node
* @return bool
*/
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 cb59d86a74512..5b9777cbfd1e7 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Attributes.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Attributes.php
@@ -16,6 +16,8 @@
use Magento\Framework\Pricing\PriceCurrencyInterface;
/**
+ * Attributes attributes block
+ *
* @api
* @since 100.0.2
*/
@@ -56,6 +58,8 @@ public function __construct(
}
/**
+ * Returns a Product
+ *
* @return Product
*/
public function getProduct()
@@ -88,9 +92,9 @@ public function getAdditionalData(array $excludeAttr = [])
$value = $this->priceCurrency->convertAndFormat($value);
}
- if (is_string($value) && strlen($value)) {
+ 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/Gallery.php b/app/code/Magento/Catalog/Block/Product/View/Gallery.php
index 706d9b83b9711..8b98fbdc8f7ef 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Gallery.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Gallery.php
@@ -207,8 +207,8 @@ public function isMainImage($image)
*/
public function getImageAttribute($imageId, $attributeName, $default = null)
{
- $attributes =
- $this->getConfigView()->getMediaAttributes('Magento_Catalog', Image::MEDIA_TYPE_CONFIG_NODE, $imageId);
+ $attributes = $this->getConfigView()
+ ->getMediaAttributes('Magento_Catalog', Image::MEDIA_TYPE_CONFIG_NODE, $imageId);
return $attributes[$attributeName] ?? $default;
}
diff --git a/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php b/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php
new file mode 100644
index 0000000000000..0384c9cd9acce
--- /dev/null
+++ b/app/code/Magento/Catalog/Block/Product/View/GalleryOptions.php
@@ -0,0 +1,156 @@
+gallery = $gallery;
+ $this->jsonSerializer = $jsonSerializer;
+ parent::__construct($context, $arrayUtils, $data);
+ }
+
+ /**
+ * Retrieve gallery options in JSON format
+ *
+ * @return string
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.ElseExpression)
+ */
+ public function getOptionsJson()
+ {
+ $optionItems = null;
+
+ //Special case for gallery/nav which can be the string "thumbs/false/dots"
+ if (is_bool($this->getVar("gallery/nav"))) {
+ $optionItems['nav'] = $this->getVar("gallery/nav") ? 'true' : 'false';
+ } else {
+ $optionItems['nav'] = $this->escapeHtml($this->getVar("gallery/nav"));
+ }
+
+ $optionItems['loop'] = $this->getVar("gallery/loop");
+ $optionItems['keyboard'] = $this->getVar("gallery/keyboard");
+ $optionItems['arrows'] = $this->getVar("gallery/arrows");
+ $optionItems['allowfullscreen'] = $this->getVar("gallery/allowfullscreen");
+ $optionItems['showCaption'] = $this->getVar("gallery/caption");
+ $optionItems['width'] = (int)$this->escapeHtml(
+ $this->gallery->getImageAttribute('product_page_image_medium', 'width')
+ );
+ $optionItems['thumbwidth'] = (int)$this->escapeHtml(
+ $this->gallery->getImageAttribute('product_page_image_small', 'width')
+ );
+
+ if ($this->gallery->getImageAttribute('product_page_image_small', 'height') ||
+ $this->gallery->getImageAttribute('product_page_image_small', 'width')) {
+ $optionItems['thumbheight'] = (int)$this->escapeHtml(
+ $this->gallery->getImageAttribute('product_page_image_small', 'height') ?:
+ $this->gallery->getImageAttribute('product_page_image_small', 'width')
+ );
+ }
+
+ if ($this->gallery->getImageAttribute('product_page_image_medium', 'height') ||
+ $this->gallery->getImageAttribute('product_page_image_medium', 'width')) {
+ $optionItems['height'] = (int)$this->escapeHtml(
+ $this->gallery->getImageAttribute('product_page_image_medium', 'height') ?:
+ $this->gallery->getImageAttribute('product_page_image_medium', 'width')
+ );
+ }
+
+ if ($this->getVar("gallery/transition/duration")) {
+ $optionItems['transitionduration'] =
+ (int)$this->escapeHtml($this->getVar("gallery/transition/duration"));
+ }
+
+ $optionItems['transition'] = $this->escapeHtml($this->getVar("gallery/transition/effect"));
+ $optionItems['navarrows'] = $this->getVar("gallery/navarrows");
+ $optionItems['navtype'] = $this->escapeHtml($this->getVar("gallery/navtype"));
+ $optionItems['navdir'] = $this->escapeHtml($this->getVar("gallery/navdir"));
+
+ if ($this->getVar("gallery/thumbmargin")) {
+ $optionItems['thumbmargin'] = (int)$this->escapeHtml($this->getVar("gallery/thumbmargin"));
+ }
+
+ return $this->jsonSerializer->serialize($optionItems);
+ }
+
+ /**
+ * Retrieve gallery fullscreen options in JSON format
+ *
+ * @return string
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
+ * @SuppressWarnings(PHPMD.NPathComplexity)
+ * @SuppressWarnings(PHPMD.ElseExpression)
+ */
+ public function getFSOptionsJson()
+ {
+ $fsOptionItems = null;
+
+ //Special case for gallery/nav which can be the string "thumbs/false/dots"
+ if (is_bool($this->getVar("gallery/fullscreen/nav"))) {
+ $fsOptionItems['nav'] = $this->getVar("gallery/fullscreen/nav") ? 'true' : 'false';
+ } else {
+ $fsOptionItems['nav'] = $this->escapeHtml($this->getVar("gallery/fullscreen/nav"));
+ }
+
+ $fsOptionItems['loop'] = $this->getVar("gallery/fullscreen/loop");
+ $fsOptionItems['navdir'] = $this->escapeHtml($this->getVar("gallery/fullscreen/navdir"));
+ $fsOptionItems['navarrows'] = $this->getVar("gallery/fullscreen/navarrows");
+ $fsOptionItems['navtype'] = $this->escapeHtml($this->getVar("gallery/fullscreen/navtype"));
+ $fsOptionItems['arrows'] = $this->getVar("gallery/fullscreen/arrows");
+ $fsOptionItems['showCaption'] = $this->getVar("gallery/fullscreen/caption");
+
+ if ($this->getVar("gallery/fullscreen/transition/duration")) {
+ $fsOptionItems['transitionduration'] = (int)$this->escapeHtml(
+ $this->getVar("gallery/fullscreen/transition/duration")
+ );
+ }
+
+ $fsOptionItems['transition'] = $this->escapeHtml($this->getVar("gallery/fullscreen/transition/effect"));
+
+ if ($this->getVar("gallery/fullscreen/keyboard")) {
+ $fsOptionItems['keyboard'] = $this->getVar("gallery/fullscreen/keyboard");
+ }
+
+ if ($this->getVar("gallery/fullscreen/thumbmargin")) {
+ $fsOptionItems['thumbmargin'] =
+ (int)$this->escapeHtml($this->getVar("gallery/fullscreen/thumbmargin"));
+ }
+
+ return $this->jsonSerializer->serialize($fsOptionItems);
+ }
+}
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/Product/View/Options/Type/Select.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
index 7df9b972e1501..d9d663b32f4de 100644
--- a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select.php
@@ -3,8 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Catalog\Block\Product\View\Options\Type;
+use Magento\Catalog\Model\Product\Option;
+use Magento\Catalog\Block\Product\View\Options\Type\Select\CheckableFactory;
+use Magento\Catalog\Block\Product\View\Options\Type\Select\MultipleFactory;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\View\Element\Template\Context;
+use Magento\Framework\Pricing\Helper\Data;
+use Magento\Catalog\Helper\Data as CatalogHelper;
+
/**
* Product options text type block
*
@@ -13,169 +22,60 @@
*/
class Select extends \Magento\Catalog\Block\Product\View\Options\AbstractOptions
{
+ /**
+ * @var CheckableFactory
+ */
+ private $checkableFactory;
+ /**
+ * @var MultipleFactory
+ */
+ private $multipleFactory;
+
+ /**
+ * Select constructor.
+ * @param Context $context
+ * @param Data $pricingHelper
+ * @param CatalogHelper $catalogData
+ * @param array $data
+ * @param CheckableFactory|null $checkableFactory
+ * @param MultipleFactory|null $multipleFactory
+ */
+ public function __construct(
+ Context $context,
+ Data $pricingHelper,
+ CatalogHelper $catalogData,
+ array $data = [],
+ CheckableFactory $checkableFactory = null,
+ MultipleFactory $multipleFactory = null
+ ) {
+ parent::__construct($context, $pricingHelper, $catalogData, $data);
+ $this->checkableFactory = $checkableFactory ?: ObjectManager::getInstance()->get(CheckableFactory::class);
+ $this->multipleFactory = $multipleFactory ?: ObjectManager::getInstance()->get(MultipleFactory::class);
+ }
+
/**
* Return html for control element
*
* @return string
- * @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
- * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function getValuesHtml()
{
- $_option = $this->getOption();
- $configValue = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $_option->getId());
- $store = $this->getProduct()->getStore();
-
- $this->setSkipJsReloadPrice(1);
- // Remove inline prototype onclick and onchange events
-
- if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN ||
- $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE
+ $option = $this->getOption();
+ $optionType = $option->getType();
+ if ($optionType === Option::OPTION_TYPE_DROP_DOWN ||
+ $optionType === Option::OPTION_TYPE_MULTIPLE
) {
- $require = $_option->getIsRequire() ? ' required' : '';
- $extraParams = '';
- $select = $this->getLayout()->createBlock(
- \Magento\Framework\View\Element\Html\Select::class
- )->setData(
- [
- 'id' => 'select_' . $_option->getId(),
- 'class' => $require . ' product-custom-option admin__control-select'
- ]
- );
- if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_DROP_DOWN) {
- $select->setName('options[' . $_option->getId() . ']')->addOption('', __('-- Please Select --'));
- } else {
- $select->setName('options[' . $_option->getId() . '][]');
- $select->setClass('multiselect admin__control-multiselect' . $require . ' product-custom-option');
- }
- foreach ($_option->getValues() as $_value) {
- $priceStr = $this->_formatPrice(
- [
- 'is_percent' => $_value->getPriceType() == 'percent',
- 'pricing_value' => $_value->getPrice($_value->getPriceType() == 'percent'),
- ],
- false
- );
- $select->addOption(
- $_value->getOptionTypeId(),
- $_value->getTitle() . ' ' . strip_tags($priceStr) . '',
- ['price' => $this->pricingHelper->currencyByStore($_value->getPrice(true), $store, false)]
- );
- }
- if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_MULTIPLE) {
- $extraParams = ' multiple="multiple"';
- }
- if (!$this->getSkipJsReloadPrice()) {
- $extraParams .= ' onchange="opConfig.reloadPrice()"';
- }
- $extraParams .= ' data-selector="' . $select->getName() . '"';
- $select->setExtraParams($extraParams);
-
- if ($configValue) {
- $select->setValue($configValue);
- }
-
- return $select->getHtml();
+ $optionBlock = $this->multipleFactory->create();
}
-
- if ($_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_RADIO ||
- $_option->getType() == \Magento\Catalog\Api\Data\ProductCustomOptionInterface::OPTION_TYPE_CHECKBOX
+ if ($optionType === Option::OPTION_TYPE_RADIO ||
+ $optionType === Option::OPTION_TYPE_CHECKBOX
) {
- $selectHtml = '';
-
- return $selectHtml;
+ $optionBlock = $this->checkableFactory->create();
}
+ return $optionBlock
+ ->setOption($option)
+ ->setProduct($this->getProduct())
+ ->setSkipJsReloadPrice(1)
+ ->_toHtml();
}
}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php
new file mode 100644
index 0000000000000..3d856f85dbd94
--- /dev/null
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Checkable.php
@@ -0,0 +1,68 @@
+ $value->getPriceType() === 'percent',
+ 'pricing_value' => $value->getPrice($value->getPriceType() === 'percent')
+ ]
+ );
+ }
+
+ /**
+ * Returns current currency for store
+ *
+ * @param ProductCustomOptionValuesInterface $value
+ * @return float|string
+ */
+ public function getCurrencyByStore(ProductCustomOptionValuesInterface $value)
+ {
+ /** @noinspection PhpMethodParametersCountMismatchInspection */
+ return $this->pricingHelper->currencyByStore(
+ $value->getPrice(true),
+ $this->getProduct()->getStore(),
+ false
+ );
+ }
+
+ /**
+ * Returns preconfigured value for given option
+ *
+ * @param Option $option
+ * @return string|array|null
+ */
+ public function getPreconfiguredValue(Option $option)
+ {
+ return $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId());
+ }
+}
diff --git a/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php
new file mode 100644
index 0000000000000..09a931dfa0693
--- /dev/null
+++ b/app/code/Magento/Catalog/Block/Product/View/Options/Type/Select/Multiple.php
@@ -0,0 +1,112 @@
+getOption();
+ $optionType = $option->getType();
+ $configValue = $this->getProduct()->getPreconfiguredValues()->getData('options/' . $option->getId());
+ $require = $option->getIsRequire() ? ' required' : '';
+ $extraParams = '';
+ /** @var Select $select */
+ $select = $this->getLayout()->createBlock(
+ Select::class
+ )->setData(
+ [
+ 'id' => 'select_' . $option->getId(),
+ 'class' => $require . ' product-custom-option admin__control-select'
+ ]
+ );
+ $select = $this->insertSelectOption($select, $option);
+ $select = $this->processSelectOption($select, $option);
+ if ($optionType === Option::OPTION_TYPE_MULTIPLE) {
+ $extraParams = ' multiple="multiple"';
+ }
+ if (!$this->getSkipJsReloadPrice()) {
+ $extraParams .= ' onchange="opConfig.reloadPrice()"';
+ }
+ $extraParams .= ' data-selector="' . $select->getName() . '"';
+ $select->setExtraParams($extraParams);
+ if ($configValue) {
+ $select->setValue($configValue);
+ }
+ return $select->getHtml();
+ }
+
+ /**
+ * Returns select with inserted option give as a parameter
+ *
+ * @param Select $select
+ * @param Option $option
+ * @return Select
+ */
+ private function insertSelectOption(Select $select, Option $option): Select
+ {
+ $require = $option->getIsRequire() ? ' required' : '';
+ if ($option->getType() === Option::OPTION_TYPE_DROP_DOWN) {
+ $select->setName('options[' . $option->getId() . ']')->addOption('', __('-- Please Select --'));
+ } else {
+ $select->setName('options[' . $option->getId() . '][]');
+ $select->setClass('multiselect admin__control-multiselect' . $require . ' product-custom-option');
+ }
+
+ return $select;
+ }
+
+ /**
+ * Returns select with formated option prices
+ *
+ * @param Select $select
+ * @param Option $option
+ * @return Select
+ */
+ private function processSelectOption(Select $select, Option $option): Select
+ {
+ $store = $this->getProduct()->getStore();
+ foreach ($option->getValues() as $_value) {
+ $isPercentPriceType = $_value->getPriceType() === 'percent';
+ $priceStr = $this->_formatPrice(
+ [
+ 'is_percent' => $isPercentPriceType,
+ 'pricing_value' => $_value->getPrice($isPercentPriceType)
+ ],
+ false
+ );
+ $select->addOption(
+ $_value->getOptionTypeId(),
+ $_value->getTitle() . ' ' . strip_tags($priceStr) . '',
+ [
+ 'price' => $this->pricingHelper->currencyByStore(
+ $_value->getPrice(true),
+ $store,
+ false
+ )
+ ]
+ );
+ }
+
+ return $select;
+ }
+}
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/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
index 39ed11b1806cd..853cc65270306 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Save.php
@@ -195,25 +195,6 @@ public function execute()
? $model->getAttributeCode()
: $this->getRequest()->getParam('attribute_code');
$attributeCode = $attributeCode ?: $this->generateCode($this->getRequest()->getParam('frontend_label')[0]);
- if (strlen($attributeCode) > 0) {
- $validatorAttrCode = new \Zend_Validate_Regex(
- ['pattern' => '/^[a-zA-Z\x{600}-\x{6FF}][a-zA-Z\x{600}-\x{6FF}_0-9]{0,30}$/u']
- );
- if (!$validatorAttrCode->isValid($attributeCode)) {
- $this->messageManager->addErrorMessage(
- __(
- 'Attribute code "%1" is invalid. Please use only letters (a-z or A-Z), ' .
- 'numbers (0-9) or underscore(_) in this field, first character should be a letter.',
- $attributeCode
- )
- );
- return $this->returnResult(
- 'catalog/*/edit',
- ['attribute_id' => $attributeId, '_current' => true],
- ['error' => true]
- );
- }
- }
$data['attribute_code'] = $attributeCode;
//validate frontend_input
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 50f58efae7127..c74a382724a00 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Attribute/Validate.php
@@ -7,12 +7,13 @@
namespace Magento\Catalog\Controller\Adminhtml\Product\Attribute;
-use Magento\Framework\Serialize\Serializer\FormData;
+use Magento\Catalog\Controller\Adminhtml\Product\Attribute as AttributeAction;
+use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator;
use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Action\HttpPostActionInterface as HttpPostActionInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\DataObject;
-use Magento\Catalog\Controller\Adminhtml\Product\Attribute as AttributeAction;
+use Magento\Framework\Serialize\Serializer\FormData;
/**
* Product attribute validate controller.
@@ -43,6 +44,11 @@ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPo
*/
private $formDataSerializer;
+ /**
+ * @var AttributeCodeValidator
+ */
+ private $attributeCodeValidator;
+
/**
* Constructor
*
@@ -54,6 +60,7 @@ class Validate extends AttributeAction implements HttpGetActionInterface, HttpPo
* @param \Magento\Framework\View\LayoutFactory $layoutFactory
* @param array $multipleAttributeList
* @param FormData|null $formDataSerializer
+ * @param AttributeCodeValidator|null $attributeCodeValidator
*/
public function __construct(
\Magento\Backend\App\Action\Context $context,
@@ -63,7 +70,8 @@ public function __construct(
\Magento\Framework\Controller\Result\JsonFactory $resultJsonFactory,
\Magento\Framework\View\LayoutFactory $layoutFactory,
array $multipleAttributeList = [],
- FormData $formDataSerializer = null
+ FormData $formDataSerializer = null,
+ AttributeCodeValidator $attributeCodeValidator = null
) {
parent::__construct($context, $attributeLabelCache, $coreRegistry, $resultPageFactory);
$this->resultJsonFactory = $resultJsonFactory;
@@ -71,6 +79,9 @@ public function __construct(
$this->multipleAttributeList = $multipleAttributeList;
$this->formDataSerializer = $formDataSerializer ?: ObjectManager::getInstance()
->get(FormData::class);
+ $this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
+ AttributeCodeValidator::class
+ );
}
/**
@@ -105,7 +116,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);
@@ -115,6 +126,12 @@ public function execute()
$response->setError(true);
$response->setProductAttribute($attribute->toArray());
}
+
+ if (!$this->attributeCodeValidator->isValid($attributeCode)) {
+ $this->setMessageToResponse($response, $this->attributeCodeValidator->getMessages());
+ $response->setError(true);
+ }
+
if ($this->getRequest()->has('new_attribute_set_name')) {
$setName = $this->getRequest()->getParam('new_attribute_set_name');
/** @var $attributeSet \Magento\Eav\Model\Entity\Attribute\Set */
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/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
index e84d9ff12906e..825d0ee032d6c 100644
--- a/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
+++ b/app/code/Magento/Catalog/Controller/Adminhtml/Product/Save.php
@@ -159,6 +159,7 @@ public function execute()
if ($redirectBack === 'duplicate') {
$product->unsetData('quantity_and_stock_status');
$newProduct = $this->productCopier->copy($product);
+ $this->checkUniqueAttributes($product);
$this->messageManager->addSuccessMessage(__('You duplicated the product.'));
}
} catch (\Magento\Framework\Exception\LocalizedException $e) {
@@ -343,4 +344,25 @@ private function persistMediaData(ProductInterface $product, array $data)
return $data;
}
+
+ /**
+ * Check unique attributes and add error to message manager
+ *
+ * @param \Magento\Catalog\Model\Product $product
+ */
+ private function checkUniqueAttributes(\Magento\Catalog\Model\Product $product)
+ {
+ $uniqueLabels = [];
+ foreach ($product->getAttributes() as $attribute) {
+ if ($attribute->getIsUnique() && $attribute->getIsUserDefined()
+ && $product->getData($attribute->getAttributeCode()) !== null
+ ) {
+ $uniqueLabels[] = $attribute->getDefaultFrontendLabel();
+ }
+ }
+ if ($uniqueLabels) {
+ $uniqueLabels = implode('", "', $uniqueLabels);
+ $this->messageManager->addErrorMessage(__('The value of attribute(s) "%1" must be unique', $uniqueLabels));
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Helper/Image.php b/app/code/Magento/Catalog/Helper/Image.php
index 170f1209ad9e6..9b8d0ad75a8c9 100644
--- a/app/code/Magento/Catalog/Helper/Image.php
+++ b/app/code/Magento/Catalog/Helper/Image.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Helper;
use Magento\Framework\App\Helper\AbstractHelper;
+use Magento\Framework\View\Element\Block\ArgumentInterface;
/**
* Catalog image helper
@@ -14,7 +15,7 @@
* @SuppressWarnings(PHPMD.TooManyFields)
* @since 100.0.2
*/
-class Image extends AbstractHelper
+class Image extends AbstractHelper implements ArgumentInterface
{
/**
* Media config node
@@ -764,7 +765,7 @@ protected function getImageFile()
protected function parseSize($string)
{
$size = explode('x', strtolower($string));
- if (sizeof($size) == 2) {
+ if (count($size) == 2) {
return ['width' => $size[0] > 0 ? $size[0] : null, 'height' => $size[1] > 0 ? $size[1] : null];
}
return false;
diff --git a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
index d3c84e69c9540..e296c8d3b8978 100644
--- a/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
+++ b/app/code/Magento/Catalog/Model/Api/SearchCriteria/CollectionProcessor/ConditionProcessor/ConditionBuilder/EavAttributeCondition.php
@@ -58,22 +58,38 @@ public function build(Filter $filter): string
$conditionValue = $this->mapConditionValue($conditionType, $filter->getValue());
// NOTE: store scope was ignored intentionally to perform search across all stores
- $attributeSelect = $this->resourceConnection->getConnection()
- ->select()
- ->from(
- [$tableAlias => $attribute->getBackendTable()],
- $tableAlias . '.' . $attribute->getEntityIdField()
- )->where(
- $this->resourceConnection->getConnection()->prepareSqlCondition(
- $tableAlias . '.' . $attribute->getIdFieldName(),
- ['eq' => $attribute->getAttributeId()]
- )
- )->where(
- $this->resourceConnection->getConnection()->prepareSqlCondition(
- $tableAlias . '.value',
- [$conditionType => $conditionValue]
- )
- );
+ if ($conditionType == 'is_null') {
+ $entityResourceModel = $attribute->getEntity();
+ $attributeSelect = $this->resourceConnection->getConnection()
+ ->select()
+ ->from(
+ [Collection::MAIN_TABLE_ALIAS => $entityResourceModel->getEntityTable()],
+ Collection::MAIN_TABLE_ALIAS . '.' . $entityResourceModel->getEntityIdField()
+ )->joinLeft(
+ [$tableAlias => $attribute->getBackendTable()],
+ $tableAlias . '.' . $attribute->getEntityIdField() . '=' . Collection::MAIN_TABLE_ALIAS .
+ '.' . $entityResourceModel->getEntityIdField() . ' AND ' . $tableAlias . '.' .
+ $attribute->getIdFieldName() . '=' . $attribute->getAttributeId(),
+ ''
+ )->where($tableAlias . '.value is null');
+ } else {
+ $attributeSelect = $this->resourceConnection->getConnection()
+ ->select()
+ ->from(
+ [$tableAlias => $attribute->getBackendTable()],
+ $tableAlias . '.' . $attribute->getEntityIdField()
+ )->where(
+ $this->resourceConnection->getConnection()->prepareSqlCondition(
+ $tableAlias . '.' . $attribute->getIdFieldName(),
+ ['eq' => $attribute->getAttributeId()]
+ )
+ )->where(
+ $this->resourceConnection->getConnection()->prepareSqlCondition(
+ $tableAlias . '.value',
+ [$conditionType => $conditionValue]
+ )
+ );
+ }
return $this->resourceConnection
->getConnection()
@@ -86,6 +102,8 @@ public function build(Filter $filter): string
}
/**
+ * Get attribute entity by its code
+ *
* @param string $field
* @return Attribute
* @throws \Magento\Framework\Exception\LocalizedException
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 790ea6b921fbe..cab8e013d9ba1 100644
--- a/app/code/Magento/Catalog/Model/CategoryList.php
+++ b/app/code/Magento/Catalog/Model/CategoryList.php
@@ -15,6 +15,9 @@
use Magento\Framework\Api\SearchCriteriaInterface;
use Magento\Framework\Api\SearchCriteria\CollectionProcessorInterface;
+/**
+ * Class for getting category list.
+ */
class CategoryList implements CategoryListInterface
{
/**
@@ -64,7 +67,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList(SearchCriteriaInterface $searchCriteria)
{
diff --git a/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php b/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php
index e2b0a91574021..10675a7b7c7e2 100644
--- a/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php
+++ b/app/code/Magento/Catalog/Model/Config/CatalogClone/Media/Image.php
@@ -5,6 +5,9 @@
*/
namespace Magento\Catalog\Model\Config\CatalogClone\Media;
+use Magento\Framework\Escaper;
+use Magento\Framework\App\ObjectManager;
+
/**
* Clone model for media images related config fields
*
@@ -26,6 +29,11 @@ class Image extends \Magento\Framework\App\Config\Value
*/
protected $_attributeCollectionFactory;
+ /**
+ * @var Escaper
+ */
+ private $escaper;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -36,6 +44,9 @@ class Image extends \Magento\Framework\App\Config\Value
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
+ * @param Escaper|null $escaper
+ *
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
\Magento\Framework\Model\Context $context,
@@ -46,8 +57,10 @@ public function __construct(
\Magento\Eav\Model\Config $eavConfig,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ Escaper $escaper = null
) {
+ $this->escaper = $escaper ?? ObjectManager::getInstance()->get(Escaper::class);
$this->_attributeCollectionFactory = $attributeCollectionFactory;
$this->_eavConfig = $eavConfig;
parent::__construct($context, $registry, $config, $cacheTypeList, $resource, $resourceCollection, $data);
@@ -71,10 +84,9 @@ public function getPrefixes()
$prefixes = [];
foreach ($collection as $attribute) {
- /* @var $attribute \Magento\Eav\Model\Entity\Attribute */
$prefixes[] = [
'field' => $attribute->getAttributeCode() . '_',
- 'label' => $attribute->getFrontend()->getLabel(),
+ 'label' => $this->escaper->escapeHtml($attribute->getFrontend()->getLabel()),
];
}
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/Product/Attribute/DataProvider.php b/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
index 2bb10d3b31a24..893000544a728 100644
--- a/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
+++ b/app/code/Magento/Catalog/Model/Product/Attribute/DataProvider.php
@@ -113,27 +113,28 @@ private function customizeAttributeCode($meta)
*/
private function customizeFrontendLabels($meta)
{
+ $labelConfigs = [];
+
foreach ($this->storeRepository->getList() as $store) {
$storeId = $store->getId();
if (!$storeId) {
continue;
}
-
- $meta['manage-titles']['children'] = [
- 'frontend_label[' . $storeId . ']' => $this->arrayManager->set(
- 'arguments/data/config',
- [],
- [
- 'formElement' => Input::NAME,
- 'componentType' => Field::NAME,
- 'label' => $store->getName(),
- 'dataType' => Text::NAME,
- 'dataScope' => 'frontend_label[' . $storeId . ']'
- ]
- ),
- ];
+ $labelConfigs['frontend_label[' . $storeId . ']'] = $this->arrayManager->set(
+ 'arguments/data/config',
+ [],
+ [
+ 'formElement' => Input::NAME,
+ 'componentType' => Field::NAME,
+ 'label' => $store->getName(),
+ 'dataType' => Text::NAME,
+ 'dataScope' => 'frontend_label[' . $storeId . ']'
+ ]
+ );
}
+ $meta['manage-titles']['children'] = $labelConfigs;
+
return $meta;
}
diff --git a/app/code/Magento/Catalog/Model/Product/Option/Repository.php b/app/code/Magento/Catalog/Model/Product/Option/Repository.php
index 9dc9695daffd1..bb4e247de32db 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/Repository.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/Repository.php
@@ -14,6 +14,8 @@
use Magento\Framework\App\ObjectManager;
/**
+ * Product custom options repository
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Repository implements \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface
@@ -83,7 +85,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList($sku)
{
@@ -92,7 +94,7 @@ public function getList($sku)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getProductOptions(ProductInterface $product, $requiredOnly = false)
{
@@ -104,7 +106,7 @@ public function getProductOptions(ProductInterface $product, $requiredOnly = fal
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function get($sku, $optionId)
{
@@ -117,7 +119,7 @@ public function get($sku, $optionId)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $entity)
{
@@ -126,7 +128,7 @@ public function delete(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $e
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function duplicate(
\Magento\Catalog\Api\Data\ProductInterface $product,
@@ -142,7 +144,7 @@ public function duplicate(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $option)
{
@@ -184,7 +186,7 @@ public function save(\Magento\Catalog\Api\Data\ProductCustomOptionInterface $opt
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteByIdentifier($sku, $optionId)
{
@@ -209,8 +211,8 @@ public function deleteByIdentifier($sku, $optionId)
/**
* Mark original values for removal if they are absent among new values
*
- * @param $newValues array
- * @param $originalValues \Magento\Catalog\Model\Product\Option\Value[]
+ * @param array $newValues
+ * @param \Magento\Catalog\Model\Product\Option\Value[] $originalValues
* @return array
*/
protected function markRemovedValues($newValues, $originalValues)
@@ -234,6 +236,8 @@ protected function markRemovedValues($newValues, $originalValues)
}
/**
+ * Get hydrator pool
+ *
* @return \Magento\Framework\EntityManager\HydratorPool
* @deprecated 101.0.0
*/
diff --git a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
index c4a2d60414a7b..9cb6cda4d0a09 100644
--- a/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
+++ b/app/code/Magento/Catalog/Model/Product/Option/SaveHandler.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Model\Product\Option;
use Magento\Catalog\Api\ProductCustomOptionRepositoryInterface as OptionRepository;
@@ -28,6 +30,8 @@ public function __construct(
}
/**
+ * Perform action on relation/extension attribute
+ *
* @param object $entity
* @param array $arguments
* @return \Magento\Catalog\Api\Data\ProductInterface|object
@@ -35,6 +39,10 @@ public function __construct(
*/
public function execute($entity, $arguments = [])
{
+ if ($entity->getOptionsSaved()) {
+ return $entity;
+ }
+
$options = $entity->getOptions();
$optionIds = [];
@@ -52,11 +60,26 @@ public function execute($entity, $arguments = [])
}
}
if ($options) {
- foreach ($options as $option) {
- $this->optionRepository->save($option);
- }
+ $this->processOptionsSaving($options, (bool)$entity->dataHasChangedFor('sku'), (string)$entity->getSku());
}
return $entity;
}
+
+ /**
+ * Save custom options
+ *
+ * @param array $options
+ * @param bool $hasChangedSku
+ * @param string $newSku
+ */
+ private function processOptionsSaving(array $options, bool $hasChangedSku, string $newSku)
+ {
+ foreach ($options as $option) {
+ if ($hasChangedSku && $option->hasData('product_sku')) {
+ $option->setProductSku($newSku);
+ }
+ $this->optionRepository->save($option);
+ }
+ }
}
diff --git a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php
index 3ec8e968aa245..24775a791e59f 100644
--- a/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php
+++ b/app/code/Magento/Catalog/Model/Product/ProductFrontendAction/Synchronizer.php
@@ -16,6 +16,8 @@
use Magento\Framework\EntityManager\EntityManager;
/**
+ * A Product Widget Synchronizer.
+ *
* Service which allows to sync product widget information, such as product id with db. In order to reuse this info
* on different devices
*/
@@ -85,9 +87,10 @@ public function __construct(
}
/**
- * Find lifetime in configuration. Configuration is hold in Stores Configuration
- * Also this configuration is generated by:
- * @see \Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration
+ * Finds lifetime in configuration.
+ *
+ * Configuration is hold in Stores Configuration. Also this configuration is generated by
+ * {@see Magento\Catalog\Model\Widget\RecentlyViewedStorageConfiguration}
*
* @param string $namespace
* @return int
@@ -108,6 +111,8 @@ private function getLifeTimeByNamespace($namespace)
}
/**
+ * Filters actions.
+ *
* In order to avoid suspicious actions, we need to filter them in DESC order, and slice only items that
* can be persisted in database.
*
@@ -138,7 +143,9 @@ private function getProductIdsByActions(array $actions)
$productIds = [];
foreach ($actions as $action) {
- $productIds[] = $action['product_id'];
+ if (isset($action['product_id'])) {
+ $productIds[] = $action['product_id'];
+ }
}
return $productIds;
@@ -159,33 +166,37 @@ public function syncActions(array $productsData, $typeId)
$customerId = $this->session->getCustomerId();
$visitorId = $this->visitor->getId();
$collection = $this->getActionsByType($typeId);
- $collection->addFieldToFilter('product_id', $this->getProductIdsByActions($productsData));
-
- /**
- * Note that collection is also filtered by visitor id and customer id
- * This collection shouldn't be flushed when visitor has products and then login
- * It can remove only products for visitor, or only products for customer
- *
- * ['product_id' => 'added_at']
- * @var ProductFrontendActionInterface $item
- */
- foreach ($collection as $item) {
- $this->entityManager->delete($item);
- }
-
- foreach ($productsData as $productId => $productData) {
- /** @var ProductFrontendActionInterface $action */
- $action = $this->productFrontendActionFactory->create([
- 'data' => [
- 'visitor_id' => $customerId ? null : $visitorId,
- 'customer_id' => $this->session->getCustomerId(),
- 'added_at' => $productData['added_at'],
- 'product_id' => $productId,
- 'type_id' => $typeId
- ]
- ]);
-
- $this->entityManager->save($action);
+ $productIds = $this->getProductIdsByActions($productsData);
+
+ if ($productIds) {
+ $collection->addFieldToFilter('product_id', $productIds);
+
+ /**
+ * Note that collection is also filtered by visitor id and customer id
+ * This collection shouldn't be flushed when visitor has products and then login
+ * It can remove only products for visitor, or only products for customer
+ *
+ * ['product_id' => 'added_at']
+ * @var ProductFrontendActionInterface $item
+ */
+ foreach ($collection as $item) {
+ $this->entityManager->delete($item);
+ }
+
+ foreach ($productsData as $productId => $productData) {
+ /** @var ProductFrontendActionInterface $action */
+ $action = $this->productFrontendActionFactory->create([
+ 'data' => [
+ 'visitor_id' => $customerId ? null : $visitorId,
+ 'customer_id' => $this->session->getCustomerId(),
+ 'added_at' => $productData['added_at'],
+ 'product_id' => $productId,
+ 'type_id' => $typeId
+ ]
+ ]);
+
+ $this->entityManager->save($action);
+ }
}
}
diff --git a/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php
index f6893a41113e6..dabfdb74f0118 100644
--- a/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php
+++ b/app/code/Magento/Catalog/Model/Product/Type/FrontSpecialPrice.php
@@ -17,6 +17,9 @@
*
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ *
+ * @deprecated
+ * @see \Magento\Catalog\Model\Product\Type\Price
*/
class FrontSpecialPrice extends Price
{
@@ -66,6 +69,8 @@ public function __construct(
/**
* @inheritdoc
+ *
+ * @deprecated
*/
protected function _applySpecialPrice($product, $finalPrice)
{
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 b9e629912a5b3..3d7f863b7c0d3 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
@@ -7,6 +7,10 @@
namespace Magento\Catalog\Model\ResourceModel;
use Magento\Eav\Model\Entity\Attribute\AbstractAttribute;
+use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
+use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
+use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
+use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
/**
* Catalog entity abstract model
@@ -37,16 +41,18 @@ abstract class AbstractResource extends \Magento\Eav\Model\Entity\AbstractEntity
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param \Magento\Catalog\Model\Factory $modelFactory
* @param array $data
+ * @param UniqueValidationInterface|null $uniqueValidator
*/
public function __construct(
\Magento\Eav\Model\Entity\Context $context,
\Magento\Store\Model\StoreManagerInterface $storeManager,
\Magento\Catalog\Model\Factory $modelFactory,
- $data = []
+ $data = [],
+ UniqueValidationInterface $uniqueValidator = null
) {
$this->_storeManager = $storeManager;
$this->_modelFactory = $modelFactory;
- parent::__construct($context, $data);
+ parent::__construct($context, $data, $uniqueValidator);
}
/**
@@ -86,16 +92,14 @@ protected function _isApplicableAttribute($object, $attribute)
/**
* Check whether attribute instance (attribute, backend, frontend or source) has method and applicable
*
- * @param AbstractAttribute|\Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend
- * |\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend
- * |\Magento\Eav\Model\Entity\Attribute\Source\AbstractSource $instance
+ * @param AbstractAttribute|AbstractBackend|AbstractFrontend|AbstractSource $instance
* @param string $method
* @param array $args array of arguments
* @return boolean
*/
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();
@@ -112,6 +116,7 @@ protected function _isCallableAttributeInstance($instance, $method, $args)
/**
* Retrieve select object for loading entity attributes values
+ *
* Join attribute store value
*
* @param \Magento\Framework\DataObject $object
@@ -244,6 +249,7 @@ protected function _saveAttributeValue($object, $attribute, $value)
/**
* Check if attribute present for non default Store View.
+ *
* Prevent "delete" query locking in a case when nothing to delete
*
* @param AbstractAttribute $attribute
@@ -485,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/Eav/Attribute.php b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
index 707ebbb2964cc..23f612582f42e 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Eav/Attribute.php
@@ -236,6 +236,8 @@ public function afterSave()
) {
$this->_indexerEavProcessor->markIndexerAsInvalid();
}
+
+ $this->_source = null;
return parent::afterSave();
}
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/Product.php b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
index d71ec23881982..24174391be829 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/Product.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/Product.php
@@ -8,6 +8,7 @@
use Magento\Catalog\Model\ResourceModel\Product\Website\Link as ProductWebsiteLink;
use Magento\Framework\App\ObjectManager;
use Magento\Catalog\Model\Indexer\Category\Product\TableMaintainer;
+use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
/**
* Product entity resource model
@@ -101,6 +102,7 @@ class Product extends AbstractResource
* @param \Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes
* @param array $data
* @param TableMaintainer|null $tableMaintainer
+ * @param UniqueValidationInterface|null $uniqueValidator
*
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -115,7 +117,8 @@ public function __construct(
\Magento\Eav\Model\Entity\TypeFactory $typeFactory,
\Magento\Catalog\Model\Product\Attribute\DefaultAttributes $defaultAttributes,
$data = [],
- TableMaintainer $tableMaintainer = null
+ TableMaintainer $tableMaintainer = null,
+ UniqueValidationInterface $uniqueValidator = null
) {
$this->_categoryCollectionFactory = $categoryCollectionFactory;
$this->_catalogCategory = $catalogCategory;
@@ -127,7 +130,8 @@ public function __construct(
$context,
$storeManager,
$modelFactory,
- $data
+ $data,
+ $uniqueValidator
);
$this->connectionName = 'catalog';
$this->tableMaintainer = $tableMaintainer ?: ObjectManager::getInstance()->get(TableMaintainer::class);
@@ -289,7 +293,7 @@ protected function _afterSave(\Magento\Framework\DataObject $product)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function delete($object)
{
@@ -593,7 +597,7 @@ public function countAll()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function validate($object)
{
@@ -633,7 +637,7 @@ public function load($object, $entityId, $attributes = [])
}
/**
- * {@inheritdoc}
+ * @inheritdoc
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
* @since 101.0.0
*/
@@ -675,6 +679,8 @@ public function save(\Magento\Framework\Model\AbstractModel $object)
}
/**
+ * Retrieve entity manager object
+ *
* @return \Magento\Framework\EntityManager\EntityManager
*/
private function getEntityManager()
@@ -687,6 +693,8 @@ private function getEntityManager()
}
/**
+ * Retrieve ProductWebsiteLink object
+ *
* @deprecated 101.1.0
* @return ProductWebsiteLink
*/
@@ -696,6 +704,8 @@ private function getProductWebsiteLink()
}
/**
+ * Retrieve CategoryLink object
+ *
* @deprecated 101.1.0
* @return \Magento\Catalog\Model\ResourceModel\Product\CategoryLink
*/
@@ -710,9 +720,10 @@ private function getProductCategoryLink()
/**
* Extends parent method to be appropriate for product.
+ *
* Store id is required to correctly identify attribute value we are working with.
*
- * {@inheritdoc}
+ * @inheritdoc
* @since 101.1.0
*/
protected function getAttributeRow($entity, $object, $attribute)
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/Observer/SetSpecialPriceStartDate.php b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php
index a597b8fddda9f..ed9f89efc6891 100644
--- a/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php
+++ b/app/code/Magento/Catalog/Observer/SetSpecialPriceStartDate.php
@@ -3,9 +3,12 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Catalog\Observer;
use Magento\Framework\Event\ObserverInterface;
+use Magento\Framework\Stdlib\DateTime\TimezoneInterface;
/**
* Set value for Special Price start date
@@ -13,21 +16,20 @@
class SetSpecialPriceStartDate implements ObserverInterface
{
/**
- * @var \Magento\Framework\Stdlib\DateTime\TimezoneInterface
+ * @var TimezoneInterface
*/
private $localeDate;
/**
- * @param \Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate
- * @codeCoverageIgnore
+ * @param TimezoneInterface $localeDate
*/
- public function __construct(\Magento\Framework\Stdlib\DateTime\TimezoneInterface $localeDate)
+ public function __construct(TimezoneInterface $localeDate)
{
$this->localeDate = $localeDate;
}
/**
- * Set the current date to Special Price From attribute if it empty
+ * Set the current date to Special Price From attribute if it's empty.
*
* @param \Magento\Framework\Event\Observer $observer
* @return $this
@@ -36,8 +38,8 @@ public function execute(\Magento\Framework\Event\Observer $observer)
{
/** @var $product \Magento\Catalog\Model\Product */
$product = $observer->getEvent()->getProduct();
- if ($product->getSpecialPrice() && !$product->getSpecialFromDate()) {
- $product->setData('special_from_date', $this->localeDate->date());
+ if ($product->getSpecialPrice() && ! $product->getSpecialFromDate()) {
+ $product->setData('special_from_date', $this->localeDate->date()->setTime(0, 0));
}
return $this;
diff --git a/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php b/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php
new file mode 100644
index 0000000000000..dd750cfbc696e
--- /dev/null
+++ b/app/code/Magento/Catalog/Plugin/Model/Product/Option/UpdateProductCustomOptionsAttributes.php
@@ -0,0 +1,56 @@
+productRepository = $productRepository;
+ }
+
+ /**
+ * Update product 'has_options' and 'required_options' attributes after option save
+ *
+ * @param \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $subject
+ * @param \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option
+ *
+ * @return \Magento\Catalog\Api\Data\ProductCustomOptionInterface
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterSave(
+ \Magento\Catalog\Api\ProductCustomOptionRepositoryInterface $subject,
+ \Magento\Catalog\Api\Data\ProductCustomOptionInterface $option
+ ) {
+ $product = $this->productRepository->get($option->getProductSku());
+ if (!$product->getHasOptions() ||
+ ($option->getIsRequire() && !$product->getRequiredOptions())) {
+ $product->setCanSaveCustomOptions(true);
+ $product->setOptionsSaved(true);
+ $currentOptions = array_filter($product->getOptions(), function ($iOption) use ($option) {
+ return $option->getOptionId() != $iOption->getOptionId();
+ });
+ $currentOptions[] = $option;
+ $product->setOptions($currentOptions);
+ $product->save();
+ }
+
+ return $option;
+ }
+}
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 0d82ba3817df9..46329dde278bc 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/AdminProductAttributeActionGroup.xml
@@ -30,6 +30,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -113,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..ad32b8edbd243 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,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/CheckProductsOrderActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
new file mode 100644
index 0000000000000..f7cd2e7076288
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CheckProductsOrderActionGroup.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
index 2d966dde64c4a..b914d5e20712d 100644
--- a/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/ActionGroup/CustomOptionsActionGroup.xml
@@ -52,4 +52,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/ConstData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
index 8a26b6babdbbc..d09880f14afbf 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ConstData.xml
@@ -13,4 +13,8 @@
1
2
+
+ "Pursuit Lumaflex™ Tone Band"
+ "x™"
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductAttributeData.xml
index 03a004e500aef..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,90 @@
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
+ global
+ false
+ false
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ true
+ ProductAttributeFrontendLabel
+
attribute
text
@@ -138,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 50e3e5864f4c3..ba4a623e35def 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductData.xml
@@ -35,6 +35,13 @@
EavStockItem
CustomAttributeCategoryIds
+
+ Pursuit Lumaflex™ Tone Band
+ x™
+
+
+ 100
+
ApiProductDescription
ApiProductShortDescription
@@ -57,6 +64,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
@@ -93,6 +142,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
@@ -107,6 +181,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
@@ -146,6 +232,15 @@
EavStockItem
CustomAttributeProductAttribute
+
+ 50
+
+
+ 60
+
+
+ 70
+
api-simple-product-two
simple
@@ -337,6 +432,11 @@
ProductOptionDropDownWithLongValuesTitle
+
+
+ ProductOptionField
+ ProductOptionArea
+
api-virtual-product
virtual
@@ -696,6 +796,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
@@ -717,4 +940,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/ProductGridData.xml b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
index ea0bcafe56c48..71c8af318e9b4 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Data/ProductGridData.xml
@@ -12,4 +12,7 @@
10
100
+
+ 1
+
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/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml b/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml
similarity index 63%
rename from app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml
rename to app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml
index 2a14f814eac16..e23a503266e33 100644
--- a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontCreateNewReturnPage.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Page/AdminNewWidgetPage.xml
@@ -8,7 +8,7 @@
-
-
+
+
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/AdminCategorySidebarTreeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
index ef6fb99e88eed..fba28b3feaff1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminCategorySidebarTreeSection.xml
@@ -13,8 +13,9 @@
-
+
+
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 05be20b14acc0..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,9 +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/AdminProductCustomizableOptionsSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
index fc78c25ec49fa..352d219351fb8 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductCustomizableOptionsSection.xml
@@ -45,5 +45,15 @@
+
-
\ No newline at end of file
+
+
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/AdminProductFormAdvancedPricingSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
index 0a1804aa284dc..697648cedb7ba 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAdvancedPricingSection.xml
@@ -20,6 +20,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml
new file mode 100644
index 0000000000000..e159a4ce5c0b6
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormAttributeSection.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductFormSection.xml
index 337a3dd53f593..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/AdminProductGridSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
index 02bdbac313076..07dd26381fe08 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/AdminProductGridSection.xml
@@ -17,6 +17,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 178e58ef2d649..51b5a0242d976 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontCategoryProductSection.xml
@@ -16,6 +16,7 @@
+
@@ -31,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/StorefrontNavigationSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
index c6bad0efb3ca7..292b2d7008bc1 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontNavigationSection.xml
@@ -12,5 +12,6 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml b/app/code/Magento/Catalog/Test/Mftf/Section/StorefrontProductInfoMainSection.xml
index 6a4ac0d7683c7..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 @@
-
+
+
@@ -31,6 +32,8 @@
+
+
@@ -72,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..bcfab6ccfdf1f
--- /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..a3f543e9cf32a
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateCustomProductAttributeWithDropdownFieldTest.xml
@@ -0,0 +1,142 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/AdminCreateNewAttributeFromProductTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml
new file mode 100644
index 0000000000000..282331924bca3
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminCreateNewAttributeFromProductTest.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/AdminFilterByNameByStoreViewOnProductGridTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml
new file mode 100644
index 0000000000000..f3ec225540c75
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminFilterByNameByStoreViewOnProductGridTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml
new file mode 100644
index 0000000000000..b24ed7f9c9a81
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminGridPageNumberAfterSaveAndCloseActionTest.xml
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml
new file mode 100644
index 0000000000000..79ff7bcade77b
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminImportCustomizableOptionToProductWithSKUTest.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/AdminProductImageAssignmentForMultipleStoresTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml
index 37fbf01a6b9aa..8149bc34087fb 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminProductImageAssignmentForMultipleStoresTest.xml
@@ -109,6 +109,7 @@
+
@@ -120,6 +121,7 @@
+
@@ -131,6 +133,7 @@
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml
index 060720ab007eb..8316f54c15a52 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminRemoveImageAffectsAllScopesTest.xml
@@ -65,6 +65,8 @@
+
+
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/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml
index 3086f4398e08d..51af9d78dfd46 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminTierPriceNotAvailableForProductOptionsWithoutTierPriceTest.xml
@@ -11,8 +11,8 @@
-
-
+
+
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..d895993217e32
--- /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/Block/Product/View/GalleryOptionsTest.php b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php
new file mode 100644
index 0000000000000..7ed8b13fce750
--- /dev/null
+++ b/app/code/Magento/Catalog/Test/Unit/Block/Product/View/GalleryOptionsTest.php
@@ -0,0 +1,223 @@
+escaper = $objectManager->getObject(Escaper::class);
+ $this->configView = $this->createMock(View::class);
+
+ $this->viewConfig = $this->createConfiguredMock(
+ Config::class,
+ [
+ 'getViewConfig' => $this->configView
+ ]
+ );
+
+ $this->context = $this->createConfiguredMock(
+ Context::class,
+ [
+ 'getEscaper' => $this->escaper,
+ 'getViewConfig' => $this->viewConfig
+ ]
+ );
+
+ $this->gallery = $this->createMock(Gallery::class);
+
+ $this->jsonSerializer = $objectManager->getObject(
+ Json::class
+ );
+
+ $this->model = $objectManager->getObject(GalleryOptions::class, [
+ 'context' => $this->context,
+ 'jsonSerializer' => $this->jsonSerializer,
+ 'gallery' => $this->gallery
+ ]);
+ }
+
+ public function testGetOptionsJson()
+ {
+ $configMap = [
+ ['Magento_Catalog', 'gallery/nav', 'thumbs'],
+ ['Magento_Catalog', 'gallery/loop', false],
+ ['Magento_Catalog', 'gallery/keyboard', true],
+ ['Magento_Catalog', 'gallery/arrows', true],
+ ['Magento_Catalog', 'gallery/caption', false],
+ ['Magento_Catalog', 'gallery/allowfullscreen', true],
+ ['Magento_Catalog', 'gallery/navdir', 'horizontal'],
+ ['Magento_Catalog', 'gallery/navarrows', true],
+ ['Magento_Catalog', 'gallery/navtype', 'slides'],
+ ['Magento_Catalog', 'gallery/thumbmargin', '5'],
+ ['Magento_Catalog', 'gallery/transition/effect', 'slide'],
+ ['Magento_Catalog', 'gallery/transition/duration', '500'],
+ ];
+
+ $imageAttributesMap = [
+ ['product_page_image_medium','height',null, 100],
+ ['product_page_image_medium','width',null, 200],
+ ['product_page_image_small','height',null, 300],
+ ['product_page_image_small','width',null, 400]
+ ];
+
+ $this->configView->expects($this->any())
+ ->method('getVarValue')
+ ->will($this->returnValueMap($configMap));
+ $this->gallery->expects($this->any())
+ ->method('getImageAttribute')
+ ->will($this->returnValueMap($imageAttributesMap));
+
+ $json = $this->model->getOptionsJson();
+
+ $decodedJson = $this->jsonSerializer->unserialize($json);
+
+ $this->assertSame('thumbs', $decodedJson['nav']);
+ $this->assertSame(false, $decodedJson['loop']);
+ $this->assertSame(true, $decodedJson['keyboard']);
+ $this->assertSame(true, $decodedJson['arrows']);
+ $this->assertSame(false, $decodedJson['showCaption']);
+ $this->assertSame(true, $decodedJson['allowfullscreen']);
+ $this->assertSame('horizontal', $decodedJson['navdir']);
+ $this->assertSame(true, $decodedJson['navarrows']);
+ $this->assertSame('slides', $decodedJson['navtype']);
+ $this->assertSame(5, $decodedJson['thumbmargin']);
+ $this->assertSame('slide', $decodedJson['transition']);
+ $this->assertSame(500, $decodedJson['transitionduration']);
+ $this->assertSame(100, $decodedJson['height']);
+ $this->assertSame(200, $decodedJson['width']);
+ $this->assertSame(300, $decodedJson['thumbheight']);
+ $this->assertSame(400, $decodedJson['thumbwidth']);
+ }
+
+ public function testGetFSOptionsJson()
+ {
+ $configMap = [
+ ['Magento_Catalog', 'gallery/fullscreen/nav', false],
+ ['Magento_Catalog', 'gallery/fullscreen/loop', true],
+ ['Magento_Catalog', 'gallery/fullscreen/keyboard', true],
+ ['Magento_Catalog', 'gallery/fullscreen/arrows', false],
+ ['Magento_Catalog', 'gallery/fullscreen/caption', true],
+ ['Magento_Catalog', 'gallery/fullscreen/navdir', 'vertical'],
+ ['Magento_Catalog', 'gallery/fullscreen/navarrows', false],
+ ['Magento_Catalog', 'gallery/fullscreen/navtype', 'thumbs'],
+ ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', '10'],
+ ['Magento_Catalog', 'gallery/fullscreen/transition/effect', 'dissolve'],
+ ['Magento_Catalog', 'gallery/fullscreen/transition/duration', '300']
+ ];
+
+ $this->configView->expects($this->any())
+ ->method('getVarValue')
+ ->will($this->returnValueMap($configMap));
+
+ $json = $this->model->getFSOptionsJson();
+
+ $decodedJson = $this->jsonSerializer->unserialize($json);
+
+ //Note, this tests the special case for nav variable set to false. It
+ //Should not be converted to boolean.
+ $this->assertSame('false', $decodedJson['nav']);
+ $this->assertSame(true, $decodedJson['loop']);
+ $this->assertSame(false, $decodedJson['arrows']);
+ $this->assertSame(true, $decodedJson['keyboard']);
+ $this->assertSame(true, $decodedJson['showCaption']);
+ $this->assertSame('vertical', $decodedJson['navdir']);
+ $this->assertSame(false, $decodedJson['navarrows']);
+ $this->assertSame(10, $decodedJson['thumbmargin']);
+ $this->assertSame('thumbs', $decodedJson['navtype']);
+ $this->assertSame('dissolve', $decodedJson['transition']);
+ $this->assertSame(300, $decodedJson['transitionduration']);
+ }
+
+ public function testGetOptionsJsonOptionals()
+ {
+ $configMap = [
+ ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', false],
+ ['Magento_Catalog', 'gallery/fullscreen/transition/duration', false]
+ ];
+
+ $this->configView->expects($this->any())
+ ->method('getVarValue')
+ ->will($this->returnValueMap($configMap));
+
+ $json = $this->model->getOptionsJson();
+
+ $decodedJson = $this->jsonSerializer->unserialize($json);
+
+ $this->assertArrayNotHasKey('thumbmargin', $decodedJson);
+ $this->assertArrayNotHasKey('transitionduration', $decodedJson);
+ }
+
+ public function testGetFSOptionsJsonOptionals()
+ {
+ $configMap = [
+ ['Magento_Catalog', 'gallery/fullscreen/keyboard', false],
+ ['Magento_Catalog', 'gallery/fullscreen/thumbmargin', false],
+ ['Magento_Catalog', 'gallery/fullscreen/transition/duration', false]
+ ];
+
+ $this->configView->expects($this->any())
+ ->method('getVarValue')
+ ->will($this->returnValueMap($configMap));
+
+ $json = $this->model->getFSOptionsJson();
+
+ $decodedJson = $this->jsonSerializer->unserialize($json);
+
+ $this->assertArrayNotHasKey('thumbmargin', $decodedJson);
+ $this->assertArrayNotHasKey('keyboard', $decodedJson);
+ $this->assertArrayNotHasKey('transitionduration', $decodedJson);
+ }
+}
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/Controller/Adminhtml/Product/Attribute/SaveTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php
index ced65b2d2e15d..30d3503e4640e 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/SaveTest.php
@@ -7,6 +7,7 @@
use Magento\Catalog\Api\Data\ProductAttributeInterface;
use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Save;
+use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator;
use Magento\Framework\Serialize\Serializer\FormData;
use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest;
use Magento\Catalog\Model\Product\AttributeSet\BuildFactory;
@@ -94,6 +95,11 @@ class SaveTest extends AttributeTest
*/
private $productAttributeMock;
+ /**
+ * @var AttributeCodeValidator|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $attributeCodeValidatorMock;
+
protected function setUp()
{
parent::setUp();
@@ -138,6 +144,9 @@ protected function setUp()
$this->formDataSerializerMock = $this->getMockBuilder(FormData::class)
->disableOriginalConstructor()
->getMock();
+ $this->attributeCodeValidatorMock = $this->getMockBuilder(AttributeCodeValidator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->productAttributeMock = $this->getMockBuilder(ProductAttributeInterface::class)
->setMethods(['getId', 'get'])
->getMockForAbstractClass();
@@ -171,6 +180,7 @@ protected function getModel()
'groupCollectionFactory' => $this->groupCollectionFactoryMock,
'layoutFactory' => $this->layoutFactoryMock,
'formDataSerializer' => $this->formDataSerializerMock,
+ 'attributeCodeValidator' => $this->attributeCodeValidatorMock
]);
}
@@ -224,6 +234,10 @@ public function testExecute()
$this->productAttributeMock
->method('getAttributeCode')
->willReturn('test_code');
+ $this->attributeCodeValidatorMock
+ ->method('isValid')
+ ->with('test_code')
+ ->willReturn(true);
$this->requestMock->expects($this->once())
->method('getPostValue')
->willReturn($data);
diff --git a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php
index c6210f93e1290..742148b1bf7f1 100644
--- a/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Controller/Adminhtml/Product/Attribute/ValidateTest.php
@@ -6,6 +6,7 @@
namespace Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\Attribute;
use Magento\Catalog\Controller\Adminhtml\Product\Attribute\Validate;
+use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator;
use Magento\Framework\Serialize\Serializer\FormData;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
use Magento\Catalog\Test\Unit\Controller\Adminhtml\Product\AttributeTest;
@@ -67,6 +68,11 @@ class ValidateTest extends AttributeTest
*/
private $formDataSerializerMock;
+ /**
+ * @var AttributeCodeValidator|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $attributeCodeValidatorMock;
+
protected function setUp()
{
parent::setUp();
@@ -95,6 +101,9 @@ protected function setUp()
$this->formDataSerializerMock = $this->getMockBuilder(FormData::class)
->disableOriginalConstructor()
->getMock();
+ $this->attributeCodeValidatorMock = $this->getMockBuilder(AttributeCodeValidator::class)
+ ->disableOriginalConstructor()
+ ->getMock();
$this->contextMock->expects($this->any())
->method('getObjectManager')
@@ -117,6 +126,7 @@ protected function getModel()
'layoutFactory' => $this->layoutFactoryMock,
'multipleAttributeList' => ['select' => 'option'],
'formDataSerializer' => $this->formDataSerializerMock,
+ 'attributeCodeValidator' => $this->attributeCodeValidatorMock,
]
);
}
@@ -141,6 +151,12 @@ public function testExecute()
$this->attributeMock->expects($this->once())
->method('loadByCode')
->willReturnSelf();
+
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('isValid')
+ ->with('test_attribute_code')
+ ->willReturn(true);
+
$this->requestMock->expects($this->once())
->method('has')
->with('new_attribute_set_name')
@@ -190,6 +206,11 @@ public function testUniqueValidation(array $options, $isError)
->with($serializedOptions)
->willReturn($options);
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('isValid')
+ ->with('test_attribute_code')
+ ->willReturn(true);
+
$this->objectManagerMock->expects($this->once())
->method('create')
->willReturn($this->attributeMock);
@@ -333,6 +354,11 @@ public function testEmptyOption(array $options, $result)
->method('loadByCode')
->willReturnSelf();
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('isValid')
+ ->with('test_attribute_code')
+ ->willReturn(true);
+
$this->resultJsonFactoryMock->expects($this->once())
->method('create')
->willReturn($this->resultJson);
@@ -444,6 +470,10 @@ public function testExecuteWithOptionsDataError()
[\Magento\Eav\Model\Entity\Attribute\Set::class, [], $this->attributeSetMock]
]);
+ $this->attributeCodeValidatorMock
+ ->method('isValid')
+ ->willReturn(true);
+
$this->attributeMock
->method('loadByCode')
->willReturnSelf();
@@ -463,4 +493,81 @@ public function testExecuteWithOptionsDataError()
$this->getModel()->execute();
}
+
+ /**
+ * Test execute with an invalid attribute code
+ *
+ * @dataProvider provideInvalidAttributeCodes
+ * @param string $attributeCode
+ * @param $result
+ * @throws \Magento\Framework\Exception\NotFoundException
+ */
+ public function testExecuteWithInvalidAttributeCode($attributeCode, $result)
+ {
+ $serializedOptions = '{"key":"value"}';
+ $this->requestMock->expects($this->any())
+ ->method('getParam')
+ ->willReturnMap([
+ ['frontend_label', null, null],
+ ['frontend_input', 'select', 'multipleselect'],
+ ['attribute_code', null, $attributeCode],
+ ['new_attribute_set_name', null, 'test_attribute_set_name'],
+ ['message_key', Validate::DEFAULT_MESSAGE_KEY, 'message'],
+ ['serialized_options', '[]', $serializedOptions],
+ ]);
+
+ $this->formDataSerializerMock
+ ->expects($this->once())
+ ->method('unserialize')
+ ->with($serializedOptions)
+ ->willReturn(["key" => "value"]);
+
+ $this->objectManagerMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->attributeMock);
+
+ $this->attributeMock->expects($this->once())
+ ->method('loadByCode')
+ ->willReturnSelf();
+
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('isValid')
+ ->with($attributeCode)
+ ->willReturn(false);
+
+ $this->attributeCodeValidatorMock->expects($this->once())
+ ->method('getMessages')
+ ->willReturn(['Invalid Attribute Code.']);
+
+ $this->resultJsonFactoryMock->expects($this->once())
+ ->method('create')
+ ->willReturn($this->resultJson);
+
+ $this->resultJson->expects($this->once())
+ ->method('setJsonData')
+ ->willReturnArgument(0);
+
+ $response = $this->getModel()->execute();
+ $responseObject = json_decode($response);
+
+ $this->assertEquals($responseObject, $result);
+ }
+
+ /**
+ * Providing invalid attribute codes
+ *
+ * @return array
+ */
+ public function provideInvalidAttributeCodes()
+ {
+ return [
+ 'invalid attribute code' => [
+ '.attribute_code',
+ (object) [
+ 'error' => true,
+ 'message' => 'Invalid Attribute Code.',
+ ]
+ ]
+ ];
+ }
}
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/Config/CatalogClone/Media/ImageTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php
index 5b1d3bf7943fc..23f0aec5b69a2 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Config/CatalogClone/Media/ImageTest.php
@@ -9,6 +9,11 @@
use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+/**
+ * Tests \Magento\Catalog\Model\Config\CatalogClone\Media\Image.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ */
class ImageTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -36,6 +41,14 @@ class ImageTest extends \PHPUnit\Framework\TestCase
*/
private $attribute;
+ /**
+ * @var \Magento\Framework\Escaper|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $escaperMock;
+
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->eavConfig = $this->getMockBuilder(\Magento\Eav\Model\Config::class)
@@ -62,54 +75,79 @@ protected function setUp()
->disableOriginalConstructor()
->getMock();
+ $this->escaperMock = $this->getMockBuilder(
+ \Magento\Framework\Escaper::class
+ )
+ ->disableOriginalConstructor()
+ ->setMethods(['escapeHtml'])
+ ->getMock();
+
$helper = new ObjectManager($this);
$this->model = $helper->getObject(
\Magento\Catalog\Model\Config\CatalogClone\Media\Image::class,
[
'eavConfig' => $this->eavConfig,
- 'attributeCollectionFactory' => $this->attributeCollectionFactory
+ 'attributeCollectionFactory' => $this->attributeCollectionFactory,
+ 'escaper' => $this->escaperMock,
]
);
}
- public function testGetPrefixes()
+ /**
+ * @param string $actualLabel
+ * @param string $expectedLabel
+ * @return void
+ *
+ * @dataProvider getPrefixesDataProvider
+ */
+ public function testGetPrefixes(string $actualLabel, string $expectedLabel): void
{
$entityTypeId = 3;
/** @var \Magento\Eav\Model\Entity\Type|\PHPUnit_Framework_MockObject_MockObject $entityType */
$entityType = $this->getMockBuilder(\Magento\Eav\Model\Entity\Type::class)
->disableOriginalConstructor()
->getMock();
- $entityType->expects($this->once())->method('getId')->will($this->returnValue($entityTypeId));
+ $entityType->expects($this->once())->method('getId')->willReturn($entityTypeId);
/** @var AbstractFrontend|\PHPUnit_Framework_MockObject_MockObject $frontend */
$frontend = $this->getMockBuilder(\Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend::class)
->setMethods(['getLabel'])
->disableOriginalConstructor()
->getMockForAbstractClass();
- $frontend->expects($this->once())->method('getLabel')->will($this->returnValue('testLabel'));
+ $frontend->expects($this->once())->method('getLabel')->willReturn($actualLabel);
- $this->attributeCollection->expects($this->once())->method('setEntityTypeFilter')->with(
- $this->equalTo($entityTypeId)
- );
- $this->attributeCollection->expects($this->once())->method('setFrontendInputTypeFilter')->with(
- $this->equalTo('media_image')
- );
+ $this->attributeCollection->expects($this->once())->method('setEntityTypeFilter')->with($entityTypeId);
+ $this->attributeCollection->expects($this->once())->method('setFrontendInputTypeFilter')->with('media_image');
- $this->attribute->expects($this->once())->method('getAttributeCode')->will(
- $this->returnValue('attributeCode')
- );
- $this->attribute->expects($this->once())->method('getFrontend')->will(
- $this->returnValue($frontend)
- );
+ $this->attribute->expects($this->once())->method('getAttributeCode')->willReturn('attributeCode');
+ $this->attribute->expects($this->once())->method('getFrontend')->willReturn($frontend);
- $this->attributeCollection->expects($this->any())->method('getIterator')->will(
- $this->returnValue(new \ArrayIterator([$this->attribute]))
- );
+ $this->attributeCollection->expects($this->any())->method('getIterator')
+ ->willReturn(new \ArrayIterator([$this->attribute]));
+
+ $this->eavConfig->expects($this->any())->method('getEntityType')->with(Product::ENTITY)
+ ->willReturn($entityType);
- $this->eavConfig->expects($this->any())->method('getEntityType')->with(
- $this->equalTo(Product::ENTITY)
- )->will($this->returnValue($entityType));
+ $this->escaperMock->expects($this->once())->method('escapeHtml')->with($actualLabel)
+ ->willReturn($expectedLabel);
- $this->assertEquals([['field' => 'attributeCode_', 'label' => 'testLabel']], $this->model->getPrefixes());
+ $this->assertEquals([['field' => 'attributeCode_', 'label' => $expectedLabel]], $this->model->getPrefixes());
+ }
+
+ /**
+ * @return array
+ */
+ public function getPrefixesDataProvider(): array
+ {
+ return [
+ [
+ 'actual_label' => 'testLabel',
+ 'expected_label' => 'testLabel',
+ ],
+ [
+ 'actual_label' => '
'<media-image-attributelabel',
+ ],
+ ];
}
}
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/Product/ProductFrontendAction/SynchronizerTest.php b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php
index fce4a02622d9e..38bed83cb9504 100644
--- a/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php
+++ b/app/code/Magento/Catalog/Test/Unit/Model/Product/ProductFrontendAction/SynchronizerTest.php
@@ -80,6 +80,7 @@ protected function setUp()
public function testFilterProductActions()
{
+ $typeId = 'recently_compared_product';
$productsData = [
1 => [
'added_at' => 12,
@@ -87,7 +88,7 @@ public function testFilterProductActions()
],
2 => [
'added_at' => 13,
- 'product_id' => 2,
+ 'product_id' => '2',
],
3 => [
'added_at' => 14,
@@ -126,10 +127,12 @@ public function testFilterProductActions()
$collection->expects($this->once())
->method('addFilterByUserIdentities')
->with(1, 34);
- $collection->expects($this->any())
+ $collection->expects($this->at(1))
->method('addFieldToFilter')
- ->withConsecutive(['type_id'], ['product_id']);
-
+ ->with('type_id', $typeId);
+ $collection->expects($this->at(2))
+ ->method('addFieldToFilter')
+ ->with('product_id', [1, 2]);
$iterator = new \IteratorIterator(new \ArrayIterator([$frontendAction]));
$collection->expects($this->once())
->method('getIterator')
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 86f1db2022cc9..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
@@ -11,6 +11,7 @@
use Magento\Catalog\Model\Config\Source\Product\Options\Price as ProductOptionsPrice;
use Magento\Framework\UrlInterface;
use Magento\Framework\Stdlib\ArrayManager;
+use Magento\Ui\Component\Form\Element\Hidden;
use Magento\Ui\Component\Modal;
use Magento\Ui\Component\Container;
use Magento\Ui\Component\DynamicRows;
@@ -867,7 +868,7 @@ protected function getPositionFieldConfig($sortOrder)
'data' => [
'config' => [
'componentType' => Field::NAME,
- 'formElement' => Input::NAME,
+ 'formElement' => Hidden::NAME,
'dataScope' => static::FIELD_SORT_ORDER_NAME,
'dataType' => Number::NAME,
'visible' => false,
diff --git a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
old mode 100755
new mode 100644
index 99f7122efa0a8..8326c3b531892
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/Form/Modifier/Eav.php
@@ -676,7 +676,7 @@ public function setupAttributeMeta(ProductAttributeInterface $attribute, $groupC
// TODO: Refactor to $attribute->getOptions() when MAGETWO-48289 is done
$attributeModel = $this->getAttributeModel($attribute);
if ($attributeModel->usesSource()) {
- $options = $attributeModel->getSource()->getAllOptions();
+ $options = $attributeModel->getSource()->getAllOptions(true, true);
$meta = $this->arrayManager->merge($configPath, $meta, [
'options' => $this->convertOptionsValueToString($options),
]);
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/ProductCollection.php b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
index f4334bc25efd8..e5451c8e49847 100644
--- a/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
+++ b/app/code/Magento/Catalog/Ui/DataProvider/Product/ProductCollection.php
@@ -5,10 +5,16 @@
*/
namespace Magento\Catalog\Ui\DataProvider\Product;
+use Magento\Catalog\Model\ResourceModel\Eav\Attribute;
+use Magento\Framework\Exception\LocalizedException;
+use Magento\Eav\Model\Entity\Attribute\AttributeInterface;
+
/**
* Collection which is used for rendering product list in the backend.
*
* Used for product grid and customizes behavior of the default Product collection for grid needs.
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class ProductCollection extends \Magento\Catalog\Model\ResourceModel\Product\Collection
{
@@ -25,4 +31,63 @@ protected function _productLimitationJoinPrice()
$this->_productLimitationFilters->setUsePriceIndex(false);
return $this->_productLimitationPrice(true);
}
+
+ /**
+ * Add attribute filter to collection
+ *
+ * @param AttributeInterface|integer|string|array $attribute
+ * @param null|string|array $condition
+ * @param string $joinType
+ * @return $this
+ * @throws LocalizedException
+ */
+ public function addAttributeToFilter($attribute, $condition = null, $joinType = 'inner')
+ {
+ $storeId = (int)$this->getStoreId();
+ if ($attribute === 'is_saleable'
+ || is_array($attribute)
+ || $storeId !== $this->getDefaultStoreId()
+ ) {
+ return parent::addAttributeToFilter($attribute, $condition, $joinType);
+ }
+
+ if ($attribute instanceof AttributeInterface) {
+ $attributeModel = $attribute;
+ } else {
+ $attributeModel = $this->getEntity()->getAttribute($attribute);
+ if ($attributeModel === false) {
+ throw new LocalizedException(
+ __('Invalid attribute identifier for filter (%1)', get_class($attribute))
+ );
+ }
+ }
+
+ if ($attributeModel->isScopeGlobal() || $attributeModel->getBackend()->isStatic()) {
+ return parent::addAttributeToFilter($attribute, $condition, $joinType);
+ }
+
+ $this->addAttributeToFilterAllStores($attributeModel, $condition);
+
+ return $this;
+ }
+
+ /**
+ * Add attribute to filter by all stores
+ *
+ * @param Attribute $attributeModel
+ * @param array $condition
+ * @return void
+ */
+ private function addAttributeToFilterAllStores(Attribute $attributeModel, array $condition): void
+ {
+ $tableName = $this->getTable($attributeModel->getBackendTable());
+ $entity = $this->getEntity();
+ $fKey = 'e.' . $this->getEntityPkName($entity);
+ $pKey = $tableName . '.' . $this->getEntityPkName($entity);
+ $condition = "({$pKey} = {$fKey}) AND ("
+ . $this->_getConditionSql("{$tableName}.value", $condition)
+ . ')';
+ $selectExistsInAllStores = $this->getConnection()->select()->from($tableName);
+ $this->getSelect()->exists($selectExistsInAllStores, $condition);
+ }
}
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/adminhtml/system.xml b/app/code/Magento/Catalog/etc/adminhtml/system.xml
index 7a05601fcd666..1d563244f1432 100644
--- a/app/code/Magento/Catalog/etc/adminhtml/system.xml
+++ b/app/code/Magento/Catalog/etc/adminhtml/system.xml
@@ -59,7 +59,7 @@
Products per Page on Grid Allowed Values
Comma-separated.
- validate-per-page-value-list
+ validate-per-page-value-list required-entry
Products per Page on Grid Default Value
@@ -69,7 +69,7 @@
Products per Page on List Allowed Values
Comma-separated.
- validate-per-page-value-list
+ validate-per-page-value-list required-entry
Products per Page on List Default Value
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/frontend/di.xml b/app/code/Magento/Catalog/etc/frontend/di.xml
index 793a2291f599c..ee9c5b29da894 100644
--- a/app/code/Magento/Catalog/etc/frontend/di.xml
+++ b/app/code/Magento/Catalog/etc/frontend/di.xml
@@ -120,5 +120,4 @@
-
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/etc/webapi_rest/di.xml b/app/code/Magento/Catalog/etc/webapi_rest/di.xml
index 2a5d60222e9f8..44cdd473bf74e 100644
--- a/app/code/Magento/Catalog/etc/webapi_rest/di.xml
+++ b/app/code/Magento/Catalog/etc/webapi_rest/di.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/app/code/Magento/Catalog/etc/webapi_soap/di.xml b/app/code/Magento/Catalog/etc/webapi_soap/di.xml
index 2a5d60222e9f8..44cdd473bf74e 100644
--- a/app/code/Magento/Catalog/etc/webapi_soap/di.xml
+++ b/app/code/Magento/Catalog/etc/webapi_soap/di.xml
@@ -19,4 +19,7 @@
+
+
+
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
index 00a1580923a7b..ee67acd0ebd46 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/checkboxes/tree.phtml
@@ -20,7 +20,7 @@
"categoryCheckboxTree": {
"dataUrl": "= $block->escapeUrl($block->getLoadTreeUrl()) ?>",
"divId": "= /* @noEscape */ $divId ?>",
- "rootVisible": = /* @noEscape */ $block->getRoot()->getIsVisible() ? 'true' : 'false' ?>,
+ "rootVisible": false,
"useAjax": = $block->escapeHtml($block->getUseAjax()) ?>,
"currentNodeId": = (int)$block->getCategoryId() ?>,
"jsFormObject": "= /* @noEscape */ $block->getJsFormObject() ?>",
@@ -28,7 +28,7 @@
"checked": "= $block->escapeHtml($block->getRoot()->getChecked()) ?>",
"allowdDrop": = /* @noEscape */ $block->getRoot()->getIsVisible() ? 'true' : 'false' ?>,
"rootId": = (int)$block->getRoot()->getId() ?>,
- "expanded": = (int)$block->getIsWasExpanded() ?>,
+ "expanded": true,
"categoryId": = (int)$block->getCategoryId() ?>,
"treeJson": = /* @noEscape */ $block->getTreeJson() ?>
}
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
index 93666470b1b2c..f448edc692ce2 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/tree.phtml
@@ -302,6 +302,7 @@
}
//updateContent(url); //commented since ajax requests replaced with http ones to load a category
+ jQuery('#tree-div').find('.x-tree-node-el').first().remove();
}
jQuery(function () {
diff --git a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
index dbe66ef1aecd3..69737b8a37c1c 100644
--- a/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
+++ b/app/code/Magento/Catalog/view/adminhtml/templates/catalog/category/widget/tree.phtml
@@ -160,7 +160,7 @@ jQuery(function()
loader: categoryLoader,
enableDD: false,
containerScroll: true,
- rootVisible: '= /* @escapeNotVerified */ $block->getRoot()->getIsVisible() ?>',
+ rootVisible: false,
useAjax: true,
currentNodeId: = (int) $block->getCategoryId() ?>,
addNodeTo: false
@@ -177,7 +177,7 @@ jQuery(function()
text: 'Psw',
draggable: false,
id: = (int) $block->getRoot()->getId() ?>,
- expanded: = (int) $block->getIsWasExpanded() ?>,
+ expanded: true,
category_id: = (int) $block->getCategoryId() ?>
};
diff --git a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
index 65090fa3ac461..d689daef4bcab 100644
--- a/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
+++ b/app/code/Magento/Catalog/view/adminhtml/ui_component/product_listing.xml
@@ -132,6 +132,7 @@
true
text
+ ui/grid/cells/html
Name
@@ -154,6 +155,7 @@
text
+ ui/grid/cells/html
SKU
@@ -190,6 +192,13 @@
Websites
+
+
+ true
+ textRange
+ Cost
+
+
entity_id
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/base/templates/product/composite/fieldset/options/view/checkable.phtml b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml
new file mode 100644
index 0000000000000..0f3b4f481a288
--- /dev/null
+++ b/app/code/Magento/Catalog/view/base/templates/product/composite/fieldset/options/view/checkable.phtml
@@ -0,0 +1,100 @@
+getOption();
+if ($option) : ?>
+ getPreconfiguredValue($option);
+ $optionType = $option->getType();
+ $arraySign = $optionType === Option::OPTION_TYPE_CHECKBOX ? '[]' : '';
+ $count = 1;
+ ?>
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml
index 8d3248896b434..41a2a6142d506 100644
--- a/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml
+++ b/app/code/Magento/Catalog/view/frontend/layout/catalog_product_view.xml
@@ -121,7 +121,12 @@
-
+
+
+ Magento\Catalog\Block\Product\View\GalleryOptions
+ Magento\Catalog\Helper\Image
+
+
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/gallery.phtml b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml
index 1bfa30478df8a..1f06b90758d0b 100644
--- a/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml
+++ b/app/code/Magento/Catalog/view/frontend/templates/product/view/gallery.phtml
@@ -12,32 +12,32 @@
* @var $block \Magento\Catalog\Block\Product\View\Gallery
*/
?>
-
-
-
-
-
-
-
-
-
+ $helper = $block->getData('imageHelper');
+ $mainImageData = $mainImage ?
+ $mainImage->getData('medium_image_url') :
+ $helper->getDefaultPlaceholderUrl('image');
+
+?>
+
+
+
+
+
\ 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/checkout-data-resolver.js b/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js
index 9cc60a3645d58..e54f464f24d02 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/model/checkout-data-resolver.js
@@ -243,7 +243,7 @@ define([
return;
}
- if (quote.isVirtual()) {
+ if (quote.isVirtual() || !quote.billingAddress()) {
isBillingAddressInitialized = addressList.some(function (addrs) {
if (addrs.isDefaultBilling()) {
selectBillingAddress(addrs);
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/js/view/minicart.js b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js
index a2f8c8c56ff33..5e29fa209a641 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/minicart.js
@@ -81,6 +81,7 @@ define([
maxItemsToDisplay: window.checkout.maxItemsToDisplay,
cart: {},
+ // jscs:disable requireCamelCaseOrUpperCaseIdentifiers
/**
* @override
*/
@@ -101,12 +102,16 @@ define([
self.isLoading(true);
});
- if (cartData()['website_id'] !== window.checkout.websiteId) {
+ if (cartData().website_id !== window.checkout.websiteId ||
+ cartData().store_id !== window.checkout.storeId
+ ) {
customerData.reload(['cart'], false);
}
return this._super();
},
+ //jscs:enable requireCamelCaseOrUpperCaseIdentifiers
+
isLoading: ko.observable(false),
initSidebar: initSidebar,
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/Checkout/view/frontend/web/template/minicart/item/default.html b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html
index 357b0e550af0f..41d442a76d510 100644
--- a/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html
+++ b/app/code/Magento/Checkout/view/frontend/web/template/minicart/item/default.html
@@ -45,7 +45,7 @@
-
+
diff --git a/app/code/Magento/CheckoutAgreements/Model/AgreementsConfigProvider.php b/app/code/Magento/CheckoutAgreements/Model/AgreementsConfigProvider.php
index 1f1b5be9683ed..1217270d780e1 100644
--- a/app/code/Magento/CheckoutAgreements/Model/AgreementsConfigProvider.php
+++ b/app/code/Magento/CheckoutAgreements/Model/AgreementsConfigProvider.php
@@ -67,17 +67,18 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getConfig()
{
$agreements = [];
$agreements['checkoutAgreements'] = $this->getAgreementsConfig();
+
return $agreements;
}
/**
- * Returns agreements config
+ * Returns agreements config.
*
* @return array
*/
@@ -99,7 +100,7 @@ protected function getAgreementsConfig()
'content' => $agreement->getIsHtml()
? $agreement->getContent()
: nl2br($this->escaper->escapeHtml($agreement->getContent())),
- 'checkboxText' => $agreement->getCheckboxText(),
+ 'checkboxText' => $this->escaper->escapeHtml($agreement->getCheckboxText()),
'mode' => $agreement->getMode(),
'agreementId' => $agreement->getAgreementId()
];
diff --git a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/AgreementsConfigProviderTest.php b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/AgreementsConfigProviderTest.php
index c59a3d2433ec2..c8309bacb0a86 100644
--- a/app/code/Magento/CheckoutAgreements/Test/Unit/Model/AgreementsConfigProviderTest.php
+++ b/app/code/Magento/CheckoutAgreements/Test/Unit/Model/AgreementsConfigProviderTest.php
@@ -8,6 +8,9 @@
use Magento\CheckoutAgreements\Model\AgreementsProvider;
use Magento\Store\Model\ScopeInterface;
+/**
+ * Tests for AgreementsConfigProvider.
+ */
class AgreementsConfigProviderTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -35,6 +38,9 @@ class AgreementsConfigProviderTest extends \PHPUnit\Framework\TestCase
*/
private $agreementsFilterMock;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
@@ -59,10 +65,16 @@ protected function setUp()
);
}
+ /**
+ * Test for getConfig if content is HTML.
+ *
+ * @return void
+ */
public function testGetConfigIfContentIsHtml()
{
$content = 'content';
$checkboxText = 'checkbox_text';
+ $escapedCheckboxText = 'escaped_checkbox_text';
$mode = \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_AUTO;
$agreementId = 100;
$expectedResult = [
@@ -71,12 +83,12 @@ public function testGetConfigIfContentIsHtml()
'agreements' => [
[
'content' => $content,
- 'checkboxText' => $checkboxText,
+ 'checkboxText' => $escapedCheckboxText,
'mode' => $mode,
- 'agreementId' => $agreementId
- ]
- ]
- ]
+ 'agreementId' => $agreementId,
+ ],
+ ],
+ ],
];
$this->scopeConfigMock->expects($this->once())
@@ -94,6 +106,11 @@ public function testGetConfigIfContentIsHtml()
->with($searchCriteriaMock)
->willReturn([$agreement]);
+ $this->escaperMock->expects($this->once())
+ ->method('escapeHtml')
+ ->with($checkboxText)
+ ->willReturn($escapedCheckboxText);
+
$agreement->expects($this->once())->method('getIsHtml')->willReturn(true);
$agreement->expects($this->once())->method('getContent')->willReturn($content);
$agreement->expects($this->once())->method('getCheckboxText')->willReturn($checkboxText);
@@ -103,11 +120,17 @@ public function testGetConfigIfContentIsHtml()
$this->assertEquals($expectedResult, $this->model->getConfig());
}
+ /**
+ * Test for getConfig if content is not HTML.
+ *
+ * @return void
+ */
public function testGetConfigIfContentIsNotHtml()
{
$content = 'content';
$escapedContent = 'escaped_content';
$checkboxText = 'checkbox_text';
+ $escapedCheckboxText = 'escaped_checkbox_text';
$mode = \Magento\CheckoutAgreements\Model\AgreementModeOptions::MODE_AUTO;
$agreementId = 100;
$expectedResult = [
@@ -116,12 +139,12 @@ public function testGetConfigIfContentIsNotHtml()
'agreements' => [
[
'content' => $escapedContent,
- 'checkboxText' => $checkboxText,
+ 'checkboxText' => $escapedCheckboxText,
'mode' => $mode,
- 'agreementId' => $agreementId
- ]
- ]
- ]
+ 'agreementId' => $agreementId,
+ ],
+ ],
+ ],
];
$this->scopeConfigMock->expects($this->once())
@@ -139,8 +162,11 @@ public function testGetConfigIfContentIsNotHtml()
->with($searchCriteriaMock)
->willReturn([$agreement]);
- $this->escaperMock->expects($this->once())->method('escapeHtml')->with($content)->willReturn($escapedContent);
-
+ $this->escaperMock->expects($this->at(0))->method('escapeHtml')->with($content)->willReturn($escapedContent);
+ $this->escaperMock->expects($this->at(1))
+ ->method('escapeHtml')
+ ->with($checkboxText)
+ ->willReturn($escapedCheckboxText);
$agreement->expects($this->once())->method('getIsHtml')->willReturn(false);
$agreement->expects($this->once())->method('getContent')->willReturn($content);
$agreement->expects($this->once())->method('getCheckboxText')->willReturn($checkboxText);
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 @@
*/
-->
-
+
-
+
-
+
getStorage()->uploadFile($path, $this->getRequest()->getParam('type'));
+ $uploaded = $this->getStorage()->uploadFile($path, $this->getRequest()->getParam('type'));
+ $response = [
+ 'name' => $uploaded['name'],
+ 'type' => $uploaded['type'],
+ 'error' => $uploaded['error'],
+ 'size' => $uploaded['size'],
+ 'file' => $uploaded['file']
+ ];
} catch (\Exception $e) {
- $result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
+ $response = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
}
/** @var \Magento\Framework\Controller\Result\Json $resultJson */
$resultJson = $this->resultJsonFactory->create();
- return $resultJson->setData($result);
+ return $resultJson->setData($response);
}
}
diff --git a/app/code/Magento/Cms/Helper/Page.php b/app/code/Magento/Cms/Helper/Page.php
index abd260b260b93..70e9437235ac3 100644
--- a/app/code/Magento/Cms/Helper/Page.php
+++ b/app/code/Magento/Cms/Helper/Page.php
@@ -187,7 +187,7 @@ public function getPageUrl($pageId = null)
{
/** @var \Magento\Cms\Model\Page $page */
$page = $this->_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..dfbbce99b6515 100644
--- a/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
+++ b/app/code/Magento/Cms/Model/Wysiwyg/Images/Storage.php
@@ -3,17 +3,24 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Cms\Model\Wysiwyg\Images;
use Magento\Cms\Helper\Wysiwyg\Images;
use Magento\Framework\App\Filesystem\DirectoryList;
/**
- * Wysiwyg Images model
+ * Wysiwyg Images model.
+ *
+ * Tightly connected with controllers responsible for managing files so it uses session and is (sort of) a part
+ * of the presentation layer.
*
* @SuppressWarnings(PHPMD.LongVariable)
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*
* @api
* @since 100.0.2
@@ -270,7 +277,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/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml b/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml
new file mode 100644
index 0000000000000..2fa1b86a61572
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/ActionGroup/ClearWidgetsFromCMSContentActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml b/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml
new file mode 100644
index 0000000000000..73db6b61343b1
--- /dev/null
+++ b/app/code/Magento/Cms/Test/Mftf/Page/CmsPageEditPage.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
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/Section/TinyMCESection.xml b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml
index 8559334238d2f..ff6167ffc10e0 100644
--- a/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Section/TinyMCESection.xml
@@ -31,6 +31,8 @@
+
+
@@ -99,6 +101,7 @@
+
@@ -111,6 +114,7 @@
+
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/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml
index 5b3679bed77e0..393e25e474f12 100644
--- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogCategoryLinkTypeTest.xml
@@ -46,7 +46,9 @@
-
+
+
+
diff --git a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml
index 123d25f92b6b7..9ee9d27de477a 100644
--- a/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml
+++ b/app/code/Magento/Cms/Test/Mftf/Test/AdminAddWidgetToWYSIWYGWithCatalogProductLinkTypeTest.xml
@@ -50,6 +50,8 @@
+
+
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/Block/System/Config/Form.php b/app/code/Magento/Config/Block/System/Config/Form.php
index 2a29fa33feb74..8378c058c1955 100644
--- a/app/code/Magento/Config/Block/System/Config/Form.php
+++ b/app/code/Magento/Config/Block/System/Config/Form.php
@@ -424,6 +424,10 @@ private function getFieldData(\Magento\Config\Model\Config\Structure\Element\Fie
$backendModel = $field->getBackendModel();
// Backend models which implement ProcessorInterface are processed by ScopeConfigInterface
if (!$backendModel instanceof ProcessorInterface) {
+ if (array_key_exists($path, $this->_configData)) {
+ $data = $this->_configData[$path];
+ }
+
$backendModel->setPath($path)
->setValue($data)
->setWebsite($this->getWebsiteCode())
diff --git a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php
index 86ae1f96749df..c622a48b7f2c8 100644
--- a/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php
+++ b/app/code/Magento/Config/Console/Command/ConfigSet/DefaultProcessor.php
@@ -90,10 +90,10 @@ public function process($path, $value, $scope, $scopeCode)
}
try {
- $config = $this->configFactory->create([
+ $config = $this->configFactory->create(['data' => [
'scope' => $scope,
'scope_code' => $scopeCode,
- ]);
+ ]]);
$config->setDataByPath($path, $value);
$config->save();
} catch (\Exception $exception) {
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/Observer/Config/Backend/Admin/AfterCustomUrlChangedObserver.php b/app/code/Magento/Config/Observer/Config/Backend/Admin/AfterCustomUrlChangedObserver.php
index bf414890d0ed4..830b6376c94bc 100644
--- a/app/code/Magento/Config/Observer/Config/Backend/Admin/AfterCustomUrlChangedObserver.php
+++ b/app/code/Magento/Config/Observer/Config/Backend/Admin/AfterCustomUrlChangedObserver.php
@@ -3,10 +3,17 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Config\Observer\Config\Backend\Admin;
use Magento\Framework\Event\ObserverInterface;
+/**
+ * Class AfterCustomUrlChangedObserver redirects to new custom admin URL.
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
+ */
class AfterCustomUrlChangedObserver implements ObserverInterface
{
/**
@@ -56,7 +63,6 @@ public function __construct(
*
* @param \Magento\Framework\Event\Observer $observer
* @return void
- * @SuppressWarnings(PHPMD.ExitExpression)
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function execute(\Magento\Framework\Event\Observer $observer)
@@ -68,6 +74,7 @@ public function execute(\Magento\Framework\Event\Observer $observer)
$this->_authSession->destroy();
$adminUrl = $this->_backendData->getHomePageUrl();
$this->_response->setRedirect($adminUrl)->sendResponse();
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit(0);
}
}
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
index 06c041fabeb35..4e9319351a130 100644
--- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
+++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigSalesTaxClassActionGroup.xml
@@ -28,4 +28,32 @@
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml
index 82411faddfed7..eefaf5f3b539c 100644
--- a/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml
+++ b/app/code/Magento/Config/Test/Mftf/ActionGroup/ConfigWYSIWYGActionGroup.xml
@@ -38,4 +38,15 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/FileTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/FileTest.php
index de18d45d26864..31215f1bdee2b 100644
--- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/FileTest.php
+++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/FileTest.php
@@ -40,7 +40,11 @@ protected function setUp()
$this->file = $objectManager->getObject(
\Magento\Config\Block\System\Config\Form\Field\File::class,
- ['data' => $this->testData]
+ [
+ '_escaper' => $objectManager->getObject(\Magento\Framework\Escaper::class),
+ 'data' => $this->testData,
+
+ ]
);
$formMock = new \Magento\Framework\DataObject();
diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/ImageTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/ImageTest.php
index 8a005a52ab614..b752f79f73446 100644
--- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/ImageTest.php
+++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/ImageTest.php
@@ -34,6 +34,7 @@ protected function setUp()
\Magento\Config\Block\System\Config\Form\Field\Image::class,
[
'urlBuilder' => $this->urlBuilderMock,
+ '_escaper' => $objectManager->getObject(\Magento\Framework\Escaper::class)
]
);
diff --git a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/Select/AllowspecificTest.php b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/Select/AllowspecificTest.php
index f5c65e848b3bf..e7ba2e8aaa2e7 100644
--- a/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/Select/AllowspecificTest.php
+++ b/app/code/Magento/Config/Test/Unit/Block/System/Config/Form/Field/Select/AllowspecificTest.php
@@ -21,7 +21,10 @@ protected function setUp()
{
$testHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->_object = $testHelper->getObject(
- \Magento\Config\Block\System\Config\Form\Field\Select\Allowspecific::class
+ \Magento\Config\Block\System\Config\Form\Field\Select\Allowspecific::class,
+ [
+ '_escaper' => $testHelper->getObject(\Magento\Framework\Escaper::class)
+ ]
);
$this->_object->setData('html_id', 'spec_element');
$this->_formMock = $this->createPartialMock(
diff --git a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php
index edb76c067bf35..35b2406b328cb 100644
--- a/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php
+++ b/app/code/Magento/Config/Test/Unit/Console/Command/ConfigSet/DefaultProcessorTest.php
@@ -103,7 +103,7 @@ public function testProcess($path, $value, $scope, $scopeCode)
$config = $this->createMock(Config::class);
$this->configFactory->expects($this->once())
->method('create')
- ->with(['scope' => $scope, 'scope_code' => $scopeCode])
+ ->with(['data' => ['scope' => $scope, 'scope_code' => $scopeCode]])
->willReturn($config);
$config->expects($this->once())
->method('setDataByPath')
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/Block/Product/View/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php
index 2502b79921e99..e07879e93a6b4 100644
--- a/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php
+++ b/app/code/Magento/ConfigurableProduct/Block/Product/View/Type/Configurable.php
@@ -15,6 +15,8 @@
use Magento\Framework\Pricing\PriceCurrencyInterface;
/**
+ * Confugurable product view type
+ *
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @api
@@ -276,6 +278,8 @@ protected function getOptionImages()
}
/**
+ * Collect price options
+ *
* @return array
*/
protected function getOptionPrices()
@@ -314,6 +318,11 @@ protected function getOptionPrices()
),
],
'tierPrices' => $tierPrices,
+ 'msrpPrice' => [
+ 'amount' => $this->localeFormat->getNumber(
+ $product->getMsrp()
+ ),
+ ],
];
}
return $prices;
diff --git a/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php b/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php
index 79c2dd812acf1..2f07f8b90ce7e 100644
--- a/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php
+++ b/app/code/Magento/ConfigurableProduct/Model/LinkManagement.php
@@ -1,6 +1,5 @@
productRepository->get($sku);
+ $product = $this->productRepository->get($sku, true);
$child = $this->productRepository->get($childSku);
$childrenIds = array_values($this->configurableType->getChildrenIds($product->getId())[0]);
@@ -150,7 +158,11 @@ public function addChild($sku, $childSku)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
+ * @throws InputException
+ * @throws NoSuchEntityException
+ * @throws StateException
+ * @throws \Magento\Framework\Exception\CouldNotSaveException
*/
public function removeChild($sku, $childSku)
{
diff --git a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
index f98075f2294cc..a849d964eaed5 100644
--- a/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
+++ b/app/code/Magento/ConfigurableProduct/Model/Product/Type/Configurable.php
@@ -24,6 +24,7 @@
* @SuppressWarnings(PHPMD.TooManyFields)
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
* @api
* @since 100.0.2
*/
@@ -1231,6 +1232,8 @@ public function isPossibleBuyFromList($product)
/**
* Returns array of sub-products for specified configurable product
*
+ * $requiredAttributeIds - one dimensional array, if provided
+ *
* Result array contains all children for specified configurable product
*
* @param \Magento\Catalog\Model\Product $product
@@ -1244,9 +1247,12 @@ public function getUsedProducts($product, $requiredAttributeIds = null)
__METHOD__,
$product->getData($metadata->getLinkField()),
$product->getStoreId(),
- $this->getCustomerSession()->getCustomerGroupId(),
- $requiredAttributeIds
+ $this->getCustomerSession()->getCustomerGroupId()
];
+ if ($requiredAttributeIds !== null) {
+ sort($requiredAttributeIds);
+ $keyParts[] = implode('', $requiredAttributeIds);
+ }
$cacheKey = $this->getUsedProductsCacheKey($keyParts);
return $this->loadUsedProducts($product, $cacheKey);
}
@@ -1385,7 +1391,7 @@ function ($item) {
*/
private function getUsedProductsCacheKey($keyParts)
{
- return md5(implode('_', $keyParts));
+ return sha1(implode('_', $keyParts));
}
/**
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..93df31a7d89e5
--- /dev/null
+++ b/app/code/Magento/ConfigurableProduct/Test/Mftf/Test/AdminCreateAndSwitchProductType.xml
@@ -0,0 +1,202 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/Block/Product/View/Type/ConfigurableTest.php b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php
index 25d8412c91056..c5c2368720b98 100644
--- a/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php
+++ b/app/code/Magento/ConfigurableProduct/Test/Unit/Block/Product/View/Type/ConfigurableTest.php
@@ -379,6 +379,9 @@ private function getExpectedArray($productId, $amount, $priceQty, $percentage):
'percentage' => $percentage,
],
],
+ 'msrpPrice' => [
+ 'amount' => null ,
+ ]
],
],
'priceFormat' => [],
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/templates/catalog/product/composite/fieldset/configurable.phtml b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml
index a8712cdc183de..ecc95cbe3d48f 100644
--- a/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml
+++ b/app/code/Magento/ConfigurableProduct/view/adminhtml/templates/catalog/product/composite/fieldset/configurable.phtml
@@ -17,12 +17,11 @@
= /* @escapeNotVerified */ __('Associated Products') ?>
-
-
-
-
getProductAttribute()
- ->getStoreLabel($_product->getStoreId());
+
+
+
+
=
+ $block->escapeHtml($_attribute->getProductAttribute()->getStoreLabel($_product->getStoreId()));
?>
getData($code)) {
continue;
}
foreach ($option['values'] as $optionValue) {
- if ($optionValue['value_index'] != $value['product']['model'][$code]) {
+ if ($optionValue['value_index'] != $model->getData($code)) {
continue;
}
$data[] = [
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 267a94a1d434e..d4780c5c0867a 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/ConfigurableProductGraphQl/etc/schema.graphqls
@@ -1,5 +1,8 @@
# Copyright © Magento, Inc. All rights reserved.
# See COPYING.txt for license details.
+type Mutation {
+ addConfigurableProductsToCart(input: AddConfigurableProductsToCartInput): AddConfigurableProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart")
+}
type ConfigurableProduct implements ProductInterface, PhysicalProductInterface, CustomizableProductInterface @doc(description: "ConfigurableProduct defines basic features of a configurable product and its simple product variants") {
variants: [ConfigurableVariant] @doc(description: "An array of variants of products") @resolver(class: "Magento\\ConfigurableProductGraphQl\\Model\\Resolver\\ConfigurableVariant")
@@ -35,3 +38,30 @@ type ConfigurableProductOptionsValues @doc(description: "ConfigurableProductOpti
store_label: String @doc(description: "The label of the product on the current store")
use_default_value: Boolean @doc(description: "Indicates whether to use the default_label")
}
+
+input AddConfigurableProductsToCartInput {
+ cart_id: String!
+ cartItems: [ConfigurableProductCartItemInput!]!
+}
+
+type AddConfigurableProductsToCartOutput {
+ cart: Cart!
+}
+
+input ConfigurableProductCartItemInput {
+ data: CartItemInput!
+ variant_sku: String!
+ customizable_options:[CustomizableOptionInput!]
+}
+
+type ConfigurableCartItem implements CartItemInterface {
+ customizable_options: [SelectedCustomizableOption]!
+ configurable_options: [SelectedConfigurableOption!]!
+}
+
+type SelectedConfigurableOption {
+ id: Int!
+ option_label: String!
+ value_id: Int!
+ value_label: String!
+}
diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml
new file mode 100644
index 0000000000000..eec2194825166
--- /dev/null
+++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/AssertMessageContactUsFormActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml
new file mode 100644
index 0000000000000..df4964ea0423d
--- /dev/null
+++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontFillContactUsFormActionGroup.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml
new file mode 100644
index 0000000000000..d333d5d998960
--- /dev/null
+++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontOpenContactUsPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml
new file mode 100644
index 0000000000000..f3fe34f20c319
--- /dev/null
+++ b/app/code/Magento/Contact/Test/Mftf/ActionGroup/StorefrontSubmitContactUsFormActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml b/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml
new file mode 100644
index 0000000000000..eadf760776c58
--- /dev/null
+++ b/app/code/Magento/Contact/Test/Mftf/Data/ContactUsData.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Lorem ipsum dolor sit amet, ne enim aliquando eam, oblique deserunt no usu. Unique:
+
+
diff --git a/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml b/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml
new file mode 100644
index 0000000000000..5e793b2338507
--- /dev/null
+++ b/app/code/Magento/Contact/Test/Mftf/Page/StorefrontContactUsPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml
new file mode 100644
index 0000000000000..fdaddf33f5170
--- /dev/null
+++ b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsFormSection.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml
new file mode 100644
index 0000000000000..0970f1f8f6b20
--- /dev/null
+++ b/app/code/Magento/Contact/Test/Mftf/Section/StorefrontContactUsMessagesSection.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less
index 0aaec05aa2afe..d79806eecbe9b 100644
--- a/app/code/Magento/Contact/view/frontend/web/css/source/_module.less
+++ b/app/code/Magento/Contact/view/frontend/web/css/source/_module.less
@@ -21,6 +21,16 @@
}
}
+//
+// Desktop
+// _____________________________________________
+
+.media-width(@extremum, @break) when (@extremum = 'min') and (@break = @screen__m) {
+ .contact-index-index .column:not(.sidebar-additional) .form.contact {
+ min-width: 600px;
+ }
+}
+
// Mobile
.media-width(@extremum, @break) when (@extremum = 'max') and (@break = @screen__m) {
.contact-index-index {
diff --git a/app/code/Magento/Cookie/Helper/Cookie.php b/app/code/Magento/Cookie/Helper/Cookie.php
index 05ab02d7a2a1a..8bab596ab4c13 100644
--- a/app/code/Magento/Cookie/Helper/Cookie.php
+++ b/app/code/Magento/Cookie/Helper/Cookie.php
@@ -42,7 +42,8 @@ class Cookie extends \Magento\Framework\App\Helper\AbstractHelper
* @param \Magento\Store\Model\StoreManagerInterface $storeManager
* @param array $data
*
- * @throws \InvalidArgumentException
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function __construct(
\Magento\Framework\App\Helper\Context $context,
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/Renderer/Region.php b/app/code/Magento/Customer/Block/Adminhtml/Edit/Renderer/Region.php
index 9a025211c9b0a..0aeed1562c51e 100644
--- a/app/code/Magento/Customer/Block/Adminhtml/Edit/Renderer/Region.php
+++ b/app/code/Magento/Customer/Block/Adminhtml/Edit/Renderer/Region.php
@@ -48,7 +48,7 @@ public function render(\Magento\Framework\Data\Form\Element\AbstractElement $ele
$regionId = $element->getForm()->getElement('region_id')->getValue();
- $html = '
';
+ $html = '
';
$element->setClass('input-text admin__control-text');
$element->setRequired(true);
$html .= $element->getLabelHtml() . '
';
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 3f2c7cda7608d..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
@@ -71,13 +71,14 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
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);
@@ -94,7 +95,7 @@ protected function _prepareCollection()
$quote = $this->getQuote();
if ($quote) {
- $collection = $quote->getItemsCollection(false);
+ $collection = $quote->getItemsCollection(true);
} else {
$collection = $this->_dataCollectionFactory->create();
}
@@ -106,7 +107,7 @@ protected function _prepareCollection()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function _prepareColumns()
{
@@ -144,7 +145,7 @@ protected function _prepareColumns()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getRowUrl($row)
{
@@ -152,7 +153,7 @@ public function getRowUrl($row)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getHeadersVisibility()
{
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/Block/Widget/Name.php b/app/code/Magento/Customer/Block/Widget/Name.php
index d50045f4a4092..6f1b051af7465 100644
--- a/app/code/Magento/Customer/Block/Widget/Name.php
+++ b/app/code/Magento/Customer/Block/Widget/Name.php
@@ -55,7 +55,7 @@ public function __construct(
}
/**
- * @return void
+ * @inheritdoc
*/
public function _construct()
{
@@ -245,10 +245,13 @@ public function getStoreLabel($attributeCode)
*/
public function getAttributeValidationClass($attributeCode)
{
- return $this->_addressHelper->getAttributeValidationClass($attributeCode);
+ $attributeMetadata = $this->_getAttribute($attributeCode);
+ return $attributeMetadata ? $attributeMetadata->getFrontendClass() : '';
}
/**
+ * Check if attribute is required
+ *
* @param string $attributeCode
* @return bool
*/
@@ -259,6 +262,8 @@ private function _isAttributeRequired($attributeCode)
}
/**
+ * Check if attribute is visible
+ *
* @param string $attributeCode
* @return bool
*/
diff --git a/app/code/Magento/Customer/Controller/Address/File/Upload.php b/app/code/Magento/Customer/Controller/Address/File/Upload.php
new file mode 100644
index 0000000000000..adb4c7abd1729
--- /dev/null
+++ b/app/code/Magento/Customer/Controller/Address/File/Upload.php
@@ -0,0 +1,146 @@
+fileUploaderFactory = $fileUploaderFactory;
+ $this->addressMetadataService = $addressMetadataService;
+ $this->logger = $logger;
+ $this->fileProcessorFactory = $fileProcessorFactory;
+ parent::__construct($context);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function execute()
+ {
+ try {
+ $requestedFiles = $this->getRequest()->getFiles('custom_attributes');
+ if (empty($requestedFiles)) {
+ $result = $this->processError(__('No files for upload.'));
+ } else {
+ $attributeCode = key($requestedFiles);
+ $attributeMetadata = $this->addressMetadataService->getAttributeMetadata($attributeCode);
+
+ /** @var FileUploader $fileUploader */
+ $fileUploader = $this->fileUploaderFactory->create([
+ 'attributeMetadata' => $attributeMetadata,
+ 'entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS,
+ 'scope' => CustomAttributesDataInterface::CUSTOM_ATTRIBUTES,
+ ]);
+
+ $errors = $fileUploader->validate();
+ if (true !== $errors) {
+ $errorMessage = implode('', $errors);
+ $result = $this->processError(($errorMessage));
+ } else {
+ $result = $fileUploader->upload();
+ $this->moveTmpFileToSuitableFolder($result);
+ }
+ }
+ } catch (LocalizedException $e) {
+ $result = $this->processError($e->getMessage(), $e->getCode());
+ } catch (\Exception $e) {
+ $this->logger->critical($e);
+ $result = $this->processError($e->getMessage(), $e->getCode());
+ }
+
+ /** @var \Magento\Framework\Controller\Result\Json $resultJson */
+ $resultJson = $this->resultFactory->create(ResultFactory::TYPE_JSON);
+ $resultJson->setData($result);
+ return $resultJson;
+ }
+
+ /**
+ * Move file from temporary folder to the 'customer_address' media folder
+ *
+ * @param array $fileInfo
+ * @throws LocalizedException
+ */
+ private function moveTmpFileToSuitableFolder(&$fileInfo)
+ {
+ $fileName = $fileInfo['file'];
+ $fileProcessor = $this->fileProcessorFactory
+ ->create(['entityTypeCode' => AddressMetadataInterface::ENTITY_TYPE_ADDRESS]);
+
+ $newFilePath = $fileProcessor->moveTemporaryFile($fileName);
+ $fileInfo['file'] = $newFilePath;
+ $fileInfo['url'] = $fileProcessor->getViewUrl(
+ $newFilePath,
+ 'file'
+ );
+ }
+
+ /**
+ * Prepare result array for errors
+ *
+ * @param string $message
+ * @param int $code
+ * @return array
+ */
+ private function processError($message, $code = 0)
+ {
+ $result = [
+ 'error' => $message,
+ 'errorcode' => $code,
+ ];
+
+ return $result;
+ }
+}
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/Controller/Adminhtml/Index/Viewfile.php b/app/code/Magento/Customer/Controller/Adminhtml/Index/Viewfile.php
index 20d330354bce4..02a045086224c 100644
--- a/app/code/Magento/Customer/Controller/Adminhtml/Index/Viewfile.php
+++ b/app/code/Magento/Customer/Controller/Adminhtml/Index/Viewfile.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Customer\Controller\Adminhtml\Index;
use Magento\Customer\Api\AccountManagementInterface;
@@ -17,7 +19,10 @@
use Magento\Framework\DataObjectFactory;
/**
+ * Class Viewfile serves to show file or image by file/image name provided in request parameters.
+ *
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
*/
class Viewfile extends \Magento\Customer\Controller\Adminhtml\Index
{
@@ -127,8 +132,6 @@ public function __construct(
*
* @return \Magento\Framework\Controller\ResultInterface|void
* @throws NotFoundException
- *
- * @SuppressWarnings(PHPMD.ExitExpression)
*/
public function execute()
{
@@ -146,6 +149,7 @@ public function execute()
}
if ($plain) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$extension = pathinfo($path, PATHINFO_EXTENSION);
switch (strtolower($extension)) {
case 'gif':
@@ -175,6 +179,7 @@ public function execute()
$resultRaw->setContents($directory->readFile($fileName));
return $resultRaw;
} else {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$name = pathinfo($path, PATHINFO_BASENAME);
$this->_fileFactory->create(
$name,
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/Address.php b/app/code/Magento/Customer/Model/Address.php
index e9aa2839095d5..ea9b103f42273 100644
--- a/app/code/Magento/Customer/Model/Address.php
+++ b/app/code/Magento/Customer/Model/Address.php
@@ -122,7 +122,7 @@ public function __construct(
}
/**
- * Init model
+ * Initialize address model
*
* @return void
*/
@@ -167,7 +167,14 @@ public function updateData(AddressInterface $address)
}
/**
- * @inheritdoc
+ * Create address data object based on current address model.
+ *
+ * @param int|null $defaultBillingAddressId
+ * @param int|null $defaultShippingAddressId
+ * @return AddressInterface
+ * Use Api/Data/AddressInterface as a result of service operations. Don't rely on the model to provide
+ * the instance of Api/Data/AddressInterface
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function getDataModel($defaultBillingAddressId = null, $defaultShippingAddressId = null)
{
@@ -257,7 +264,7 @@ public function getDefaultAttributeCodes()
}
/**
- * Clone object handler
+ * Clone address
*
* @return void
*/
@@ -356,7 +363,11 @@ public function reindex()
}
/**
- * @inheritdoc
+ * Get a list of custom attribute codes.
+ *
+ * By default, entity can be extended only using extension attributes functionality.
+ *
+ * @return string[]
* @since 100.0.6
*/
protected function getCustomAttributesCodes()
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/Customer.php b/app/code/Magento/Customer/Model/Customer.php
index b00f393f53734..1287dbe5df708 100644
--- a/app/code/Magento/Customer/Model/Customer.php
+++ b/app/code/Magento/Customer/Model/Customer.php
@@ -1054,17 +1054,6 @@ public function resetErrors()
return $this;
}
- /**
- * Prepare customer for delete
- *
- * @return $this
- */
- public function beforeDelete()
- {
- //TODO : Revisit and figure handling permissions in MAGETWO-11084 Implementation: Service Context Provider
- return parent::beforeDelete();
- }
-
/**
* Processing object after save data
*
@@ -1305,7 +1294,7 @@ public function getResetPasswordLinkExpirationPeriod()
}
/**
- * Create address instance
+ * Create Address from Factory
*
* @return Address
*/
@@ -1315,7 +1304,7 @@ protected function _createAddressInstance()
}
/**
- * Create address collection instance
+ * Create Address Collection from Factory
*
* @return \Magento\Customer\Model\ResourceModel\Address\Collection
*/
@@ -1325,7 +1314,7 @@ protected function _createAddressCollection()
}
/**
- * Returns templates types
+ * Get Template Types
*
* @return array
*/
diff --git a/app/code/Magento/Customer/Model/FileProcessor.php b/app/code/Magento/Customer/Model/FileProcessor.php
index 6a8472758c169..c16faea284296 100644
--- a/app/code/Magento/Customer/Model/FileProcessor.php
+++ b/app/code/Magento/Customer/Model/FileProcessor.php
@@ -3,8 +3,13 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Customer\Model;
+/**
+ * Processor class for work with uploaded files
+ */
class FileProcessor
{
/**
@@ -232,7 +237,7 @@ public function moveTemporaryFile($fileName)
);
}
- $fileName = $dispersionPath . '/' . $fileName;
+ $fileName = $dispersionPath . '/' . $destinationFileName;
return $fileName;
}
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/ResourceModel/CustomerRepository.php b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
index b25838e245488..ddc0576cb0dae 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/CustomerRepository.php
@@ -171,7 +171,14 @@ public function __construct(
}
/**
- * @inheritdoc
+ * Create or update a customer.
+ *
+ * @param \Magento\Customer\Api\Data\CustomerInterface $customer
+ * @param string $passwordHash
+ * @return \Magento\Customer\Api\Data\CustomerInterface
+ * @throws \Magento\Framework\Exception\InputException If bad input is provided
+ * @throws \Magento\Framework\Exception\State\InputMismatchException If the provided email is already used
+ * @throws \Magento\Framework\Exception\LocalizedException
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
@@ -304,7 +311,13 @@ private function populateCustomerWithSecureData($customerModel, $passwordHash =
}
/**
- * @inheritdoc
+ * Retrieve customer.
+ *
+ * @param string $email
+ * @param int|null $websiteId
+ * @return \Magento\Customer\Api\Data\CustomerInterface
+ * @throws \Magento\Framework\Exception\NoSuchEntityException If customer with the specified email does not exist.
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function get($email, $websiteId = null)
{
@@ -313,7 +326,12 @@ public function get($email, $websiteId = null)
}
/**
- * @inheritdoc
+ * Get customer by Customer ID.
+ *
+ * @param int $customerId
+ * @return \Magento\Customer\Api\Data\CustomerInterface
+ * @throws \Magento\Framework\Exception\NoSuchEntityException If customer with the specified ID does not exist.
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getById($customerId)
{
@@ -322,7 +340,15 @@ public function getById($customerId)
}
/**
- * @inheritdoc
+ * Retrieve customers which match a specified criteria.
+ *
+ * This call returns an array of objects, but detailed information about each object’s attributes might not be
+ * included. See http://devdocs.magento.com/codelinks/attributes.html#CustomerRepositoryInterface to determine
+ * which call to use to get detailed information about all attributes for an object.
+ *
+ * @param \Magento\Framework\Api\SearchCriteriaInterface $searchCriteria
+ * @return \Magento\Customer\Api\Data\CustomerSearchResultsInterface
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function getList(SearchCriteriaInterface $searchCriteria)
{
@@ -362,7 +388,11 @@ public function getList(SearchCriteriaInterface $searchCriteria)
}
/**
- * @inheritdoc
+ * Delete customer.
+ *
+ * @param \Magento\Customer\Api\Data\CustomerInterface $customer
+ * @return bool true on success
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function delete(CustomerInterface $customer)
{
@@ -370,7 +400,12 @@ public function delete(CustomerInterface $customer)
}
/**
- * @inheritdoc
+ * Delete customer by Customer ID.
+ *
+ * @param int $customerId
+ * @return bool true on success
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
public function deleteById($customerId)
{
diff --git a/app/code/Magento/Customer/Model/ResourceModel/Group.php b/app/code/Magento/Customer/Model/ResourceModel/Group.php
index 80203e742e09a..987723c5c9f58 100644
--- a/app/code/Magento/Customer/Model/ResourceModel/Group.php
+++ b/app/code/Magento/Customer/Model/ResourceModel/Group.php
@@ -29,8 +29,8 @@ class Group extends \Magento\Framework\Model\ResourceModel\Db\VersionControl\Abs
/**
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
- * @param Snapshot $entitySnapshot,
- * @param RelationComposite $entityRelationComposite,
+ * @param Snapshot $entitySnapshot
+ * @param RelationComposite $entityRelationComposite
* @param \Magento\Customer\Api\GroupManagementInterface $groupManagement
* @param Customer\CollectionFactory $customersFactory
* @param string $connectionName
@@ -110,6 +110,8 @@ protected function _afterDelete(\Magento\Framework\Model\AbstractModel $group)
}
/**
+ * Create customers collection.
+ *
* @return \Magento\Customer\Model\ResourceModel\Customer\Collection
*/
protected function _createCustomersCollection()
@@ -131,7 +133,7 @@ protected function _beforeSave(\Magento\Framework\Model\AbstractModel $group)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function _afterSave(\Magento\Framework\Model\AbstractModel $object)
{
diff --git a/app/code/Magento/Customer/Model/Vat.php b/app/code/Magento/Customer/Model/Vat.php
index f608a6cf4c11c..123a9eef4b75a 100644
--- a/app/code/Magento/Customer/Model/Vat.php
+++ b/app/code/Magento/Customer/Model/Vat.php
@@ -179,18 +179,21 @@ public function checkVatNumber($countryCode, $vatNumber, $requesterCountryCode =
return $gatewayResponse;
}
+ $countryCodeForVatNumber = $this->getCountryCodeForVatNumber($countryCode);
+ $requesterCountryCodeForVatNumber = $this->getCountryCodeForVatNumber($requesterCountryCode);
+
try {
$soapClient = $this->createVatNumberValidationSoapClient();
$requestParams = [];
- $requestParams['countryCode'] = $countryCode;
+ $requestParams['countryCode'] = $countryCodeForVatNumber;
$vatNumberSanitized = $this->isCountryInEU($countryCode)
- ? str_replace([' ', '-', $countryCode], ['', '', ''], $vatNumber)
+ ? str_replace([' ', '-', $countryCodeForVatNumber], ['', '', ''], $vatNumber)
: str_replace([' ', '-'], ['', ''], $vatNumber);
$requestParams['vatNumber'] = $vatNumberSanitized;
- $requestParams['requesterCountryCode'] = $requesterCountryCode;
+ $requestParams['requesterCountryCode'] = $requesterCountryCodeForVatNumber;
$reqVatNumSanitized = $this->isCountryInEU($requesterCountryCode)
- ? str_replace([' ', '-', $requesterCountryCode], ['', '', ''], $requesterVatNumber)
+ ? str_replace([' ', '-', $requesterCountryCodeForVatNumber], ['', '', ''], $requesterVatNumber)
: str_replace([' ', '-'], ['', ''], $requesterVatNumber);
$requestParams['requesterVatNumber'] = $reqVatNumSanitized;
// Send request to service
@@ -301,4 +304,22 @@ public function isCountryInEU($countryCode, $storeId = null)
);
return in_array($countryCode, $euCountries);
}
+
+ /**
+ * Returns the country code to use in the VAT number which is not always the same as the normal country code
+ *
+ * @param string $countryCode
+ * @return string
+ */
+ private function getCountryCodeForVatNumber(string $countryCode): string
+ {
+ // Greece uses a different code for VAT numbers then its country code
+ // See: http://ec.europa.eu/taxation_customs/vies/faq.html#item_11
+ // And https://en.wikipedia.org/wiki/VAT_identification_number:
+ // "The full identifier starts with an ISO 3166-1 alpha-2 (2 letters) country code
+ // (except for Greece, which uses the ISO 639-1 language code EL for the Greek language,
+ // instead of its ISO 3166-1 alpha-2 country code GR)"
+
+ return $countryCode === 'GR' ? 'EL' : $countryCode;
+ }
}
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/AssertCustomerLoggedInActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml
new file mode 100644
index 0000000000000..d9da950fe7115
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerLoggedInActionGroup.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml
new file mode 100644
index 0000000000000..644254443d129
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertCustomerResetPasswordActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml
new file mode 100644
index 0000000000000..ab48184ed98a8
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerChangeAccountInfoActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml
new file mode 100644
index 0000000000000..65c9b025a9c2d
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerCreateAccountActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml
new file mode 100644
index 0000000000000..6b88661985873
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/AssertMessageCustomerLoginActionGroup.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
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/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/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml
new file mode 100644
index 0000000000000..dfb9d1b2c259a
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickCreateAnAccountCustomerAccountCreationFormActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
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/StorefrontClickSignOnCustomerLoginFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.xml
new file mode 100644
index 0000000000000..9cd52b841fca4
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontClickSignOnCustomerLoginFormActionGroup.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/StorefrontCustomerChangeEmailActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml
new file mode 100644
index 0000000000000..844d13aa1fe43
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerChangeEmailActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/StorefrontCustomerResetPasswordActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml
new file mode 100644
index 0000000000000..a28593f1b77b7
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontCustomerResetPasswordActionGroup.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml
new file mode 100644
index 0000000000000..62d77d4548cce
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerAccountCreationFormActionGroup.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml
new file mode 100644
index 0000000000000..22883ada7c2b1
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontFillCustomerLoginFormActionGroup.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml
new file mode 100644
index 0000000000000..0ae470d0497ab
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountCreatePageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml
new file mode 100644
index 0000000000000..c1ea2da8a9519
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerAccountInfoEditPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml
new file mode 100644
index 0000000000000..0a5c72265528a
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/ActionGroup/StorefrontOpenCustomerLoginPageActionGroup.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
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/CustomerConfigData.xml b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml
index 3cbd70d342824..11a47459ab7b3 100644
--- a/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Data/CustomerConfigData.xml
@@ -21,4 +21,11 @@
0
+
+
+ CustomerAccountSharingInherit
+
+
+ true
+
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_config_account_sharing-meta.xml b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml
index 41701bfac11ad..c3132b5b6a44f 100644
--- a/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Metadata/customer_config_account_sharing-meta.xml
@@ -18,4 +18,16 @@
+
+
+
+
+
+ boolean
+
+
+
+
+
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/StorefrontCustomerEditPage.xml b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerEditPage.xml
new file mode 100644
index 0000000000000..d4cf90dde08ff
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Page/StorefrontCustomerEditPage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
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/AdminCustomerConfigSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml
index 9e104eb52cf90..a934d71397b8c 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerConfigSection.xml
@@ -8,5 +8,8 @@
diff --git a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml
index 02d9bc2eb5f12..17a4a283c2648 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/AdminCustomerFiltersSection.xml
@@ -16,5 +16,8 @@
+
+
+
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..8a633ec5bc271 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountInformationSection.xml
@@ -11,9 +11,14 @@
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..c00b54b3964da
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerAccountMainSection.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
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..a9859cf58751b
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerLoginMessagesSection.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
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 7482193031091..407c6480e9dde 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontCustomerSidebarSection.xml
@@ -9,6 +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..7bc057b8be7b7 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..dab298f6b416b 100644
--- a/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml
+++ b/app/code/Magento/Customer/Test/Mftf/Section/StorefrontPanelHeaderSection.xml
@@ -9,11 +9,15 @@
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/AllowedCountriesRestrictionApplyOnBackendTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml
new file mode 100644
index 0000000000000..f39394ef312e4
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/AllowedCountriesRestrictionApplyOnBackendTest.xml
@@ -0,0 +1,118 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..8d4be5fda3c79
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontForgotPasswordTest.xml
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/StorefrontResetCustomerPasswordFailedTest.xml b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml
new file mode 100644
index 0000000000000..3121bd0da9d2d
--- /dev/null
+++ b/app/code/Magento/Customer/Test/Mftf/Test/StorefrontResetCustomerPasswordFailedTest.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/Test/Unit/Model/Renderer/RegionTest.php b/app/code/Magento/Customer/Test/Unit/Model/Renderer/RegionTest.php
index c655ff7056ed6..e67adc47b8884 100644
--- a/app/code/Magento/Customer/Test/Unit/Model/Renderer/RegionTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Model/Renderer/RegionTest.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Customer\Test\Unit\Model\Renderer;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
class RegionTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -58,6 +60,14 @@ public function testRender($regionCollection)
]
)
);
+
+ $objectManager = new ObjectManager($this);
+ $escaper = $objectManager->getObject(\Magento\Framework\Escaper::class);
+ $reflection = new \ReflectionClass($elementMock);
+ $reflection_property = $reflection->getProperty('_escaper');
+ $reflection_property->setAccessible(true);
+ $reflection_property->setValue($elementMock, $escaper);
+
$formMock->expects(
$this->any()
)->method(
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/adminhtml/ui_component/customer_listing.xml b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml
index 6b479ad1cb290..f845d407d401a 100644
--- a/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml
+++ b/app/code/Magento/Customer/view/adminhtml/ui_component/customer_listing.xml
@@ -174,6 +174,7 @@
+
select
select
Country
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/Console/InputValidator.php b/app/code/Magento/Deploy/Console/InputValidator.php
index b3301f60fec26..772410d58a461 100644
--- a/app/code/Magento/Deploy/Console/InputValidator.php
+++ b/app/code/Magento/Deploy/Console/InputValidator.php
@@ -9,6 +9,8 @@
use Magento\Deploy\Console\DeployStaticOptions as Options;
use Magento\Framework\Validator\Locale;
use Symfony\Component\Console\Input\InputInterface;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Validator\RegexFactory;
/**
* Command input arguments validator class
@@ -55,14 +57,24 @@ class InputValidator
*/
private $localeValidator;
+ /**
+ * @var RegexFactory
+ */
+ private $versionValidatorFactory;
+
/**
* InputValidator constructor
*
* @param Locale $localeValidator
+ * @param RegexFactory $versionValidatorFactory
*/
- public function __construct(Locale $localeValidator)
- {
+ public function __construct(
+ Locale $localeValidator,
+ ?RegexFactory $versionValidatorFactory = null
+ ) {
$this->localeValidator = $localeValidator;
+ $this->versionValidatorFactory = $versionValidatorFactory ?:
+ ObjectManager::getInstance()->get(RegexFactory::class);
}
/**
@@ -85,6 +97,9 @@ public function validate(InputInterface $input)
$input->getArgument(Options::LANGUAGES_ARGUMENT) ?: ['all'],
$input->getOption(Options::EXCLUDE_LANGUAGE)
);
+ $this->checkVersionInput(
+ $input->getOption(Options::CONTENT_VERSION) ?: ''
+ );
}
/**
@@ -147,4 +162,29 @@ private function checkLanguagesInput(array $languagesInclude, array $languagesEx
}
}
}
+
+ /**
+ * Version input checks
+ *
+ * @param string $contentVersion
+ * @throws \InvalidArgumentException
+ */
+ private function checkVersionInput(string $contentVersion): void
+ {
+ if ($contentVersion) {
+ $versionValidator = $this->versionValidatorFactory->create(
+ [
+ 'pattern' => '/^[A-Za-z0-9_.]+$/'
+ ]
+ );
+
+ if (!$versionValidator->isValid($contentVersion)) {
+ throw new \InvalidArgumentException(
+ 'Argument "' .
+ Options::CONTENT_VERSION
+ . '" has invalid value, content version should contain only characters, digits and dots'
+ );
+ }
+ }
+ }
}
diff --git a/app/code/Magento/Deploy/Process/Queue.php b/app/code/Magento/Deploy/Process/Queue.php
index c4c31b8ff77bf..fd7aad44e0a5b 100644
--- a/app/code/Magento/Deploy/Process/Queue.php
+++ b/app/code/Magento/Deploy/Process/Queue.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Deploy\Process;
use Magento\Deploy\Package\Package;
@@ -125,6 +127,8 @@ public function __construct(
}
/**
+ * Adds deployment package.
+ *
* @param Package $package
* @param Package[] $dependencies
* @return bool true on success
@@ -140,6 +144,8 @@ public function add(Package $package, array $dependencies = [])
}
/**
+ * Returns packages array.
+ *
* @return Package[]
*/
public function getPackages()
@@ -162,6 +168,7 @@ public function process()
$this->assertAndExecute($name, $packages, $packageJob);
}
$this->logger->info('.');
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
sleep(3);
foreach ($this->inProgress as $name => $package) {
if ($this->isDeployed($package)) {
@@ -209,6 +216,8 @@ private function assertAndExecute($name, array & $packages, array $packageJob)
}
/**
+ * Executes deployment package.
+ *
* @param Package $package
* @param string $name
* @param array $packages
@@ -244,6 +253,7 @@ private function awaitForAllProcesses()
}
}
$this->logger->info('.');
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
sleep(5);
}
if ($this->isCanBeParalleled()) {
@@ -253,6 +263,8 @@ private function awaitForAllProcesses()
}
/**
+ * Checks if can be parallel.
+ *
* @return bool
*/
private function isCanBeParalleled()
@@ -261,9 +273,11 @@ private function isCanBeParalleled()
}
/**
+ * Executes the process.
+ *
* @param Package $package
* @return bool true on success for main process and exit for child process
- * @SuppressWarnings(PHPMD.ExitExpression)
+ * @throws \RuntimeException
*/
private function execute(Package $package)
{
@@ -291,6 +305,7 @@ function () use ($package) {
);
if ($this->isCanBeParalleled()) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$pid = pcntl_fork();
if ($pid === -1) {
throw new \RuntimeException('Unable to fork a new process');
@@ -305,6 +320,7 @@ function () use ($package) {
// process child process
$this->inProgress = [];
$this->deployPackageService->deploy($package, $this->options, true);
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit(0);
} else {
$this->deployPackageService->deploy($package, $this->options);
@@ -313,6 +329,8 @@ function () use ($package) {
}
/**
+ * Checks if package is deployed.
+ *
* @param Package $package
* @return bool
*/
@@ -320,11 +338,13 @@ private function isDeployed(Package $package)
{
if ($this->isCanBeParalleled()) {
if ($package->getState() === null) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$pid = pcntl_waitpid($this->getPid($package), $status, WNOHANG);
if ($pid === $this->getPid($package)) {
$package->setState(Package::STATE_COMPLETED);
unset($this->inProgress[$package->getPath()]);
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
return pcntl_wexitstatus($status) === 0;
}
return false;
@@ -334,17 +354,19 @@ private function isDeployed(Package $package)
}
/**
+ * Returns process ID or null if not found.
+ *
* @param Package $package
* @return int|null
*/
private function getPid(Package $package)
{
- return isset($this->processIds[$package->getPath()])
- ? $this->processIds[$package->getPath()]
- : null;
+ return isset($this->processIds[$package->getPath()]) ?? null;
}
/**
+ * Checks timeout.
+ *
* @return bool
*/
private function checkTimeout()
@@ -357,11 +379,13 @@ private function checkTimeout()
*
* Protect against zombie process
*
+ * @throws \RuntimeException
* @return void
*/
public function __destruct()
{
foreach ($this->inProgress as $package) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
if (pcntl_waitpid($this->getPid($package), $status) === -1) {
throw new \RuntimeException(
'Error while waiting for package deployed: ' . $this->getPid($package) . '; Status: ' . $status
diff --git a/app/code/Magento/Deploy/Service/DeployStaticContent.php b/app/code/Magento/Deploy/Service/DeployStaticContent.php
index 66ec6e7418afd..8903997159914 100644
--- a/app/code/Magento/Deploy/Service/DeployStaticContent.php
+++ b/app/code/Magento/Deploy/Service/DeployStaticContent.php
@@ -9,6 +9,7 @@
use Magento\Deploy\Process\QueueFactory;
use Magento\Deploy\Console\DeployStaticOptions as Options;
use Magento\Framework\App\View\Deployment\Version\StorageInterface;
+use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\ObjectManagerInterface;
use Psr\Log\LoggerInterface;
@@ -16,6 +17,7 @@
* Main service for static content deployment
*
* Aggregates services to deploy static files, static files bundles, translations and minified templates
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class DeployStaticContent
{
@@ -71,6 +73,7 @@ public function __construct(
* Run deploy procedure
*
* @param array $options
+ * @throws LocalizedException
* @return void
*/
public function deploy(array $options)
@@ -85,24 +88,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 +138,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 +149,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/Console/InputValidatorTest.php b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php
new file mode 100644
index 0000000000000..f3991222dfa8c
--- /dev/null
+++ b/app/code/Magento/Deploy/Test/Unit/Console/InputValidatorTest.php
@@ -0,0 +1,210 @@
+objectManagerHelper = new ObjectManagerHelper($this);
+
+ $regexFactoryMock = $this->getMockBuilder(RegexFactory::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['create'])
+ ->getMock();
+
+ $regexObject = new Regex('/^[A-Za-z0-9_.]+$/');
+
+ $regexFactoryMock->expects($this->any())->method('create')
+ ->willReturn($regexObject);
+
+ $localeObjectMock = $this->getMockBuilder(Locale::class)->setMethods(['isValid'])
+ ->disableOriginalConstructor()
+ ->getMock();
+
+ $localeObjectMock->expects($this->any())->method('isValid')
+ ->with('en_US')
+ ->will($this->returnValue(true));
+
+ $this->inputValidator = $this->objectManagerHelper->getObject(
+ InputValidator::class,
+ [
+ 'localeValidator' => $localeObjectMock,
+ 'versionValidatorFactory' => $regexFactoryMock
+ ]
+ );
+ }
+
+ /**
+ * @throws \Zend_Validate_Exception
+ */
+ public function testValidate()
+ {
+ $input = $this->getMockBuilder(ArrayInput::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['getOption', 'getArgument'])
+ ->getMock();
+
+ $input->expects($this->atLeastOnce())->method('getArgument')->willReturn(['all']);
+
+ $input->expects($this->atLeastOnce())->method('getOption')
+ ->willReturnMap(
+ [
+ [Options::AREA, ['all']],
+ [Options::EXCLUDE_AREA, ['none']],
+ [Options::THEME, ['all']],
+ [Options::EXCLUDE_THEME, ['none']],
+ [Options::EXCLUDE_LANGUAGE, ['none']],
+ [Options::CONTENT_VERSION, '12345']
+ ]
+ );
+
+ /** @noinspection PhpParamsInspection */
+ $this->inputValidator->validate($input);
+ }
+
+ /**
+ * @covers \Magento\Deploy\Console\InputValidator::checkAreasInput()
+ */
+ public function testCheckAreasInputException()
+ {
+ $options = [
+ new InputOption(Options::AREA, null, 4, '', ['test']),
+ new InputOption(Options::EXCLUDE_AREA, null, 4, '', ['test'])
+ ];
+
+ $inputDefinition = new InputDefinition($options);
+
+ try {
+ $this->inputValidator->validate(
+ new ArrayInput([], $inputDefinition)
+ );
+ } catch (\Exception $e) {
+ $this->assertContains('--area (-a) and --exclude-area cannot be used at the same time', $e->getMessage());
+ $this->assertInstanceOf(InvalidArgumentException::class, $e);
+ }
+ }
+
+ /**
+ * @covers \Magento\Deploy\Console\InputValidator::checkThemesInput()
+ */
+ public function testCheckThemesInputException()
+ {
+ $options = [
+ new InputOption(Options::AREA, null, 4, '', ['all']),
+ new InputOption(Options::EXCLUDE_AREA, null, 4, '', ['none']),
+ new InputOption(Options::THEME, null, 4, '', ['blank']),
+ new InputOption(Options::EXCLUDE_THEME, null, 4, '', ['luma'])
+ ];
+
+ $inputDefinition = new InputDefinition($options);
+
+ try {
+ $this->inputValidator->validate(
+ new ArrayInput([], $inputDefinition)
+ );
+ } catch (\Exception $e) {
+ $this->assertContains('--theme (-t) and --exclude-theme cannot be used at the same time', $e->getMessage());
+ $this->assertInstanceOf(InvalidArgumentException::class, $e);
+ }
+ }
+
+ public function testCheckLanguagesInputException()
+ {
+ $options = [
+ new InputOption(Options::AREA, null, 4, '', ['all']),
+ new InputOption(Options::EXCLUDE_AREA, '', 4, '', ['none']),
+ new InputOption(Options::THEME, null, 4, '', ['all']),
+ new InputOption(Options::EXCLUDE_THEME, null, 4, '', ['none']),
+ new InputArgument(Options::LANGUAGES_ARGUMENT, 2, '', ['en_US']),
+ new InputOption(Options::EXCLUDE_LANGUAGE, null, 4, '', ['all'])
+ ];
+
+ $inputDefinition = new InputDefinition($options);
+
+ try {
+ $this->inputValidator->validate(
+ new ArrayInput([], $inputDefinition)
+ );
+ } catch (\Exception $e) {
+ $this->assertContains(
+ '--language (-l) and --exclude-language cannot be used at the same time',
+ $e->getMessage()
+ );
+
+ $this->assertInstanceOf(InvalidArgumentException::class, $e);
+ }
+ }
+
+ public function testCheckVersionInputException()
+ {
+ $options = [
+ new InputOption(Options::AREA, null, 4, '', ['all']),
+ new InputOption(Options::EXCLUDE_AREA, null, 4, '', ['none']),
+ new InputOption(Options::THEME, null, 4, '', ['all']),
+ new InputOption(Options::EXCLUDE_THEME, null, 4, '', ['none']),
+ new InputArgument(Options::LANGUAGES_ARGUMENT, 2, '', ['en_US']),
+ new InputOption(Options::EXCLUDE_LANGUAGE, null, 4, '', ['none']),
+ new InputOption(Options::CONTENT_VERSION, null, 4, '', '/*!#')
+ ];
+
+ $inputDefinition = new InputDefinition($options);
+
+ try {
+ $this->inputValidator->validate(
+ new ArrayInput([], $inputDefinition)
+ );
+ } catch (\Exception $e) {
+ $this->assertContains(
+ 'Argument "' .
+ Options::CONTENT_VERSION
+ . '" has invalid value, content version should contain only characters, digits and dots',
+ $e->getMessage()
+ );
+
+ $this->assertInstanceOf(InvalidArgumentException::class, $e);
+ }
+ }
+}
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/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php b/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php
index 5cdcc6eb99af5..b752eaa111fa4 100644
--- a/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php
+++ b/app/code/Magento/Developer/Model/Setup/Declaration/Schema/WhitelistGenerator.php
@@ -206,12 +206,15 @@ private function getElementsWithAutogeneratedName(Schema $schema, string $tableN
foreach ($tableData[$elementType] as $tableElementData) {
if ($tableElementData['type'] === 'foreign') {
$referenceTable = $schema->getTableByName($tableElementData['referenceTable']);
- $constraintName = $this->elementNameResolver->getFullFKName(
- $table,
- $table->getColumnByName($tableElementData['column']),
- $referenceTable,
- $referenceTable->getColumnByName($tableElementData['referenceColumn'])
- );
+ $column = $table->getColumnByName($tableElementData['column']);
+ $referenceColumn = $referenceTable->getColumnByName($tableElementData['referenceColumn']);
+ $constraintName = ($column !== false && $referenceColumn !== false) ?
+ $this->elementNameResolver->getFullFKName(
+ $table,
+ $column,
+ $referenceTable,
+ $referenceColumn
+ ) : null;
} else {
$constraintName = $this->elementNameResolver->getFullIndexName(
$table,
@@ -219,7 +222,9 @@ private function getElementsWithAutogeneratedName(Schema $schema, string $tableN
$tableElementData['type']
);
}
- $declaredStructure[$elementType][$constraintName] = true;
+ if ($constraintName) {
+ $declaredStructure[$elementType][$constraintName] = true;
+ }
}
}
diff --git a/app/code/Magento/Developer/i18n/de_DE.csv b/app/code/Magento/Developer/i18n/de_DE.csv
deleted file mode 100644
index e9c858101b71c..0000000000000
--- a/app/code/Magento/Developer/i18n/de_DE.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-"Front-end development workflow","Front-end development workflow"
-"Workflow type","Workflow type"
-"Server side less compilation","Server side less compilation"
-"Client side less compilation","Client side less compilation"
-"Not available in production mode","Not available in production mode"
diff --git a/app/code/Magento/Developer/i18n/es_ES.csv b/app/code/Magento/Developer/i18n/es_ES.csv
deleted file mode 100644
index e9c858101b71c..0000000000000
--- a/app/code/Magento/Developer/i18n/es_ES.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-"Front-end development workflow","Front-end development workflow"
-"Workflow type","Workflow type"
-"Server side less compilation","Server side less compilation"
-"Client side less compilation","Client side less compilation"
-"Not available in production mode","Not available in production mode"
diff --git a/app/code/Magento/Developer/i18n/fr_FR.csv b/app/code/Magento/Developer/i18n/fr_FR.csv
deleted file mode 100644
index e9c858101b71c..0000000000000
--- a/app/code/Magento/Developer/i18n/fr_FR.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-"Front-end development workflow","Front-end development workflow"
-"Workflow type","Workflow type"
-"Server side less compilation","Server side less compilation"
-"Client side less compilation","Client side less compilation"
-"Not available in production mode","Not available in production mode"
diff --git a/app/code/Magento/Developer/i18n/nl_NL.csv b/app/code/Magento/Developer/i18n/nl_NL.csv
deleted file mode 100644
index e9c858101b71c..0000000000000
--- a/app/code/Magento/Developer/i18n/nl_NL.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-"Front-end development workflow","Front-end development workflow"
-"Workflow type","Workflow type"
-"Server side less compilation","Server side less compilation"
-"Client side less compilation","Client side less compilation"
-"Not available in production mode","Not available in production mode"
diff --git a/app/code/Magento/Developer/i18n/pt_BR.csv b/app/code/Magento/Developer/i18n/pt_BR.csv
deleted file mode 100644
index e9c858101b71c..0000000000000
--- a/app/code/Magento/Developer/i18n/pt_BR.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-"Front-end development workflow","Front-end development workflow"
-"Workflow type","Workflow type"
-"Server side less compilation","Server side less compilation"
-"Client side less compilation","Client side less compilation"
-"Not available in production mode","Not available in production mode"
diff --git a/app/code/Magento/Developer/i18n/zh_Hans_CN.csv b/app/code/Magento/Developer/i18n/zh_Hans_CN.csv
deleted file mode 100644
index e9c858101b71c..0000000000000
--- a/app/code/Magento/Developer/i18n/zh_Hans_CN.csv
+++ /dev/null
@@ -1,5 +0,0 @@
-"Front-end development workflow","Front-end development workflow"
-"Workflow type","Workflow type"
-"Server side less compilation","Server side less compilation"
-"Client side less compilation","Client side less compilation"
-"Not available in production mode","Not available in production mode"
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/CurrencyConfig.php b/app/code/Magento/Directory/Model/CurrencyConfig.php
index fdb561c224170..f7230df6e86ea 100644
--- a/app/code/Magento/Directory/Model/CurrencyConfig.php
+++ b/app/code/Magento/Directory/Model/CurrencyConfig.php
@@ -57,7 +57,7 @@ public function __construct(
*/
public function getConfigCurrencies(string $path)
{
- $result = $this->appState->getAreaCode() === Area::AREA_ADMINHTML
+ $result = in_array($this->appState->getAreaCode(), [Area::AREA_ADMINHTML, Area::AREA_CRONTAB])
? $this->getConfigForAllStores($path)
: $this->getConfigForCurrentStore($path);
sort($result);
diff --git a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php
index 827a32dcea4f9..4ec34a3842fa2 100644
--- a/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php
+++ b/app/code/Magento/Directory/Model/ResourceModel/Country/Collection.php
@@ -327,7 +327,7 @@ private function addDefaultCountryToOptions(array &$options)
foreach ($options as $key => $option) {
if (isset($defaultCountry[$option['value']])) {
- $options[$key]['is_default'] = $defaultCountry[$option['value']];
+ $options[$key]['is_default'] = !empty($defaultCountry[$option['value']]);
}
}
}
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/Directory/Test/Unit/Model/CurrencyConfigTest.php b/app/code/Magento/Directory/Test/Unit/Model/CurrencyConfigTest.php
index 9b52bae26f90f..e594be90b26dd 100644
--- a/app/code/Magento/Directory/Test/Unit/Model/CurrencyConfigTest.php
+++ b/app/code/Magento/Directory/Test/Unit/Model/CurrencyConfigTest.php
@@ -68,7 +68,7 @@ protected function setUp()
}
/**
- * Test get currency config for admin and storefront areas.
+ * Test get currency config for admin, crontab and storefront areas.
*
* @dataProvider getConfigCurrenciesDataProvider
* @return void
@@ -91,7 +91,7 @@ public function testGetConfigCurrencies(string $areCode)
->method('getCode')
->willReturn('testCode');
- if ($areCode === Area::AREA_ADMINHTML) {
+ if (in_array($areCode, [Area::AREA_ADMINHTML, Area::AREA_CRONTAB])) {
$this->storeManager->expects(self::once())
->method('getStores')
->willReturn([$store]);
@@ -121,6 +121,7 @@ public function getConfigCurrenciesDataProvider()
{
return [
['areaCode' => Area::AREA_ADMINHTML],
+ ['areaCode' => Area::AREA_CRONTAB],
['areaCode' => Area::AREA_FRONTEND],
];
}
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/Controller/Download/Link.php b/app/code/Magento/Downloadable/Controller/Download/Link.php
index 765546d080e5d..4766f1699afb6 100644
--- a/app/code/Magento/Downloadable/Controller/Download/Link.php
+++ b/app/code/Magento/Downloadable/Controller/Download/Link.php
@@ -1,15 +1,21 @@
setStatus(PurchasedLink::LINK_STATUS_EXPIRED);
}
$linkPurchasedItem->save();
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit(0);
} catch (\Exception $e) {
$this->messageManager->addError(__('Something went wrong while getting the requested content.'));
diff --git a/app/code/Magento/Downloadable/Controller/Download/LinkSample.php b/app/code/Magento/Downloadable/Controller/Download/LinkSample.php
index 76ec791611c9c..f40df744dd3ea 100644
--- a/app/code/Magento/Downloadable/Controller/Download/LinkSample.php
+++ b/app/code/Magento/Downloadable/Controller/Download/LinkSample.php
@@ -1,21 +1,26 @@
_processDownload($resource, $resourceType);
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit(0);
} catch (\Exception $e) {
$this->messageManager->addError(
diff --git a/app/code/Magento/Downloadable/Controller/Download/Sample.php b/app/code/Magento/Downloadable/Controller/Download/Sample.php
index 4a4f88d81b37a..ac9eeac678f8d 100644
--- a/app/code/Magento/Downloadable/Controller/Download/Sample.php
+++ b/app/code/Magento/Downloadable/Controller/Download/Sample.php
@@ -1,21 +1,26 @@
_processDownload($resource, $resourceType);
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit(0);
} catch (\Exception $e) {
$this->messageManager->addError(
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 0522ea0432176..1fd71e446e6bb 100644
--- a/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
+++ b/app/code/Magento/Eav/Model/Entity/AbstractEntity.php
@@ -10,6 +10,7 @@
use Magento\Eav\Model\Entity\Attribute\Backend\AbstractBackend;
use Magento\Eav\Model\Entity\Attribute\Frontend\AbstractFrontend;
use Magento\Eav\Model\Entity\Attribute\Source\AbstractSource;
+use Magento\Eav\Model\Entity\Attribute\UniqueValidationInterface;
use Magento\Framework\App\Config\Element;
use Magento\Framework\DataObject;
use Magento\Framework\DB\Adapter\DuplicateException;
@@ -215,12 +216,21 @@ abstract class AbstractEntity extends AbstractResource implements EntityInterfac
*/
protected $objectRelationProcessor;
+ /**
+ * @var UniqueValidationInterface
+ */
+ private $uniqueValidator;
+
/**
* @param Context $context
* @param array $data
+ * @param UniqueValidationInterface|null $uniqueValidator
*/
- public function __construct(Context $context, $data = [])
- {
+ public function __construct(
+ Context $context,
+ $data = [],
+ UniqueValidationInterface $uniqueValidator = null
+ ) {
$this->_eavConfig = $context->getEavConfig();
$this->_resource = $context->getResource();
$this->_attrSetEntity = $context->getAttributeSetEntity();
@@ -229,6 +239,8 @@ public function __construct(Context $context, $data = [])
$this->_universalFactory = $context->getUniversalFactory();
$this->transactionManager = $context->getTransactionManager();
$this->objectRelationProcessor = $context->getObjectRelationProcessor();
+ $this->uniqueValidator = $uniqueValidator ?:
+ ObjectManager::getInstance()->get(UniqueValidationInterface::class);
parent::__construct();
$properties = get_object_vars($this);
foreach ($data as $key => $value) {
@@ -488,6 +500,7 @@ public function addAttribute(AbstractAttribute $attribute, $object = null)
/**
* Get attributes by scope
*
+ * @param string $suffix
* @return array
*/
private function getAttributesByScope($suffix)
@@ -958,12 +971,8 @@ public function checkAttributeUniqueValue(AbstractAttribute $attribute, $object)
$data = $connection->fetchCol($select, $bind);
- $objectId = $object->getData($entityIdField);
- if ($objectId) {
- if (isset($data[0])) {
- return $data[0] == $objectId;
- }
- return true;
+ if ($object->getData($entityIdField)) {
+ return $this->uniqueValidator->validate($attribute, $object, $this, $entityIdField, $data);
}
return !count($data);
@@ -1674,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();
@@ -1972,7 +1983,8 @@ public function afterDelete(DataObject $object)
/**
* Load attributes for object
- * if the object will not pass all attributes for this entity type will be loaded
+ *
+ * If the object will not pass all attributes for this entity type will be loaded
*
* @param array $attributes
* @param AbstractEntity|null $object
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute.php b/app/code/Magento/Eav/Model/Entity/Attribute.php
index 06a4abb985802..23054ad613c21 100644
--- a/app/code/Magento/Eav/Model/Entity/Attribute.php
+++ b/app/code/Magento/Eav/Model/Entity/Attribute.php
@@ -5,7 +5,9 @@
*/
namespace Magento\Eav\Model\Entity;
+use Magento\Eav\Model\Validator\Attribute\Code as AttributeCodeValidator;
use Magento\Framework\Api\AttributeValueFactory;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Stdlib\DateTime;
use Magento\Framework\Stdlib\DateTime\DateTimeFormatterInterface;
@@ -80,6 +82,11 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im
*/
protected $dateTimeFormatter;
+ /**
+ * @var AttributeCodeValidator|null
+ */
+ private $attributeCodeValidator;
+
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
@@ -100,6 +107,7 @@ class Attribute extends \Magento\Eav\Model\Entity\Attribute\AbstractAttribute im
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\AbstractDb $resourceCollection
* @param array $data
+ * @param AttributeCodeValidator|null $attributeCodeValidator
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
* @codeCoverageIgnore
*/
@@ -122,7 +130,8 @@ public function __construct(
DateTimeFormatterInterface $dateTimeFormatter,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
- array $data = []
+ array $data = [],
+ AttributeCodeValidator $attributeCodeValidator = null
) {
parent::__construct(
$context,
@@ -145,6 +154,9 @@ public function __construct(
$this->_localeResolver = $localeResolver;
$this->reservedAttributeList = $reservedAttributeList;
$this->dateTimeFormatter = $dateTimeFormatter;
+ $this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
+ AttributeCodeValidator::class
+ );
}
/**
@@ -230,6 +242,13 @@ public function loadEntityAttributeIdBySet()
*/
public function beforeSave()
{
+ if (isset($this->_data['attribute_code'])
+ && !$this->attributeCodeValidator->isValid($this->_data['attribute_code'])
+ ) {
+ $errorMessages = implode("\n", $this->attributeCodeValidator->getMessages());
+ throw new LocalizedException(__($errorMessages));
+ }
+
// prevent overriding product data
if (isset($this->_data['attribute_code']) && $this->reservedAttributeList->isReservedAttribute($this)) {
throw new LocalizedException(
@@ -240,25 +259,6 @@ public function beforeSave()
);
}
- /**
- * Check for maximum attribute_code length
- */
- if (isset(
- $this->_data['attribute_code']
- ) && !\Zend_Validate::is(
- $this->_data['attribute_code'],
- 'StringLength',
- ['max' => self::ATTRIBUTE_CODE_MAX_LENGTH]
- )
- ) {
- throw new LocalizedException(
- __(
- 'The attribute code needs to be %1 characters or fewer. Re-enter the code and try again.',
- self::ATTRIBUTE_CODE_MAX_LENGTH
- )
- );
- }
-
$defaultValue = $this->getDefaultValue();
$hasDefaultValue = (string)$defaultValue != '';
@@ -513,7 +513,7 @@ public function __sleep()
public function __wakeup()
{
parent::__wakeup();
- $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
+ $objectManager = ObjectManager::getInstance();
$this->_localeDate = $objectManager->get(\Magento\Framework\Stdlib\DateTime\TimezoneInterface::class);
$this->_localeResolver = $objectManager->get(\Magento\Framework\Locale\ResolverInterface::class);
$this->reservedAttributeList = $objectManager->get(\Magento\Catalog\Model\Product\ReservedAttributeList::class);
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 0991b3f9f4b23..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,13 +73,15 @@ public function getOptionText($value)
}
}
// End
- if (isset($options[$value])) {
+ if (is_scalar($value) && isset($options[$value])) {
return $options[$value];
}
return false;
}
/**
+ * Get option id.
+ *
* @param string $value
* @return null|string
*/
diff --git a/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php b/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php
new file mode 100644
index 0000000000000..b68e79d7b7d20
--- /dev/null
+++ b/app/code/Magento/Eav/Model/Entity/Attribute/UniqueValidationInterface.php
@@ -0,0 +1,33 @@
+getData($entityLinkField);
+ }
+ return true;
+ }
+}
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/Model/Form.php b/app/code/Magento/Eav/Model/Form.php
index c8c50521f5509..a34b53eede354 100644
--- a/app/code/Magento/Eav/Model/Form.php
+++ b/app/code/Magento/Eav/Model/Form.php
@@ -286,7 +286,8 @@ public function getFormCode()
}
/**
- * Return entity type instance
+ * Return entity type instance.
+ *
* Return EAV entity type if entity type is not defined
*
* @return \Magento\Eav\Model\Entity\Type
@@ -323,6 +324,8 @@ public function getAttributes()
if ($this->_attributes === null) {
$this->_attributes = [];
$this->_userAttributes = [];
+ $this->_systemAttributes = [];
+ $this->_allowedAttributes = [];
/** @var $attribute \Magento\Eav\Model\Attribute */
foreach ($this->_getFilteredFormAttributeCollection() as $attribute) {
$this->_attributes[$attribute->getAttributeCode()] = $attribute;
diff --git a/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php b/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
index cd2fe7477ca60..7f6dfa2a5e9ab 100644
--- a/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
+++ b/app/code/Magento/Eav/Model/ResourceModel/ReadHandler.php
@@ -5,13 +5,19 @@
*/
namespace Magento\Eav\Model\ResourceModel;
+use Magento\Eav\Model\Config;
use Magento\Framework\DataObject;
+use Magento\Framework\DB\Select;
+use Magento\Framework\DB\Sql\UnionExpression;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\EntityManager\Operation\AttributeInterface;
use Magento\Framework\Model\Entity\ScopeInterface;
use Magento\Framework\Model\Entity\ScopeResolver;
use Psr\Log\LoggerInterface;
+/**
+ * EAV read handler
+ */
class ReadHandler implements AttributeInterface
{
/**
@@ -30,23 +36,21 @@ class ReadHandler implements AttributeInterface
private $logger;
/**
- * @var \Magento\Eav\Model\Config
+ * @var Config
*/
private $config;
/**
- * ReadHandler constructor.
- *
* @param MetadataPool $metadataPool
* @param ScopeResolver $scopeResolver
* @param LoggerInterface $logger
- * @param \Magento\Eav\Model\Config $config
+ * @param Config $config
*/
public function __construct(
MetadataPool $metadataPool,
ScopeResolver $scopeResolver,
LoggerInterface $logger,
- \Magento\Eav\Model\Config $config
+ Config $config
) {
$this->metadataPool = $metadataPool;
$this->scopeResolver = $scopeResolver;
@@ -86,6 +90,8 @@ private function getEntityAttributes(string $entityType, DataObject $entity): ar
}
/**
+ * Get context variables
+ *
* @param ScopeInterface $scope
* @return array
*/
@@ -99,6 +105,8 @@ protected function getContextVariables(ScopeInterface $scope)
}
/**
+ * Execute read handler
+ *
* @param string $entityType
* @param array $entityData
* @param array $arguments
@@ -129,33 +137,40 @@ public function execute($entityType, $entityData, $arguments = [])
}
}
if (count($attributeTables)) {
- $attributeTables = array_keys($attributeTables);
- foreach ($attributeTables as $attributeTable) {
+ $identifiers = null;
+ foreach ($attributeTables as $attributeTable => $attributeIds) {
$select = $connection->select()
->from(
['t' => $attributeTable],
['value' => 't.value', 'attribute_id' => 't.attribute_id']
)
- ->where($metadata->getLinkField() . ' = ?', $entityData[$metadata->getLinkField()]);
+ ->where($metadata->getLinkField() . ' = ?', $entityData[$metadata->getLinkField()])
+ ->where('attribute_id IN (?)', $attributeIds);
+ $attributeIdentifiers = [];
foreach ($context as $scope) {
//TODO: if (in table exists context field)
$select->where(
- $metadata->getEntityConnection()->quoteIdentifier($scope->getIdentifier()) . ' IN (?)',
+ $connection->quoteIdentifier($scope->getIdentifier()) . ' IN (?)',
$this->getContextVariables($scope)
- )->order('t.' . $scope->getIdentifier() . ' DESC');
+ );
+ $attributeIdentifiers[] = $scope->getIdentifier();
}
+ $attributeIdentifiers = array_unique($attributeIdentifiers);
+ $identifiers = array_intersect($identifiers ?? $attributeIdentifiers, $attributeIdentifiers);
$selects[] = $select;
}
- $unionSelect = new \Magento\Framework\DB\Sql\UnionExpression(
- $selects,
- \Magento\Framework\DB\Select::SQL_UNION_ALL
- );
- foreach ($connection->fetchAll($unionSelect) as $attributeValue) {
+ $this->applyIdentifierForSelects($selects, $identifiers);
+ $unionSelect = new UnionExpression($selects, Select::SQL_UNION_ALL, '( %s )');
+ $orderedUnionSelect = $connection->select();
+ $orderedUnionSelect->from(['u' => $unionSelect]);
+ $this->applyIdentifierForUnion($orderedUnionSelect, $identifiers);
+ $attributes = $connection->fetchAll($orderedUnionSelect);
+ foreach ($attributes as $attributeValue) {
if (isset($attributesMap[$attributeValue['attribute_id']])) {
$entityData[$attributesMap[$attributeValue['attribute_id']]] = $attributeValue['value'];
} else {
$this->logger->warning(
- "Attempt to load value of nonexistent EAV attribute '{$attributeValue['attribute_id']}'
+ "Attempt to load value of nonexistent EAV attribute '{$attributeValue['attribute_id']}'
for entity type '$entityType'."
);
}
@@ -163,4 +178,32 @@ public function execute($entityType, $entityData, $arguments = [])
}
return $entityData;
}
+
+ /**
+ * Apply identifiers column on select array
+ *
+ * @param Select[] $selects
+ * @param array $identifiers
+ */
+ private function applyIdentifierForSelects(array $selects, array $identifiers)
+ {
+ foreach ($selects as $select) {
+ foreach ($identifiers as $identifier) {
+ $select->columns($identifier, 't');
+ }
+ }
+ }
+
+ /**
+ * Apply identifiers order on union select
+ *
+ * @param Select $unionSelect
+ * @param array $identifiers
+ */
+ private function applyIdentifierForUnion(Select $unionSelect, array $identifiers)
+ {
+ foreach ($identifiers as $identifier) {
+ $unionSelect->order($identifier);
+ }
+ }
}
diff --git a/app/code/Magento/Eav/Model/Validator/Attribute/Code.php b/app/code/Magento/Eav/Model/Validator/Attribute/Code.php
new file mode 100644
index 0000000000000..f3ee37721b8ce
--- /dev/null
+++ b/app/code/Magento/Eav/Model/Validator/Attribute/Code.php
@@ -0,0 +1,72 @@
+ $minLength, 'max' => $maxLength]
+ );
+ if (!$isAllowedLength) {
+ $errorMessages[] = __(
+ 'An attribute code must not be less than %1 and more than %2 characters.',
+ $minLength,
+ $maxLength
+ );
+ }
+
+ $this->_addMessages($errorMessages);
+
+ return !$this->hasMessages();
+ }
+}
diff --git a/app/code/Magento/Eav/Setup/EavSetup.php b/app/code/Magento/Eav/Setup/EavSetup.php
index 6e81ddc36e9c9..de285e81b1d03 100644
--- a/app/code/Magento/Eav/Setup/EavSetup.php
+++ b/app/code/Magento/Eav/Setup/EavSetup.php
@@ -9,13 +9,15 @@
use Magento\Eav\Model\Entity\Setup\Context;
use Magento\Eav\Model\Entity\Setup\PropertyMapperInterface;
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)
@@ -80,6 +82,11 @@ class EavSetup
*/
private $_defaultAttributeSetName = 'Default';
+ /**
+ * @var Code
+ */
+ private $attributeCodeValidator;
+
/**
* Init
*
@@ -87,21 +94,27 @@ class EavSetup
* @param Context $context
* @param CacheInterface $cache
* @param CollectionFactory $attrGroupCollectionFactory
+ * @param Code|null $attributeCodeValidator
*/
public function __construct(
ModuleDataSetupInterface $setup,
Context $context,
CacheInterface $cache,
- CollectionFactory $attrGroupCollectionFactory
+ CollectionFactory $attrGroupCollectionFactory,
+ Code $attributeCodeValidator = null
) {
$this->cache = $cache;
$this->attrGroupCollectionFactory = $attrGroupCollectionFactory;
$this->attributeMapper = $context->getAttributeMapper();
$this->setup = $setup;
+ $this->attributeCodeValidator = $attributeCodeValidator ?: ObjectManager::getInstance()->get(
+ Code::class
+ );
}
/**
- * Gets setup model
+ * Gets setup model.
+ *
* @deprecated
* @return ModuleDataSetupInterface
*/
@@ -568,6 +581,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
@@ -774,38 +789,6 @@ private function _getValue($array, $key, $default = null)
return isset($array[$key]) ? $array[$key] : $default;
}
- /**
- * Validate attribute data before insert into table
- *
- * @param array $data
- * @return true
- * @throws LocalizedException
- */
- private function _validateAttributeData($data)
- {
- $minLength = \Magento\Eav\Model\Entity\Attribute::ATTRIBUTE_CODE_MIN_LENGTH;
- $maxLength = \Magento\Eav\Model\Entity\Attribute::ATTRIBUTE_CODE_MAX_LENGTH;
- $attributeCode = isset($data['attribute_code']) ? $data['attribute_code'] : '';
-
- $isAllowedLength = \Zend_Validate::is(
- trim($attributeCode),
- 'StringLength',
- ['min' => $minLength, 'max' => $maxLength]
- );
-
- if (!$isAllowedLength) {
- $errorMessage = __(
- 'An attribute code must not be less than %1 and more than %2 characters.',
- $minLength,
- $maxLength
- );
-
- throw new LocalizedException($errorMessage);
- }
-
- return true;
- }
-
/**
* Add attribute to an entity type
*
@@ -815,6 +798,8 @@ private function _validateAttributeData($data)
* @param string $code
* @param array $attr
* @return $this
+ * @throws LocalizedException
+ * @throws \Zend_Validate_Exception
*/
public function addAttribute($entityTypeId, $code, array $attr)
{
@@ -825,7 +810,7 @@ public function addAttribute($entityTypeId, $code, array $attr)
$this->attributeMapper->map($attr, $entityTypeId)
);
- $this->_validateAttributeData($data);
+ $this->validateAttributeCode($data);
$sortOrder = isset($attr['sort_order']) ? $attr['sort_order'] : null;
$attributeId = $this->getAttribute($entityTypeId, $code, 'attribute_id');
@@ -1063,7 +1048,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));
@@ -1546,4 +1531,21 @@ private function _insertAttributeAdditionalData($entityTypeId, array $data)
return $this;
}
+
+ /**
+ * Validate attribute code.
+ *
+ * @param array $data
+ * @throws LocalizedException
+ * @throws \Zend_Validate_Exception
+ */
+ private function validateAttributeCode(array $data): void
+ {
+ $attributeCode = $data['attribute_code'] ?? '';
+ if (!$this->attributeCodeValidator->isValid($attributeCode)) {
+ $errorMessage = implode('\n', $this->attributeCodeValidator->getMessages());
+
+ throw new LocalizedException(__($errorMessage));
+ }
+ }
}
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/Test/Unit/Model/Validator/Attribute/CodeTest.php b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php
new file mode 100644
index 0000000000000..9db290bcba3e1
--- /dev/null
+++ b/app/code/Magento/Eav/Test/Unit/Model/Validator/Attribute/CodeTest.php
@@ -0,0 +1,67 @@
+assertEquals($expected, $validator->isValid($attributeCode));
+ }
+
+ /**
+ * Data provider for testIsValid
+ *
+ * @return array
+ */
+ public function isValidDataProvider(): array
+ {
+ return [
+ [
+ 'Attribute_code',
+ true
+ ], [
+ 'attribute_1',
+ true
+ ],[
+ 'Attribute_1',
+ true
+ ], [
+ '_attribute_code',
+ false
+ ], [
+ 'attribute.code',
+ false
+ ], [
+ '1attribute_code',
+ false
+ ], [
+ 'more_than_60_chars_more_than_60_chars_more_than_60_chars_more',
+ false
+ ]
+ ];
+ }
+}
diff --git a/app/code/Magento/Eav/etc/di.xml b/app/code/Magento/Eav/etc/di.xml
index 8e897b979d2f0..db6f9b0a64f9f 100644
--- a/app/code/Magento/Eav/etc/di.xml
+++ b/app/code/Magento/Eav/etc/di.xml
@@ -8,6 +8,7 @@
+
@@ -209,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/BatchDataMapper/PriceFieldsProvider.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php
index 875d384a20596..56c84593256be 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/PriceFieldsProvider.php
@@ -72,13 +72,15 @@ public function __construct(
*/
public function getFields(array $productIds, $storeId)
{
+ $websiteId = $this->storeManager->getStore($storeId)->getWebsiteId();
+
$priceData = $this->dataProvider->getSearchableAttribute('price')
? $this->resourceIndex->getPriceIndexData($productIds, $storeId)
: [];
$fields = [];
foreach ($productIds as $productId) {
- $fields[$productId] = $this->getProductPriceData($productId, $storeId, $priceData);
+ $fields[$productId] = $this->getProductPriceData($productId, $websiteId, $priceData);
}
return $fields;
diff --git a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
index e4f5de46c4c86..270ca37e2d42c 100644
--- a/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
+++ b/app/code/Magento/Elasticsearch/Model/Adapter/BatchDataMapper/ProductDataMapper.php
@@ -12,12 +12,18 @@
use Magento\Elasticsearch\Model\Adapter\BatchDataMapperInterface;
use Magento\Elasticsearch\Model\Adapter\FieldType\Date as DateFieldType;
use Magento\AdvancedSearch\Model\Adapter\DataMapper\AdditionalFieldsProviderInterface;
+use Magento\Eav\Api\Data\AttributeOptionInterface;
/**
* Map product index data to search engine metadata
*/
class ProductDataMapper implements BatchDataMapperInterface
{
+ /**
+ * @var AttributeOptionInterface[]
+ */
+ private $attributeOptionsCache;
+
/**
* @var Builder
*/
@@ -95,6 +101,7 @@ public function __construct(
$this->excludedAttributes = array_merge($this->defaultExcludedAttributes, $excludedAttributes);
$this->additionalFieldsProvider = $additionalFieldsProvider;
$this->dataProvider = $dataProvider;
+ $this->attributeOptionsCache = [];
}
/**
@@ -272,7 +279,13 @@ private function isAttributeDate(Attribute $attribute): bool
private function getValuesLabels(Attribute $attribute, array $attributeValues): array
{
$attributeLabels = [];
- foreach ($attribute->getOptions() as $option) {
+
+ $options = $this->getAttributeOptions($attribute);
+ if (empty($options)) {
+ return $attributeLabels;
+ }
+
+ foreach ($options as $option) {
if (\in_array($option->getValue(), $attributeValues)) {
$attributeLabels[] = $option->getLabel();
}
@@ -281,6 +294,22 @@ private function getValuesLabels(Attribute $attribute, array $attributeValues):
return $attributeLabels;
}
+ /**
+ * Retrieve options for attribute
+ *
+ * @param Attribute $attribute
+ * @return array
+ */
+ private function getAttributeOptions(Attribute $attribute): array
+ {
+ if (!isset($this->attributeOptionsCache[$attribute->getId()])) {
+ $options = $attribute->getOptions() ?? [];
+ $this->attributeOptionsCache[$attribute->getId()] = $options;
+ }
+
+ return $this->attributeOptionsCache[$attribute->getId()];
+ }
+
/**
* Retrieve value for field. If field have only one value this method return it.
* Otherwise will be returned array of these values.
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 c7e2a4beabb5c..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,
- ['customerGroupId' => $group->getId(), 'websiteId' => $context['websiteId']]
+ $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/Aggregation/Builder/Term.php b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
index bcfb7f5565b86..eeb48f805bccf 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Aggregation/Builder/Term.php
@@ -8,10 +8,13 @@
use Magento\Framework\Search\Request\BucketInterface as RequestBucketInterface;
use Magento\Framework\Search\Dynamic\DataProviderInterface;
+/**
+ * Builder for term buckets.
+ */
class Term implements BucketBuilderInterface
{
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function build(
RequestBucketInterface $bucket,
@@ -19,13 +22,15 @@ public function build(
array $queryResult,
DataProviderInterface $dataProvider
) {
+ $buckets = $queryResult['aggregations'][$bucket->getName()]['buckets'] ?? [];
$values = [];
- foreach ($queryResult['aggregations'][$bucket->getName()]['buckets'] as $resultBucket) {
+ foreach ($buckets as $resultBucket) {
$values[$resultBucket['key']] = [
'value' => $resultBucket['key'],
'count' => $resultBucket['doc_count'],
];
}
+
return $values;
}
}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder.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 f1c3451482bab..afd383c13421f 100644
--- a/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/Builder/Match.php
@@ -5,11 +5,18 @@
*/
namespace Magento\Elasticsearch\SearchAdapter\Query\Builder;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Search\Request\Query\BoolExpression;
use Magento\Framework\Search\Request\QueryInterface as RequestQueryInterface;
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
use Magento\Framework\Search\Adapter\Preprocessor\PreprocessorInterface;
+/**
+ * Builder for match query.
+ */
class Match implements QueryInterface
{
/**
@@ -23,24 +30,53 @@ class Match implements QueryInterface
private $fieldMapper;
/**
+ * @deprecated
+ * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
* @var PreprocessorInterface[]
*/
protected $preprocessorContainer;
+ /**
+ * @var AttributeProvider
+ */
+ private $attributeProvider;
+
+ /**
+ * @var TypeResolver
+ */
+ private $fieldTypeResolver;
+
+ /**
+ * @var ValueTransformerPool
+ */
+ private $valueTransformerPool;
+
/**
* @param FieldMapperInterface $fieldMapper
* @param PreprocessorInterface[] $preprocessorContainer
+ * @param AttributeProvider|null $attributeProvider
+ * @param TypeResolver|null $fieldTypeResolver
+ * @param ValueTransformerPool|null $valueTransformerPool
*/
public function __construct(
FieldMapperInterface $fieldMapper,
- array $preprocessorContainer
+ array $preprocessorContainer,
+ AttributeProvider $attributeProvider = null,
+ TypeResolver $fieldTypeResolver = null,
+ ValueTransformerPool $valueTransformerPool = null
) {
$this->fieldMapper = $fieldMapper;
$this->preprocessorContainer = $preprocessorContainer;
+ $this->attributeProvider = $attributeProvider ?? ObjectManager::getInstance()
+ ->get(AttributeProvider::class);
+ $this->fieldTypeResolver = $fieldTypeResolver ?? ObjectManager::getInstance()
+ ->get(TypeResolver::class);
+ $this->valueTransformerPool = $valueTransformerPool ?? ObjectManager::getInstance()
+ ->get(ValueTransformerPool::class);
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function build(array $selectQuery, RequestQueryInterface $requestQuery, $conditionType)
{
@@ -61,16 +97,14 @@ public function build(array $selectQuery, RequestQueryInterface $requestQuery, $
}
/**
+ * Prepare query.
+ *
* @param string $queryValue
* @param string $conditionType
* @return array
*/
protected function prepareQuery($queryValue, $conditionType)
{
- $queryValue = $this->escape($queryValue);
- foreach ($this->preprocessorContainer as $preprocessor) {
- $queryValue = $preprocessor->process($queryValue);
- }
$condition = $conditionType === BoolExpression::QUERY_CONDITION_NOT ?
self::QUERY_CONDITION_MUST_NOT : $conditionType;
return [
@@ -99,10 +133,24 @@ protected function buildQueries(array $matches, array $queryValue)
// Checking for quoted phrase \"phrase test\", trim escaped surrounding quotes if found
$count = 0;
- $value = preg_replace('#^\\\\"(.*)\\\\"$#m', '$1', $queryValue['value'], -1, $count);
+ $value = preg_replace('#^"(.*)"$#m', '$1', $queryValue['value'], -1, $count);
$condition = ($count) ? 'match_phrase' : 'match';
+ $transformedTypes = [];
foreach ($matches as $match) {
+ $attributeAdapter = $this->attributeProvider->getByAttributeCode($match['field']);
+ $fieldType = $this->fieldTypeResolver->getFieldType($attributeAdapter);
+ $valueTransformer = $this->valueTransformerPool->get($fieldType ?? 'text');
+ $valueTransformerHash = \spl_object_hash($valueTransformer);
+ if (!isset($transformedTypes[$valueTransformerHash])) {
+ $transformedTypes[$valueTransformerHash] = $valueTransformer->transform($value);
+ }
+ $transformedValue = $transformedTypes[$valueTransformerHash];
+ if (null === $transformedValue) {
+ //Value is incompatible with this field type.
+ continue;
+ }
+
$resolvedField = $this->fieldMapper->getFieldName(
$match['field'],
['type' => FieldMapperInterface::TYPE_QUERY]
@@ -112,8 +160,8 @@ protected function buildQueries(array $matches, array $queryValue)
'body' => [
$condition => [
$resolvedField => [
- 'query' => $value,
- 'boost' => isset($match['boost']) ? $match['boost'] : 1,
+ 'query' => $transformedValue,
+ 'boost' => $match['boost'] ?? 1,
],
],
],
@@ -124,18 +172,15 @@ protected function buildQueries(array $matches, array $queryValue)
}
/**
- * Cut trailing plus or minus sign, and @ symbol, using of which causes InnoDB to report a syntax error.
- * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs.
- *
* Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
*
+ * @deprecated
+ * @see \Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
* @param string $value
* @return string
*/
protected function escape($value)
{
- $value = preg_replace('/@+|[@+-]+$/', '', $value);
-
$pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
$replace = '\\\$1';
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/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/Query/ValueTransformer/DateTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php
new file mode 100644
index 0000000000000..49eca6e9d82a6
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/DateTransformer.php
@@ -0,0 +1,44 @@
+dateFieldType = $dateFieldType;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function transform(string $value): ?string
+ {
+ try {
+ $formattedDate = $this->dateFieldType->formatDate(null, $value);
+ } catch (\Exception $e) {
+ $formattedDate = null;
+ }
+
+ return $formattedDate;
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php
new file mode 100644
index 0000000000000..5e330076d3df7
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformer/FloatTransformer.php
@@ -0,0 +1,24 @@
+preprocessors = $preprocessors;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function transform(string $value): string
+ {
+ $value = $this->escape($value);
+ foreach ($this->preprocessors as $preprocessor) {
+ $value = $preprocessor->process($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * Escape a value for special query characters such as ':', '(', ')', '*', '?', etc.
+ *
+ * @param string $value
+ * @return string
+ */
+ private function escape(string $value): string
+ {
+ $pattern = '/(\+|-|&&|\|\||!|\(|\)|\{|}|\[|]|\^|"|~|\*|\?|:|\\\)/';
+ $replace = '\\\$1';
+
+ return preg_replace($pattern, $replace, $value);
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php
new file mode 100644
index 0000000000000..c84ddc69cc7a8
--- /dev/null
+++ b/app/code/Magento/Elasticsearch/SearchAdapter/Query/ValueTransformerInterface.php
@@ -0,0 +1,22 @@
+transformers = $valueTransformers;
+ }
+
+ /**
+ * Get value transformer related to field type.
+ *
+ * @param string $fieldType
+ * @return ValueTransformerInterface
+ */
+ public function get(string $fieldType): ValueTransformerInterface
+ {
+ return $this->transformers[$fieldType] ?? $this->transformers['default'];
+ }
+}
diff --git a/app/code/Magento/Elasticsearch/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 8114feb09d35d..d0ffc6debcd8a 100644
--- a/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
+++ b/app/code/Magento/Elasticsearch/Test/Unit/SearchAdapter/Query/Builder/MatchTest.php
@@ -5,14 +5,29 @@
*/
namespace Magento\Elasticsearch\Test\Unit\SearchAdapter\Query\Builder;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeAdapter;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\AttributeProvider;
+use Magento\Elasticsearch\Model\Adapter\FieldMapper\Product\FieldProvider\FieldType\ResolverInterface as TypeResolver;
use Magento\Elasticsearch\Model\Adapter\FieldMapperInterface;
use Magento\Elasticsearch\SearchAdapter\Query\Builder\Match as MatchQueryBuilder;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerInterface;
+use Magento\Elasticsearch\SearchAdapter\Query\ValueTransformerPool;
use Magento\Framework\Search\Request\Query\Match as MatchRequestQuery;
use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
-use PHPUnit_Framework_MockObject_MockObject as MockObject;
+use PHPUnit\Framework\MockObject\MockObject as MockObject;
class MatchTest extends \PHPUnit\Framework\TestCase
{
+ /**
+ * @var AttributeProvider|MockObject
+ */
+ private $attributeProvider;
+
+ /**
+ * @var TypeResolver|MockObject
+ */
+ private $fieldTypeResolver;
+
/**
* @var MatchQueryBuilder
*/
@@ -23,46 +38,63 @@ class MatchTest extends \PHPUnit\Framework\TestCase
*/
protected function setUp()
{
+ $this->attributeProvider = $this->createMock(AttributeProvider::class);
+ $this->fieldTypeResolver = $this->createMock(TypeResolver::class);
+
+ $valueTransformerPoolMock = $this->createMock(ValueTransformerPool::class);
+ $valueTransformerMock = $this->createMock(ValueTransformerInterface::class);
+ $valueTransformerPoolMock->method('get')
+ ->willReturn($valueTransformerMock);
+ $valueTransformerMock->method('transform')
+ ->willReturnArgument(0);
+
$this->matchQueryBuilder = (new ObjectManager($this))->getObject(
MatchQueryBuilder::class,
[
'fieldMapper' => $this->getFieldMapper(),
'preprocessorContainer' => [],
+ 'attributeProvider' => $this->attributeProvider,
+ 'fieldTypeResolver' => $this->fieldTypeResolver,
+ 'valueTransformerPool' => $valueTransformerPoolMock,
]
);
}
/**
* Tests that method constructs a correct select query.
- * @see MatchQueryBuilder::build
- *
- * @dataProvider queryValuesInvariantsProvider
*
- * @param string $rawQueryValue
- * @param string $errorMessage
+ * @see MatchQueryBuilder::build
*/
- public function testBuild($rawQueryValue, $errorMessage)
+ public function testBuild()
{
- $this->assertSelectQuery(
- $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not'),
- $errorMessage
- );
- }
+ $attributeAdapter = $this->createMock(AttributeAdapter::class);
+ $this->attributeProvider->expects($this->once())
+ ->method('getByAttributeCode')
+ ->with('some_field')
+ ->willReturn($attributeAdapter);
+ $this->fieldTypeResolver->expects($this->once())
+ ->method('getFieldType')
+ ->with($attributeAdapter)
+ ->willReturn('text');
+
+ $rawQueryValue = 'query_value';
+ $selectQuery = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'not');
- /**
- * @link https://dev.mysql.com/doc/refman/5.7/en/fulltext-boolean.html Fulltext-boolean search docs.
- *
- * @return array
- */
- public function queryValuesInvariantsProvider()
- {
- return [
- ['query_value', 'Select query field must match simple raw query value.'],
- ['query_value+', 'Specifying a trailing plus sign causes InnoDB to report a syntax error.'],
- ['query_value-', 'Specifying a trailing minus sign causes InnoDB to report a syntax error.'],
- ['query_@value', 'The @ symbol is reserved for use by the @distance proximity search operator.'],
- ['query_value+@', 'The @ symbol is reserved for use by the @distance proximity search operator.'],
+ $expectedSelectQuery = [
+ 'bool' => [
+ 'must_not' => [
+ [
+ 'match' => [
+ 'some_field' => [
+ 'query' => $rawQueryValue,
+ 'boost' => 43,
+ ],
+ ],
+ ],
+ ],
+ ],
];
+ $this->assertEquals($expectedSelectQuery, $selectQuery);
}
/**
@@ -76,6 +108,16 @@ public function queryValuesInvariantsProvider()
*/
public function testBuildMatchQuery($rawQueryValue, $queryValue, $match)
{
+ $attributeAdapter = $this->createMock(AttributeAdapter::class);
+ $this->attributeProvider->expects($this->once())
+ ->method('getByAttributeCode')
+ ->with('some_field')
+ ->willReturn($attributeAdapter);
+ $this->fieldTypeResolver->expects($this->once())
+ ->method('getFieldType')
+ ->with($attributeAdapter)
+ ->willReturn('text');
+
$query = $this->matchQueryBuilder->build([], $this->getMatchRequestQuery($rawQueryValue), 'should');
$expectedSelectQuery = [
@@ -111,30 +153,6 @@ public function matchProvider()
];
}
- /**
- * @param array $selectQuery
- * @param string $errorMessage
- */
- private function assertSelectQuery($selectQuery, $errorMessage)
- {
- $expectedSelectQuery = [
- 'bool' => [
- 'must_not' => [
- [
- 'match' => [
- 'some_field' => [
- 'query' => 'query_value',
- 'boost' => 43,
- ],
- ],
- ],
- ],
- ],
- ];
-
- $this->assertEquals($expectedSelectQuery, $selectQuery, $errorMessage);
- }
-
/**
* Gets fieldMapper mock object.
*
diff --git a/app/code/Magento/Elasticsearch/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 7e219bb2f918f..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,25 @@
- 10000
- - 2147483647
+ - 10000
+
+
+
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\TextTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\DateTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\FloatTransformer
+ - Magento\Elasticsearch\SearchAdapter\Query\ValueTransformer\IntegerTransformer
+
+
+
+
+
+
+ - Magento\Elasticsearch\SearchAdapter\Query\Preprocessor\Stopwords
+ - Magento\Search\Adapter\Query\Preprocessor\Synonyms
diff --git a/app/code/Magento/Elasticsearch6/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/GraphQl/Controller/GraphQl.php b/app/code/Magento/GraphQl/Controller/GraphQl.php
index c04bb7f5775a0..9e27ca5d608f0 100644
--- a/app/code/Magento/GraphQl/Controller/GraphQl.php
+++ b/app/code/Magento/GraphQl/Controller/GraphQl.php
@@ -12,6 +12,7 @@
use Magento\Framework\App\RequestInterface;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\GraphQl\Exception\ExceptionFormatter;
+use Magento\Framework\GraphQl\Exception\GraphQlInputException;
use Magento\Framework\GraphQl\Query\QueryProcessor;
use Magento\Framework\GraphQl\Query\Resolver\ContextInterface;
use Magento\Framework\GraphQl\Schema\SchemaGeneratorInterface;
@@ -47,12 +48,12 @@ class GraphQl implements FrontControllerInterface
private $queryProcessor;
/**
- * @var \Magento\Framework\GraphQl\Exception\ExceptionFormatter
+ * @var ExceptionFormatter
*/
private $graphQlError;
/**
- * @var \Magento\Framework\GraphQl\Query\Resolver\ContextInterface
+ * @var ContextInterface
*/
private $resolverContext;
@@ -71,8 +72,8 @@ class GraphQl implements FrontControllerInterface
* @param SchemaGeneratorInterface $schemaGenerator
* @param SerializerInterface $jsonSerializer
* @param QueryProcessor $queryProcessor
- * @param \Magento\Framework\GraphQl\Exception\ExceptionFormatter $graphQlError
- * @param \Magento\Framework\GraphQl\Query\Resolver\ContextInterface $resolverContext
+ * @param ExceptionFormatter $graphQlError
+ * @param ContextInterface $resolverContext
* @param HttpRequestProcessor $requestProcessor
* @param QueryFields $queryFields
*/
@@ -107,12 +108,14 @@ public function dispatch(RequestInterface $request) : ResponseInterface
$statusCode = 200;
try {
/** @var Http $request */
+ $this->requestProcessor->validateRequest($request);
$this->requestProcessor->processHeaders($request);
- $data = $this->jsonSerializer->unserialize($request->getContent());
- $query = isset($data['query']) ? $data['query'] : '';
- $variables = isset($data['variables']) ? $data['variables'] : null;
- // We have to extract queried field names to avoid instantiation of non necessary fields in webonyx schema
+ $data = $this->getDataFromRequest($request);
+ $query = $data['query'] ?? '';
+ $variables = $data['variables'] ?? null;
+
+ // We must extract queried field names to avoid instantiation of unnecessary fields in webonyx schema
// Temporal coupling is required for performance optimization
$this->queryFields->setQuery($query, $variables);
$schema = $this->schemaGenerator->generate();
@@ -121,7 +124,7 @@ public function dispatch(RequestInterface $request) : ResponseInterface
$schema,
$query,
$this->resolverContext,
- isset($data['variables']) ? $data['variables'] : []
+ $data['variables'] ?? []
);
} catch (\Exception $error) {
$result['errors'] = isset($result) && isset($result['errors']) ? $result['errors'] : [];
@@ -134,4 +137,26 @@ public function dispatch(RequestInterface $request) : ResponseInterface
)->setHttpResponseCode($statusCode);
return $this->response;
}
+
+ /**
+ * Get data from request body or query string
+ *
+ * @param RequestInterface $request
+ * @return array
+ */
+ private function getDataFromRequest(RequestInterface $request) : array
+ {
+ /** @var Http $request */
+ if ($request->isPost()) {
+ $data = $this->jsonSerializer->unserialize($request->getContent());
+ } elseif ($request->isGet()) {
+ $data = $request->getParams();
+ $data['variables'] = isset($data['variables']) ?
+ $this->jsonSerializer->unserialize($data['variables']) : null;
+ } else {
+ return [];
+ }
+
+ return $data;
+ }
}
diff --git a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php b/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php
deleted file mode 100644
index 2270f2616e67b..0000000000000
--- a/app/code/Magento/GraphQl/Controller/HttpHeaderProcessor/ContentTypeProcessor.php
+++ /dev/null
@@ -1,32 +0,0 @@
-headerProcessors = $graphQlHeaders;
+ $this->requestValidators = $requestValidators;
}
/**
@@ -39,4 +46,17 @@ public function processHeaders(Http $request) : void
$headerClass->processHeaderValue((string)$request->getHeader($headerName));
}
}
+
+ /**
+ * Validate HTTP request
+ *
+ * @param Http $request
+ * @return void
+ */
+ public function validateRequest(Http $request) : void
+ {
+ foreach ($this->requestValidators as $requestValidator) {
+ $requestValidator->validate($request);
+ }
+ }
}
diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php
new file mode 100644
index 0000000000000..555048aac6771
--- /dev/null
+++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/ContentTypeValidator.php
@@ -0,0 +1,40 @@
+getHeader($headerName);
+ if ($request->isPost()
+ && strpos($headerValue, $requiredHeaderValue) === false
+ ) {
+ throw new GraphQlInputException(
+ new \Magento\Framework\Phrase('Request content type must be application/json')
+ );
+ }
+ }
+}
diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php
new file mode 100644
index 0000000000000..300b3d4f44dca
--- /dev/null
+++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidator/HttpVerbValidator.php
@@ -0,0 +1,40 @@
+isPost()) {
+ $query = $request->getParam('query', '');
+ // The easiest way to determine mutations without additional parsing
+ if (strpos(trim($query), 'mutation') === 0) {
+ throw new GraphQlInputException(
+ new \Magento\Framework\Phrase('Mutation requests allowed only for POST requests')
+ );
+ }
+ }
+ }
+}
diff --git a/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php b/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php
new file mode 100644
index 0000000000000..c0873b0caff89
--- /dev/null
+++ b/app/code/Magento/GraphQl/Controller/HttpRequestValidatorInterface.php
@@ -0,0 +1,24 @@
+
- - Magento\GraphQl\Controller\HttpHeaderProcessor\ContentTypeProcessor
- Magento\GraphQl\Controller\HttpHeaderProcessor\StoreProcessor
+
+ - Magento\GraphQl\Controller\HttpRequestValidator\ContentTypeValidator
+ - Magento\GraphQl\Controller\HttpRequestValidator\HttpVerbValidator
+
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/GroupedProduct/view/frontend/templates/product/view/type/grouped.phtml b/app/code/Magento/GroupedProduct/view/frontend/templates/product/view/type/grouped.phtml
index 900d4a1bd5bbc..0be71f20a3822 100644
--- a/app/code/Magento/GroupedProduct/view/frontend/templates/product/view/type/grouped.phtml
+++ b/app/code/Magento/GroupedProduct/view/frontend/templates/product/view/type/grouped.phtml
@@ -33,8 +33,8 @@
-
+
= $block->escapeHtml($_item->getName()) ?>
@@ -80,8 +80,8 @@
-
+
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..2a4d6904b11b5 100644
--- a/app/code/Magento/ImportExport/Model/Import.php
+++ b/app/code/Magento/ImportExport/Model/Import.php
@@ -126,6 +126,11 @@ class Import extends \Magento\ImportExport\Model\AbstractModel
*/
protected $_importExportData = null;
+ /**
+ * @var \Magento\Framework\App\Config\ScopeConfigInterface
+ */
+ private $_coreConfig;
+
/**
* @var \Magento\ImportExport\Model\Import\ConfigInterface
*/
@@ -312,7 +317,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 +635,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/Entity/AbstractEntity.php b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
index 1fc3257ff2c1e..10b29a50a4064 100644
--- a/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
+++ b/app/code/Magento/ImportExport/Model/Import/Entity/AbstractEntity.php
@@ -408,7 +408,9 @@ protected function _saveValidatedBunches()
if ($source->valid()) {
try {
$rowData = $source->current();
- $skuSet[$rowData['sku']] = true;
+ if (array_key_exists('sku', $rowData)) {
+ $skuSet[$rowData['sku']] = true;
+ }
} catch (\InvalidArgumentException $e) {
$this->addRowError($e->getMessage(), $this->_processedRowsCount);
$this->_processedRowsCount++;
@@ -436,7 +438,7 @@ protected function _saveValidatedBunches()
$source->next();
}
}
- $this->_processedEntitiesCount = count($skuSet);
+ $this->_processedEntitiesCount = (count($skuSet)) ? : $this->_processedRowsCount;
return $this;
}
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/Model/ProcessManager.php b/app/code/Magento/Indexer/Model/ProcessManager.php
index 04cd713fffb11..2f2c500e028cf 100644
--- a/app/code/Magento/Indexer/Model/ProcessManager.php
+++ b/app/code/Magento/Indexer/Model/ProcessManager.php
@@ -71,6 +71,7 @@ public function execute($userFunctions)
private function simpleThreadExecute($userFunctions)
{
foreach ($userFunctions as $userFunction) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
call_user_func($userFunction);
}
}
@@ -79,6 +80,7 @@ private function simpleThreadExecute($userFunctions)
* Execute user functions in multiThreads mode
*
* @param \Traversable $userFunctions
+ * @throws \RuntimeException
* @SuppressWarnings(PHPMD.UnusedLocalVariable)
*/
private function multiThreadsExecute($userFunctions)
@@ -86,6 +88,7 @@ private function multiThreadsExecute($userFunctions)
$this->resource->closeConnection(null);
$threadNumber = 0;
foreach ($userFunctions as $userFunction) {
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$pid = pcntl_fork();
if ($pid == -1) {
throw new \RuntimeException('Unable to fork a new process');
@@ -95,6 +98,7 @@ private function multiThreadsExecute($userFunctions)
$this->startChildProcess($userFunction);
}
}
+ // phpcs:ignore Magento2.CodeAnalysis.EmptyBlock,Magento2.Functions.DiscouragedFunction
while (pcntl_waitpid(0, $status) != -1) {
//Waiting for the completion of child processes
}
@@ -128,12 +132,13 @@ private function isSetupMode(): bool
* Start child process
*
* @param callable $userFunction
- * @SuppressWarnings(PHPMD.ExitExpression)
*/
private function startChildProcess(callable $userFunction)
{
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
$status = call_user_func($userFunction);
- $status = is_integer($status) ? $status : 0;
+ $status = is_int($status) ? $status : 0;
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit($status);
}
@@ -146,8 +151,10 @@ private function executeParentProcess(int &$threadNumber)
{
$threadNumber++;
if ($threadNumber >= $this->threadsCount) {
+ // phpcs:disable Magento2.Functions.DiscouragedFunction
pcntl_wait($status);
if (pcntl_wexitstatus($status) !== 0) {
+ // phpcs:enable
$this->failInChildProcess = true;
}
$threadNumber--;
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
+ */
+ public function invoke(QueueInterface $queue, $maxNumberOfMessages, $callback)
+ {
+ $this->poisonPillVersion = $this->poisonPillRead->getLatestVersion();
+ for ($i = $maxNumberOfMessages; $i > 0; $i--) {
+ do {
+ $message = $queue->dequeue();
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ } while ($message === null && (sleep(1) === 0));
+ if (false === $this->poisonPillCompare->isLatestVersion($this->poisonPillVersion)) {
+ $queue->reject($message);
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
+ 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 b4ec34ebee19c..2f6dd2da9bbc4 100644
--- a/app/code/Magento/Msrp/Helper/Data.php
+++ b/app/code/Magento/Msrp/Helper/Data.php
@@ -7,13 +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;
/**
* Msrp data helper
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Data extends AbstractHelper
{
@@ -42,6 +46,11 @@ class Data extends AbstractHelper
*/
protected $productRepository;
+ /**
+ * @var MsrpPriceCalculatorInterface
+ */
+ private $msrpPriceCalculator;
+
/**
* @param Context $context
* @param StoreManagerInterface $storeManager
@@ -50,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,
@@ -58,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;
@@ -67,17 +78,19 @@ public function __construct(
$this->config = $config;
$this->priceCurrency = $priceCurrency;
$this->productRepository = $productRepository;
+ $this->msrpPriceCalculator = $msrpPriceCalculator
+ ?: ObjectManager::getInstance()->get(MsrpPriceCalculatorInterface::class);
}
/**
- * Check if can apply Minimum Advertise price to product
- * in specific visibility
+ * Check if can apply Minimum Advertise price to product in specific visibility
*
* @param int|Product $product
* @param int|null $visibility Check displaying price in concrete place (by default generally)
* @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)
{
@@ -135,8 +150,11 @@ public function isShowPriceOnGesture($product)
}
/**
+ * Check if we should show MAP proce before order confirmation
+ *
* @param int|Product $product
* @return bool
+ * @throws \Magento\Framework\Exception\NoSuchEntityException
*/
public function isShowBeforeOrderConfirm($product)
{
@@ -144,23 +162,19 @@ 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) {
- return false;
- } else {
- $msrp = $product->getTypeInstance()->getChildrenMsrp($product);
- }
- }
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/Mftf/Data/MsrpSettingsData.xml b/app/code/Magento/Msrp/Test/Mftf/Data/MsrpSettingsData.xml
new file mode 100644
index 0000000000000..3922bb4868914
--- /dev/null
+++ b/app/code/Magento/Msrp/Test/Mftf/Data/MsrpSettingsData.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+ EnableMAP
+
+
+ 1
+
+
+
+ DisableMAP
+
+
+ 0
+
+
diff --git a/app/code/Magento/Msrp/Test/Mftf/Metadata/msrp_settings-meta.xml b/app/code/Magento/Msrp/Test/Mftf/Metadata/msrp_settings-meta.xml
new file mode 100644
index 0000000000000..be91a548ad909
--- /dev/null
+++ b/app/code/Magento/Msrp/Test/Mftf/Metadata/msrp_settings-meta.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+ string
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml
new file mode 100644
index 0000000000000..a874de3b223a2
--- /dev/null
+++ b/app/code/Magento/Msrp/Test/Mftf/Test/StorefrontProductWithMapAssignedConfigProductIsCorrectTest.xml
@@ -0,0 +1,157 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 6e7bf61063a2a..a2e6da6de5387 100644
--- a/app/code/Magento/Msrp/composer.json
+++ b/app/code/Magento/Msrp/composer.json
@@ -10,7 +10,6 @@
"magento/module-catalog": "*",
"magento/module-downloadable": "*",
"magento/module-eav": "*",
- "magento/module-grouped-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/de_DE.csv b/app/code/Magento/Msrp/i18n/de_DE.csv
deleted file mode 100644
index be8e733a4bfd1..0000000000000
--- a/app/code/Magento/Msrp/i18n/de_DE.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront."
-"Warning!","Warning!"
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/i18n/es_ES.csv b/app/code/Magento/Msrp/i18n/es_ES.csv
deleted file mode 100644
index be8e733a4bfd1..0000000000000
--- a/app/code/Magento/Msrp/i18n/es_ES.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront."
-"Warning!","Warning!"
diff --git a/app/code/Magento/Msrp/i18n/fr_FR.csv b/app/code/Magento/Msrp/i18n/fr_FR.csv
deleted file mode 100644
index be8e733a4bfd1..0000000000000
--- a/app/code/Magento/Msrp/i18n/fr_FR.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront."
-"Warning!","Warning!"
diff --git a/app/code/Magento/Msrp/i18n/nl_NL.csv b/app/code/Magento/Msrp/i18n/nl_NL.csv
deleted file mode 100644
index be8e733a4bfd1..0000000000000
--- a/app/code/Magento/Msrp/i18n/nl_NL.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront."
-"Warning!","Warning!"
diff --git a/app/code/Magento/Msrp/i18n/pt_BR.csv b/app/code/Magento/Msrp/i18n/pt_BR.csv
deleted file mode 100644
index be8e733a4bfd1..0000000000000
--- a/app/code/Magento/Msrp/i18n/pt_BR.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront."
-"Warning!","Warning!"
diff --git a/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv b/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv
deleted file mode 100644
index be8e733a4bfd1..0000000000000
--- a/app/code/Magento/Msrp/i18n/zh_Hans_CN.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-"Enabling MAP by default will hide all product prices on Storefront.","Enabling MAP by default will hide all product prices on Storefront."
-"Warning!","Warning!"
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 dd5abd433073d..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();
/** @var $product \Magento\Catalog\Model\Product */
$product = $block->getSaleableItem();
$productId = $product->getId();
+
+$amount = $block->getMsrpPriceCalculator()->getMsrpPriceValue($product);
+
$msrpPrice = $block->renderAmount(
- $priceType->getCustomAmount($product->getMsrp() ?: $product->getTypeInstance()->getChildrenMsrp($product)),
+ $priceType->getCustomAmount($amount),
[
'price_id' => $block->getPriceId() ? $block->getPriceId() : 'old-price-' . $productId,
'include_container' => false,
@@ -29,54 +32,56 @@ $msrpPrice = $block->renderAmount(
]
);
$priceElementIdPrefix = $block->getPriceElementIdPrefix() ? $block->getPriceElementIdPrefix() : 'product-price-';
-
-$addToCartUrl = '';
-if ($product->isSaleable()) {
- /** @var Magento\Catalog\Block\Product\AbstractProduct $addToCartUrlGenerator */
- $addToCartUrlGenerator = $block->getLayout()->getBlockSingleton('Magento\Catalog\Block\Product\AbstractProduct');
- $addToCartUrl = $addToCartUrlGenerator->getAddToCartUrl(
- $product,
- ['_query' => [
- \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED =>
- $this->helper('Magento\Framework\Url\Helper\Data')->getEncodedUrl(
- $addToCartUrlGenerator->getAddToCartUrl($product)
- ),
- ]]
- );
-}
?>
-getMsrp()): ?>
+
+
= /* @escapeNotVerified */ $msrpPrice ?>
+ = /* @escapeNotVerified */ $msrpPrice ?>
isShowPriceOnGesture()): ?>
getIdSuffix();
- $popupId = 'msrp-popup-' . $productId . $block->getRandomString(20);
- $data = ['addToCart' => [
- 'origin'=> 'msrp',
- 'popupId' => '#' . $popupId,
- 'productName' => $block->escapeJs($block->escapeHtml($product->getName())),
- 'productId' => $productId,
- 'productIdInput' => 'input[type="hidden"][name="product"]',
- 'realPrice' => $block->getRealPriceHtml(),
- 'isSaleable' => $product->isSaleable(),
- 'msrpPrice' => $msrpPrice,
- 'priceElementId' => $priceElementId,
- 'closeButtonId' => '#map-popup-close',
- 'addToCartUrl' => $addToCartUrl,
- 'paymentButtons' => '[data-label=or]'
- ]];
- if ($block->getRequest()->getFullActionName() === 'catalog_product_view') {
- $data['addToCart']['addToCartButton'] = '#product_addtocart_form [type=submit]';
- } else {
- $data['addToCart']['addToCartButton'] = sprintf(
- 'form:has(input[type="hidden"][name="product"][value="%s"]) button[type="submit"]',
- (int) $productId) . ',' .
- sprintf('.block.widget .price-box[data-product-id=%s]+.product-item-actions button.tocart',
- (int) $productId
- );
- }
+
+ $addToCartUrl = '';
+ if ($product->isSaleable()) {
+ /** @var Magento\Catalog\Block\Product\AbstractProduct $addToCartUrlGenerator */
+ $addToCartUrlGenerator = $block->getLayout()->getBlockSingleton('Magento\Catalog\Block\Product\AbstractProduct');
+ $addToCartUrl = $addToCartUrlGenerator->getAddToCartUrl(
+ $product,
+ ['_query' => [
+ \Magento\Framework\App\ActionInterface::PARAM_NAME_URL_ENCODED =>
+ $this->helper('Magento\Framework\Url\Helper\Data')->getEncodedUrl(
+ $addToCartUrlGenerator->getAddToCartUrl($product)
+ ),
+ ]]
+ );
+ }
+
+ $priceElementId = $priceElementIdPrefix . $productId . $block->getIdSuffix();
+ $popupId = 'msrp-popup-' . $productId . $block->getRandomString(20);
+ $data = ['addToCart' => [
+ 'origin'=> 'msrp',
+ 'popupId' => '#' . $popupId,
+ 'productName' => $block->escapeJs($block->escapeHtml($product->getName())),
+ 'productId' => $productId,
+ 'productIdInput' => 'input[type="hidden"][name="product"]',
+ 'realPrice' => $block->getRealPriceHtml(),
+ 'isSaleable' => $product->isSaleable(),
+ 'msrpPrice' => $msrpPrice,
+ 'priceElementId' => $priceElementId,
+ 'closeButtonId' => '#map-popup-close',
+ 'addToCartUrl' => $addToCartUrl,
+ 'paymentButtons' => '[data-label=or]'
+ ]];
+ if ($block->getRequest()->getFullActionName() === 'catalog_product_view') {
+ $data['addToCart']['addToCartButton'] = '#product_addtocart_form [type=submit]';
+ } else {
+ $data['addToCart']['addToCartButton'] = sprintf(
+ 'form:has(input[type="hidden"][name="product"][value="%s"]) button[type="submit"]',
+ (int) $productId . ',' .
+ sprintf('.block.widget .price-box[data-product-id=%s]+.product-item-actions button.tocart',
+ (int) $productId));
+ }
?>
isSaleable()) {
"productName": "= $block->escapeJs($block->escapeHtml($product->getName())) ?>",
"closeButtonId": "#map-popup-close"}}'>= /* @escapeNotVerified */ __("What's this?") ?>
-
+
\ No newline at end of file
diff --git a/app/code/Magento/Msrp/view/base/web/js/msrp.js b/app/code/Magento/Msrp/view/base/web/js/msrp.js
index deeadd9b55b82..a0bd3ec132de6 100644
--- a/app/code/Magento/Msrp/view/base/web/js/msrp.js
+++ b/app/code/Magento/Msrp/view/base/web/js/msrp.js
@@ -4,11 +4,12 @@
*/
define([
'jquery',
+ 'Magento_Catalog/js/price-utils',
'underscore',
'jquery/ui',
'mage/dropdown',
'mage/template'
-], function ($) {
+], function ($, priceUtils, _) {
'use strict';
$.widget('mage.addToCart', {
@@ -24,7 +25,14 @@ define([
// Selectors
cartForm: '.form.map.checkout',
msrpLabelId: '#map-popup-msrp',
+ msrpPriceElement: '#map-popup-msrp .price-wrapper',
priceLabelId: '#map-popup-price',
+ priceElement: '#map-popup-price .price',
+ mapInfoLinks: '.map-show-info',
+ displayPriceElement: '.old-price.map-old-price .price-wrapper',
+ fallbackPriceElement: '.normal-price.map-fallback-price .price-wrapper',
+ displayPriceContainer: '.old-price.map-old-price',
+ fallbackPriceContainer: '.normal-price.map-fallback-price',
popUpAttr: '[data-role=msrp-popup-template]',
popupCartButtonId: '#map-popup-button',
paypalCheckoutButons: '[data-action=checkout-form-submit]',
@@ -59,9 +67,11 @@ define([
shadowHinter: 'popup popup-pointer'
},
popupOpened: false,
+ wasOpened: false,
/**
* Creates widget instance
+ *
* @private
*/
_create: function () {
@@ -73,10 +83,13 @@ define([
this.initTierPopup();
}
$(this.options.cartButtonId).on('click', this._addToCartSubmit.bind(this));
+ $(document).on('updateMsrpPriceBlock', this.onUpdateMsrpPrice.bind(this));
+ $(this.options.cartForm).on('submit', this._onSubmitForm.bind(this));
},
/**
* Init msrp popup
+ *
* @private
*/
initMsrpPopup: function () {
@@ -89,7 +102,7 @@ define([
$msrpPopup.find('button')
.on('click',
- this.handleMsrpAddToCart.bind(this))
+ this.handleMsrpAddToCart.bind(this))
.filter(this.options.popupCartButtonId)
.text($(this.options.addToCartButton).text());
@@ -104,6 +117,7 @@ define([
/**
* Init info popup
+ *
* @private
*/
initInfoPopup: function () {
@@ -212,8 +226,12 @@ define([
var options = this.tierOptions || this.options;
this.popUpOptions.position.of = $(event.target);
- this.$popup.find(this.options.msrpLabelId).html(options.msrpPrice);
- this.$popup.find(this.options.priceLabelId).html(options.realPrice);
+
+ if (!this.wasOpened) {
+ this.$popup.find(this.options.msrpLabelId).html(options.msrpPrice);
+ this.$popup.find(this.options.priceLabelId).html(options.realPrice);
+ this.wasOpened = true;
+ }
this.$popup.dropdownDialog(this.popUpOptions).dropdownDialog('open');
this._toggle(this.$popup);
@@ -223,6 +241,7 @@ define([
},
/**
+ * Toggle MAP popup visibility
*
* @param {HTMLElement} $elem
* @private
@@ -239,6 +258,7 @@ define([
},
/**
+ * Close MAP information popup
*
* @param {HTMLElement} $elem
*/
@@ -249,8 +269,10 @@ define([
/**
* Handler for addToCart action
+ *
+ * @param {Object} e
*/
- _addToCartSubmit: function () {
+ _addToCartSubmit: function (e) {
this.element.trigger('addToCart', this.element);
if (this.element.data('stop-processing')) {
@@ -266,9 +288,106 @@ define([
if (this.options.addToCartUrl) {
$('.mage-dropdown-dialog > .ui-dialog-content').dropdownDialog('close');
}
+
+ e.preventDefault();
$(this.options.cartForm).submit();
+ },
+ /**
+ * Call on event updatePrice. Proxy to updateMsrpPrice method.
+ *
+ * @param {Event} event
+ * @param {mixed} priceIndex
+ * @param {Object} prices
+ */
+ onUpdateMsrpPrice: function onUpdateMsrpPrice(event, priceIndex, prices) {
+
+ var defaultMsrp,
+ defaultPrice,
+ msrpPrice,
+ finalPrice;
+
+ defaultMsrp = _.chain(prices).map(function (price) {
+ return price.msrpPrice.amount;
+ }).reject(function (p) {
+ return p === null;
+ }).max().value();
+
+ defaultPrice = _.chain(prices).map(function (p) {
+ return p.finalPrice.amount;
+ }).min().value();
+
+ if (typeof priceIndex !== 'undefined') {
+ msrpPrice = prices[priceIndex].msrpPrice.amount;
+ finalPrice = prices[priceIndex].finalPrice.amount;
+
+ if (msrpPrice === null || msrpPrice <= finalPrice) {
+ this.updateNonMsrpPrice(priceUtils.formatPrice(finalPrice));
+ } else {
+ this.updateMsrpPrice(
+ priceUtils.formatPrice(finalPrice),
+ priceUtils.formatPrice(msrpPrice),
+ false);
+ }
+ } else {
+ this.updateMsrpPrice(
+ priceUtils.formatPrice(defaultPrice),
+ priceUtils.formatPrice(defaultMsrp),
+ true);
+ }
+ },
+
+ /**
+ * Update prices for configurable product with MSRP enabled
+ *
+ * @param {String} finalPrice
+ * @param {String} msrpPrice
+ * @param {Boolean} useDefaultPrice
+ */
+ updateMsrpPrice: function (finalPrice, msrpPrice, useDefaultPrice) {
+ var options = this.tierOptions || this.options;
+
+ $(this.options.fallbackPriceContainer).hide();
+ $(this.options.displayPriceContainer).show();
+ $(this.options.mapInfoLinks).show();
+
+ if (useDefaultPrice || !this.wasOpened) {
+ this.$popup.find(this.options.msrpLabelId).html(options.msrpPrice);
+ this.$popup.find(this.options.priceLabelId).html(options.realPrice);
+ $(this.options.displayPriceElement).html(msrpPrice);
+ this.wasOpened = true;
+ }
+
+ if (!useDefaultPrice) {
+ this.$popup.find(this.options.msrpPriceElement).html(msrpPrice);
+ this.$popup.find(this.options.priceElement).html(finalPrice);
+ $(this.options.displayPriceElement).html(msrpPrice);
+ }
+ },
+
+ /**
+ * Display non MAP price for irrelevant products
+ *
+ * @param {String} price
+ */
+ updateNonMsrpPrice: function (price) {
+ $(this.options.fallbackPriceElement).html(price);
+ $(this.options.displayPriceContainer).hide();
+ $(this.options.mapInfoLinks).hide();
+ $(this.options.fallbackPriceContainer).show();
+ },
+
+ /**
+ * Handler for submit form
+ *
+ * @private
+ */
+ _onSubmitForm: function () {
+ if ($(this.options.cartForm).valid()) {
+ $(this.options.cartButtonId).prop('disabled', true);
+ }
}
+
});
return $.mage.addToCart;
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 @@
+
= /* @noEscape */ $block->getCheckoutData()->getAddressHtml($block->getAddress()); ?>
+
@@ -80,35 +84,44 @@
$block->setMethodFormTemplate($code, $methodsForms[$code]);
}
?>
-
- 1) : ?>
-
- checked="checked"
+
+
+ 1) : ?>
+
+ checked="checked"
+
+ class="radio"/>
+
+
- class="radio"/>
-
-
-
-
- = $block->escapeHtml($_method->getTitle()) ?>
-
-
- getChildHtml('payment.method.' . $code)) : ?>
-
- = /* @noEscape */ $html; ?>
-
-
+
+ = $block->escapeHtml($_method->getTitle()) ?>
+
+
+ getChildHtml('payment.method.' . $code)) : ?>
+
+ = /* @noEscape */ $html; ?>
+
+
+
= $block->getChildHtml('payment_methods_after') ?>
diff --git a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
index 9b867cd7217b1..3a6d73e304974 100644
--- a/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
+++ b/app/code/Magento/Multishipping/view/frontend/web/js/overview.js
@@ -15,7 +15,7 @@ define([
opacity: 0.5, // CSS opacity for the 'Place Order' button when it's clicked and then disabled.
pleaseWaitLoader: 'span.please-wait', // 'Submitting order information...' Ajax loader.
placeOrderSubmit: 'button[type="submit"]', // The 'Place Order' button.
- agreements: '#checkout-agreements' // Container for all of the checkout agreements and terms/conditions
+ agreements: '.checkout-agreements' // Container for all of the checkout agreements and terms/conditions
},
/**
diff --git a/app/code/Magento/Newsletter/Controller/Subscriber/Confirm.php b/app/code/Magento/Newsletter/Controller/Subscriber/Confirm.php
index 4e338c2d1df34..c27717f4c7793 100644
--- a/app/code/Magento/Newsletter/Controller/Subscriber/Confirm.php
+++ b/app/code/Magento/Newsletter/Controller/Subscriber/Confirm.php
@@ -4,13 +4,20 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
namespace Magento\Newsletter\Controller\Subscriber;
-class Confirm extends \Magento\Newsletter\Controller\Subscriber
+use Magento\Framework\App\Action\HttpGetActionInterface;
+
+/**
+ * Confirm subscription controller.
+ */
+class Confirm extends \Magento\Newsletter\Controller\Subscriber implements HttpGetActionInterface
{
/**
- * Subscription confirm action
- * @return void
+ * Subscription confirm action.
+ *
+ * @return \Magento\Framework\Controller\Result\Redirect
*/
public function execute()
{
@@ -23,17 +30,17 @@ public function execute()
if ($subscriber->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/Newsletter/view/adminhtml/layout/newsletter_problem_block.xml b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_problem_block.xml
index 3eb7de194d242..5cc268333de71 100644
--- a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_problem_block.xml
+++ b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_problem_block.xml
@@ -15,6 +15,7 @@
true
true
1
+
diff --git a/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml b/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml
index a64185ce67958..532ecde456077 100644
--- a/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml
+++ b/app/code/Magento/Newsletter/view/adminhtml/templates/preview/iframeswitcher.phtml
@@ -16,7 +16,16 @@
-
+
= $block->getChildHtml('preview_form') ?>
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/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml b/app/code/Magento/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml
new file mode 100644
index 0000000000000..32810ecef20da
--- /dev/null
+++ b/app/code/Magento/OfflinePayments/view/frontend/layout/multishipping_checkout_billing.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+ - Magento_OfflinePayments::multishipping/checkmo_form.phtml
+
+
+
+
+
diff --git a/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml b/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml
new file mode 100644
index 0000000000000..b96918243a7a7
--- /dev/null
+++ b/app/code/Magento/OfflinePayments/view/frontend/templates/multishipping/checkmo_form.phtml
@@ -0,0 +1,28 @@
+
+
diff --git a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
index bb81f9ebb475f..373d64afc8cc3 100644
--- a/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
+++ b/app/code/Magento/OfflineShipping/Model/Carrier/Tablerate.php
@@ -227,12 +227,12 @@ public function getCode($type, $code = '')
$codes = [
'condition_name' => [
'package_weight' => __('Weight vs. Destination'),
- 'package_value' => __('Price vs. Destination'),
+ 'package_value_with_discount' => __('Price vs. Destination'),
'package_qty' => __('# of Items vs. Destination'),
],
'condition_name_short' => [
'package_weight' => __('Weight (and above)'),
- 'package_value' => __('Order Subtotal (and above)'),
+ 'package_value_with_discount' => __('Order Subtotal (and above)'),
'package_qty' => __('# of Items (and above)'),
],
];
diff --git a/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php b/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php
new file mode 100644
index 0000000000000..070105846fdd8
--- /dev/null
+++ b/app/code/Magento/OfflineShipping/Setup/Patch/Data/UpdateShippingTablerate.php
@@ -0,0 +1,73 @@
+moduleDataSetup = $moduleDataSetup;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ $this->moduleDataSetup->getConnection()->startSetup();
+ $connection = $this->moduleDataSetup->getConnection();
+ $connection->update(
+ $this->moduleDataSetup->getTable('shipping_tablerate'),
+ ['condition_name' => 'package_value_with_discount'],
+ [new \Zend_Db_Expr('condition_name = \'package_value\'')]
+ );
+ $connection->update(
+ $this->moduleDataSetup->getTable('core_config_data'),
+ ['value' => 'package_value_with_discount'],
+ [
+ new \Zend_Db_Expr('value = \'package_value\''),
+ new \Zend_Db_Expr('path = \'carriers/tablerate/condition_name\'')
+ ]
+ );
+ $this->moduleDataSetup->getConnection()->endSetup();
+
+ $connection->endSetup();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/OfflineShipping/Test/Unit/Block/Adminhtml/Form/Field/ImportTest.php b/app/code/Magento/OfflineShipping/Test/Unit/Block/Adminhtml/Form/Field/ImportTest.php
index 8d75cc32914b4..a1fb2e449d7bf 100644
--- a/app/code/Magento/OfflineShipping/Test/Unit/Block/Adminhtml/Form/Field/ImportTest.php
+++ b/app/code/Magento/OfflineShipping/Test/Unit/Block/Adminhtml/Form/Field/ImportTest.php
@@ -33,7 +33,10 @@ protected function setUp()
$testHelper = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->_object = $testHelper->getObject(
\Magento\OfflineShipping\Block\Adminhtml\Form\Field\Import::class,
- ['data' => $testData]
+ [
+ 'data' => $testData,
+ '_escaper' => $testHelper->getObject(\Magento\Framework\Escaper::class)
+ ]
);
$this->_object->setForm($this->_formMock);
}
diff --git a/app/code/Magento/OfflineShipping/etc/db_schema.xml b/app/code/Magento/OfflineShipping/etc/db_schema.xml
index 0510ce9b9b8eb..5129e8a29b2a1 100644
--- a/app/code/Magento/OfflineShipping/etc/db_schema.xml
+++ b/app/code/Magento/OfflineShipping/etc/db_schema.xml
@@ -18,7 +18,7 @@
default="0" comment="Destination Region Id"/>
-
+
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/PageCache/etc/varnish4.vcl b/app/code/Magento/PageCache/etc/varnish4.vcl
index 8068447e5ca99..c73c4e39170e6 100644
--- a/app/code/Magento/PageCache/etc/varnish4.vcl
+++ b/app/code/Magento/PageCache/etc/varnish4.vcl
@@ -91,10 +91,11 @@ sub vcl_recv {
}
}
- # Remove Google gclid parameters to minimize the cache objects
- set req.url = regsuball(req.url,"\?gclid=[^&]+$",""); # strips when QS = "?gclid=AAA"
- set req.url = regsuball(req.url,"\?gclid=[^&]+&","?"); # strips when QS = "?gclid=AAA&foo=bar"
- set req.url = regsuball(req.url,"&gclid=[^&]+",""); # strips when QS = "?foo=bar&gclid=AAA" or QS = "?foo=bar&gclid=AAA&bar=baz"
+ # Remove all marketing get parameters to minimize the cache objects
+ if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=") {
+ set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", "");
+ set req.url = regsub(req.url, "[?|&]+$", "");
+ }
# Static files caching
if (req.url ~ "^/(pub/)?(media|static)/") {
diff --git a/app/code/Magento/PageCache/etc/varnish5.vcl b/app/code/Magento/PageCache/etc/varnish5.vcl
index 6c8414a5cb641..ea586858eff75 100644
--- a/app/code/Magento/PageCache/etc/varnish5.vcl
+++ b/app/code/Magento/PageCache/etc/varnish5.vcl
@@ -92,10 +92,11 @@ sub vcl_recv {
}
}
- # Remove Google gclid parameters to minimize the cache objects
- set req.url = regsuball(req.url,"\?gclid=[^&]+$",""); # strips when QS = "?gclid=AAA"
- set req.url = regsuball(req.url,"\?gclid=[^&]+&","?"); # strips when QS = "?gclid=AAA&foo=bar"
- set req.url = regsuball(req.url,"&gclid=[^&]+",""); # strips when QS = "?foo=bar&gclid=AAA" or QS = "?foo=bar&gclid=AAA&bar=baz"
+ # Remove all marketing get parameters to minimize the cache objects
+ if (req.url ~ "(\?|&)(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=") {
+ set req.url = regsuball(req.url, "(gclid|cx|ie|cof|siteurl|zanpid|origin|mc_[a-z]+|utm_[a-z]+)=[-_A-z0-9+()%.]+&?", "");
+ set req.url = regsub(req.url, "[?|&]+$", "");
+ }
# Static files caching
if (req.url ~ "^/(pub/)?(media|static)/") {
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/Payment/view/frontend/templates/transparent/iframe.phtml b/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml
index afa71fe591495..ea06c60c30e20 100644
--- a/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml
+++ b/app/code/Magento/Payment/view/frontend/templates/transparent/iframe.phtml
@@ -40,7 +40,7 @@ $params = $block->getParams();
$(parent).trigger('clearTimeout');
fullScreenLoader.stopLoader();
globalMessageList.addErrorMessage({
- message: $t(= /* @escapeNotVerified */ json_encode($params['error_msg'])?>)
+ message: $t(= /* @noEscape */ json_encode($params['error_msg'])?>)
});
}
);
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/Ipn/Index.php b/app/code/Magento/Paypal/Controller/Ipn/Index.php
index 4bcc3a9b3606c..a879266bc1915 100644
--- a/app/code/Magento/Paypal/Controller/Ipn/Index.php
+++ b/app/code/Magento/Paypal/Controller/Ipn/Index.php
@@ -4,6 +4,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Paypal\Controller\Ipn;
@@ -16,6 +17,8 @@
/**
* Unified IPN controller for all supported PayPal methods
+ *
+ * @SuppressWarnings(PHPMD.AllPurposeAction)
*/
class Index extends \Magento\Framework\App\Action\Action implements CsrfAwareActionInterface
{
@@ -73,7 +76,6 @@ public function validateForCsrf(RequestInterface $request): ?bool
* Instantiate IPN model and pass IPN request to it
*
* @return void
- * @SuppressWarnings(PHPMD.ExitExpression)
*/
public function execute()
{
@@ -95,6 +97,7 @@ public function execute()
$this->_logger->critical($e);
$this->getResponse()->setStatusHeader(503, '1.1', 'Service Unavailable')->sendResponse();
/** @todo eliminate usage of exit statement */
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit;
} catch (\Exception $e) {
$this->_logger->critical($e);
diff --git a/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php b/app/code/Magento/Paypal/Controller/Transparent/RequestSecureToken.php
index 847388eb755a1..e2701bab1f062 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);
}
@@ -87,6 +90,10 @@ public function execute()
return $this->getErrorResponse();
}
+ if (!$this->transparent->isActive($quote->getStoreId())) {
+ return $this->getErrorResponse();
+ }
+
$this->sessionTransparent->setQuoteId($quote->getId());
try {
$token = $this->secureTokenService->requestToken($quote);
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/README.md b/app/code/Magento/Paypal/README.md
index 8f4453ae0a058..0ed4f2e90291b 100644
--- a/app/code/Magento/Paypal/README.md
+++ b/app/code/Magento/Paypal/README.md
@@ -1,6 +1,6 @@
Module Magento\PayPal implements integration with the PayPal payment system. Namely, it enables the following payment methods:
-*PayPal Express Checkout
-*PayPal Payments Standard
-*PayPal Payments Pro
-*PayPal Credit
-*PayFlow Payment Gateway
+* PayPal Express Checkout
+* PayPal Payments Standard
+* PayPal Payments Pro
+* PayPal Credit
+* PayFlow Payment Gateway
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/Test/Unit/Block/Adminhtml/System/Config/Field/Enable/AbstractEnableTest.php b/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Field/Enable/AbstractEnableTest.php
index b33d2f5723961..b9ea53c154014 100644
--- a/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Field/Enable/AbstractEnableTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Block/Adminhtml/System/Config/Field/Enable/AbstractEnableTest.php
@@ -5,6 +5,8 @@
*/
namespace Magento\Paypal\Test\Unit\Block\Adminhtml\System\Config\Field\Enable;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+
/**
* Class AbstractEnableTest
*
@@ -43,8 +45,18 @@ protected function setUp()
)->disableOriginalConstructor()
->getMockForAbstractClass();
+ $objectManager = new ObjectManager($this);
+ $escaper = $objectManager->getObject(\Magento\Framework\Escaper::class);
+ $reflection = new \ReflectionClass($this->elementMock);
+ $reflection_property = $reflection->getProperty('_escaper');
+ $reflection_property->setAccessible(true);
+ $reflection_property->setValue($this->elementMock, $escaper);
+
$this->abstractEnable = $objectManager->getObject(
- \Magento\Paypal\Test\Unit\Block\Adminhtml\System\Config\Field\Enable\AbstractEnable\Stub::class
+ \Magento\Paypal\Test\Unit\Block\Adminhtml\System\Config\Field\Enable\AbstractEnable\Stub::class,
+ [
+ '_escaper' => $objectManager->getObject(\Magento\Framework\Escaper::class)
+ ]
);
}
diff --git a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php
index 60451a9827097..6752eab6a7783 100644
--- a/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Controller/Transparent/RequestSecureTokenTest.php
@@ -3,16 +3,18 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Paypal\Test\Unit\Controller\Transparent;
use Magento\Framework\App\Action\Context;
use Magento\Framework\Controller\Result\JsonFactory;
use Magento\Framework\Session\Generic;
use Magento\Framework\Session\SessionManager;
-use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
use Magento\Paypal\Controller\Transparent\RequestSecureToken;
use Magento\Paypal\Model\Payflow\Service\Request\SecureToken;
use Magento\Paypal\Model\Payflow\Transparent;
+use PHPUnit\Framework\MockObject\MockObject;
/**
* Class RequestSecureTokenTest
@@ -22,39 +24,39 @@
class RequestSecureTokenTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var Transparent|\PHPUnit_Framework_MockObject_MockObject
+ * @var Transparent|MockObject
*/
- protected $transparentMock;
+ private $transparent;
/**
- * @var RequestSecureToken|\PHPUnit_Framework_MockObject_MockObject
+ * @var RequestSecureToken|MockObject
*/
- protected $controller;
+ private $controller;
/**
- * @var Context|\PHPUnit_Framework_MockObject_MockObject
+ * @var Context|MockObject
*/
- protected $contextMock;
+ private $context;
/**
- * @var JsonFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var JsonFactory|MockObject
*/
- protected $resultJsonFactoryMock;
+ private $resultJsonFactory;
/**
- * @var Generic|\PHPUnit_Framework_MockObject_MockObject
+ * @var Generic|MockObject
*/
- protected $sessionTransparentMock;
+ private $sessionTransparent;
/**
- * @var SecureToken|\PHPUnit_Framework_MockObject_MockObject
+ * @var SecureToken|MockObject
*/
- protected $secureTokenServiceMock;
+ private $secureTokenService;
/**
- * @var SessionManager|\PHPUnit_Framework_MockObject_MockObject
+ * @var SessionManager|MockObject
*/
- protected $sessionManagerMock;
+ private $sessionManager;
/**
* Set up
@@ -64,45 +66,46 @@ class RequestSecureTokenTest extends \PHPUnit\Framework\TestCase
protected function setUp()
{
- $this->contextMock = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class)
+ $this->context = $this->getMockBuilder(\Magento\Framework\App\Action\Context::class)
->disableOriginalConstructor()
->getMock();
- $this->resultJsonFactoryMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\JsonFactory::class)
+ $this->resultJsonFactory = $this->getMockBuilder(\Magento\Framework\Controller\Result\JsonFactory::class)
->setMethods(['create'])
->disableOriginalConstructor()
->getMock();
- $this->sessionTransparentMock = $this->getMockBuilder(\Magento\Framework\Session\Generic::class)
+ $this->sessionTransparent = $this->getMockBuilder(\Magento\Framework\Session\Generic::class)
->setMethods(['setQuoteId'])
->disableOriginalConstructor()
->getMock();
- $this->secureTokenServiceMock = $this->getMockBuilder(
+ $this->secureTokenService = $this->getMockBuilder(
\Magento\Paypal\Model\Payflow\Service\Request\SecureToken::class
)
->setMethods(['requestToken'])
->disableOriginalConstructor()
->getMock();
- $this->sessionManagerMock = $this->getMockBuilder(\Magento\Framework\Session\SessionManager::class)
+ $this->sessionManager = $this->getMockBuilder(\Magento\Framework\Session\SessionManager::class)
->setMethods(['getQuote'])
->disableOriginalConstructor()
->getMock();
- $this->transparentMock = $this->getMockBuilder(\Magento\Paypal\Model\Payflow\Transparent::class)
- ->setMethods(['getCode'])
+ $this->transparent = $this->getMockBuilder(\Magento\Paypal\Model\Payflow\Transparent::class)
+ ->setMethods(['getCode', 'isActive'])
->disableOriginalConstructor()
->getMock();
$this->controller = new \Magento\Paypal\Controller\Transparent\RequestSecureToken(
- $this->contextMock,
- $this->resultJsonFactoryMock,
- $this->sessionTransparentMock,
- $this->secureTokenServiceMock,
- $this->sessionManagerMock,
- $this->transparentMock
+ $this->context,
+ $this->resultJsonFactory,
+ $this->sessionTransparent,
+ $this->secureTokenService,
+ $this->sessionManager,
+ $this->transparent
);
}
public function testExecuteSuccess()
{
$quoteId = 99;
+ $storeId = 2;
$tokenFields = ['fields-1', 'fields-2', 'fields-3'];
$secureToken = 'token_hash';
$resultExpectation = [
@@ -116,6 +119,8 @@ public function testExecuteSuccess()
$quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class)
->disableOriginalConstructor()
->getMock();
+ $quoteMock->method('getStoreId')
+ ->willReturn($storeId);
$tokenMock = $this->getMockBuilder(\Magento\Framework\DataObject::class)
->disableOriginalConstructor()
->getMock();
@@ -123,21 +128,23 @@ public function testExecuteSuccess()
->disableOriginalConstructor()
->getMock();
- $this->sessionManagerMock->expects($this->atLeastOnce())
+ $this->sessionManager->expects($this->atLeastOnce())
->method('getQuote')
->willReturn($quoteMock);
+ $this->transparent->method('isActive')
+ ->with($storeId)
+ ->willReturn(true);
$quoteMock->expects($this->once())
->method('getId')
->willReturn($quoteId);
- $this->sessionTransparentMock->expects($this->once())
+ $this->sessionTransparent->expects($this->once())
->method('setQuoteId')
->with($quoteId);
- $this->secureTokenServiceMock->expects($this->once())
+ $this->secureTokenService->expects($this->once())
->method('requestToken')
->with($quoteMock)
->willReturn($tokenMock);
- $this->transparentMock->expects($this->once())
- ->method('getCode')
+ $this->transparent->method('getCode')
->willReturn('transparent');
$tokenMock->expects($this->atLeastOnce())
->method('getData')
@@ -147,7 +154,7 @@ public function testExecuteSuccess()
['securetoken', null, $secureToken]
]
);
- $this->resultJsonFactoryMock->expects($this->once())
+ $this->resultJsonFactory->expects($this->once())
->method('create')
->willReturn($jsonMock);
$jsonMock->expects($this->once())
@@ -161,6 +168,7 @@ public function testExecuteSuccess()
public function testExecuteTokenRequestException()
{
$quoteId = 99;
+ $storeId = 2;
$resultExpectation = [
'success' => false,
'error' => true,
@@ -170,24 +178,29 @@ public function testExecuteTokenRequestException()
$quoteMock = $this->getMockBuilder(\Magento\Quote\Model\Quote::class)
->disableOriginalConstructor()
->getMock();
+ $quoteMock->method('getStoreId')
+ ->willReturn($storeId);
$jsonMock = $this->getMockBuilder(\Magento\Framework\Controller\Result\Json::class)
->disableOriginalConstructor()
->getMock();
- $this->sessionManagerMock->expects($this->atLeastOnce())
+ $this->sessionManager->expects($this->atLeastOnce())
->method('getQuote')
->willReturn($quoteMock);
$quoteMock->expects($this->once())
->method('getId')
->willReturn($quoteId);
- $this->sessionTransparentMock->expects($this->once())
+ $this->transparent->method('isActive')
+ ->with($storeId)
+ ->willReturn(true);
+ $this->sessionTransparent->expects($this->once())
->method('setQuoteId')
->with($quoteId);
- $this->secureTokenServiceMock->expects($this->once())
+ $this->secureTokenService->expects($this->once())
->method('requestToken')
->with($quoteMock)
->willThrowException(new \Exception());
- $this->resultJsonFactoryMock->expects($this->once())
+ $this->resultJsonFactory->expects($this->once())
->method('create')
->willReturn($jsonMock);
$jsonMock->expects($this->once())
@@ -211,10 +224,10 @@ public function testExecuteEmptyQuoteError()
->disableOriginalConstructor()
->getMock();
- $this->sessionManagerMock->expects($this->atLeastOnce())
+ $this->sessionManager->expects($this->atLeastOnce())
->method('getQuote')
->willReturn($quoteMock);
- $this->resultJsonFactoryMock->expects($this->once())
+ $this->resultJsonFactory->expects($this->once())
->method('create')
->willReturn($jsonMock);
$jsonMock->expects($this->once())
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/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml
index 425e4cffb666c..694e517816b22 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/system/paypal_payflowpro_with_express_checkout.xml
@@ -6,7 +6,7 @@
*/
-->
-
+
Payflow Pro
0
@@ -30,7 +30,7 @@
-
+
Enable PayPal Credit
See Details.
"
+"PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you.
+ You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®.
+ Learn More ","PayPal Express Checkout Payflow Edition lets you give customers access to financing through PayPal Credit® - at no additional cost to you.
+ You get paid up front, even though customers have more time to pay. A pre-integrated payment button lets customers pay quickly with PayPal Credit®.
+ Learn More "
"Customize Smart Buttons","Customize Smart Buttons"
"Checkout Page","Checkout Page"
"Label","Label"
@@ -731,4 +736,4 @@ User,User
"PayPal will automatically display each enabled funding option to eligible buyers. For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is offered and the currency offered by the merchant is USD.","PayPal will automatically display each enabled funding option to eligible buyers. For example, PayPal Credit is only shown to buyers in countries where PayPal Credit is offered and the currency offered by the merchant is USD."
"PayPal Credit","PayPal Credit"
"PayPal Guest Checkout Credit Card Icons","PayPal Guest Checkout Credit Card Icons"
-"Elektronisches Lastschriftverfahren - German ELV","Elektronisches Lastschriftverfahren - German ELV"
\ No newline at end of file
+"Elektronisches Lastschriftverfahren - German ELV","Elektronisches Lastschriftverfahren - German ELV"
diff --git a/app/code/Magento/Paypal/view/frontend/web/template/payment/payflowpro-form.html b/app/code/Magento/Paypal/view/frontend/web/template/payment/payflowpro-form.html
index f1b14831bab31..d6fb2f3e6fc75 100644
--- a/app/code/Magento/Paypal/view/frontend/web/template/payment/payflowpro-form.html
+++ b/app/code/Magento/Paypal/view/frontend/web/template/payment/payflowpro-form.html
@@ -58,6 +58,7 @@
+
diff --git a/app/code/Magento/PaypalCaptcha/LICENSE.txt b/app/code/Magento/PaypalCaptcha/LICENSE.txt
new file mode 100644
index 0000000000000..49525fd99da9c
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/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/PaypalCaptcha/LICENSE_AFL.txt b/app/code/Magento/PaypalCaptcha/LICENSE_AFL.txt
new file mode 100644
index 0000000000000..f39d641b18a19
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/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/PaypalCaptcha/Model/Checkout/ConfigProviderPayPal.php b/app/code/Magento/PaypalCaptcha/Model/Checkout/ConfigProviderPayPal.php
new file mode 100644
index 0000000000000..289a1631ed1f6
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/Model/Checkout/ConfigProviderPayPal.php
@@ -0,0 +1,135 @@
+storeManager = $storeManager;
+ $this->captchaData = $captchaData;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getConfig(): array
+ {
+ $config['captchaPayments'][self::$formId] = [
+ 'isCaseSensitive' => $this->isCaseSensitive(self::$formId),
+ 'imageHeight' => $this->getImageHeight(self::$formId),
+ 'imageSrc' => $this->getImageSrc(self::$formId),
+ 'refreshUrl' => $this->getRefreshUrl(),
+ 'isRequired' => $this->isRequired(self::$formId),
+ 'timestamp' => time()
+ ];
+
+ return $config;
+ }
+
+ /**
+ * Returns is captcha case sensitive
+ *
+ * @param string $formId
+ * @return bool
+ */
+ private function isCaseSensitive(string $formId): bool
+ {
+ return (bool)$this->getCaptchaModel($formId)->isCaseSensitive();
+ }
+
+ /**
+ * Returns captcha image height
+ *
+ * @param string $formId
+ * @return int
+ */
+ private function getImageHeight(string $formId): int
+ {
+ return (int)$this->getCaptchaModel($formId)->getHeight();
+ }
+
+ /**
+ * Returns captcha image source path
+ *
+ * @param string $formId
+ * @return string
+ */
+ private function getImageSrc(string $formId): string
+ {
+ if ($this->isRequired($formId)) {
+ $captcha = $this->getCaptchaModel($formId);
+ $captcha->generate();
+ return $captcha->getImgSrc();
+ }
+
+ return '';
+ }
+
+ /**
+ * Returns URL to controller action which returns new captcha image
+ *
+ * @return string
+ */
+ private function getRefreshUrl(): string
+ {
+ $store = $this->storeManager->getStore();
+ return $store->getUrl('captcha/refresh', ['_secure' => $store->isCurrentlySecure()]);
+ }
+
+ /**
+ * Whether captcha is required to be inserted to this form
+ *
+ * @param string $formId
+ * @return bool
+ */
+ private function isRequired(string $formId): bool
+ {
+ return (bool)$this->getCaptchaModel($formId)->isRequired();
+ }
+
+ /**
+ * Return captcha model for specified form
+ *
+ * @param string $formId
+ * @return CaptchaInterface
+ */
+ private function getCaptchaModel(string $formId): CaptchaInterface
+ {
+ return $this->captchaData->getCaptcha($formId);
+ }
+}
diff --git a/app/code/Magento/PaypalCaptcha/Observer/CaptchaRequestToken.php b/app/code/Magento/PaypalCaptcha/Observer/CaptchaRequestToken.php
new file mode 100644
index 0000000000000..e7cb282b1799b
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/Observer/CaptchaRequestToken.php
@@ -0,0 +1,76 @@
+helper = $helper;
+ $this->jsonSerializer = $jsonSerializer;
+ $this->actionFlag = $actionFlag;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function execute(Observer $observer)
+ {
+ $formId = 'co-payment-form';
+ $captcha = $this->helper->getCaptcha($formId);
+
+ if (!$captcha->isRequired()) {
+ return;
+ }
+
+ /** @var Action $controller */
+ $controller = $observer->getControllerAction();
+ $word = $controller->getRequest()->getPost('captcha_string');
+ if ($captcha->isCorrect($word)) {
+ return;
+ }
+
+ $data = $this->jsonSerializer->serialize([
+ 'success' => false,
+ 'error' => true,
+ 'error_messages' => __('Incorrect CAPTCHA.')
+ ]);
+ $this->actionFlag->set('', Action::FLAG_NO_DISPATCH, true);
+ $controller->getResponse()->representJson($data);
+ }
+}
diff --git a/app/code/Magento/PaypalCaptcha/README.md b/app/code/Magento/PaypalCaptcha/README.md
new file mode 100644
index 0000000000000..71588599a5ecd
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/README.md
@@ -0,0 +1 @@
+The PayPal Captcha module provides a possibility to enable Captcha validation on Payflow Pro payment form.
\ No newline at end of file
diff --git a/app/code/Magento/PaypalCaptcha/composer.json b/app/code/Magento/PaypalCaptcha/composer.json
new file mode 100644
index 0000000000000..e71ef8c0ec7de
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/composer.json
@@ -0,0 +1,30 @@
+{
+ "name": "magento/module-paypal-captcha",
+ "description": "Provides CAPTCHA validation for PayPal Payflow Pro",
+ "config": {
+ "sort-packages": true
+ },
+ "require": {
+ "php": "~7.1.3||~7.2.0",
+ "magento/framework": "*",
+ "magento/module-captcha": "*",
+ "magento/module-checkout": "*",
+ "magento/module-store": "*"
+ },
+ "suggest": {
+ "magento/module-paypal": "*"
+ },
+ "type": "magento2-module",
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\PaypalCaptcha\\": ""
+ }
+ }
+}
diff --git a/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml b/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml
new file mode 100644
index 0000000000000..12afd8ceda60e
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/etc/adminhtml/system.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+ CAPTCHA for "Create user", "Forgot password", "Payflow Pro" forms is always enabled if chosen.
+
+
+
+
+
diff --git a/app/code/Magento/PaypalCaptcha/etc/config.xml b/app/code/Magento/PaypalCaptcha/etc/config.xml
new file mode 100644
index 0000000000000..133a78a42f7b4
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/etc/config.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+
+
+
+ Payflow Pro
+
+
+
+
+
+
diff --git a/app/code/Magento/PaypalCaptcha/etc/frontend/di.xml b/app/code/Magento/PaypalCaptcha/etc/frontend/di.xml
new file mode 100644
index 0000000000000..c236d5ea04ca0
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/etc/frontend/di.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+ - Magento\PaypalCaptcha\Model\Checkout\ConfigProviderPayPal
+
+
+
+
diff --git a/app/code/Magento/PaypalCaptcha/etc/frontend/events.xml b/app/code/Magento/PaypalCaptcha/etc/frontend/events.xml
new file mode 100644
index 0000000000000..ae706c4485d61
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/etc/frontend/events.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/app/code/Magento/PaypalCaptcha/etc/module.xml b/app/code/Magento/PaypalCaptcha/etc/module.xml
new file mode 100644
index 0000000000000..425c829a5d391
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/etc/module.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/PaypalCaptcha/registration.php b/app/code/Magento/PaypalCaptcha/registration.php
new file mode 100644
index 0000000000000..4dac0582a6d1b
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/registration.php
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+ -
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
- uiComponent
+ - paypal-captcha
+ - paypal-captcha
+ - checkoutProvider
+ -
+
- Magento_Checkout/payment/before-place-order
+
+ -
+
-
+
- Magento_PaypalCaptcha/js/view/checkout/paymentCaptcha
+ - paypal-captcha
+ - co-payment-form
+ - checkoutConfig
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/requirejs-config.js b/app/code/Magento/PaypalCaptcha/view/frontend/requirejs-config.js
new file mode 100644
index 0000000000000..78e7add4ec690
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/view/frontend/requirejs-config.js
@@ -0,0 +1,14 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+var config = {
+ config: {
+ mixins: {
+ 'Magento_Checkout/js/view/payment/list': {
+ 'Magento_PaypalCaptcha/js/view/payment/list-mixin': true
+ }
+ }
+ }
+};
diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/checkout/paymentCaptcha.js b/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/checkout/paymentCaptcha.js
new file mode 100644
index 0000000000000..f8f119e3b3396
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/checkout/paymentCaptcha.js
@@ -0,0 +1,44 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'jquery',
+ 'Magento_Captcha/js/view/checkout/defaultCaptcha',
+ 'Magento_Captcha/js/model/captchaList',
+ 'Magento_Captcha/js/model/captcha'
+],
+function ($, defaultCaptcha, captchaList, Captcha) {
+ 'use strict';
+
+ return defaultCaptcha.extend({
+
+ /** @inheritdoc */
+ initialize: function () {
+ var captchaConfigPayment,
+ currentCaptcha;
+
+ this._super();
+
+ if (window[this.configSource] && window[this.configSource].captchaPayments) {
+ captchaConfigPayment = window[this.configSource].captchaPayments;
+
+ $.each(captchaConfigPayment, function (formId, captchaData) {
+ var captcha;
+
+ captchaData.formId = formId;
+ captcha = Captcha(captchaData);
+ captchaList.add(captcha);
+ });
+ }
+
+ currentCaptcha = captchaList.getCaptchaByFormId(this.formId);
+
+ if (currentCaptcha != null) {
+ currentCaptcha.setIsVisible(true);
+ this.setCurrentCaptcha(currentCaptcha);
+ }
+ }
+ });
+});
diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/payment/list-mixin.js b/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/payment/list-mixin.js
new file mode 100644
index 0000000000000..60172f696e9ed
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/view/frontend/web/js/view/payment/list-mixin.js
@@ -0,0 +1,54 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+
+define([
+ 'jquery',
+ 'Magento_Captcha/js/model/captchaList'
+], function ($, captchaList) {
+ 'use strict';
+
+ var mixin = {
+
+ formId: 'co-payment-form',
+
+ /**
+ * Sets custom template for Payflow Pro
+ *
+ * @param {Object} payment
+ * @returns {Object}
+ */
+ createComponent: function (payment) {
+
+ var component = this._super(payment);
+
+ if (component.component === 'Magento_Paypal/js/view/payment/method-renderer/payflowpro-method') {
+ component.template = 'Magento_PaypalCaptcha/payment/payflowpro-form';
+ $(window).off('clearTimeout')
+ .on('clearTimeout', this.clearTimeout.bind(this));
+ }
+
+ return component;
+ },
+
+ /**
+ * Overrides default window.clearTimeout() to catch errors from iframe and reload Captcha.
+ */
+ clearTimeout: function () {
+ var captcha = captchaList.getCaptchaByFormId(this.formId);
+
+ if (captcha !== null) {
+ captcha.refresh();
+ }
+ clearTimeout();
+ }
+ };
+
+ /**
+ * Overrides `Magento_Checkout/js/view/payment/list::createComponent`
+ */
+ return function (target) {
+ return target.extend(mixin);
+ };
+});
diff --git a/app/code/Magento/PaypalCaptcha/view/frontend/web/template/payment/payflowpro-form.html b/app/code/Magento/PaypalCaptcha/view/frontend/web/template/payment/payflowpro-form.html
new file mode 100644
index 0000000000000..fec5cf96b0324
--- /dev/null
+++ b/app/code/Magento/PaypalCaptcha/view/frontend/web/template/payment/payflowpro-form.html
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Persistent/Model/QuoteManager.php b/app/code/Magento/Persistent/Model/QuoteManager.php
index 35c2c70be30dc..8ae22e4c26c6f 100644
--- a/app/code/Magento/Persistent/Model/QuoteManager.php
+++ b/app/code/Magento/Persistent/Model/QuoteManager.php
@@ -7,6 +7,8 @@
/**
* Class QuoteManager
+ *
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class QuoteManager
{
@@ -87,6 +89,7 @@ public function setGuest($checkQuote = false)
->setCustomerLastname(null)
->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID)
->setIsPersistent(false)
+ ->setCustomerIsGuest(true)
->removeAllAddresses();
//Create guest addresses
$quote->getShippingAddress();
diff --git a/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php b/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php
index f3720960ca6e5..79fdf44c3c551 100644
--- a/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php
+++ b/app/code/Magento/Persistent/Observer/CheckExpirePersistentQuoteObserver.php
@@ -1,6 +1,5 @@
_persistentSession->isPersistent() &&
!$this->_customerSession->isLoggedIn() &&
$this->_checkoutSession->getQuoteId() &&
- !$this->isRequestFromCheckoutPage($this->request)
+ !$this->isRequestFromCheckoutPage($this->request) &&
// persistent session does not expire on onepage checkout page
+ (
+ $this->_checkoutSession->getQuote()->getIsPersistent() ||
+ $this->_checkoutSession->getQuote()->getCustomerIsGuest()
+ )
) {
$this->_eventManager->dispatch('persistent_session_expired');
$this->quoteManager->expire();
diff --git a/app/code/Magento/Persistent/Observer/SetQuotePersistentDataObserver.php b/app/code/Magento/Persistent/Observer/SetQuotePersistentDataObserver.php
index db6b6d1ee370d..2803bc998dcbe 100644
--- a/app/code/Magento/Persistent/Observer/SetQuotePersistentDataObserver.php
+++ b/app/code/Magento/Persistent/Observer/SetQuotePersistentDataObserver.php
@@ -1,6 +1,5 @@
_persistentSession->isPersistent() && !$this->_customerSession->isLoggedIn())
- && !$this->_persistentData->isShoppingCartPersist()
+ ($this->_persistentSession->isPersistent())
+ && $this->_persistentData->isShoppingCartPersist()
)
&& $this->quoteManager->isPersistent()
) {
diff --git a/app/code/Magento/Persistent/Test/Mftf/Test/ShippingQuotePersistedForGuestTest.xml b/app/code/Magento/Persistent/Test/Mftf/Test/ShippingQuotePersistedForGuestTest.xml
new file mode 100644
index 0000000000000..e5c77ee414362
--- /dev/null
+++ b/app/code/Magento/Persistent/Test/Mftf/Test/ShippingQuotePersistedForGuestTest.xml
@@ -0,0 +1,84 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 150
+
+
+
+ John1
+ Doe1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{US_Address_CA.postcode}}
+ grabTextPostCode
+
+
+
diff --git a/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php
index 46dda1be365d4..b096dd2317a33 100644
--- a/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php
+++ b/app/code/Magento/Persistent/Test/Unit/Observer/CheckExpirePersistentQuoteObserverTest.php
@@ -1,12 +1,16 @@
checkoutSessionMock,
$this->requestMock
);
+ $this->quoteMock = $this->getMockBuilder(Quote::class)
+ ->setMethods(['getCustomerIsGuest', 'getIsPersistent'])
+ ->disableOriginalConstructor()
+ ->getMock();
}
public function testExecuteWhenCanNotApplyPersistentData()
@@ -133,6 +146,11 @@ public function testExecuteWhenPersistentIsEnabled(
->willReturn(true);
$this->persistentHelperMock->expects($this->once())->method('isEnabled')->willReturn(true);
$this->sessionMock->expects($this->once())->method('isPersistent')->willReturn(false);
+ $this->checkoutSessionMock
+ ->method('getQuote')
+ ->willReturn($this->quoteMock);
+ $this->quoteMock->method('getCustomerIsGuest')->willReturn(true);
+ $this->quoteMock->method('getIsPersistent')->willReturn(true);
$this->customerSessionMock
->expects($this->atLeastOnce())
->method('isLoggedIn')
diff --git a/app/code/Magento/Persistent/Test/Unit/Observer/SetQuotePersistentDataObserverTest.php b/app/code/Magento/Persistent/Test/Unit/Observer/SetQuotePersistentDataObserverTest.php
index 6724743789cea..ffa829e8456cc 100644
--- a/app/code/Magento/Persistent/Test/Unit/Observer/SetQuotePersistentDataObserverTest.php
+++ b/app/code/Magento/Persistent/Test/Unit/Observer/SetQuotePersistentDataObserverTest.php
@@ -7,6 +7,9 @@
namespace Magento\Persistent\Test\Unit\Observer;
+/**
+ * Observer test for setting "is_persistent" value to quote
+ */
class SetQuotePersistentDataObserverTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -83,7 +86,6 @@ public function testExecuteWhenQuoteNotExist()
->method('getEvent')
->will($this->returnValue($this->eventManagerMock));
$this->eventManagerMock->expects($this->once())->method('getQuote');
- $this->customerSessionMock->expects($this->never())->method('isLoggedIn');
$this->model->execute($this->observerMock);
}
@@ -98,8 +100,7 @@ public function testExecuteWhenSessionIsPersistent()
->expects($this->once())
->method('getQuote')
->will($this->returnValue($this->quoteMock));
- $this->customerSessionMock->expects($this->once())->method('isLoggedIn')->will($this->returnValue(false));
- $this->helperMock->expects($this->once())->method('isShoppingCartPersist')->will($this->returnValue(false));
+ $this->helperMock->expects($this->once())->method('isShoppingCartPersist')->will($this->returnValue(true));
$this->quoteManagerMock->expects($this->once())->method('isPersistent')->will($this->returnValue(true));
$this->quoteMock->expects($this->once())->method('setIsPersistent')->with(true);
$this->model->execute($this->observerMock);
diff --git a/app/code/Magento/ProductVideo/i18n/de_DE.csv b/app/code/Magento/ProductVideo/i18n/de_DE.csv
deleted file mode 100644
index ca24668bb8d16..0000000000000
--- a/app/code/Magento/ProductVideo/i18n/de_DE.csv
+++ /dev/null
@@ -1,10 +0,0 @@
-"Add video","Add video"
-"New Video","New Video"
-"Product Video","Product Video"
-"YouTube API key","YouTube API key"
-"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved."
-"Url","Url"
-"Preview Image","Preview Image"
-"Get Video Information","Get Video Information"
-"Youtube or Vimeo supported","Youtube or Vimeo supported"
-"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/es_ES.csv b/app/code/Magento/ProductVideo/i18n/es_ES.csv
deleted file mode 100644
index ca24668bb8d16..0000000000000
--- a/app/code/Magento/ProductVideo/i18n/es_ES.csv
+++ /dev/null
@@ -1,10 +0,0 @@
-"Add video","Add video"
-"New Video","New Video"
-"Product Video","Product Video"
-"YouTube API key","YouTube API key"
-"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved."
-"Url","Url"
-"Preview Image","Preview Image"
-"Get Video Information","Get Video Information"
-"Youtube or Vimeo supported","Youtube or Vimeo supported"
-"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/fr_FR.csv b/app/code/Magento/ProductVideo/i18n/fr_FR.csv
deleted file mode 100644
index ca24668bb8d16..0000000000000
--- a/app/code/Magento/ProductVideo/i18n/fr_FR.csv
+++ /dev/null
@@ -1,10 +0,0 @@
-"Add video","Add video"
-"New Video","New Video"
-"Product Video","Product Video"
-"YouTube API key","YouTube API key"
-"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved."
-"Url","Url"
-"Preview Image","Preview Image"
-"Get Video Information","Get Video Information"
-"Youtube or Vimeo supported","Youtube or Vimeo supported"
-"Delete image in all store views","Delete image in all store views"
diff --git a/app/code/Magento/ProductVideo/i18n/nl_NL.csv b/app/code/Magento/ProductVideo/i18n/nl_NL.csv
deleted file mode 100644
index 5ad8386573040..0000000000000
--- a/app/code/Magento/ProductVideo/i18n/nl_NL.csv
+++ /dev/null
@@ -1,10 +0,0 @@
-"Add video","Add video"
-"New Video","New Video"
-"Product Video","Product Video"
-"YouTube API key","YouTube API key"
-"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved."
-"Url","Url"
-"Preview Image","Preview Image"
-"Get Video Information","Get Video Information"
-"Youtube or Vimeo supported","Youtube or Vimeo supported"
-"Delete image in all store views","Delete image in all store views"
\ No newline at end of file
diff --git a/app/code/Magento/ProductVideo/i18n/pt_BR.csv b/app/code/Magento/ProductVideo/i18n/pt_BR.csv
deleted file mode 100644
index 5ad8386573040..0000000000000
--- a/app/code/Magento/ProductVideo/i18n/pt_BR.csv
+++ /dev/null
@@ -1,10 +0,0 @@
-"Add video","Add video"
-"New Video","New Video"
-"Product Video","Product Video"
-"YouTube API key","YouTube API key"
-"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved."
-"Url","Url"
-"Preview Image","Preview Image"
-"Get Video Information","Get Video Information"
-"Youtube or Vimeo supported","Youtube or Vimeo supported"
-"Delete image in all store views","Delete image in all store views"
\ No newline at end of file
diff --git a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv b/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv
deleted file mode 100644
index 5ad8386573040..0000000000000
--- a/app/code/Magento/ProductVideo/i18n/zh_Hans_CN.csv
+++ /dev/null
@@ -1,10 +0,0 @@
-"Add video","Add video"
-"New Video","New Video"
-"Product Video","Product Video"
-"YouTube API key","YouTube API key"
-"You have not entered youtube API key. No information about youtube video will be retrieved.","You have not entered youtube API key. No information about youtube video will be retrieved."
-"Url","Url"
-"Preview Image","Preview Image"
-"Get Video Information","Get Video Information"
-"Youtube or Vimeo supported","Youtube or Vimeo supported"
-"Delete image in all store views","Delete image in all store views"
\ No newline at end of file
diff --git a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
index 20deba5b9b46a..cd0f3b3d630a6 100644
--- a/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
+++ b/app/code/Magento/ProductVideo/view/frontend/web/js/fotorama-add-video-events.js
@@ -177,12 +177,14 @@ define([
* @private
*/
clearEvents: function () {
- this.fotoramaItem.off(
- 'fotorama:show.' + this.PV +
- ' fotorama:showend.' + this.PV +
- ' fotorama:fullscreenenter.' + this.PV +
- ' fotorama:fullscreenexit.' + this.PV
- );
+ if (this.fotoramaItem !== undefined) {
+ this.fotoramaItem.off(
+ 'fotorama:show.' + this.PV +
+ ' fotorama:showend.' + this.PV +
+ ' fotorama:fullscreenenter.' + this.PV +
+ ' fotorama:fullscreenexit.' + this.PV
+ );
+ }
},
/**
diff --git a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php
index e18ab8587fc71..60e5ad9f4caff 100644
--- a/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php
+++ b/app/code/Magento/Quote/Model/Cart/CartTotalRepository.php
@@ -79,7 +79,7 @@ public function __construct(
}
/**
- * {@inheritDoc}
+ * @inheritdoc
*
* @param int $cartId The cart ID.
* @return Totals Quote totals data.
diff --git a/app/code/Magento/Quote/Model/Quote.php b/app/code/Magento/Quote/Model/Quote.php
index 17d3b9205625f..b1f68d0411cf0 100644
--- a/app/code/Magento/Quote/Model/Quote.php
+++ b/app/code/Magento/Quote/Model/Quote.php
@@ -1375,14 +1375,13 @@ public function addShippingAddress(\Magento\Quote\Api\Data\AddressInterface $add
*
* @param bool $useCache
* @return \Magento\Eav\Model\Entity\Collection\AbstractCollection
- * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function getItemsCollection($useCache = true)
{
- if ($this->hasItemsCollection()) {
+ if ($this->hasItemsCollection() && $useCache) {
return $this->getData('items_collection');
}
- if (null === $this->_items) {
+ if (null === $this->_items || !$useCache) {
$this->_items = $this->_quoteItemCollectionFactory->create();
$this->extensionAttributesJoinProcessor->process($this->_items);
$this->_items->setQuote($this);
diff --git a/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php b/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php
index c5b8dc1c4b124..6fdb70350ed72 100644
--- a/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php
+++ b/app/code/Magento/Quote/Model/Quote/Address/BillingAddressPersister.php
@@ -12,6 +12,9 @@
use Magento\Quote\Model\QuoteAddressValidator;
use Magento\Customer\Api\AddressRepositoryInterface;
+/**
+ * Saves billing address for quotes.
+ */
class BillingAddressPersister
{
/**
@@ -37,6 +40,8 @@ public function __construct(
}
/**
+ * Save address for billing.
+ *
* @param CartInterface $quote
* @param AddressInterface $address
* @param bool $useForShipping
@@ -47,7 +52,7 @@ public function __construct(
public function save(CartInterface $quote, AddressInterface $address, $useForShipping = false)
{
/** @var \Magento\Quote\Model\Quote $quote */
- $this->addressValidator->validate($address);
+ $this->addressValidator->validateForCart($quote, $address);
$customerAddressId = $address->getCustomerAddressId();
$shippingAddress = null;
$addressData = [];
diff --git a/app/code/Magento/Quote/Model/QuoteAddressValidator.php b/app/code/Magento/Quote/Model/QuoteAddressValidator.php
index 9a86829bfc4ce..e7750f5879de5 100644
--- a/app/code/Magento/Quote/Model/QuoteAddressValidator.php
+++ b/app/code/Magento/Quote/Model/QuoteAddressValidator.php
@@ -6,10 +6,13 @@
namespace Magento\Quote\Model;
use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Quote\Api\Data\AddressInterface;
+use Magento\Quote\Api\Data\CartInterface;
/**
* Quote shipping/billing address validator service.
*
+ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
*/
class QuoteAddressValidator
{
@@ -28,7 +31,7 @@ class QuoteAddressValidator
protected $customerRepository;
/**
- * @var \Magento\Customer\Model\Session
+ * @deprecated This class is not a part of HTML presentation layer and should not use sessions.
*/
protected $customerSession;
@@ -50,44 +53,80 @@ public function __construct(
}
/**
- * Validates the fields in a specified address data object.
+ * Validate address.
*
- * @param \Magento\Quote\Api\Data\AddressInterface $addressData The address data object.
- * @return bool
+ * @param AddressInterface $address
+ * @param int|null $customerId Cart belongs to
+ * @return void
* @throws \Magento\Framework\Exception\InputException The specified address belongs to another customer.
* @throws \Magento\Framework\Exception\NoSuchEntityException The specified customer ID or address ID is not valid.
*/
- public function validate(\Magento\Quote\Api\Data\AddressInterface $addressData)
+ private function doValidate(AddressInterface $address, ?int $customerId): void
{
//validate customer id
- if ($addressData->getCustomerId()) {
- $customer = $this->customerRepository->getById($addressData->getCustomerId());
+ if ($customerId) {
+ $customer = $this->customerRepository->getById($customerId);
if (!$customer->getId()) {
throw new \Magento\Framework\Exception\NoSuchEntityException(
- __('Invalid customer id %1', $addressData->getCustomerId())
+ __('Invalid customer id %1', $customerId)
);
}
}
- if ($addressData->getCustomerAddressId()) {
+ if ($address->getCustomerAddressId()) {
+ //Existing address cannot belong to a guest
+ if (!$customerId) {
+ throw new \Magento\Framework\Exception\NoSuchEntityException(
+ __('Invalid customer address id %1', $address->getCustomerAddressId())
+ );
+ }
+ //Validating address ID
try {
- $this->addressRepository->getById($addressData->getCustomerAddressId());
+ $this->addressRepository->getById($address->getCustomerAddressId());
} catch (NoSuchEntityException $e) {
throw new \Magento\Framework\Exception\NoSuchEntityException(
- __('Invalid address id %1', $addressData->getId())
+ __('Invalid address id %1', $address->getId())
);
}
-
+ //Finding available customer's addresses
$applicableAddressIds = array_map(function ($address) {
/** @var \Magento\Customer\Api\Data\AddressInterface $address */
return $address->getId();
- }, $this->customerRepository->getById($addressData->getCustomerId())->getAddresses());
- if (!in_array($addressData->getCustomerAddressId(), $applicableAddressIds)) {
+ }, $this->customerRepository->getById($customerId)->getAddresses());
+ if (!in_array($address->getCustomerAddressId(), $applicableAddressIds)) {
throw new \Magento\Framework\Exception\NoSuchEntityException(
- __('Invalid customer address id %1', $addressData->getCustomerAddressId())
+ __('Invalid customer address id %1', $address->getCustomerAddressId())
);
}
}
+ }
+
+ /**
+ * Validates the fields in a specified address data object.
+ *
+ * @param \Magento\Quote\Api\Data\AddressInterface $addressData The address data object.
+ * @return bool
+ * @throws \Magento\Framework\Exception\InputException The specified address belongs to another customer.
+ * @throws \Magento\Framework\Exception\NoSuchEntityException The specified customer ID or address ID is not valid.
+ */
+ public function validate(AddressInterface $addressData)
+ {
+ $this->doValidate($addressData, $addressData->getCustomerId());
+
return true;
}
+
+ /**
+ * Validate address to be used for cart.
+ *
+ * @param CartInterface $cart
+ * @param AddressInterface $address
+ * @return void
+ * @throws \Magento\Framework\Exception\InputException The specified address belongs to another customer.
+ * @throws \Magento\Framework\Exception\NoSuchEntityException The specified customer ID or address ID is not valid.
+ */
+ public function validateForCart(CartInterface $cart, AddressInterface $address): void
+ {
+ $this->doValidate($address, $cart->getCustomerIsGuest() ? null : $cart->getCustomer()->getId());
+ }
}
diff --git a/app/code/Magento/Quote/Model/QuoteManagement.php b/app/code/Magento/Quote/Model/QuoteManagement.php
index 6ed8393f80658..2fcfd2dfadabb 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);
}
@@ -524,19 +532,7 @@ protected function submitQuote(QuoteEntity $quote, $orderData = [])
);
$this->quoteRepository->save($quote);
} catch (\Exception $e) {
- if (!empty($this->addressesToSync)) {
- foreach ($this->addressesToSync as $addressId) {
- $this->addressRepository->deleteById($addressId);
- }
- }
- $this->eventManager->dispatch(
- 'sales_model_service_quote_submit_failure',
- [
- 'order' => $order,
- 'quote' => $quote,
- 'exception' => $e
- ]
- );
+ $this->rollbackAddresses($quote, $order, $e);
throw $e;
}
return $order;
@@ -603,4 +599,41 @@ protected function _prepareCustomerQuote($quote)
$shipping->setIsDefaultBilling(true);
}
}
+
+ /**
+ * Remove related to order and quote addresses and submit exception to further processing.
+ *
+ * @param Quote $quote
+ * @param \Magento\Sales\Api\Data\OrderInterface $order
+ * @param \Exception $e
+ * @throws \Exception
+ */
+ private function rollbackAddresses(
+ QuoteEntity $quote,
+ \Magento\Sales\Api\Data\OrderInterface $order,
+ \Exception $e
+ ): void {
+ try {
+ if (!empty($this->addressesToSync)) {
+ foreach ($this->addressesToSync as $addressId) {
+ $this->addressRepository->deleteById($addressId);
+ }
+ }
+ $this->eventManager->dispatch(
+ 'sales_model_service_quote_submit_failure',
+ [
+ 'order' => $order,
+ 'quote' => $quote,
+ 'exception' => $e,
+ ]
+ );
+ } catch (\Exception $consecutiveException) {
+ $message = sprintf(
+ "An exception occurred on 'sales_model_service_quote_submit_failure' event: %s",
+ $consecutiveException->getMessage()
+ );
+
+ throw new \Exception($message, 0, $e);
+ }
+ }
}
diff --git a/app/code/Magento/Quote/Model/ResourceModel/Quote.php b/app/code/Magento/Quote/Model/ResourceModel/Quote.php
index 946c0e0c5f3b8..ae26407c74522 100644
--- a/app/code/Magento/Quote/Model/ResourceModel/Quote.php
+++ b/app/code/Magento/Quote/Model/ResourceModel/Quote.php
@@ -23,8 +23,8 @@ class Quote extends AbstractDb
/**
* @param \Magento\Framework\Model\ResourceModel\Db\Context $context
- * @param Snapshot $entitySnapshot,
- * @param RelationComposite $entityRelationComposite,
+ * @param Snapshot $entitySnapshot
+ * @param RelationComposite $entityRelationComposite
* @param \Magento\SalesSequence\Model\Manager $sequenceManager
* @param string $connectionName
*/
@@ -296,7 +296,7 @@ public function markQuotesRecollect($productIds)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(\Magento\Framework\Model\AbstractModel $object)
{
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/Model/ShippingAddressManagement.php b/app/code/Magento/Quote/Model/ShippingAddressManagement.php
index d8e70c68ba33f..b9edcc13d0077 100644
--- a/app/code/Magento/Quote/Model/ShippingAddressManagement.php
+++ b/app/code/Magento/Quote/Model/ShippingAddressManagement.php
@@ -79,7 +79,7 @@ public function __construct(
}
/**
- * {@inheritDoc}
+ * @inheritDoc
* @SuppressWarnings(PHPMD.NPathComplexity)
*/
public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $address)
@@ -95,7 +95,7 @@ public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $addres
$saveInAddressBook = $address->getSaveInAddressBook() ? 1 : 0;
$sameAsBilling = $address->getSameAsBilling() ? 1 : 0;
$customerAddressId = $address->getCustomerAddressId();
- $this->addressValidator->validate($address);
+ $this->addressValidator->validateForCart($quote, $address);
$quote->setShippingAddress($address);
$address = $quote->getShippingAddress();
@@ -123,7 +123,7 @@ public function assign($cartId, \Magento\Quote\Api\Data\AddressInterface $addres
}
/**
- * {@inheritDoc}
+ * @inheritDoc
*/
public function get($cartId)
{
diff --git a/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php b/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php
new file mode 100644
index 0000000000000..19a7e03264d8a
--- /dev/null
+++ b/app/code/Magento/Quote/Plugin/UpdateQuoteItemStore.php
@@ -0,0 +1,72 @@
+quoteRepository = $quoteRepository;
+ $this->checkoutSession = $checkoutSession;
+ }
+
+ /**
+ * Update store id in active quote after store view switching.
+ *
+ * @param StoreSwitcherInterface $subject
+ * @param string $result
+ * @param StoreInterface $fromStore store where we came from
+ * @param StoreInterface $targetStore store where to go to
+ * @param string $redirectUrl original url requested for redirect after switching
+ * @return string url to be redirected after switching
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function afterSwitch(
+ StoreSwitcherInterface $subject,
+ $result,
+ StoreInterface $fromStore,
+ StoreInterface $targetStore,
+ string $redirectUrl
+ ): string {
+ $quote = $this->checkoutSession->getQuote();
+ if ($quote->getIsActive()) {
+ $quote->setStoreId(
+ $targetStore->getId()
+ );
+ $quote->getItemsCollection(false);
+ $this->quoteRepository->save($quote);
+ }
+ return $result;
+ }
+}
diff --git a/app/code/Magento/Quote/Setup/Patch/Data/ConvertSerializedDataToJson.php b/app/code/Magento/Quote/Setup/Patch/Data/ConvertSerializedDataToJson.php
index f537280272227..6c23379a37cf0 100644
--- a/app/code/Magento/Quote/Setup/Patch/Data/ConvertSerializedDataToJson.php
+++ b/app/code/Magento/Quote/Setup/Patch/Data/ConvertSerializedDataToJson.php
@@ -3,18 +3,15 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-
namespace Magento\Quote\Setup\Patch\Data;
-use Magento\Framework\App\ResourceConnection;
use Magento\Quote\Setup\ConvertSerializedDataToJsonFactory;
use Magento\Quote\Setup\QuoteSetupFactory;
use Magento\Framework\Setup\Patch\DataPatchInterface;
use Magento\Framework\Setup\Patch\PatchVersionInterface;
/**
- * Class ConvertSerializedDataToJson
- * @package Magento\Quote\Setup\Patch
+ * Convert quote serialized data to json.
*/
class ConvertSerializedDataToJson implements DataPatchInterface, PatchVersionInterface
{
@@ -36,6 +33,8 @@ class ConvertSerializedDataToJson implements DataPatchInterface, PatchVersionInt
/**
* PatchInitial constructor.
* @param \Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup
+ * @param QuoteSetupFactory $quoteSetupFactory
+ * @param ConvertSerializedDataToJsonFactory $convertSerializedDataToJsonFactory
*/
public function __construct(
\Magento\Framework\Setup\ModuleDataSetupInterface $moduleDataSetup,
@@ -48,7 +47,7 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function apply()
{
@@ -57,7 +56,7 @@ public function apply()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getDependencies()
{
@@ -67,7 +66,7 @@ public static function getDependencies()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public static function getVersion()
{
@@ -75,7 +74,7 @@ public static function getVersion()
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getAliases()
{
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/Cart/CartTotalRepositoryTest.php b/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
index 1e999cb5e523e..804f0863d2d2a 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/Cart/CartTotalRepositoryTest.php
@@ -77,7 +77,8 @@ protected function setUp()
'getAllVisibleItems',
'getBaseCurrencyCode',
'getQuoteCurrencyCode',
- 'getItemsQty'
+ 'getItemsQty',
+ 'collectTotals'
]);
$this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class);
$this->addressMock = $this->createPartialMock(
diff --git a/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php b/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php
deleted file mode 100644
index 08f5f6a808561..0000000000000
--- a/app/code/Magento/Quote/Test/Unit/Model/QuoteAddressValidatorTest.php
+++ /dev/null
@@ -1,128 +0,0 @@
-objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
-
- $this->addressRepositoryMock = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class);
- $this->quoteAddressMock = $this->createPartialMock(
- \Magento\Quote\Model\Quote\Address::class,
- ['getCustomerId', 'load', 'getId', '__wakeup']
- );
- $this->customerRepositoryMock = $this->createMock(\Magento\Customer\Api\CustomerRepositoryInterface::class);
- $this->customerSessionMock = $this->createMock(\Magento\Customer\Model\Session::class);
- $this->model = $this->objectManager->getObject(
- \Magento\Quote\Model\QuoteAddressValidator::class,
- [
- 'addressRepository' => $this->addressRepositoryMock,
- 'customerRepository' => $this->customerRepositoryMock,
- 'customerSession' => $this->customerSessionMock
- ]
- );
- }
-
- /**
- * @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedExceptionMessage Invalid customer id 100
- */
- public function testValidateInvalidCustomer()
- {
- $customerId = 100;
- $address = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
- $customerMock = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class);
-
- $address->expects($this->atLeastOnce())->method('getCustomerId')->willReturn($customerId);
- $this->customerRepositoryMock->expects($this->once())->method('getById')->with($customerId)
- ->willReturn($customerMock);
- $this->model->validate($address);
- }
-
- /**
- * @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedExceptionMessage Invalid address id 101
- */
- public function testValidateInvalidAddress()
- {
- $address = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
- $this->customerRepositoryMock->expects($this->never())->method('getById');
- $address->expects($this->atLeastOnce())->method('getCustomerAddressId')->willReturn(101);
- $address->expects($this->once())->method('getId')->willReturn(101);
-
- $this->addressRepositoryMock->expects($this->once())->method('getById')
- ->willThrowException(new \Magento\Framework\Exception\NoSuchEntityException());
-
- $this->model->validate($address);
- }
-
- /**
- * Neither customer id used nor address id exists
- */
- public function testValidateNewAddress()
- {
- $this->customerRepositoryMock->expects($this->never())->method('getById');
- $this->addressRepositoryMock->expects($this->never())->method('getById');
- $address = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
- $this->assertTrue($this->model->validate($address));
- }
-
- public function testValidateWithValidAddress()
- {
- $addressCustomer = 100;
- $customerAddressId = 42;
-
- $address = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
- $address->expects($this->atLeastOnce())->method('getCustomerId')->willReturn($addressCustomer);
- $address->expects($this->atLeastOnce())->method('getCustomerAddressId')->willReturn($customerAddressId);
- $customerMock = $this->createMock(\Magento\Customer\Api\Data\CustomerInterface::class);
- $customerAddress = $this->createMock(\Magento\Quote\Api\Data\AddressInterface::class);
-
- $this->customerRepositoryMock->expects($this->exactly(2))->method('getById')->willReturn($customerMock);
- $customerMock->expects($this->once())->method('getId')->willReturn($addressCustomer);
-
- $this->addressRepositoryMock->expects($this->once())->method('getById')->willReturn($this->quoteAddressMock);
- $this->quoteAddressMock->expects($this->any())->method('getCustomerId')->willReturn($addressCustomer);
-
- $customerMock->expects($this->once())->method('getAddresses')->willReturn([$customerAddress]);
- $customerAddress->expects($this->once())->method('getId')->willReturn(42);
-
- $this->assertTrue($this->model->validate($address));
- }
-}
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/Quote/Test/Unit/Model/ShippingAddressManagementTest.php b/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php
deleted file mode 100644
index 89fea2bec73a8..0000000000000
--- a/app/code/Magento/Quote/Test/Unit/Model/ShippingAddressManagementTest.php
+++ /dev/null
@@ -1,282 +0,0 @@
-objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
- $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class);
- $this->scopeConfigMock = $this->createMock(\Magento\Framework\App\Config\ScopeConfigInterface::class);
-
- $this->quoteAddressMock = $this->createPartialMock(\Magento\Quote\Model\Quote\Address::class, [
- 'setSameAsBilling',
- 'setCollectShippingRates',
- '__wakeup',
- 'collectTotals',
- 'save',
- 'getId',
- 'getCustomerAddressId',
- 'getSaveInAddressBook',
- 'getSameAsBilling',
- 'importCustomerAddressData',
- 'setSaveInAddressBook',
- ]);
- $this->validatorMock = $this->createMock(\Magento\Quote\Model\QuoteAddressValidator::class);
- $this->totalsCollectorMock = $this->createMock(\Magento\Quote\Model\Quote\TotalsCollector::class);
- $this->addressRepository = $this->createMock(\Magento\Customer\Api\AddressRepositoryInterface::class);
-
- $this->amountErrorMessageMock = $this->createPartialMock(
- \Magento\Quote\Model\Quote\Validator\MinimumOrderAmount\ValidationMessage::class,
- ['getMessage']
- );
-
- $this->service = $this->objectManager->getObject(
- \Magento\Quote\Model\ShippingAddressManagement::class,
- [
- 'quoteRepository' => $this->quoteRepositoryMock,
- 'addressValidator' => $this->validatorMock,
- 'logger' => $this->createMock(\Psr\Log\LoggerInterface::class),
- 'scopeConfig' => $this->scopeConfigMock,
- 'totalsCollector' => $this->totalsCollectorMock,
- 'addressRepository' => $this->addressRepository
- ]
- );
- }
-
- /**
- * @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedExceptionMessage error345
- */
- public function testSetAddressValidationFailed()
- {
- $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
- $this->quoteRepositoryMock->expects($this->once())
- ->method('getActive')
- ->with('cart654')
- ->will($this->returnValue($quoteMock));
-
- $this->validatorMock->expects($this->once())->method('validate')
- ->will($this->throwException(new \Magento\Framework\Exception\NoSuchEntityException(__('error345'))));
-
- $this->service->assign('cart654', $this->quoteAddressMock);
- }
-
- public function testSetAddress()
- {
- $addressId = 1;
- $customerAddressId = 150;
-
- $quoteMock = $this->createPartialMock(
- \Magento\Quote\Model\Quote::class,
- ['getIsMultiShipping', 'isVirtual', 'validateMinimumAmount', 'setShippingAddress', 'getShippingAddress']
- );
- $this->quoteRepositoryMock->expects($this->once())
- ->method('getActive')
- ->with('cart867')
- ->willReturn($quoteMock);
- $quoteMock->expects($this->once())->method('isVirtual')->will($this->returnValue(false));
- $quoteMock->expects($this->once())
- ->method('setShippingAddress')
- ->with($this->quoteAddressMock)
- ->willReturnSelf();
-
- $this->quoteAddressMock->expects($this->once())->method('getSaveInAddressBook')->willReturn(1);
- $this->quoteAddressMock->expects($this->once())->method('getSameAsBilling')->willReturn(1);
- $this->quoteAddressMock->expects($this->once())->method('getCustomerAddressId')->willReturn($customerAddressId);
-
- $customerAddressMock = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class);
-
- $this->addressRepository->expects($this->once())
- ->method('getById')
- ->with($customerAddressId)
- ->willReturn($customerAddressMock);
-
- $this->validatorMock->expects($this->once())->method('validate')
- ->with($this->quoteAddressMock)
- ->willReturn(true);
-
- $quoteMock->expects($this->exactly(3))->method('getShippingAddress')->willReturn($this->quoteAddressMock);
- $this->quoteAddressMock->expects($this->once())
- ->method('importCustomerAddressData')
- ->with($customerAddressMock)
- ->willReturnSelf();
-
- $this->quoteAddressMock->expects($this->once())->method('setSameAsBilling')->with(1)->willReturnSelf();
- $this->quoteAddressMock->expects($this->once())->method('setSaveInAddressBook')->with(1)->willReturnSelf();
- $this->quoteAddressMock->expects($this->once())
- ->method('setCollectShippingRates')
- ->with(true)
- ->willReturnSelf();
-
- $this->quoteAddressMock->expects($this->once())->method('save')->willReturnSelf();
- $this->quoteAddressMock->expects($this->once())->method('getId')->will($this->returnValue($addressId));
-
- $this->assertEquals($addressId, $this->service->assign('cart867', $this->quoteAddressMock));
- }
-
- /**
- * @expectedException \Magento\Framework\Exception\NoSuchEntityException
- * @expectedExceptionMessage The Cart includes virtual product(s) only, so a shipping address is not used.
- */
- public function testSetAddressForVirtualProduct()
- {
- $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
- $this->quoteRepositoryMock->expects($this->once())
- ->method('getActive')
- ->with('cart867')
- ->will($this->returnValue($quoteMock));
- $quoteMock->expects($this->once())->method('isVirtual')->will($this->returnValue(true));
- $quoteMock->expects($this->never())->method('setShippingAddress');
-
- $this->quoteAddressMock->expects($this->never())->method('getCustomerAddressId');
- $this->quoteAddressMock->expects($this->never())->method('setSaveInAddressBook');
-
- $quoteMock->expects($this->never())->method('save');
-
- $this->service->assign('cart867', $this->quoteAddressMock);
- }
-
- /**
- * @expectedException \Magento\Framework\Exception\InputException
- * @expectedExceptionMessage The address failed to save. Verify the address and try again.
- */
- public function testSetAddressWithInabilityToSaveQuote()
- {
- $this->quoteAddressMock->expects($this->once())->method('save')->willThrowException(
- new \Exception('The address failed to save. Verify the address and try again.')
- );
-
- $customerAddressId = 150;
-
- $quoteMock = $this->createPartialMock(
- \Magento\Quote\Model\Quote::class,
- ['getIsMultiShipping', 'isVirtual', 'validateMinimumAmount', 'setShippingAddress', 'getShippingAddress']
- );
- $this->quoteRepositoryMock->expects($this->once())
- ->method('getActive')
- ->with('cart867')
- ->willReturn($quoteMock);
- $quoteMock->expects($this->once())->method('isVirtual')->will($this->returnValue(false));
- $quoteMock->expects($this->once())
- ->method('setShippingAddress')
- ->with($this->quoteAddressMock)
- ->willReturnSelf();
-
- $customerAddressMock = $this->createMock(\Magento\Customer\Api\Data\AddressInterface::class);
-
- $this->addressRepository->expects($this->once())
- ->method('getById')
- ->with($customerAddressId)
- ->willReturn($customerAddressMock);
-
- $this->validatorMock->expects($this->once())->method('validate')
- ->with($this->quoteAddressMock)
- ->willReturn(true);
-
- $this->quoteAddressMock->expects($this->once())->method('getSaveInAddressBook')->willReturn(1);
- $this->quoteAddressMock->expects($this->once())->method('getSameAsBilling')->willReturn(1);
- $this->quoteAddressMock->expects($this->once())->method('getCustomerAddressId')->willReturn($customerAddressId);
-
- $quoteMock->expects($this->exactly(2))->method('getShippingAddress')->willReturn($this->quoteAddressMock);
- $this->quoteAddressMock->expects($this->once())
- ->method('importCustomerAddressData')
- ->with($customerAddressMock)
- ->willReturnSelf();
-
- $this->quoteAddressMock->expects($this->once())->method('setSameAsBilling')->with(1)->willReturnSelf();
- $this->quoteAddressMock->expects($this->once())->method('setSaveInAddressBook')->with(1)->willReturnSelf();
- $this->quoteAddressMock->expects($this->once())
- ->method('setCollectShippingRates')
- ->with(true)
- ->willReturnSelf();
-
- $this->service->assign('cart867', $this->quoteAddressMock);
- }
-
- public function testGetAddress()
- {
- $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
- $this->quoteRepositoryMock->expects($this->once())->method('getActive')->with('cartId')->will(
- $this->returnValue($quoteMock)
- );
-
- $addressMock = $this->createMock(\Magento\Quote\Model\Quote\Address::class);
- $quoteMock->expects($this->any())->method('getShippingAddress')->will($this->returnValue($addressMock));
- $quoteMock->expects($this->any())->method('isVirtual')->will($this->returnValue(false));
- $this->assertEquals($addressMock, $this->service->get('cartId'));
- }
-
- /**
- * @expectedException \Exception
- * @expectedExceptionMessage The Cart includes virtual product(s) only, so a shipping address is not used.
- */
- public function testGetAddressOfQuoteWithVirtualProducts()
- {
- $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
- $this->quoteRepositoryMock->expects($this->once())->method('getActive')->with('cartId')->will(
- $this->returnValue($quoteMock)
- );
-
- $quoteMock->expects($this->any())->method('isVirtual')->will($this->returnValue(true));
- $quoteMock->expects($this->never())->method('getShippingAddress');
-
- $this->service->get('cartId');
- }
-}
diff --git a/app/code/Magento/Quote/etc/db_schema.xml b/app/code/Magento/Quote/etc/db_schema.xml
index 6f9f81ba6b3fa..48954f1af90fc 100644
--- a/app/code/Magento/Quote/etc/db_schema.xml
+++ b/app/code/Magento/Quote/etc/db_schema.xml
@@ -202,6 +202,8 @@
+
+
diff --git a/app/code/Magento/Quote/etc/frontend/di.xml b/app/code/Magento/Quote/etc/frontend/di.xml
index 125afb96f20fd..ecad94fbbc249 100644
--- a/app/code/Magento/Quote/etc/frontend/di.xml
+++ b/app/code/Magento/Quote/etc/frontend/di.xml
@@ -12,6 +12,9 @@
Magento\Checkout\Model\Session\Proxy
+
+
+
diff --git a/app/code/Magento/Quote/etc/sales.xml b/app/code/Magento/Quote/etc/sales.xml
index 3d54a6375c8d9..3db72a1226236 100644
--- a/app/code/Magento/Quote/etc/sales.xml
+++ b/app/code/Magento/Quote/etc/sales.xml
@@ -9,7 +9,7 @@
diff --git a/app/code/Magento/Quote/i18n/en_US.csv b/app/code/Magento/Quote/i18n/en_US.csv
index ae7453aa0d0cc..b24179297493a 100644
--- a/app/code/Magento/Quote/i18n/en_US.csv
+++ b/app/code/Magento/Quote/i18n/en_US.csv
@@ -65,3 +65,5 @@ error345,error345
Carts,Carts
"Manage carts","Manage carts"
"Invalid state change requested","Invalid state change requested"
+"Validated Country Code","Validated Country Code"
+"Validated Vat Number","Validated Vat Number"
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/AddProductsToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php
index 96259f2264943..005cf3a10ca80 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddProductsToCart.php
@@ -45,6 +45,8 @@ public function __construct(
* @param Quote $cart
* @param array $cartItems
* @throws GraphQlInputException
+ * @throws \Magento\Framework\Exception\LocalizedException
+ * @throws \Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException
*/
public function execute(Quote $cart, array $cartItems): void
{
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
index aa5b41daebdc3..6868ce3f7f1ff 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
@@ -61,11 +61,17 @@ public function __construct(
* @return void
* @throws GraphQlNoSuchEntityException
* @throws GraphQlInputException
+ * @throws \Magento\Framework\Exception\LocalizedException
*/
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 {
@@ -74,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/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..3506ffc8c8792 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetCartForUser.php
@@ -13,6 +13,7 @@
use Magento\Quote\Api\CartRepositoryInterface;
use Magento\Quote\Model\MaskedQuoteIdToQuoteIdInterface;
use Magento\Quote\Model\Quote;
+use Magento\Store\Model\StoreManagerInterface;
/**
* Get cart
@@ -29,28 +30,36 @@ class GetCartForUser
*/
private $cartRepository;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
+
/**
* @param MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId
* @param CartRepositoryInterface $cartRepository
+ * @param StoreManagerInterface $storeManager
*/
public function __construct(
MaskedQuoteIdToQuoteIdInterface $maskedQuoteIdToQuoteId,
- CartRepositoryInterface $cartRepository
+ CartRepositoryInterface $cartRepository,
+ StoreManagerInterface $storeManager
) {
$this->maskedQuoteIdToQuoteId = $maskedQuoteIdToQuoteId;
$this->cartRepository = $cartRepository;
+ $this->storeManager = $storeManager;
}
/**
* 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 +78,29 @@ 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.')
+ );
+ }
+
+ if ((int)$cart->getStoreId() !== (int)$this->storeManager->getStore()->getId()) {
+ throw new GraphQlNoSuchEntityException(
+ __(
+ 'Wrong store code specified for cart "%masked_cart_id"',
+ ['masked_cart_id' => $cartHash]
+ )
+ );
+ }
+
+ $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..89124c594dd87
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/GetQuoteAddress.php
@@ -0,0 +1,83 @@
+quoteAddressFactory = $quoteAddressFactory;
+ $this->quoteAddressResource = $quoteAddressResource;
+ }
+
+ /**
+ * Get quote address
+ *
+ * @param CartInterface $cart
+ * @param int $quoteAddressId
+ * @param int|null $customerId
+ * @return AddressInterface
+ * @throws GraphQlAuthorizationException
+ * @throws GraphQlNoSuchEntityException
+ */
+ public function execute(CartInterface $cart, 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])
+ );
+ }
+
+ // TODO: GetQuoteAddress::execute should depend only on AddressInterface contract
+ // https://github.com/magento/graphql-ce/issues/550
+ if ($quoteAddress->getQuoteId() !== $cart->getId()) {
+ throw new GraphQlNoSuchEntityException(
+ __('Cart does not contain address with ID "%cart_address_id"', ['cart_address_id' => $quoteAddressId])
+ );
+ }
+
+ if ((int)$quoteAddress->getCustomerId() !== (int)$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..13d6a1d3dce70
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php
@@ -0,0 +1,99 @@
+quoteAddressFactory = $quoteAddressFactory;
+ $this->getCustomerAddress = $getCustomerAddress;
+ $this->addressHelper = $addressHelper;
+ }
+
+ /**
+ * Create QuoteAddress based on input data
+ *
+ * @param array $addressInput
+ * @return QuoteAddress
+ * @throws GraphQlInputException
+ */
+ public function createBasedOnInputData(array $addressInput): QuoteAddress
+ {
+ $addressInput['country_id'] = $addressInput['country_code'] ?? '';
+
+ $maxAllowedLineCount = $this->addressHelper->getStreetLines();
+ if (is_array($addressInput['street']) && count($addressInput['street']) > $maxAllowedLineCount) {
+ throw new GraphQlInputException(
+ __('"Street Address" cannot contain more than %1 lines.', $maxAllowedLineCount)
+ );
+ }
+
+ $quoteAddress = $this->quoteAddressFactory->create();
+ $quoteAddress->addData($addressInput);
+ return $quoteAddress;
+ }
+
+ /**
+ * Create Quote Address based on Customer Address
+ *
+ * @param int $customerAddressId
+ * @param int $customerId
+ * @return QuoteAddress
+ * @throws GraphQlInputException
+ * @throws GraphQlAuthorizationException
+ * @throws GraphQlNoSuchEntityException
+ */
+ public function createBasedOnCustomerAddress(int $customerAddressId, int $customerId): QuoteAddress
+ {
+ $customerAddress = $this->getCustomerAddress->execute((int)$customerAddressId, $customerId);
+
+ $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
new file mode 100644
index 0000000000000..c2bac13c07067
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php
@@ -0,0 +1,103 @@
+quoteAddressFactory = $quoteAddressFactory;
+ $this->getCustomer = $getCustomer;
+ $this->assignBillingAddressToCart = $assignBillingAddressToCart;
+ }
+
+ /**
+ * Set billing address for a specified shopping cart
+ *
+ * @param ContextInterface $context
+ * @param CartInterface $cart
+ * @param array $billingAddressInput
+ * @return void
+ * @throws GraphQlInputException
+ * @throws GraphQlAuthenticationException
+ * @throws GraphQlAuthorizationException
+ * @throws GraphQlNoSuchEntityException
+ */
+ public function execute(ContextInterface $context, CartInterface $cart, array $billingAddressInput): void
+ {
+ $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->quoteAddressFactory->createBasedOnInputData($addressInput);
+ } else {
+ $customer = $this->getCustomer->execute($context);
+ $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress(
+ (int)$customerAddressId,
+ (int)$customer->getId()
+ );
+ }
+
+ $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..6b0e2a311bf44
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingAddressesOnCart.php
@@ -0,0 +1,88 @@
+quoteAddressFactory = $quoteAddressFactory;
+ $this->getCustomer = $getCustomer;
+ $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);
+ $shippingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress(
+ (int)$customerAddressId,
+ (int)$customer->getId()
+ );
+ }
+
+ $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..730cf1b0ffee3
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetShippingMethodsOnCart.php
@@ -0,0 +1,71 @@
+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($cart, $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/CartAddresses.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php
similarity index 62%
rename from app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php
rename to app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php
index 69544672bf12e..8251089abcd60 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartAddresses.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/AppliedCoupon.php
@@ -11,25 +11,25 @@
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;
+use Magento\Quote\Api\CouponManagementInterface;
/**
* @inheritdoc
*/
-class CartAddresses implements ResolverInterface
+class AppliedCoupon implements ResolverInterface
{
/**
- * @var AddressDataProvider
+ * @var CouponManagementInterface
*/
- private $addressDataProvider;
+ private $couponManagement;
/**
- * @param AddressDataProvider $addressDataProvider
+ * @param CouponManagementInterface $couponManagement
*/
public function __construct(
- AddressDataProvider $addressDataProvider
+ CouponManagementInterface $couponManagement
) {
- $this->addressDataProvider = $addressDataProvider;
+ $this->couponManagement = $couponManagement;
}
/**
@@ -40,9 +40,10 @@ public function resolve(Field $field, $context, ResolveInfo $info, array $value
if (!isset($value['model'])) {
throw new LocalizedException(__('"model" value should be specified'));
}
-
$cart = $value['model'];
+ $cartId = $cart->getId();
- return $this->addressDataProvider->getCartAddresses($cart);
+ $appliedCoupon = $this->couponManagement->get($cartId);
+ 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..4de0464681186 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,20 @@ 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) {
+ $message = $e->getMessage();
+ if (preg_match('/The "\d+" Cart doesn\'t contain products/', $message)) {
+ $message = 'Cart does not contain products.';
+ }
+ throw new GraphQlNoSuchEntityException(__($message), $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
new file mode 100644
index 0000000000000..a6bb0b0d04df1
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/BillingAddress.php
@@ -0,0 +1,54 @@
+extractQuoteAddressData = $extractQuoteAddressData;
+ }
+
+ /**
+ * @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 CartInterface $cart */
+ $cart = $value['model'];
+
+ $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/PlaceOrder.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php
new file mode 100644
index 0000000000000..3bd46a664f2ab
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/PlaceOrder.php
@@ -0,0 +1,83 @@
+getCartForUser = $getCartForUser;
+ $this->cartManagement = $cartManagement;
+ $this->orderRepository = $orderRepository;
+ }
+
+ /**
+ * @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'];
+
+ $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
+
+ try {
+ $orderId = $this->cartManagement->placeOrder($cart->getId());
+ $order = $this->orderRepository->get($orderId);
+
+ return [
+ 'order' => [
+ 'order_id' => $order->getIncrementId(),
+ ],
+ ];
+ } catch (NoSuchEntityException $e) {
+ throw new GraphQlNoSuchEntityException(__($e->getMessage()), $e);
+ } catch (LocalizedException $e) {
+ throw new GraphQlInputException(__('Unable to place order: %message', ['message' => $e->getMessage()]), $e);
+ }
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/RemoveCouponFromCart.php
index c21d869ddac7d..f81ea3020d3d0 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,20 @@ 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) {
+ $message = $e->getMessage();
+ if (preg_match('/The "\d+" Cart doesn\'t contain products/', $message)) {
+ $message = 'Cart does not contain products';
+ }
+ throw new GraphQlNoSuchEntityException(__($message), $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/SelectedPaymentMethod.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php
new file mode 100644
index 0000000000000..7a99b04638ac3
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SelectedPaymentMethod.php
@@ -0,0 +1,42 @@
+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
new file mode 100644
index 0000000000000..f7c9a4b0697b8
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/SetBillingAddressOnCart.php
@@ -0,0 +1,68 @@
+getCartForUser = $getCartForUser;
+ $this->setBillingAddressOnCart = $setBillingAddressOnCart;
+ }
+
+ /**
+ * @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']['billing_address']) || empty($args['input']['billing_address'])) {
+ throw new GraphQlInputException(__('Required parameter "billing_address" is missing'));
+ }
+ $billingAddress = $args['input']['billing_address'];
+
+ $cart = $this->getCartForUser->execute($maskedCartId, $context->getUserId());
+ $this->setBillingAddressOnCart->execute($context, $cart, $billingAddress);
+
+ return [
+ 'cart' => [
+ '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
new file mode 100644
index 0000000000000..eb3b0966740eb
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/ShippingAddresses.php
@@ -0,0 +1,56 @@
+extractQuoteAddressData = $extractQuoteAddressData;
+ }
+
+ /**
+ * @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 Quote $cart */
+ $cart = $value['model'];
+
+ $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/composer.json b/app/code/Magento/QuoteGraphQl/composer.json
index 1bf4d581a5fe3..22ca9cfdfae9a 100644
--- a/app/code/Magento/QuoteGraphQl/composer.json
+++ b/app/code/Magento/QuoteGraphQl/composer.json
@@ -10,7 +10,8 @@
"magento/module-catalog": "*",
"magento/module-store": "*",
"magento/module-customer": "*",
- "magento/module-customer-graph-ql": "*"
+ "magento/module-customer-graph-ql": "*",
+ "magento/module-sales": "*"
},
"suggest": {
"magento/module-graph-ql": "*"
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 4c1101a5f90a8..9ec3492f64531 100644
--- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
@@ -2,30 +2,62 @@
# 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")
- setBillingAddressOnCart(input: SetBillingAddressOnCartInput): SetBillingAddressOnCartOutput
+ 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")
+ placeOrder(input: PlaceOrderInput): PlaceOrderOutput @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\PlaceOrder")
}
-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,11 +65,30 @@ 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!
+}
+
+input BillingAddressInput {
customer_address_id: Int
address: CartAddressInput
- # TODO: consider adding "Same as shipping" option
+ use_for_shipping: Boolean
}
input CartAddressInput {
@@ -55,52 +106,64 @@ 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 PlaceOrderInput {
+ cart_id: String!
}
-type SetShippingAddressesOnCartOutput {
+input SetPaymentMethodOnCartInput {
+ cart_id: String!
+ payment_method: PaymentMethodInput!
+}
+
+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 {
cart: Cart!
}
+type PlaceOrderOutput {
+ order: Order!
+}
+
type Cart {
- cart_id: String
- items: [CartItemInterface]
- applied_coupon: AppliedCoupon
- addresses: [CartAddress]! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddresses")
+ 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
@@ -111,15 +174,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!
}
@@ -133,12 +196,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 {
@@ -158,22 +242,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!
}
@@ -181,9 +262,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") {
@@ -214,3 +294,7 @@ type CartItemSelectedOptionValuePrice {
units: String!
type: PriceTypeEnum!
}
+
+type Order {
+ order_id: String
+}
diff --git a/app/code/Magento/ReleaseNotification/etc/di.xml b/app/code/Magento/ReleaseNotification/etc/di.xml
index 1404a6adb0a10..a4c434ff7f623 100644
--- a/app/code/Magento/ReleaseNotification/etc/di.xml
+++ b/app/code/Magento/ReleaseNotification/etc/di.xml
@@ -6,7 +6,6 @@
*/
-->
-
diff --git a/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/RefreshLifetime.php b/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/RefreshLifetime.php
index 1b7ae6398d30e..b868394593558 100644
--- a/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/RefreshLifetime.php
+++ b/app/code/Magento/Reports/Controller/Adminhtml/Report/Statistics/RefreshLifetime.php
@@ -1,12 +1,17 @@
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/Block/Adminhtml/Edit.php b/app/code/Magento/Review/Block/Adminhtml/Edit.php
index d6868eae6fcbc..f6f0ccef9b4e7 100644
--- a/app/code/Magento/Review/Block/Adminhtml/Edit.php
+++ b/app/code/Magento/Review/Block/Adminhtml/Edit.php
@@ -159,13 +159,13 @@ protected function _construct()
}
if ($this->getRequest()->getParam('ret', false) == 'pending') {
- $this->buttonList->update('back', 'onclick', 'setLocation(\'' . $this->getUrl('catalog/*/pending') . '\')');
+ $this->buttonList->update('back', 'onclick', 'setLocation(\'' . $this->getUrl('review/*/pending') . '\')');
$this->buttonList->update(
'delete',
'onclick',
'deleteConfirm(' . '\'' . __(
'Are you sure you want to do this?'
- ) . '\' ' . '\'' . $this->getUrl(
+ ) . '\', ' . '\'' . $this->getUrl(
'*/*/delete',
[$this->_objectId => $this->getRequest()->getParam($this->_objectId), 'ret' => 'pending']
) . '\'' . ')'
diff --git a/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php b/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php
index 35187e46933bc..6217729f53e50 100644
--- a/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php
+++ b/app/code/Magento/Review/Controller/Adminhtml/Product/Save.php
@@ -10,9 +10,14 @@
use Magento\Framework\Controller\ResultFactory;
use Magento\Framework\Exception\LocalizedException;
+/**
+ * Save Review action.
+ */
class Save extends ProductController implements HttpPostActionInterface
{
/**
+ * Save Review action.
+ *
* @return \Magento\Backend\Model\View\Result\Redirect
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
@@ -64,7 +69,7 @@ public function execute()
if ($nextId) {
$resultRedirect->setPath('review/*/edit', ['id' => $nextId]);
} elseif ($this->getRequest()->getParam('ret') == 'pending') {
- $resultRedirect->setPath('*/*/pending');
+ $resultRedirect->setPath('review/*/pending');
} else {
$resultRedirect->setPath('*/*/');
}
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 397cc1cce61d6..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 e3532483f88af..7376329471921 100644
--- a/app/code/Magento/Review/etc/adminhtml/menu.xml
+++ b/app/code/Magento/Review/etc/adminhtml/menu.xml
@@ -8,7 +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/Block/Editable.php b/app/code/Magento/Rule/Block/Editable.php
index 67e4671236ea0..d53213a7df876 100644
--- a/app/code/Magento/Rule/Block/Editable.php
+++ b/app/code/Magento/Rule/Block/Editable.php
@@ -9,6 +9,8 @@
use Magento\Framework\View\Element\AbstractBlock;
/**
+ * Renderer for Editable sales rules
+ *
* @api
* @since 100.0.2
*/
@@ -52,9 +54,9 @@ public function render(\Magento\Framework\Data\Form\Element\AbstractElement $ele
if ($element->getShowAsText()) {
$html = ' getSelect()->order("FIELD($attributeField, $conditions)");
+ }
+ } else {
+ // Select ::where method adds braces even on empty expression
+ $collection->getSelect()->where($whereExpression);
+ }
}
}
}
diff --git a/app/code/Magento/Rule/view/adminhtml/web/rules.js b/app/code/Magento/Rule/view/adminhtml/web/rules.js
index 8e36562ebd7fe..202337c39da35 100644
--- a/app/code/Magento/Rule/view/adminhtml/web/rules.js
+++ b/app/code/Magento/Rule/view/adminhtml/web/rules.js
@@ -101,6 +101,9 @@ define([
if (!elem.multiple) {
Event.observe(elem, 'change', this.hideParamInputField.bind(this, container));
+
+ this.changeVisibilityForValueRuleParam(elem);
+
}
Event.observe(elem, 'blur', this.hideParamInputField.bind(this, container));
}
@@ -262,6 +265,8 @@ define([
label.innerHTML = str != '' ? str : '...';
}
+ this.changeVisibilityForValueRuleParam(elem);
+
elem = Element.down(container, 'input.input-text');
if (elem) {
@@ -293,6 +298,23 @@ define([
this.shownElement = null;
},
+ changeVisibilityForValueRuleParam: function(elem) {
+ let parsedElementId = elem.id.split('__');
+ if (parsedElementId[2] != 'operator') {
+ return false;
+ }
+
+ let valueElement = jQuery('#' + parsedElementId[0] + '__' + parsedElementId[1] + '__value');
+
+ if(elem.value == '<=>') {
+ valueElement.closest('.rule-param').hide();
+ } else {
+ valueElement.closest('.rule-param').show();
+ }
+
+ return true;
+ },
+
addRuleNewChild: function (elem) {
var parent_id = elem.id.replace(/^.*__(.*)__.*$/, '$1');
var children_ul_id = elem.id.replace(/__/g, ':').replace(/[^:]*$/, 'children').replace(/:/g, '__');
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/Block/Adminhtml/Order/Create/Search/Grid.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php
index 4bd2227d4bb1e..9a271f741edda 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid.php
@@ -5,12 +5,17 @@
*/
namespace Magento\Sales\Block\Adminhtml\Order\Create\Search;
+use Magento\Sales\Block\Adminhtml\Order\Create\Search\Grid\DataProvider\ProductCollection
+ as ProductCollectionDataProvider;
+use Magento\Framework\App\ObjectManager;
+
/**
* Adminhtml sales order create search products block
*
* @api
* @author Magento Core Team
* @since 100.0.2
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class Grid extends \Magento\Backend\Block\Widget\Grid\Extended
{
@@ -42,6 +47,11 @@ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended
*/
protected $_productFactory;
+ /**
+ * @var ProductCollectionDataProvider $productCollectionProvider
+ */
+ private $productCollectionProvider;
+
/**
* @param \Magento\Backend\Block\Template\Context $context
* @param \Magento\Backend\Helper\Data $backendHelper
@@ -50,6 +60,7 @@ class Grid extends \Magento\Backend\Block\Widget\Grid\Extended
* @param \Magento\Backend\Model\Session\Quote $sessionQuote
* @param \Magento\Sales\Model\Config $salesConfig
* @param array $data
+ * @param ProductCollectionDataProvider|null $productCollectionProvider
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
@@ -58,12 +69,15 @@ public function __construct(
\Magento\Catalog\Model\Config $catalogConfig,
\Magento\Backend\Model\Session\Quote $sessionQuote,
\Magento\Sales\Model\Config $salesConfig,
- array $data = []
+ array $data = [],
+ ProductCollectionDataProvider $productCollectionProvider = null
) {
$this->_productFactory = $productFactory;
$this->_catalogConfig = $catalogConfig;
$this->_sessionQuote = $sessionQuote;
$this->_salesConfig = $salesConfig;
+ $this->productCollectionProvider = $productCollectionProvider
+ ?: ObjectManager::getInstance()->get(ProductCollectionDataProvider::class);
parent::__construct($context, $backendHelper, $data);
}
@@ -140,20 +154,18 @@ protected function _addColumnFilterToCollection($column)
*/
protected function _prepareCollection()
{
+
$attributes = $this->_catalogConfig->getProductAttributes();
+ $store = $this->getStore();
+
/* @var $collection \Magento\Catalog\Model\ResourceModel\Product\Collection */
- $collection = $this->_productFactory->create()->getCollection();
- $collection->setStore(
- $this->getStore()
- )->addAttributeToSelect(
+ $collection = $this->productCollectionProvider->getCollectionForStore($store);
+ $collection->addAttributeToSelect(
$attributes
- )->addAttributeToSelect(
- 'sku'
- )->addStoreFilter()->addAttributeToFilter(
+ );
+ $collection->addAttributeToFilter(
'type_id',
$this->_salesConfig->getAvailableProductTypes()
- )->addAttributeToSelect(
- 'gift_message_available'
);
$this->setCollection($collection);
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php
new file mode 100644
index 0000000000000..733791a2f9549
--- /dev/null
+++ b/app/code/Magento/Sales/Block/Adminhtml/Order/Create/Search/Grid/DataProvider/ProductCollection.php
@@ -0,0 +1,55 @@
+collectionFactory = $collectionFactory;
+ }
+
+ /**
+ * Provide products collection filtered with store
+ *
+ * @param Store $store
+ * @return Collection
+ */
+ public function getCollectionForStore(Store $store):Collection
+ {
+ /** @var Collection $collection */
+ $collection = $this->collectionFactory->create();
+
+ $collection->setStore($store);
+ $collection->addAttributeToSelect(
+ 'gift_message_available'
+ );
+ $collection->addAttributeToSelect(
+ 'sku'
+ );
+ $collection->addStoreFilter();
+
+ return $collection;
+ }
+}
diff --git a/app/code/Magento/Sales/Block/Adminhtml/Totals.php b/app/code/Magento/Sales/Block/Adminhtml/Totals.php
index 83b155293c2b9..8172a3c0db4ad 100644
--- a/app/code/Magento/Sales/Block/Adminhtml/Totals.php
+++ b/app/code/Magento/Sales/Block/Adminhtml/Totals.php
@@ -5,6 +5,11 @@
*/
namespace Magento\Sales\Block\Adminhtml;
+use Magento\Sales\Model\Order;
+
+/**
+ * Adminhtml sales totals block
+ */
class Totals extends \Magento\Sales\Block\Order\Totals
{
/**
@@ -67,12 +72,16 @@ protected function _initTotals()
if (!$this->getSource()->getIsVirtual() && ((double)$this->getSource()->getShippingAmount() ||
$this->getSource()->getShippingDescription())
) {
+ $shippingLabel = __('Shipping & Handling');
+ if ($this->isFreeShipping($this->getOrder()) && $this->getSource()->getDiscountDescription()) {
+ $shippingLabel .= sprintf(' (%s)', $this->getSource()->getDiscountDescription());
+ }
$this->_totals['shipping'] = new \Magento\Framework\DataObject(
[
'code' => 'shipping',
'value' => $this->getSource()->getShippingAmount(),
'base_value' => $this->getSource()->getBaseShippingAmount(),
- 'label' => __('Shipping & Handling'),
+ 'label' => $shippingLabel,
]
);
}
@@ -109,4 +118,23 @@ protected function _initTotals()
return $this;
}
+
+ /**
+ * Availability of free shipping in at least one order item
+ *
+ * @param Order $order
+ * @return bool
+ */
+ private function isFreeShipping(Order $order): bool
+ {
+ $isFreeShipping = false;
+ foreach ($order->getItems() as $orderItem) {
+ if ($orderItem->getFreeShipping() == '1') {
+ $isFreeShipping = true;
+ break;
+ }
+ }
+
+ return $isFreeShipping;
+ }
}
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/Download/DownloadCustomOption.php b/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php
index d30839e96dccb..fc4e238d47c99 100644
--- a/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php
+++ b/app/code/Magento/Sales/Controller/Download/DownloadCustomOption.php
@@ -4,6 +4,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Sales\Controller\Download;
@@ -121,10 +122,10 @@ public function execute()
* Ends execution process
*
* @return void
- * @SuppressWarnings(PHPMD.ExitExpression)
*/
protected function endExecute()
{
+ // phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit(0);
}
}
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/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php
index 11908864236f6..48934e24a3795 100644
--- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php
+++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Creditmemo/DefaultCreditmemo.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Sales\Model\Order\Pdf\Items\Creditmemo;
/**
@@ -66,11 +68,18 @@ public function draw()
$lines = [];
// draw Product name
- $lines[0] = [['text' => $this->string->split($item->getName(), 35, true, true), 'feed' => 35]];
+ $lines[0] = [
+ [
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ 'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true),
+ 'feed' => 35
+ ]
+ ];
// draw SKU
$lines[0][] = [
- 'text' => $this->string->split($this->getSku($item), 17),
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17),
'feed' => 255,
'align' => 'right',
];
diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php
index 8562328025540..23c2c00daadc3 100644
--- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php
+++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Invoice/DefaultInvoice.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Sales\Model\Order\Pdf\Items\Invoice;
/**
@@ -66,11 +68,18 @@ public function draw()
$lines = [];
// draw Product name
- $lines[0] = [['text' => $this->string->split($item->getName(), 35, true, true), 'feed' => 35]];
+ $lines[0] = [
+ [
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ 'text' => $this->string->split(html_entity_decode($item->getName()), 35, true, true),
+ 'feed' => 35
+ ]
+ ];
// draw SKU
$lines[0][] = [
- 'text' => $this->string->split($this->getSku($item), 17),
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 17),
'feed' => 290,
'align' => 'right',
];
diff --git a/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php b/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php
index 6007e1dcf2b47..a88b508ba0789 100644
--- a/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php
+++ b/app/code/Magento/Sales/Model/Order/Pdf/Items/Shipment/DefaultShipment.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Sales\Model\Order\Pdf\Items\Shipment;
/**
@@ -65,14 +67,21 @@ public function draw()
$lines = [];
// draw Product name
- $lines[0] = [['text' => $this->string->split($item->getName(), 60, true, true), 'feed' => 100]];
+ $lines[0] = [
+ [
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ 'text' => $this->string->split(html_entity_decode($item->getName()), 60, true, true),
+ 'feed' => 100
+ ]
+ ];
// draw QTY
$lines[0][] = ['text' => $item->getQty() * 1, 'feed' => 35];
// draw SKU
$lines[0][] = [
- 'text' => $this->string->split($this->getSku($item), 25),
+ // phpcs:ignore Magento2.Functions.DiscouragedFunction
+ 'text' => $this->string->split(html_entity_decode($this->getSku($item)), 25),
'feed' => 565,
'align' => 'right',
];
diff --git a/app/code/Magento/Sales/Model/Service/InvoiceService.php b/app/code/Magento/Sales/Model/Service/InvoiceService.php
index 791274d3f23bd..02242e92c8bf5 100644
--- a/app/code/Magento/Sales/Model/Service/InvoiceService.php
+++ b/app/code/Magento/Sales/Model/Service/InvoiceService.php
@@ -7,9 +7,14 @@
use Magento\Sales\Api\InvoiceManagementInterface;
use Magento\Sales\Model\Order;
+use Magento\Framework\App\ObjectManager;
+use Magento\Framework\Serialize\Serializer\Json;
+use Magento\Catalog\Model\Product\Type;
/**
* Class InvoiceService
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class InvoiceService implements InvoiceManagementInterface
{
@@ -58,6 +63,13 @@ class InvoiceService implements InvoiceManagementInterface
*/
protected $orderConverter;
+ /**
+ * Serializer interface instance.
+ *
+ * @var Json
+ */
+ private $serializer;
+
/**
* Constructor
*
@@ -68,6 +80,7 @@ class InvoiceService implements InvoiceManagementInterface
* @param \Magento\Sales\Model\Order\InvoiceNotifier $notifier
* @param \Magento\Sales\Api\OrderRepositoryInterface $orderRepository
* @param \Magento\Sales\Model\Convert\Order $orderConverter
+ * @param Json|null $serializer
*/
public function __construct(
\Magento\Sales\Api\InvoiceRepositoryInterface $repository,
@@ -76,7 +89,8 @@ public function __construct(
\Magento\Framework\Api\FilterBuilder $filterBuilder,
\Magento\Sales\Model\Order\InvoiceNotifier $notifier,
\Magento\Sales\Api\OrderRepositoryInterface $orderRepository,
- \Magento\Sales\Model\Convert\Order $orderConverter
+ \Magento\Sales\Model\Convert\Order $orderConverter,
+ Json $serializer = null
) {
$this->repository = $repository;
$this->commentRepository = $commentRepository;
@@ -85,6 +99,7 @@ public function __construct(
$this->invoiceNotifier = $notifier;
$this->orderRepository = $orderRepository;
$this->orderConverter = $orderConverter;
+ $this->serializer = $serializer ?: ObjectManager::getInstance()->get(Json::class);
}
/**
@@ -172,13 +187,18 @@ private function prepareItemsQty(Order $order, array $qtys = [])
{
foreach ($order->getAllItems() as $orderItem) {
if (empty($qtys[$orderItem->getId()])) {
- $parentId = $orderItem->getParentItemId();
- if ($parentId && array_key_exists($parentId, $qtys)) {
- $qtys[$orderItem->getId()] = $qtys[$parentId];
+ 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;
}
}
+
$this->prepareItemQty($orderItem, $qtys);
}
@@ -186,20 +206,27 @@ private function prepareItemsQty(Order $order, array $qtys = [])
}
/**
- * Prepare qty to invoice item.
+ * Prepare qty_invoiced for order item
*
- * @param Order\Item $orderItem
+ * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem
* @param array $qtys
- * @return void
*/
private function prepareItemQty(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, &$qtys)
{
+ $this->prepareBundleQty($orderItem, $qtys);
+
if ($orderItem->isDummy()) {
if ($orderItem->getHasChildren()) {
foreach ($orderItem->getChildrenItems() as $child) {
if (!isset($qtys[$child->getId()])) {
$qtys[$child->getId()] = $child->getQtyToInvoice();
}
+ $parentId = $orderItem->getParentItemId();
+ if ($parentId && array_key_exists($parentId, $qtys)) {
+ $qtys[$orderItem->getId()] = $qtys[$parentId];
+ } else {
+ continue;
+ }
}
} elseif ($orderItem->getParentItem()) {
$parent = $orderItem->getParentItem();
@@ -210,6 +237,26 @@ private function prepareItemQty(\Magento\Sales\Api\Data\OrderItemInterface $orde
}
}
+ /**
+ * Prepare qty to invoice for bundle products
+ *
+ * @param \Magento\Sales\Api\Data\OrderItemInterface $orderItem
+ * @param array $qtys
+ */
+ private function prepareBundleQty(\Magento\Sales\Api\Data\OrderItemInterface $orderItem, &$qtys)
+ {
+ if ($orderItem->getProductType() == Type::TYPE_BUNDLE && !$orderItem->isShipSeparately()) {
+ foreach ($orderItem->getChildrenItems() as $childItem) {
+ $bundleSelectionAttributes = $childItem->getProductOptionByCode('bundle_selection_attributes');
+ if (is_string($bundleSelectionAttributes)) {
+ $bundleSelectionAttributes = $this->serializer->unserialize($bundleSelectionAttributes);
+ }
+
+ $qtys[$childItem->getId()] = $qtys[$orderItem->getId()] * $bundleSelectionAttributes['qty'];
+ }
+ }
+ }
+
/**
* Check if order item can be invoiced.
*
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..b90bac7e0881b 100644
--- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminInvoiceActionGroup.xml
@@ -19,18 +19,15 @@
-
-
-
@@ -38,29 +35,49 @@
-
-
-
+
+
+
+
+
+
-
-
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/ActionGroup/OrderAndReturnActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml
similarity index 64%
rename from app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml
rename to app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml
index c46dd612022fd..fcea25f997591 100644
--- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/OrderAndReturnActionGroup.xml
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/StorefrontOrderActionGroupActionGroup.xml
@@ -9,11 +9,11 @@
-
+
-
-
+
+
@@ -24,13 +24,4 @@
-
-
-
-
-
-
-
-
-
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/Data/SalesEnableRMAStorefrontConfigData.xml b/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml
deleted file mode 100644
index 76ff20813483e..0000000000000
--- a/app/code/Magento/Sales/Test/Mftf/Data/SalesEnableRMAStorefrontConfigData.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
-
- EnableRMAStorefront
-
-
- 1
-
-
-
- DisableRMAStorefront
-
-
- 0
-
-
diff --git a/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml b/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml
deleted file mode 100644
index 86226265dd146..0000000000000
--- a/app/code/Magento/Sales/Test/Mftf/Metadata/sales_enable_rma_config-meta.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
- string
-
-
-
-
-
-
diff --git a/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderProcessDataPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderProcessDataPage.xml
new file mode 100644
index 0000000000000..2041bf8f3c9ae
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Page/AdminOrderProcessDataPage.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
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/Page/StorefrontOrdersAndReturnsPage.xml b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml
index 32d94c3175807..ee546174d9680 100644
--- a/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Page/StorefrontOrdersAndReturnsPage.xml
@@ -9,7 +9,6 @@
-
-
+
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/AdminOrderFormItemsOrderedSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml
index 11673f1f0fe26..beb566b20806c 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderFormItemsOrderedSection.xml
@@ -15,5 +15,6 @@
+
-
\ No newline at end of file
+
diff --git a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml
index 53aeeb62c6b70..5c2ff296ebeee 100644
--- a/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml
+++ b/app/code/Magento/Sales/Test/Mftf/Section/AdminOrderItemsOrderedSection.xml
@@ -23,8 +23,10 @@
+
+
@@ -35,4 +37,4 @@
-
\ 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/Section/StorefrontCreateNewReturnMainSection.xml b/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml
deleted file mode 100644
index fe8391cf3c28f..0000000000000
--- a/app/code/Magento/Sales/Test/Mftf/Section/StorefrontCreateNewReturnMainSection.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
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
new file mode 100644
index 0000000000000..f869841153aea
--- /dev/null
+++ b/app/code/Magento/Sales/Test/Mftf/Test/AdminCorrectnessInvoicedItemInBundleProductTest.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 10
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/adminhtml/templates/order/create/form/address.phtml b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml
index b0a88b8fa37dc..d1a90783c68c7 100644
--- a/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml
+++ b/app/code/Magento/Sales/view/adminhtml/templates/order/create/form/address.phtml
@@ -89,11 +89,8 @@ endif; ?>
= $block->getForm()->toHtml() ?>
- getDontSaveInAddressBook() && $block->getAddress()->getSaveInAddressBook()): ?> checked="checked"
- class="admin__control-checkbox"/>
+ getDontSaveInAddressBook()): ?> checked="checked" class="admin__control-checkbox"/>
= /* @escapeNotVerified */ __('Save in address book') ?>
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}}
{{/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}}