diff --git a/app/code/Magento/AmqpStore/README.md b/app/code/Magento/AmqpStore/README.md
index 0f84c8ff3276e..88459a495401f 100644
--- a/app/code/Magento/AmqpStore/README.md
+++ b/app/code/Magento/AmqpStore/README.md
@@ -1,3 +1,9 @@
-# Amqp Store
+# Magento_AmqpStore module
-**AmqpStore** provides ability to specify store before publish messages with Amqp.
+The Magento_AmqpStore module provides the ability to specify a store before publishing messages with the Advanced Message Queuing Protocol (AMQP).
+
+## Extensibility
+
+Extension developers can interact with the Magento_AmqpStore module using plugins. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html).
+
+[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AmqpStore module.
diff --git a/app/code/Magento/AsynchronousOperations/README.md b/app/code/Magento/AsynchronousOperations/README.md
index fb7d53df1b81c..896fddedab5fa 100644
--- a/app/code/Magento/AsynchronousOperations/README.md
+++ b/app/code/Magento/AsynchronousOperations/README.md
@@ -1 +1,48 @@
- This component is designed to provide response for client who launched the bulk operation as soon as possible and postpone handling of operations moving them to background handler.
\ No newline at end of file
+# Magento_AsynchronousOperations module
+
+This component is designed to provide a response for a client that launched the bulk operation as soon as possible and postpone handling of operations moving them to the background handler.
+
+## Installation details
+
+The Magento_AsynchronousOperations module creates the following tables in the database:
+
+- `magento_bulk`
+- `magento_operation`
+- `magento_acknowledged_bulk`
+
+Before disabling or uninstalling this module, note that the following modules depends on this module:
+
+- Magento_WebapiAsync
+
+For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.3/install-gde/install/cli/install-cli-subcommands-enable.html).
+
+## Extensibility
+
+Extension developers can interact with the Magento_AsynchronousOperations module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html).
+
+[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AsynchronousOperations module.
+
+### Layouts
+
+This module introduces the following layouts and layout handles in the `view/adminhtml/layout` directory:
+
+- `bulk_bulk_details`
+- `bulk_bulk_details_modal`
+- `bulk_index_index`
+
+For more information about layouts in Magento 2, see the [Layout documentation](https://devdocs.magento.com/guides/v2.3/frontend-dev-guide/layouts/layout-overview.html).
+
+### UI components
+
+You can extend Magento_AsynchronousOperations module using the following configuration files in the `view/adminhtml/ui_component/` directory:
+
+- `bulk_details_form`
+- `bulk_details_form_modal`
+- `bulk_listing`
+- `failed_operation_listing`
+- `failed_operation_modal_listing`
+- `notification_area`
+- `retriable_operation_listing`
+- `retriable_operation_modal_listing`
+
+For information about UI components in Magento 2, see [Overview of UI components](https://devdocs.magento.com/guides/v2.3/ui_comp_guide/bk-ui_comps.html).
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php
index 53a1f13fa8786..3cdfcf23ba607 100644
--- a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php
+++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundTransactionStrategyCommand.php
@@ -59,6 +59,7 @@ public function execute(array $commandSubject): void
* @param array $commandSubject
* @return string
* @throws CommandException
+ * @throws \Magento\Framework\Exception\NotFoundException
*/
private function getCommand(array $commandSubject): string
{
@@ -66,12 +67,37 @@ private function getCommand(array $commandSubject): string
->execute($commandSubject)
->get();
- if ($details['transaction']['transactionStatus'] === 'capturedPendingSettlement') {
+ if ($this->canVoid($details, $commandSubject)) {
return self::VOID;
- } elseif ($details['transaction']['transactionStatus'] !== 'settledSuccessfully') {
+ }
+
+ if ($details['transaction']['transactionStatus'] !== 'settledSuccessfully') {
throw new CommandException(__('This transaction cannot be refunded with its current status.'));
}
return self::REFUND;
}
+
+ /**
+ * Checks if void command can be performed.
+ *
+ * @param array $details
+ * @param array $commandSubject
+ * @return bool
+ * @throws CommandException
+ */
+ private function canVoid(array $details, array $commandSubject) :bool
+ {
+ if ($details['transaction']['transactionStatus'] === 'capturedPendingSettlement') {
+ if ((float) $details['transaction']['authAmount'] !== (float) $commandSubject['amount']) {
+ throw new CommandException(
+ __('The transaction has not been settled, a partial refund is not yet available.')
+ );
+ }
+
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/ClosePartialTransactionHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/ClosePartialTransactionHandler.php
new file mode 100644
index 0000000000000..fd8af3d28c4d4
--- /dev/null
+++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/ClosePartialTransactionHandler.php
@@ -0,0 +1,27 @@
+getCreditmemo()->getInvoice()->canRefund();
+ }
+}
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php
index acbde62bacd77..fa9bf55462111 100644
--- a/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php
+++ b/app/code/Magento/AuthorizenetAcceptjs/Gateway/Response/CloseTransactionHandler.php
@@ -47,7 +47,19 @@ public function handle(array $handlingSubject, array $response): void
if ($payment instanceof Payment) {
$payment->setIsTransactionClosed($this->closeTransaction);
- $payment->setShouldCloseParentTransaction(true);
+ $payment->setShouldCloseParentTransaction($this->shouldCloseParentTransaction($payment));
}
}
+
+ /**
+ * Whether parent transaction should be closed.
+ *
+ * @param Payment $payment
+ * @return bool
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function shouldCloseParentTransaction(Payment $payment)
+ {
+ return true;
+ }
}
diff --git a/app/code/Magento/AuthorizenetAcceptjs/README.md b/app/code/Magento/AuthorizenetAcceptjs/README.md
index b066f8a2d7509..b507f97a5a223 100644
--- a/app/code/Magento/AuthorizenetAcceptjs/README.md
+++ b/app/code/Magento/AuthorizenetAcceptjs/README.md
@@ -1 +1,29 @@
+# Magento_AuthorizenetAcceptjs module
+
The Magento_AuthorizenetAcceptjs module implements the integration with the Authorize.Net payment gateway and makes the latter available as a payment method in Magento.
+
+## Installation details
+
+Before disabling or uninstalling this module, note that the `Magento_AuthorizenetCardinal` module depends on this module.
+
+For information about module installation in Magento 2, see [Enable or disable modules](https://devdocs.magento.com/guides/v2.3/install-gde/install/cli/install-cli-subcommands-enable.html).
+
+## Structure
+
+`Gateway/` - the directory that contains payment gateway command interfaces and service classes.
+
+For information about typical file structure of a module in Magento 2, see [Module file structure](http://devdocs.magento.com/guides/v2.3/extension-dev-guide/build/module-file-structure.html#module-file-structure).
+
+## Extensibility
+
+Extension developers can interact with the Magento_AuthorizenetAcceptjs module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html).
+
+[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AuthorizenetAcceptjs module.
+
+### Events
+
+This module observes the following events:
+
+- `payment_method_assign_data_authorizenet_acceptjs` event in the `Magento\AuthorizenetAcceptjs\Observer\DataAssignObserver` file.
+
+For information about an event in Magento 2, see [Events and observers](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events).
diff --git a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php
index df6d89d7bc585..79477b06e0e6c 100644
--- a/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php
+++ b/app/code/Magento/AuthorizenetAcceptjs/Test/Unit/Gateway/Command/RefundTransactionStrategyCommandTest.php
@@ -62,20 +62,75 @@ public function testCommandWillVoidWhenTransactionIsPendingSettlement()
->method('execute');
$this->commandPoolMock->method('get')
- ->willReturnMap([
- ['get_transaction_details', $this->transactionDetailsCommandMock],
- ['void', $this->commandMock]
- ]);
+ ->willReturnMap(
+ [
+ [
+ 'get_transaction_details',
+ $this->transactionDetailsCommandMock
+ ],
+ [
+ 'void',
+ $this->commandMock
+ ]
+ ]
+ );
$this->transactionResultMock->method('get')
- ->willReturn([
- 'transaction' => [
- 'transactionStatus' => 'capturedPendingSettlement'
+ ->willReturn(
+ [
+ 'transaction' => [
+ 'transactionStatus' => 'capturedPendingSettlement',
+ 'authAmount' => '20.19',
+ ]
]
- ]);
+ );
$buildSubject = [
- 'foo' => '123'
+ 'foo' => '123',
+ 'amount' => '20.19',
+ ];
+
+ $this->transactionDetailsCommandMock->expects($this->once())
+ ->method('execute')
+ ->with($buildSubject)
+ ->willReturn($this->transactionResultMock);
+
+ $this->command->execute($buildSubject);
+ }
+
+ /**
+ * @expectedException \Magento\Payment\Gateway\Command\CommandException
+ * @expectedExceptionMessage The transaction has not been settled, a partial refund is not yet available.
+ */
+ public function testCommandWillThrowExceptionWhenVoidTransactionIsPartial()
+ {
+ // Assert command is executed
+ $this->commandMock->expects($this->never())
+ ->method('execute');
+
+ $this->commandPoolMock->method('get')
+ ->willReturnMap(
+ [
+ [
+ 'get_transaction_details',
+ $this->transactionDetailsCommandMock
+ ],
+ ]
+ );
+
+ $this->transactionResultMock->method('get')
+ ->willReturn(
+ [
+ 'transaction' => [
+ 'transactionStatus' => 'capturedPendingSettlement',
+ 'authAmount' => '20.19',
+ ]
+ ]
+ );
+
+ $buildSubject = [
+ 'foo' => '123',
+ 'amount' => '10.19',
];
$this->transactionDetailsCommandMock->expects($this->once())
@@ -93,17 +148,27 @@ public function testCommandWillRefundWhenTransactionIsSettled()
->method('execute');
$this->commandPoolMock->method('get')
- ->willReturnMap([
- ['get_transaction_details', $this->transactionDetailsCommandMock],
- ['refund_settled', $this->commandMock]
- ]);
+ ->willReturnMap(
+ [
+ [
+ 'get_transaction_details',
+ $this->transactionDetailsCommandMock
+ ],
+ [
+ 'refund_settled',
+ $this->commandMock
+ ]
+ ]
+ );
$this->transactionResultMock->method('get')
- ->willReturn([
- 'transaction' => [
- 'transactionStatus' => 'settledSuccessfully'
+ ->willReturn(
+ [
+ 'transaction' => [
+ 'transactionStatus' => 'settledSuccessfully'
+ ]
]
- ]);
+ );
$buildSubject = [
'foo' => '123'
@@ -128,16 +193,23 @@ public function testCommandWillThrowExceptionWhenTransactionIsInInvalidState()
->method('execute');
$this->commandPoolMock->method('get')
- ->willReturnMap([
- ['get_transaction_details', $this->transactionDetailsCommandMock],
- ]);
+ ->willReturnMap(
+ [
+ [
+ 'get_transaction_details',
+ $this->transactionDetailsCommandMock
+ ],
+ ]
+ );
$this->transactionResultMock->method('get')
- ->willReturn([
- 'transaction' => [
- 'transactionStatus' => 'somethingIsWrong'
+ ->willReturn(
+ [
+ 'transaction' => [
+ 'transactionStatus' => 'somethingIsWrong'
+ ]
]
- ]);
+ );
$buildSubject = [
'foo' => '123'
diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml
index 7324421d3c14b..6fdbb98a78f8b 100644
--- a/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml
+++ b/app/code/Magento/AuthorizenetAcceptjs/etc/config.xml
@@ -24,6 +24,7 @@
0
1
1
+ 1
1
1
1
diff --git a/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml b/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml
index 145bcf22fd912..1bff19e15a65f 100644
--- a/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml
+++ b/app/code/Magento/AuthorizenetAcceptjs/etc/di.xml
@@ -206,8 +206,7 @@
- Magento\AuthorizenetAcceptjs\Gateway\Response\TransactionIdHandler
- - Magento\AuthorizenetAcceptjs\Gateway\Response\CloseParentTransactionHandler
- - Magento\AuthorizenetAcceptjs\Gateway\Response\CloseTransactionHandler
+ - Magento\AuthorizenetAcceptjs\Gateway\Response\ClosePartialTransactionHandler
diff --git a/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv b/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv
index da518301652f4..3c5b677c88cc8 100644
--- a/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv
+++ b/app/code/Magento/AuthorizenetAcceptjs/i18n/en_US.csv
@@ -19,3 +19,4 @@ Authorize.net,Authorize.net
"ccLast4","Last 4 Digits of Card"
"There was an error while trying to process the refund.","There was an error while trying to process the refund."
"This transaction cannot be refunded with its current status.","This transaction cannot be refunded with its current status."
+"The transaction has not been settled, a partial refund is not yet available.","The transaction has not been settled, a partial refund is not yet available."
diff --git a/app/code/Magento/AuthorizenetCardinal/README.md b/app/code/Magento/AuthorizenetCardinal/README.md
index 2324f680bafc9..0bd63130471bd 100644
--- a/app/code/Magento/AuthorizenetCardinal/README.md
+++ b/app/code/Magento/AuthorizenetCardinal/README.md
@@ -1 +1,23 @@
-The AuthorizenetCardinal module provides a possibility to enable 3-D Secure 2.0 support for AuthorizenetAcceptjs payment integration.
\ No newline at end of file
+# Magento_AuthorizenetCardinal module
+
+Use the Magento_AuthorizenetCardinal module to enable 3D Secure 2.0 support for AuthorizenetAcceptjs payment integrations.
+
+## Structure
+
+`Gateway/` - the directory that contains payment gateway command interfaces and service classes.
+
+For information about typical file structure of a module in Magento 2, see [Module file structure](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/build/module-file-structure.html#module-file-structure).
+
+## Extensibility
+
+Extension developers can interact with the Magento_AuthorizenetCardinal module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html).
+
+[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AuthorizenetCardinal module.
+
+### Events
+
+This module observes the following events:
+
+- `payment_method_assign_data_authorizenet_acceptjs` event in the `Magento\AuthorizenetCardinal\Observer\DataAssignObserver` file.
+
+For information about an event in Magento 2, see [Events and observers](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/events-and-observers.html#events).
diff --git a/app/code/Magento/AuthorizenetGraphQl/README.md b/app/code/Magento/AuthorizenetGraphQl/README.md
index 8b920e569341f..2af2b6a1024af 100644
--- a/app/code/Magento/AuthorizenetGraphQl/README.md
+++ b/app/code/Magento/AuthorizenetGraphQl/README.md
@@ -1,3 +1,9 @@
-# AuthorizenetGraphQl
+# Magento_AuthorizenetGraphQl module
- **AuthorizenetGraphQl** defines the data types needed to pass payment information data from the client to Magento.
+The Magento_AuthorizenetGraphQl module defines the data types needed to pass payment information data from the client to Magento.
+
+## Extensibility
+
+Extension developers can interact with the Magento_AuthorizenetGraphQl module. For more information about the Magento extension mechanism, see [Magento plug-ins](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/plugins.html).
+
+[The Magento dependency injection mechanism](https://devdocs.magento.com/guides/v2.3/extension-dev-guide/depend-inj.html) enables you to override the functionality of the Magento_AuthorizenetGraphQl module.
diff --git a/app/code/Magento/Backend/Block/System/Store/Edit/Form/Store.php b/app/code/Magento/Backend/Block/System/Store/Edit/Form/Store.php
index dfbf60e1524ff..fee756b5d66be 100644
--- a/app/code/Magento/Backend/Block/System/Store/Edit/Form/Store.php
+++ b/app/code/Magento/Backend/Block/System/Store/Edit/Form/Store.php
@@ -3,6 +3,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Backend\Block\System\Store\Edit\Form;
/**
@@ -129,6 +132,7 @@ protected function _prepareStoreFieldset(\Magento\Framework\Data\Form $form)
'label' => __('Sort Order'),
'value' => $storeModel->getSortOrder(),
'required' => false,
+ 'class' => 'validate-number validate-zero-or-greater',
'disabled' => $storeModel->isReadOnly()
]
);
diff --git a/app/code/Magento/Backend/Block/System/Store/Edit/Form/Website.php b/app/code/Magento/Backend/Block/System/Store/Edit/Form/Website.php
index ec4e2cd8b444c..545620d99c4c5 100644
--- a/app/code/Magento/Backend/Block/System/Store/Edit/Form/Website.php
+++ b/app/code/Magento/Backend/Block/System/Store/Edit/Form/Website.php
@@ -3,6 +3,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Backend\Block\System\Store\Edit\Form;
/**
@@ -85,6 +88,7 @@ protected function _prepareStoreFieldset(\Magento\Framework\Data\Form $form)
'label' => __('Sort Order'),
'value' => $websiteModel->getSortOrder(),
'required' => false,
+ 'class' => 'validate-number validate-zero-or-greater',
'disabled' => $websiteModel->isReadOnly()
]
);
diff --git a/app/code/Magento/Backend/i18n/en_US.csv b/app/code/Magento/Backend/i18n/en_US.csv
index ea06e06275f8f..51fe8bfe542a2 100644
--- a/app/code/Magento/Backend/i18n/en_US.csv
+++ b/app/code/Magento/Backend/i18n/en_US.csv
@@ -258,7 +258,7 @@ Minute,Minute
"To use this website you must first enable JavaScript in your browser.","To use this website you must first enable JavaScript in your browser."
"This is only a demo store. You can browse and place orders, but nothing will be processed.","This is only a demo store. You can browse and place orders, but nothing will be processed."
"Report an Issue","Report an Issue"
-"Store View:","Store View:"
+"Scope:","Scope:"
"Stores Configuration","Stores Configuration"
"Please confirm scope switching. All data that hasn't been saved will be lost.","Please confirm scope switching. All data that hasn't been saved will be lost."
"Additional Cache Management","Additional Cache Management"
diff --git a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml
index da18bc183759b..df9323a7276df 100644
--- a/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml
+++ b/app/code/Magento/Backend/view/adminhtml/templates/store/switcher.phtml
@@ -9,7 +9,7 @@
getWebsites()) : ?>
-
= $block->escapeHtml(__('Store View:')) ?>
+
= $block->escapeHtml(__('Scope:')) ?>
buttonBlocks = $buttonBlocks;
+ }
/**
* Add Braintree PayPal shortcut buttons
@@ -35,7 +53,13 @@ public function execute(Observer $observer)
/** @var ShortcutButtons $shortcutButtons */
$shortcutButtons = $observer->getEvent()->getContainer();
- $shortcut = $shortcutButtons->getLayout()->createBlock(self::PAYPAL_SHORTCUT_BLOCK);
+ if ($observer->getData('is_shopping_cart')) {
+ $shortcut = $shortcutButtons->getLayout()
+ ->createBlock($this->buttonBlocks[self::PAYPAL_SHOPPINGCART_ALIAS]);
+ } else {
+ $shortcut = $shortcutButtons->getLayout()
+ ->createBlock($this->buttonBlocks[self::PAYPAL_MINICART_ALIAS]);
+ }
$shortcutButtons->addShortcut($shortcut);
}
diff --git a/app/code/Magento/Braintree/Test/Unit/Observer/AddPaypalShortcutsTest.php b/app/code/Magento/Braintree/Test/Unit/Observer/AddPaypalShortcutsTest.php
index 377b4d3c650ae..7bf1722e317ef 100644
--- a/app/code/Magento/Braintree/Test/Unit/Observer/AddPaypalShortcutsTest.php
+++ b/app/code/Magento/Braintree/Test/Unit/Observer/AddPaypalShortcutsTest.php
@@ -19,9 +19,17 @@
*/
class AddPaypalShortcutsTest extends \PHPUnit\Framework\TestCase
{
+ /**
+ * Tests PayPal shortcuts observer.
+ */
public function testExecute()
{
- $addPaypalShortcuts = new AddPaypalShortcuts();
+ $addPaypalShortcuts = new AddPaypalShortcuts(
+ [
+ 'mini_cart' => 'Minicart-block',
+ 'shopping_cart' => 'Shoppingcart-block'
+ ]
+ );
/** @var Observer|\PHPUnit_Framework_MockObject_MockObject $observerMock */
$observerMock = $this->getMockBuilder(Observer::class)
@@ -60,7 +68,7 @@ public function testExecute()
$layoutMock->expects(self::once())
->method('createBlock')
- ->with(AddPaypalShortcuts::PAYPAL_SHORTCUT_BLOCK)
+ ->with('Minicart-block')
->willReturn($blockMock);
$shortcutButtonsMock->expects(self::once())
diff --git a/app/code/Magento/Braintree/etc/frontend/di.xml b/app/code/Magento/Braintree/etc/frontend/di.xml
index d8d3a93b71dc3..330fa51258c44 100644
--- a/app/code/Magento/Braintree/etc/frontend/di.xml
+++ b/app/code/Magento/Braintree/etc/frontend/di.xml
@@ -55,6 +55,23 @@
BraintreePayPalFacade
+
+
+
+ - Magento_Braintree::paypal/button_shopping_cart.phtml
+ - braintree.paypal.mini-cart
+ - braintree-paypal-mini-cart
+
+
+
+
+
+
+ - Magento\Braintree\Block\Paypal\Button
+ - Magento\Braintree\Block\Paypal\ButtonShoppingCartVirtual
+
+
+
diff --git a/app/code/Magento/Braintree/view/frontend/templates/paypal/button_shopping_cart.phtml b/app/code/Magento/Braintree/view/frontend/templates/paypal/button_shopping_cart.phtml
new file mode 100644
index 0000000000000..913e1035b910f
--- /dev/null
+++ b/app/code/Magento/Braintree/view/frontend/templates/paypal/button_shopping_cart.phtml
@@ -0,0 +1,32 @@
+getContainerId() . random_int(0, PHP_INT_MAX);
+
+$config = [
+ 'Magento_Braintree/js/paypal/button_shopping_cart' => [
+ 'id' => $id,
+ 'clientToken' => $block->getClientToken(),
+ 'displayName' => $block->getMerchantName(),
+ 'actionSuccess' => $block->getActionSuccess(),
+ 'environment' => $block->getEnvironment()
+ ]
+];
+
+?>
+
diff --git a/app/code/Magento/Braintree/view/frontend/web/js/paypal/button_shopping_cart.js b/app/code/Magento/Braintree/view/frontend/web/js/paypal/button_shopping_cart.js
new file mode 100644
index 0000000000000..9dd249998c152
--- /dev/null
+++ b/app/code/Magento/Braintree/view/frontend/web/js/paypal/button_shopping_cart.js
@@ -0,0 +1,36 @@
+/**
+ * Copyright © Magento, Inc. All rights reserved.
+ * See COPYING.txt for license details.
+ */
+define(
+ [
+ 'Magento_Braintree/js/paypal/button',
+ 'Magento_Checkout/js/model/quote',
+ 'domReady!'
+ ],
+ function (
+ Component,
+ quote
+ ) {
+ 'use strict';
+
+ return Component.extend({
+
+ /**
+ * Overrides amount with a value from quote.
+ *
+ * @returns {Object}
+ * @private
+ */
+ getClientConfig: function (data) {
+ var config = this._super(data);
+
+ if (config.amount !== quote.totals()['base_grand_total']) {
+ config.amount = quote.totals()['base_grand_total'];
+ }
+
+ return config;
+ }
+ });
+ }
+);
diff --git a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
index 4a55714a27ec5..4b7a623b15c19 100644
--- a/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
+++ b/app/code/Magento/Catalog/Model/Product/Image/ParamsBuilder.php
@@ -68,11 +68,12 @@ public function __construct(
* Build image params
*
* @param array $imageArguments
+ * @param int $scopeId
* @return array
* @SuppressWarnings(PHPMD.NPathComplexity)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
- public function build(array $imageArguments): array
+ public function build(array $imageArguments, int $scopeId = null): array
{
$miscParams = [
'image_type' => $imageArguments['type'] ?? null,
@@ -81,7 +82,7 @@ public function build(array $imageArguments): array
];
$overwritten = $this->overwriteDefaultValues($imageArguments);
- $watermark = isset($miscParams['image_type']) ? $this->getWatermark($miscParams['image_type']) : [];
+ $watermark = isset($miscParams['image_type']) ? $this->getWatermark($miscParams['image_type'], $scopeId) : [];
return array_merge($miscParams, $overwritten, $watermark);
}
@@ -117,27 +118,32 @@ private function overwriteDefaultValues(array $imageArguments): array
* Get watermark
*
* @param string $type
+ * @param int $scopeId
* @return array
*/
- private function getWatermark(string $type): array
+ private function getWatermark(string $type, int $scopeId = null): array
{
$file = $this->scopeConfig->getValue(
"design/watermark/{$type}_image",
- ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE,
+ $scopeId
);
if ($file) {
$size = $this->scopeConfig->getValue(
"design/watermark/{$type}_size",
- ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE,
+ $scopeId
);
$opacity = $this->scopeConfig->getValue(
"design/watermark/{$type}_imageOpacity",
- ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE,
+ $scopeId
);
$position = $this->scopeConfig->getValue(
"design/watermark/{$type}_position",
- ScopeInterface::SCOPE_STORE
+ ScopeInterface::SCOPE_STORE,
+ $scopeId
);
$width = !empty($size['width']) ? $size['width'] : null;
$height = !empty($size['height']) ? $size['height'] : null;
diff --git a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
index 3d7f863b7c0d3..3946be32184ec 100644
--- a/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
+++ b/app/code/Magento/Catalog/Model/ResourceModel/AbstractResource.php
@@ -15,6 +15,7 @@
/**
* Catalog entity abstract model
*
+ * phpcs:disable Magento2.Classes.AbstractApi
* @api
*
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
@@ -468,7 +469,7 @@ protected function _getOrigObject($object)
*
* @param AbstractAttribute $attribute
* @param mixed $value New value of the attribute.
- * @param array &$origData
+ * @param array $origData
* @return bool
*/
protected function _canUpdateAttribute(AbstractAttribute $attribute, $value, array &$origData)
@@ -560,15 +561,19 @@ public function getAttributeRawValue($entityId, $attribute, $store)
$store = (int) $store;
if ($typedAttributes) {
foreach ($typedAttributes as $table => $_attributes) {
+ $defaultJoinCondition = [
+ $connection->quoteInto('default_value.attribute_id IN (?)', array_keys($_attributes)),
+ "default_value.{$this->getLinkField()} = e.{$this->getLinkField()}",
+ 'default_value.store_id = 0',
+ ];
+
$select = $connection->select()
- ->from(['default_value' => $table], ['attribute_id'])
- ->join(
- ['e' => $this->getTable($this->getEntityTable())],
- 'e.' . $this->getLinkField() . ' = ' . 'default_value.' . $this->getLinkField(),
- ''
- )->where('default_value.attribute_id IN (?)', array_keys($_attributes))
- ->where("e.entity_id = :entity_id")
- ->where('default_value.store_id = ?', 0);
+ ->from(['e' => $this->getTable($this->getEntityTable())], [])
+ ->joinLeft(
+ ['default_value' => $table],
+ implode(' AND ', $defaultJoinCondition),
+ []
+ )->where("e.entity_id = :entity_id");
$bind = ['entity_id' => $entityId];
@@ -578,6 +583,11 @@ public function getAttributeRawValue($entityId, $attribute, $store)
'default_value.value',
'store_value.value'
);
+ $attributeIdExpr = $connection->getCheckSql(
+ 'store_value.attribute_id IS NULL',
+ 'default_value.attribute_id',
+ 'store_value.attribute_id'
+ );
$joinCondition = [
$connection->quoteInto('store_value.attribute_id IN (?)', array_keys($_attributes)),
"store_value.{$this->getLinkField()} = e.{$this->getLinkField()}",
@@ -587,23 +597,28 @@ public function getAttributeRawValue($entityId, $attribute, $store)
$select->joinLeft(
['store_value' => $table],
implode(' AND ', $joinCondition),
- ['attr_value' => $valueExpr]
+ ['attribute_id' => $attributeIdExpr, 'attr_value' => $valueExpr]
);
$bind['store_id'] = $store;
} else {
- $select->columns(['attr_value' => 'value'], 'default_value');
+ $select->columns(
+ ['attribute_id' => 'attribute_id', 'attr_value' => 'value'],
+ 'default_value'
+ );
}
$result = $connection->fetchPairs($select, $bind);
foreach ($result as $attrId => $value) {
- $attrCode = $typedAttributes[$table][$attrId];
- $attributesData[$attrCode] = $value;
+ if ($attrId !== '') {
+ $attrCode = $typedAttributes[$table][$attrId];
+ $attributesData[$attrCode] = $value;
+ }
}
}
}
- if (is_array($attributesData) && sizeof($attributesData) == 1) {
+ if (is_array($attributesData) && count($attributesData) == 1) {
$attributesData = array_shift($attributesData);
}
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
index eb014ca7f884d..5b6207e135796 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/AdminSortingByWebsitesTest.xml
@@ -40,6 +40,8 @@
+
+
diff --git a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
index 1bb3bcc69cb57..e9e23cf157a26 100644
--- a/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
+++ b/app/code/Magento/Catalog/Test/Mftf/Test/StorefrontPurchaseProductWithCustomOptionsWithLongValuesTitle.xml
@@ -17,6 +17,9 @@
+
+
+
diff --git a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
index aa0145b9f96cd..dcaf7fb3a561d 100644
--- a/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
+++ b/app/code/Magento/CatalogSearch/Test/Mftf/Section/AdminCatalogSearchTermIndexSection.xml
@@ -21,5 +21,6 @@
+
\ No newline at end of file
diff --git a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
index 33c0cafc8f081..704b60a8aaf2a 100644
--- a/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
+++ b/app/code/Magento/CatalogUrlRewrite/Observer/AfterImportDataObserver.php
@@ -299,12 +299,16 @@ protected function _populateForUrlGeneration($rowData)
*/
private function isNeedToPopulateForUrlGeneration($rowData, $newSku, $oldSku): bool
{
- if ((empty($newSku) || !isset($newSku['entity_id']))
- || ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE
- && empty($rowData[self::URL_KEY_ATTRIBUTE_CODE]))
- || (array_key_exists($rowData[ImportProduct::COL_SKU], $oldSku)
- && !isset($rowData[self::URL_KEY_ATTRIBUTE_CODE])
- && $this->import->getBehavior() === ImportExport::BEHAVIOR_APPEND)) {
+ if ((
+ (empty($newSku) || !isset($newSku['entity_id']))
+ || ($this->import->getRowScope($rowData) == ImportProduct::SCOPE_STORE
+ && empty($rowData[self::URL_KEY_ATTRIBUTE_CODE]))
+ || (array_key_exists(strtolower($rowData[ImportProduct::COL_SKU]), $oldSku)
+ && !isset($rowData[self::URL_KEY_ATTRIBUTE_CODE])
+ && $this->import->getBehavior() === ImportExport::BEHAVIOR_APPEND)
+ )
+ && !isset($rowData["categories"])
+ ) {
return false;
}
return true;
@@ -477,7 +481,7 @@ protected function currentUrlRewritesRegenerate()
$url = $currentUrlRewrite->getIsAutogenerated()
? $this->generateForAutogenerated($currentUrlRewrite, $category)
: $this->generateForCustom($currentUrlRewrite, $category);
- $urlRewrites = array_merge($urlRewrites, $url);
+ $urlRewrites = $url + $urlRewrites;
}
$this->product = null;
diff --git a/app/code/Magento/Checkout/Model/Session.php b/app/code/Magento/Checkout/Model/Session.php
index f983ece3e635a..a654c78853d7a 100644
--- a/app/code/Magento/Checkout/Model/Session.php
+++ b/app/code/Magento/Checkout/Model/Session.php
@@ -17,6 +17,7 @@
* @api
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
* @SuppressWarnings(PHPMD.CookieAndSessionMisuse)
+ * @SuppressWarnings(PHPMD.TooManyFields)
*/
class Session extends \Magento\Framework\Session\SessionManager
{
@@ -46,6 +47,15 @@ class Session extends \Magento\Framework\Session\SessionManager
*/
protected $_loadInactive = false;
+ /**
+ * A flag to track when the quote is being loaded and attached to the session object.
+ *
+ * Used in trigger_recollect infinite loop detection.
+ *
+ * @var bool
+ */
+ private $isLoading = false;
+
/**
* Loaded order instance
*
@@ -227,6 +237,10 @@ public function getQuote()
$this->_eventManager->dispatch('custom_quote_process', ['checkout_session' => $this]);
if ($this->_quote === null) {
+ if ($this->isLoading) {
+ throw new \LogicException("Infinite loop detected, review the trace for the looping path");
+ }
+ $this->isLoading = true;
$quote = $this->quoteFactory->create();
if ($this->getQuoteId()) {
try {
@@ -289,6 +303,7 @@ public function getQuote()
$quote->setStore($this->_storeManager->getStore());
$this->_quote = $quote;
+ $this->isLoading = false;
}
if (!$this->isQuoteMasked() && !$this->_customerSession->isLoggedIn() && $this->getQuoteId()) {
diff --git a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml
index 77dde1eab482a..86c9a357af23c 100644
--- a/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml
+++ b/app/code/Magento/Checkout/view/frontend/templates/cart/item/default.phtml
@@ -50,7 +50,7 @@ $canApplyMsrp = $helper->isShowBeforeOrderConfirm($product) && $helper->isMinima
= $block->escapeHtml($_formatedOptionValue['full_view']) ?>
- = $block->escapeHtml($_formatedOptionValue['value'], ['span']) ?>
+ = $block->escapeHtml($_formatedOptionValue['value'], ['span', 'a']) ?>
diff --git a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js
index b2367fbda5412..2158873842687 100644
--- a/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js
+++ b/app/code/Magento/Checkout/view/frontend/web/js/view/shipping-information.js
@@ -28,9 +28,16 @@ define([
* @return {String}
*/
getShippingMethodTitle: function () {
- var shippingMethod = quote.shippingMethod();
+ var shippingMethod = quote.shippingMethod(),
+ shippingMethodTitle = '';
- return shippingMethod ? shippingMethod['carrier_title'] + ' - ' + shippingMethod['method_title'] : '';
+ if (typeof shippingMethod['method_title'] !== 'undefined') {
+ shippingMethodTitle = ' - ' + shippingMethod['method_title'];
+ }
+
+ return shippingMethod ?
+ shippingMethod['carrier_title'] + shippingMethodTitle :
+ shippingMethod['carrier_title'];
},
/**
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 41d442a76d510..32bbd66d13e68 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
@@ -44,7 +44,10 @@
-
+
+
+
+
diff --git a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml
index 98e7957d0af8e..f249a417f1046 100644
--- a/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml
+++ b/app/code/Magento/ConfigurableProductGraphQl/etc/module.xml
@@ -12,6 +12,7 @@
+
diff --git a/app/code/Magento/Cron/Console/Command/CronInstallCommand.php b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php
index a3087b4c3d730..5e30076c21e76 100644
--- a/app/code/Magento/Cron/Console/Command/CronInstallCommand.php
+++ b/app/code/Magento/Cron/Console/Command/CronInstallCommand.php
@@ -3,6 +3,8 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
+
namespace Magento\Cron\Console\Command;
use Magento\Framework\Crontab\CrontabManagerInterface;
@@ -19,6 +21,9 @@
*/
class CronInstallCommand extends Command
{
+ private const COMMAND_OPTION_FORCE = 'force';
+ private const COMMAND_OPTION_NON_OPTIONAL = 'non-optional';
+
/**
* @var CrontabManagerInterface
*/
@@ -44,19 +49,27 @@ public function __construct(
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
protected function configure()
{
$this->setName('cron:install')
->setDescription('Generates and installs crontab for current user')
- ->addOption('force', 'f', InputOption::VALUE_NONE, 'Force install tasks');
+ ->addOption(self::COMMAND_OPTION_FORCE, 'f', InputOption::VALUE_NONE, 'Force install tasks')
+ // @codingStandardsIgnoreStart
+ ->addOption(self::COMMAND_OPTION_NON_OPTIONAL, 'd', InputOption::VALUE_NONE, 'Install only the non-optional (default) tasks');
+ // @codingStandardsIgnoreEnd
parent::configure();
}
/**
- * {@inheritdoc}
+ * Executes "cron:install" command.
+ *
+ * @param InputInterface $input
+ * @param OutputInterface $output
+ * @return int|null
+ * @throws LocalizedException
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
@@ -65,8 +78,13 @@ protected function execute(InputInterface $input, OutputInterface $output)
return Cli::RETURN_FAILURE;
}
+ $tasks = $this->tasksProvider->getTasks();
+ if ($input->getOption(self::COMMAND_OPTION_NON_OPTIONAL)) {
+ $tasks = $this->extractNonOptionalTasks($tasks);
+ }
+
try {
- $this->crontabManager->saveTasks($this->tasksProvider->getTasks());
+ $this->crontabManager->saveTasks($tasks);
} catch (LocalizedException $e) {
$output->writeln('' . $e->getMessage() . '');
return Cli::RETURN_FAILURE;
@@ -76,4 +94,23 @@ protected function execute(InputInterface $input, OutputInterface $output)
return Cli::RETURN_SUCCESS;
}
+
+ /**
+ * Returns an array of non-optional tasks
+ *
+ * @param array $tasks
+ * @return array
+ */
+ private function extractNonOptionalTasks(array $tasks = []): array
+ {
+ $defaultTasks = [];
+
+ foreach ($tasks as $taskCode => $taskParams) {
+ if (!$taskParams['optional']) {
+ $defaultTasks[$taskCode] = $taskParams;
+ }
+ }
+
+ return $defaultTasks;
+ }
}
diff --git a/app/code/Magento/Cron/etc/di.xml b/app/code/Magento/Cron/etc/di.xml
index 3e3bdc2053576..eadfa15d49414 100644
--- a/app/code/Magento/Cron/etc/di.xml
+++ b/app/code/Magento/Cron/etc/di.xml
@@ -66,12 +66,15 @@
-
- {magentoRoot}bin/magento cron:run | grep -v "Ran jobs by schedule" >> {magentoLog}magento.cron.log
+ - false
-
- {magentoRoot}update/cron.php >> {magentoLog}update.cron.log
+ - true
-
- {magentoRoot}bin/magento setup:cron:run >> {magentoLog}setup.cron.log
+ - true
diff --git a/app/code/Magento/Customer/Block/Widget/Dob.php b/app/code/Magento/Customer/Block/Widget/Dob.php
index 55101fb82afd0..d874729d9132e 100644
--- a/app/code/Magento/Customer/Block/Widget/Dob.php
+++ b/app/code/Magento/Customer/Block/Widget/Dob.php
@@ -99,11 +99,34 @@ public function isRequired()
*/
public function setDate($date)
{
- $this->setTime($date ? strtotime($date) : false);
+ $this->setTime($this->filterTime($date));
$this->setValue($this->applyOutputFilter($date));
return $this;
}
+ /**
+ * Sanitizes time
+ *
+ * @param mixed $value
+ * @return bool|int
+ */
+ private function filterTime($value)
+ {
+ $time = false;
+ if ($value) {
+ if ($value instanceof \DateTimeInterface) {
+ $time = $value->getTimestamp();
+ } elseif (is_numeric($value)) {
+ $time = $value;
+ } elseif (is_string($value)) {
+ $time = strtotime($value);
+ $time = $time === false ? $this->_localeDate->date($value, null, false, false)->getTimestamp() : $time;
+ }
+ }
+
+ return $time;
+ }
+
/**
* Return Data Form Filter or false
*
@@ -200,21 +223,23 @@ public function getStoreLabel($attributeCode)
*/
public function getFieldHtml()
{
- $this->dateElement->setData([
- 'extra_params' => $this->getHtmlExtraParams(),
- 'name' => $this->getHtmlId(),
- 'id' => $this->getHtmlId(),
- 'class' => $this->getHtmlClass(),
- 'value' => $this->getValue(),
- 'date_format' => $this->getDateFormat(),
- 'image' => $this->getViewFileUrl('Magento_Theme::calendar.png'),
- 'years_range' => '-120y:c+nn',
- 'max_date' => '-1d',
- 'change_month' => 'true',
- 'change_year' => 'true',
- 'show_on' => 'both',
- 'first_day' => $this->getFirstDay()
- ]);
+ $this->dateElement->setData(
+ [
+ 'extra_params' => $this->getHtmlExtraParams(),
+ 'name' => $this->getHtmlId(),
+ 'id' => $this->getHtmlId(),
+ 'class' => $this->getHtmlClass(),
+ 'value' => $this->getValue(),
+ 'date_format' => $this->getDateFormat(),
+ 'image' => $this->getViewFileUrl('Magento_Theme::calendar.png'),
+ 'years_range' => '-120y:c+nn',
+ 'max_date' => '-1d',
+ 'change_month' => 'true',
+ 'change_year' => 'true',
+ 'show_on' => 'both',
+ 'first_day' => $this->getFirstDay()
+ ]
+ );
return $this->dateElement->getHtml();
}
diff --git a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php
index 6926eee4f28f2..8bfddac3cef8f 100644
--- a/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php
+++ b/app/code/Magento/Customer/Test/Unit/Block/Widget/DobTest.php
@@ -6,14 +6,31 @@
namespace Magento\Customer\Test\Unit\Block\Widget;
+use Magento\Customer\Api\CustomerMetadataInterface;
+use Magento\Customer\Api\Data\AttributeMetadataInterface;
+use Magento\Customer\Api\Data\ValidationRuleInterface;
+use Magento\Customer\Helper\Address;
+use Magento\Framework\App\CacheInterface;
+use Magento\Framework\Cache\FrontendInterface;
+use Magento\Framework\Data\Form\FilterFactory;
+use Magento\Framework\Escaper;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Customer\Block\Widget\Dob;
use Magento\Framework\Locale\Resolver;
+use Magento\Framework\Locale\ResolverInterface;
+use Magento\Framework\Stdlib\DateTime\Timezone;
+use Magento\Framework\TestFramework\Unit\Helper\ObjectManager;
+use Magento\Framework\View\Element\Html\Date;
+use Magento\Framework\View\Element\Template\Context;
+use PHPUnit\Framework\TestCase;
+use PHPUnit_Framework_MockObject_MockObject;
+use Zend_Cache_Backend_BlackHole;
+use Zend_Cache_Core;
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
-class DobTest extends \PHPUnit\Framework\TestCase
+class DobTest extends TestCase
{
/** Constants used in the unit tests */
const MIN_DATE = '01/01/2010';
@@ -43,82 +60,105 @@ class DobTest extends \PHPUnit\Framework\TestCase
const YEAR_HTML =
'';
- /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Api\Data\AttributeMetadataInterface */
+ /** @var PHPUnit_Framework_MockObject_MockObject|AttributeMetadataInterface */
protected $attribute;
/** @var Dob */
protected $_block;
- /** @var \PHPUnit_Framework_MockObject_MockObject|\Magento\Customer\Api\CustomerMetadataInterface */
+ /** @var PHPUnit_Framework_MockObject_MockObject|CustomerMetadataInterface */
protected $customerMetadata;
/**
- * @var \Magento\Framework\Data\Form\FilterFactory|\PHPUnit_Framework_MockObject_MockObject
+ * @var FilterFactory|PHPUnit_Framework_MockObject_MockObject
*/
protected $filterFactory;
/**
- * @var \Magento\Framework\Escaper
+ * @var Escaper
*/
private $escaper;
/**
- * @var \Magento\Framework\View\Element\Template\Context
+ * @var Context
*/
private $context;
+ /**
+ * @var string
+ */
+ private $_locale;
+ /**
+ * @inheritDoc
+ */
protected function setUp()
{
- $zendCacheCore = new \Zend_Cache_Core();
- $zendCacheCore->setBackend(new \Zend_Cache_Backend_BlackHole());
+ $zendCacheCore = new Zend_Cache_Core();
+ $zendCacheCore->setBackend(new Zend_Cache_Backend_BlackHole());
$frontendCache = $this->getMockForAbstractClass(
- \Magento\Framework\Cache\FrontendInterface::class,
+ FrontendInterface::class,
[],
'',
false
);
$frontendCache->expects($this->any())->method('getLowLevelFrontend')->will($this->returnValue($zendCacheCore));
- $cache = $this->createMock(\Magento\Framework\App\CacheInterface::class);
+ $cache = $this->createMock(CacheInterface::class);
$cache->expects($this->any())->method('getFrontend')->will($this->returnValue($frontendCache));
- $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
- $localeResolver = $this->createMock(\Magento\Framework\Locale\ResolverInterface::class);
+ $objectManager = new ObjectManager($this);
+ $localeResolver = $this->createMock(ResolverInterface::class);
$localeResolver->expects($this->any())
->method('getLocale')
- ->willReturn(Resolver::DEFAULT_LOCALE);
+ ->willReturnCallback(
+ function () {
+ return $this->_locale;
+ }
+ );
$timezone = $objectManager->getObject(
- \Magento\Framework\Stdlib\DateTime\Timezone::class,
+ Timezone::class,
['localeResolver' => $localeResolver]
);
- $this->context = $this->createMock(\Magento\Framework\View\Element\Template\Context::class);
+ $this->_locale = Resolver::DEFAULT_LOCALE;
+ $this->context = $this->createMock(Context::class);
$this->context->expects($this->any())->method('getLocaleDate')->will($this->returnValue($timezone));
- $this->escaper = $this->getMockBuilder(\Magento\Framework\Escaper::class)
+ $this->escaper = $this->getMockBuilder(Escaper::class)
->disableOriginalConstructor()
->setMethods(['escapeHtml'])
->getMock();
$this->context->expects($this->any())->method('getEscaper')->will($this->returnValue($this->escaper));
- $this->attribute = $this->getMockBuilder(\Magento\Customer\Api\Data\AttributeMetadataInterface::class)
+ $this->attribute = $this->getMockBuilder(AttributeMetadataInterface::class)
->getMockForAbstractClass();
- $this->customerMetadata = $this->getMockBuilder(\Magento\Customer\Api\CustomerMetadataInterface::class)
+ $this->attribute
+ ->expects($this->any())
+ ->method('getInputFilter')
+ ->willReturn('date');
+ $this->customerMetadata = $this->getMockBuilder(CustomerMetadataInterface::class)
->getMockForAbstractClass();
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will($this->returnValue($this->attribute));
- date_default_timezone_set('America/Los_Angeles');
-
- $this->filterFactory = $this->getMockBuilder(\Magento\Framework\Data\Form\FilterFactory::class)
- ->disableOriginalConstructor()
- ->getMock();
+ $this->filterFactory = $this->createMock(FilterFactory::class);
+ $this->filterFactory
+ ->expects($this->any())
+ ->method('create')
+ ->willReturnCallback(
+ function () use ($timezone, $localeResolver) {
+ return new \Magento\Framework\Data\Form\Filter\Date(
+ $timezone->getDateFormatWithLongYear(),
+ $localeResolver
+ );
+ }
+ );
- $this->_block = new \Magento\Customer\Block\Widget\Dob(
+ $this->_block = new Dob(
$this->context,
- $this->createMock(\Magento\Customer\Helper\Address::class),
+ $this->createMock(Address::class),
$this->customerMetadata,
- $this->createMock(\Magento\Framework\View\Element\Html\Date::class),
+ $this->createMock(Date::class),
$this->filterFactory
);
}
@@ -143,17 +183,22 @@ public function isEnabledDataProvider()
return [[true, true], [false, false]];
}
+ /**
+ * Tests isEnabled()
+ */
public function testIsEnabledWithException()
{
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will(
- $this->throwException(new NoSuchEntityException(
- __(
- 'No such entity with %fieldName = %fieldValue',
- ['fieldName' => 'field', 'fieldValue' => 'value']
+ $this->throwException(
+ new NoSuchEntityException(
+ __(
+ 'No such entity with %fieldName = %fieldValue',
+ ['fieldName' => 'field', 'fieldValue' => 'value']
+ )
)
- ))
+ )
);
$this->assertSame(false, $this->_block->isEnabled());
}
@@ -175,12 +220,14 @@ public function testIsRequiredWithException()
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will(
- $this->throwException(new NoSuchEntityException(
- __(
- 'No such entity with %fieldName = %fieldValue',
- ['fieldName' => 'field', 'fieldValue' => 'value']
+ $this->throwException(
+ new NoSuchEntityException(
+ __(
+ 'No such entity with %fieldName = %fieldValue',
+ ['fieldName' => 'field', 'fieldValue' => 'value']
+ )
)
- ))
+ )
);
$this->assertSame(false, $this->_block->isRequired());
}
@@ -197,14 +244,15 @@ public function isRequiredDataProvider()
* @param string|bool $date Date (e.g. '01/01/2020' or false for no date)
* @param int|bool $expectedTime The value we expect from Dob::getTime()
* @param string|bool $expectedDate The value we expect from Dob::getData('date')
- *
+ * @param string $locale
* @dataProvider setDateDataProvider
*/
- public function testSetDate($date, $expectedTime, $expectedDate)
+ public function testSetDate($date, $expectedTime, $expectedDate, $locale = Resolver::DEFAULT_LOCALE)
{
+ $this->_locale = $locale;
$this->assertSame($this->_block, $this->_block->setDate($date));
- $this->assertEquals($expectedTime, $this->_block->getTime());
- $this->assertEquals($expectedDate, $this->_block->getValue());
+ $this->assertSame($expectedTime, $this->_block->getTime());
+ $this->assertSame($expectedDate, $this->_block->getValue());
}
/**
@@ -212,32 +260,19 @@ public function testSetDate($date, $expectedTime, $expectedDate)
*/
public function setDateDataProvider()
{
- return [[self::DATE, strtotime(self::DATE), self::DATE], [false, false, false]];
- }
-
- public function testSetDateWithFilter()
- {
- $date = '2014-01-01';
- $filterCode = 'date';
-
- $this->attribute->expects($this->once())
- ->method('getInputFilter')
- ->willReturn($filterCode);
-
- $filterMock = $this->getMockBuilder(\Magento\Framework\Data\Form\Filter\Date::class)
- ->disableOriginalConstructor()
- ->getMock();
- $filterMock->expects($this->once())
- ->method('outputFilter')
- ->with($date)
- ->willReturn(self::DATE);
-
- $this->filterFactory->expects($this->once())
- ->method('create')
- ->with($filterCode, ['format' => self::DATE_FORMAT])
- ->willReturn($filterMock);
-
- $this->_block->setDate($date);
+ return [
+ [false, false, false],
+ ['', false, ''],
+ ['12/31/1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'],
+ ['31-12-1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'],
+ ['1999-12-31', strtotime('1999-12-31'), '12/31/1999', 'en_US'],
+ ['31 December 1999', strtotime('1999-12-31'), '12/31/1999', 'en_US'],
+ ['12/31/1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ['31-12-1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ['31/12/1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ['1999-12-31', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ['31 Décembre 1999', strtotime('1999-12-31'), '31/12/1999', 'fr_FR'],
+ ];
}
/**
@@ -301,7 +336,6 @@ public function getYearDataProvider()
}
/**
- * The \Magento\Framework\Locale\ResolverInterface::DEFAULT_LOCALE
* is used to derive the Locale that is used to determine the
* value of Dob::getDateFormat() for that Locale.
*/
@@ -358,12 +392,12 @@ public function testGetMinDateRange($validationRules, $expectedValue)
*/
public function getMinDateRangeDataProvider()
{
- $emptyValidationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class)
+ $emptyValidationRule = $this->getMockBuilder(ValidationRuleInterface::class)
->disableOriginalConstructor()
->setMethods(['getName', 'getValue'])
->getMockForAbstractClass();
- $validationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class)
+ $validationRule = $this->getMockBuilder(ValidationRuleInterface::class)
->disableOriginalConstructor()
->setMethods(['getName', 'getValue'])
->getMockForAbstractClass();
@@ -390,17 +424,22 @@ public function getMinDateRangeDataProvider()
];
}
+ /**
+ * Tests getMinDateRange()
+ */
public function testGetMinDateRangeWithException()
{
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will(
- $this->throwException(new NoSuchEntityException(
- __(
- 'No such entity with %fieldName = %fieldValue',
- ['fieldName' => 'field', 'fieldValue' => 'value']
+ $this->throwException(
+ new NoSuchEntityException(
+ __(
+ 'No such entity with %fieldName = %fieldValue',
+ ['fieldName' => 'field', 'fieldValue' => 'value']
+ )
)
- ))
+ )
);
$this->assertNull($this->_block->getMinDateRange());
}
@@ -424,12 +463,12 @@ public function testGetMaxDateRange($validationRules, $expectedValue)
*/
public function getMaxDateRangeDataProvider()
{
- $emptyValidationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class)
+ $emptyValidationRule = $this->getMockBuilder(ValidationRuleInterface::class)
->disableOriginalConstructor()
->setMethods(['getName', 'getValue'])
->getMockForAbstractClass();
- $validationRule = $this->getMockBuilder(\Magento\Customer\Api\Data\ValidationRuleInterface::class)
+ $validationRule = $this->getMockBuilder(ValidationRuleInterface::class)
->disableOriginalConstructor()
->setMethods(['getName', 'getValue'])
->getMockForAbstractClass();
@@ -455,21 +494,29 @@ public function getMaxDateRangeDataProvider()
];
}
+ /**
+ * Tests getMaxDateRange()
+ */
public function testGetMaxDateRangeWithException()
{
$this->customerMetadata->expects($this->any())
->method('getAttributeMetadata')
->will(
- $this->throwException(new NoSuchEntityException(
- __(
- 'No such entity with %fieldName = %fieldValue',
- ['fieldName' => 'field', 'fieldValue' => 'value']
+ $this->throwException(
+ new NoSuchEntityException(
+ __(
+ 'No such entity with %fieldName = %fieldValue',
+ ['fieldName' => 'field', 'fieldValue' => 'value']
+ )
)
- ))
+ )
);
$this->assertNull($this->_block->getMaxDateRange());
}
+ /**
+ * Tests getHtmlExtraParams() without required options
+ */
public function testGetHtmlExtraParamsWithoutRequiredOption()
{
$this->escaper->expects($this->any())
@@ -487,6 +534,9 @@ public function testGetHtmlExtraParamsWithoutRequiredOption()
);
}
+ /**
+ * Tests getHtmlExtraParams() with required options
+ */
public function testGetHtmlExtraParamsWithRequiredOption()
{
$this->attribute->expects($this->once())
diff --git a/app/code/Magento/DownloadableGraphQl/Resolver/Customer/DownloadableProducts.php b/app/code/Magento/CustomerDownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php
similarity index 95%
rename from app/code/Magento/DownloadableGraphQl/Resolver/Customer/DownloadableProducts.php
rename to app/code/Magento/CustomerDownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php
index 0d99b64d7ba4d..da5c6cf794bf5 100644
--- a/app/code/Magento/DownloadableGraphQl/Resolver/Customer/DownloadableProducts.php
+++ b/app/code/Magento/CustomerDownloadableGraphQl/Model/Resolver/CustomerDownloadableProducts.php
@@ -5,7 +5,7 @@
*/
declare(strict_types=1);
-namespace Magento\DownloadableGraphQl\Resolver\Customer;
+namespace Magento\CustomerDownloadableGraphQl\Model\Resolver;
use Magento\DownloadableGraphQl\Model\ResourceModel\GetPurchasedDownloadableProducts;
use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException;
@@ -20,7 +20,7 @@
*
* Returns available downloadable products for customer
*/
-class DownloadableProducts implements ResolverInterface
+class CustomerDownloadableProducts implements ResolverInterface
{
/**
* @var GetPurchasedDownloadableProducts
diff --git a/app/code/Magento/CustomerDownloadableGraphQl/README.md b/app/code/Magento/CustomerDownloadableGraphQl/README.md
new file mode 100644
index 0000000000000..044eeaf768a92
--- /dev/null
+++ b/app/code/Magento/CustomerDownloadableGraphQl/README.md
@@ -0,0 +1,4 @@
+# DownloadableGraphQl
+
+**CustomerDownloadableGraphQl** provides type and resolver information for the GraphQl module
+to generate downloadable product information.
diff --git a/app/code/Magento/CustomerDownloadableGraphQl/Test/Mftf/README.md b/app/code/Magento/CustomerDownloadableGraphQl/Test/Mftf/README.md
new file mode 100644
index 0000000000000..51a682e004f58
--- /dev/null
+++ b/app/code/Magento/CustomerDownloadableGraphQl/Test/Mftf/README.md
@@ -0,0 +1,3 @@
+# Downloadable Graph Ql Functional Tests
+
+The Functional Test Module for **Magento Customer Downloadable Graph Ql** module.
diff --git a/app/code/Magento/CustomerDownloadableGraphQl/composer.json b/app/code/Magento/CustomerDownloadableGraphQl/composer.json
new file mode 100644
index 0000000000000..418d2b57b8b42
--- /dev/null
+++ b/app/code/Magento/CustomerDownloadableGraphQl/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "magento/module-customer-downloadable-graph-ql",
+ "description": "N/A",
+ "type": "magento2-module",
+ "require": {
+ "php": "~7.1.3||~7.2.0||~7.3.0",
+ "magento/module-downloadable-graph-ql": "*",
+ "magento/module-graph-ql": "*",
+ "magento/framework": "*"
+ },
+ "suggest": {
+ "magento/module-catalog-graph-ql": "*"
+ },
+ "license": [
+ "OSL-3.0",
+ "AFL-3.0"
+ ],
+ "autoload": {
+ "files": [
+ "registration.php"
+ ],
+ "psr-4": {
+ "Magento\\CustomerDownloadableGraphQl\\": ""
+ }
+ }
+}
diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml
new file mode 100644
index 0000000000000..934bc81ae629a
--- /dev/null
+++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/module.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls
new file mode 100644
index 0000000000000..5ff0dc664ff74
--- /dev/null
+++ b/app/code/Magento/CustomerDownloadableGraphQl/etc/schema.graphqls
@@ -0,0 +1,18 @@
+# Copyright © Magento, Inc. All rights reserved.
+# See COPYING.txt for license details.
+
+type Query {
+ customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\CustomerDownloadableGraphQl\\Model\\Resolver\\CustomerDownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") @cache(cacheable: false)
+}
+
+type CustomerDownloadableProducts {
+ items: [CustomerDownloadableProduct] @doc(description: "List of purchased downloadable items")
+}
+
+type CustomerDownloadableProduct {
+ order_increment_id: String
+ date: String
+ status: String
+ download_url: String
+ remaining_downloads: String
+}
diff --git a/app/code/Magento/CustomerDownloadableGraphQl/registration.php b/app/code/Magento/CustomerDownloadableGraphQl/registration.php
new file mode 100644
index 0000000000000..c029446bb5651
--- /dev/null
+++ b/app/code/Magento/CustomerDownloadableGraphQl/registration.php
@@ -0,0 +1,9 @@
+_nextEntityId) {
- /** @var $addressResource \Magento\Customer\Model\ResourceModel\Address */
- $addressResource = $this->_addressFactory->create()->getResource();
- $addressTable = $addressResource->getEntityTable();
- $this->_nextEntityId = $this->_resourceHelper->getNextAutoincrement($addressTable);
+ $this->_nextEntityId = $this->_resourceHelper->getNextAutoincrement($this->_entityTable);
}
return $this->_nextEntityId++;
}
@@ -587,7 +588,6 @@ protected function _mergeEntityAttributes(array $newAttributes, array $attribute
*/
protected function _prepareDataForUpdate(array $rowData):array
{
- $multiSeparator = $this->getMultipleValueSeparator();
$email = strtolower($rowData[self::COLUMN_EMAIL]);
$customerId = $this->_getCustomerId($email, $rowData[self::COLUMN_WEBSITE]);
// entity table data
@@ -621,27 +621,18 @@ protected function _prepareDataForUpdate(array $rowData):array
if (array_key_exists($attributeAlias, $rowData)) {
$attributeParams = $this->adjustAttributeDataForWebsite($attributeParams, $websiteId);
+ $value = $rowData[$attributeAlias];
+
if (!strlen($rowData[$attributeAlias])) {
- if ($newAddress) {
- $value = null;
- } else {
+ if (!$newAddress) {
continue;
}
- } elseif ($newAddress && !strlen($rowData[$attributeAlias])) {
- } elseif (in_array($attributeParams['type'], ['select', 'boolean'])) {
- $value = $this->getSelectAttrIdByValue($attributeParams, mb_strtolower($rowData[$attributeAlias]));
- } elseif ('datetime' == $attributeParams['type']) {
- $value = (new \DateTime())->setTimestamp(strtotime($rowData[$attributeAlias]));
- $value = $value->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT);
- } elseif ('multiselect' == $attributeParams['type']) {
- $ids = [];
- foreach (explode($multiSeparator, mb_strtolower($rowData[$attributeAlias])) as $subValue) {
- $ids[] = $this->getSelectAttrIdByValue($attributeParams, $subValue);
- }
- $value = implode(',', $ids);
- } else {
- $value = $rowData[$attributeAlias];
+
+ $value = null;
+ } elseif (in_array($attributeParams['type'], ['select', 'boolean', 'datetime', 'multiselect'])) {
+ $value = $this->getValueByAttributeType($rowData[$attributeAlias], $attributeParams);
}
+
if ($attributeParams['is_static']) {
$entityRow[$attributeAlias] = $value;
} else {
@@ -651,22 +642,18 @@ protected function _prepareDataForUpdate(array $rowData):array
}
foreach (self::getDefaultAddressAttributeMapping() as $columnName => $attributeCode) {
if (!empty($rowData[$columnName])) {
- /** @var $attribute \Magento\Eav\Model\Entity\Attribute\AbstractAttribute */
$table = $this->_getCustomerEntity()->getResource()->getTable('customer_entity');
$defaults[$table][$customerId][$attributeCode] = $addressId;
}
}
// let's try to find region ID
$entityRow['region_id'] = null;
- if (!empty($rowData[self::COLUMN_REGION])) {
- $countryNormalized = strtolower($rowData[self::COLUMN_COUNTRY_ID]);
- $regionNormalized = strtolower($rowData[self::COLUMN_REGION]);
-
- if (isset($this->_countryRegions[$countryNormalized][$regionNormalized])) {
- $regionId = $this->_countryRegions[$countryNormalized][$regionNormalized];
- $entityRow[self::COLUMN_REGION] = $this->_regions[$regionId];
- $entityRow['region_id'] = $regionId;
- }
+
+ if (!empty($rowData[self::COLUMN_REGION])
+ && $this->getCountryRegionId($rowData[self::COLUMN_COUNTRY_ID], $rowData[self::COLUMN_REGION]) !== false) {
+ $regionId = $this->getCountryRegionId($rowData[self::COLUMN_COUNTRY_ID], $rowData[self::COLUMN_REGION]);
+ $entityRow[self::COLUMN_REGION] = $this->_regions[$regionId];
+ $entityRow['region_id'] = $regionId;
}
if ($newAddress) {
$entityRowNew = $entityRow;
@@ -684,6 +671,39 @@ protected function _prepareDataForUpdate(array $rowData):array
];
}
+ /**
+ * Process row data, based on attirbute type
+ *
+ * @param string $rowAttributeData
+ * @param array $attributeParams
+ * @return \DateTime|int|string
+ * @throws \Exception
+ */
+ protected function getValueByAttributeType(string $rowAttributeData, array $attributeParams)
+ {
+ $multiSeparator = $this->getMultipleValueSeparator();
+ $value = $rowAttributeData;
+ switch ($attributeParams['type']) {
+ case 'select':
+ case 'boolean':
+ $value = $this->getSelectAttrIdByValue($attributeParams, mb_strtolower($rowAttributeData));
+ break;
+ case 'datetime':
+ $value = (new \DateTime())->setTimestamp(strtotime($rowAttributeData));
+ $value = $value->format(\Magento\Framework\Stdlib\DateTime::DATETIME_PHP_FORMAT);
+ break;
+ case 'multiselect':
+ $ids = [];
+ foreach (explode($multiSeparator, mb_strtolower($rowAttributeData)) as $subValue) {
+ $ids[] = $this->getSelectAttrIdByValue($attributeParams, $subValue);
+ }
+ $value = implode(',', $ids);
+ break;
+ }
+
+ return $value;
+ }
+
/**
* Update and insert data in entity table
*
@@ -741,6 +761,7 @@ protected function _saveCustomerDefaults(array $defaults)
{
foreach ($defaults as $tableName => $data) {
foreach ($data as $customerId => $defaultsData) {
+ // phpcs:ignore Magento2.Performance.ForeachArrayMerge
$data = array_merge(
['entity_id' => $customerId],
$defaultsData
@@ -781,11 +802,13 @@ public function getEntityTypeCode()
*
* @static
* @return array
+ * phpcs:disable Magento2.Functions.StaticFunction
*/
public static function getDefaultAddressAttributeMapping()
{
return self::$_defaultAddressAttributeMapping;
}
+ // phpcs:enable
/**
* Check if address for import is empty (for customer composite mode)
@@ -820,7 +843,6 @@ protected function _isOptionalAddressEmpty(array $rowData)
* @param int $rowNumber
* @return void
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
- * @SuppressWarnings(PHPMD.NPathComplexity)
*/
protected function _validateRowForUpdate(array $rowData, $rowNumber)
{
@@ -833,38 +855,36 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber)
if ($customerId === false) {
$this->addRowError(self::ERROR_CUSTOMER_NOT_FOUND, $rowNumber);
+ } elseif ($this->_checkRowDuplicate($customerId, $addressId)) {
+ $this->addRowError(self::ERROR_DUPLICATE_PK, $rowNumber);
} else {
- if ($this->_checkRowDuplicate($customerId, $addressId)) {
- $this->addRowError(self::ERROR_DUPLICATE_PK, $rowNumber);
- } else {
- // check simple attributes
- foreach ($this->_attributes as $attributeCode => $attributeParams) {
- $websiteId = $this->_websiteCodeToId[$website];
- $attributeParams = $this->adjustAttributeDataForWebsite($attributeParams, $websiteId);
+ // check simple attributes
+ foreach ($this->_attributes as $attributeCode => $attributeParams) {
+ $websiteId = $this->_websiteCodeToId[$website];
+ $attributeParams = $this->adjustAttributeDataForWebsite($attributeParams, $websiteId);
- if (in_array($attributeCode, $this->_ignoredAttributes)) {
- continue;
- }
- if (isset($rowData[$attributeCode]) && strlen($rowData[$attributeCode])) {
- $this->isAttributeValid(
- $attributeCode,
- $attributeParams,
- $rowData,
- $rowNumber,
- $multiSeparator
- );
- } elseif ($attributeParams['is_required']
- && !$this->addressStorage->doesExist(
- (string)$addressId,
- (string)$customerId
- )
- ) {
- $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, $attributeCode);
- }
+ if (in_array($attributeCode, $this->_ignoredAttributes)) {
+ continue;
+ } elseif (isset($rowData[$attributeCode]) && strlen($rowData[$attributeCode])) {
+ $this->isAttributeValid(
+ $attributeCode,
+ $attributeParams,
+ $rowData,
+ $rowNumber,
+ $multiSeparator
+ );
+ } elseif ($attributeParams['is_required']
+ && !$this->addressStorage->doesExist(
+ (string)$addressId,
+ (string)$customerId
+ )
+ ) {
+ $this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, $attributeCode);
}
+ }
+ if (isset($rowData[self::COLUMN_COUNTRY_ID])) {
if (isset($rowData[self::COLUMN_POSTCODE])
- && isset($rowData[self::COLUMN_COUNTRY_ID])
&& !$this->postcodeValidator->isValid(
$rowData[self::COLUMN_COUNTRY_ID],
$rowData[self::COLUMN_POSTCODE]
@@ -873,19 +893,14 @@ protected function _validateRowForUpdate(array $rowData, $rowNumber)
$this->addRowError(self::ERROR_VALUE_IS_REQUIRED, $rowNumber, self::COLUMN_POSTCODE);
}
- if (isset($rowData[self::COLUMN_COUNTRY_ID]) && isset($rowData[self::COLUMN_REGION])) {
- $countryRegions = isset(
- $this->_countryRegions[strtolower($rowData[self::COLUMN_COUNTRY_ID])]
- ) ? $this->_countryRegions[strtolower(
- $rowData[self::COLUMN_COUNTRY_ID]
- )] : [];
-
- if (!empty($rowData[self::COLUMN_REGION]) && !empty($countryRegions) && !isset(
- $countryRegions[strtolower($rowData[self::COLUMN_REGION])]
+ if (isset($rowData[self::COLUMN_REGION])
+ && !empty($rowData[self::COLUMN_REGION])
+ && false === $this->getCountryRegionId(
+ $rowData[self::COLUMN_COUNTRY_ID],
+ $rowData[self::COLUMN_REGION]
)
- ) {
- $this->addRowError(self::ERROR_INVALID_REGION, $rowNumber, self::COLUMN_REGION);
- }
+ ) {
+ $this->addRowError(self::ERROR_INVALID_REGION, $rowNumber, self::COLUMN_REGION);
}
}
}
@@ -909,15 +924,13 @@ protected function _validateRowForDelete(array $rowData, $rowNumber)
$customerId = $this->_getCustomerId($email, $website);
if ($customerId === false) {
$this->addRowError(self::ERROR_CUSTOMER_NOT_FOUND, $rowNumber);
- } else {
- if (!strlen($addressId)) {
- $this->addRowError(self::ERROR_ADDRESS_ID_IS_EMPTY, $rowNumber);
- } elseif (!$this->addressStorage->doesExist(
- (string)$addressId,
- (string)$customerId
- )) {
- $this->addRowError(self::ERROR_ADDRESS_NOT_FOUND, $rowNumber);
- }
+ } elseif (!strlen($addressId)) {
+ $this->addRowError(self::ERROR_ADDRESS_ID_IS_EMPTY, $rowNumber);
+ } elseif (!$this->addressStorage->doesExist(
+ (string)$addressId,
+ (string)$customerId
+ )) {
+ $this->addRowError(self::ERROR_ADDRESS_NOT_FOUND, $rowNumber);
}
}
}
@@ -931,19 +944,18 @@ protected function _validateRowForDelete(array $rowData, $rowNumber)
*/
protected function _checkRowDuplicate($customerId, $addressId)
{
- if ($this->addressStorage->doesExist(
+ $isAddressExists = $this->addressStorage->doesExist(
(string)$addressId,
(string)$customerId
- )) {
- if (!isset($this->_importedRowPks[$customerId][$addressId])) {
- $this->_importedRowPks[$customerId][$addressId] = true;
- return false;
- } else {
- return true;
- }
- } else {
- return false;
+ );
+
+ $isPkRowSet = isset($this->_importedRowPks[$customerId][$addressId]);
+
+ if ($isAddressExists && !$isPkRowSet) {
+ $this->_importedRowPks[$customerId][$addressId] = true;
}
+
+ return $isAddressExists && $isPkRowSet;
}
/**
@@ -957,4 +969,24 @@ public function setCustomerAttributes($customerAttributes)
$this->_customerAttributes = $customerAttributes;
return $this;
}
+
+ /**
+ * Get RegionID from the initialized data
+ *
+ * @param string $countryId
+ * @param string $region
+ * @return bool|int
+ */
+ private function getCountryRegionId(string $countryId, string $region)
+ {
+ $countryNormalized = strtolower($countryId);
+ $regionNormalized = strtolower($region);
+
+ if (isset($this->_countryRegions[$countryNormalized])
+ && isset($this->_countryRegions[$countryNormalized][$regionNormalized])) {
+ return $this->_countryRegions[$countryNormalized][$regionNormalized];
+ }
+
+ return false;
+ }
}
diff --git a/app/code/Magento/Dhl/etc/adminhtml/system.xml b/app/code/Magento/Dhl/etc/adminhtml/system.xml
index 597f33b579282..ea3da2ca031a5 100644
--- a/app/code/Magento/Dhl/etc/adminhtml/system.xml
+++ b/app/code/Magento/Dhl/etc/adminhtml/system.xml
@@ -137,6 +137,7 @@
+ validate-number validate-zero-or-greater
diff --git a/app/code/Magento/Directory/Model/Data/ExchangeRate.php b/app/code/Magento/Directory/Model/Data/ExchangeRate.php
index 35aee75f10481..500244304b7b8 100644
--- a/app/code/Magento/Directory/Model/Data/ExchangeRate.php
+++ b/app/code/Magento/Directory/Model/Data/ExchangeRate.php
@@ -5,6 +5,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Directory\Model\Data;
/**
@@ -17,6 +20,7 @@ class ExchangeRate extends \Magento\Framework\Api\AbstractExtensibleObject imple
{
const KEY_CURRENCY_TO = 'currency_to';
const KEY_RATE = 'rate';
+ private const KEY_EXCHANGE_RATES = 'exchange_rates';
/**
* @inheritDoc
diff --git a/app/code/Magento/DownloadableGraphQl/composer.json b/app/code/Magento/DownloadableGraphQl/composer.json
index 947b4001a5da5..e2e1098031766 100644
--- a/app/code/Magento/DownloadableGraphQl/composer.json
+++ b/app/code/Magento/DownloadableGraphQl/composer.json
@@ -6,7 +6,6 @@
"php": "~7.1.3||~7.2.0||~7.3.0",
"magento/module-catalog": "*",
"magento/module-downloadable": "*",
- "magento/module-graph-ql": "*",
"magento/module-quote": "*",
"magento/module-quote-graph-ql": "*",
"magento/framework": "*"
diff --git a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
index 2b986694e2996..db452d1e5ace1 100644
--- a/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/DownloadableGraphQl/etc/schema.graphqls
@@ -1,10 +1,6 @@
# Copyright © Magento, Inc. All rights reserved.
# See COPYING.txt for license details.
-type Query {
- customerDownloadableProducts: CustomerDownloadableProducts @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\Customer\\DownloadableProducts") @doc(description: "The query returns the contents of a customer's downloadable products") @cache(cacheable: false)
-}
-
type Mutation {
addDownloadableProductsToCart(input: AddDownloadableProductsToCartInput): AddDownloadableProductsToCartOutput @resolver(class: "Magento\\QuoteGraphQl\\Model\\Resolver\\AddSimpleProductsToCart")
}
@@ -34,18 +30,6 @@ type DownloadableCartItem implements CartItemInterface @doc(description: "Downlo
samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\DownloadableCartItem\\Samples") @doc(description: "DownloadableProductSamples defines characteristics of a downloadable product")
}
-type CustomerDownloadableProducts {
- items: [CustomerDownloadableProduct] @doc(description: "List of purchased downloadable items")
-}
-
-type CustomerDownloadableProduct {
- order_increment_id: String
- date: String
- status: String
- download_url: String
- remaining_downloads: String
-}
-
type DownloadableProduct implements ProductInterface, CustomizableProductInterface @doc(description: "DownloadableProduct defines a product that the customer downloads") {
downloadable_product_samples: [DownloadableProductSamples] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\Product\\Samples") @doc(description: "An array containing information about samples of this downloadable product.")
downloadable_product_links: [DownloadableProductLinks] @resolver(class: "Magento\\DownloadableGraphQl\\Resolver\\Product\\Links") @doc(description: "An array containing information about the links for this downloadable product")
@@ -54,8 +38,8 @@ type DownloadableProduct implements ProductInterface, CustomizableProductInterfa
}
enum DownloadableFileTypeEnum @deprecated(reason: "`sample_url` serves to get the downloadable sample") {
- FILE
- URL
+ FILE @deprecated(reason: "`sample_url` serves to get the downloadable sample")
+ URL @deprecated(reason: "`sample_url` serves to get the downloadable sample")
}
type DownloadableProductLinks @doc(description: "DownloadableProductLinks defines characteristics of a downloadable product") {
diff --git a/app/code/Magento/Eav/Model/AttributeProvider.php b/app/code/Magento/Eav/Model/AttributeProvider.php
index 7f9d0620b6096..419a33664e0eb 100644
--- a/app/code/Magento/Eav/Model/AttributeProvider.php
+++ b/app/code/Magento/Eav/Model/AttributeProvider.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Eav\Model;
@@ -52,7 +53,7 @@ public function __construct(
* Returns array of fields
*
* @param string $entityType
- * @return array
+ * @return string[]
* @throws \Exception
*/
public function getAttributes($entityType)
@@ -66,6 +67,7 @@ public function getAttributes($entityType)
foreach ($searchResult->getItems() as $attribute) {
$attributes[] = $attribute->getAttributeCode();
}
+
return $attributes;
}
}
diff --git a/app/code/Magento/Fedex/etc/adminhtml/system.xml b/app/code/Magento/Fedex/etc/adminhtml/system.xml
index bbaea4023a794..fdbe10a1a352e 100644
--- a/app/code/Magento/Fedex/etc/adminhtml/system.xml
+++ b/app/code/Magento/Fedex/etc/adminhtml/system.xml
@@ -135,6 +135,7 @@
+ validate-number validate-zero-or-greater
diff --git a/app/code/Magento/MediaStorage/Service/ImageResize.php b/app/code/Magento/MediaStorage/Service/ImageResize.php
index 90cdcb7159b0c..63353b2536a5a 100644
--- a/app/code/Magento/MediaStorage/Service/ImageResize.php
+++ b/app/code/Magento/MediaStorage/Service/ImageResize.php
@@ -7,10 +7,12 @@
namespace Magento\MediaStorage\Service;
+use Generator;
use Magento\Catalog\Helper\Image as ImageHelper;
use Magento\Catalog\Model\Product\Image\ParamsBuilder;
use Magento\Catalog\Model\View\Asset\ImageFactory as AssertImageFactory;
use Magento\Framework\App\Area;
+use Magento\Framework\App\ObjectManager;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\Filesystem;
use Magento\Framework\Image;
@@ -19,10 +21,12 @@
use Magento\Framework\App\State;
use Magento\Framework\View\ConfigInterface as ViewConfig;
use \Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage;
+use Magento\Store\Model\StoreManagerInterface;
use Magento\Theme\Model\Config\Customization as ThemeCustomizationConfig;
use Magento\Theme\Model\ResourceModel\Theme\Collection;
use Magento\Framework\App\Filesystem\DirectoryList;
use Magento\MediaStorage\Helper\File\Storage\Database;
+use Magento\Theme\Model\Theme;
/**
* Image resize service.
@@ -90,6 +94,10 @@ class ImageResize
* @var Database
*/
private $fileStorageDatabase;
+ /**
+ * @var StoreManagerInterface
+ */
+ private $storeManager;
/**
* @param State $appState
@@ -103,6 +111,8 @@ class ImageResize
* @param Collection $themeCollection
* @param Filesystem $filesystem
* @param Database $fileStorageDatabase
+ * @param StoreManagerInterface $storeManager
+ * @throws \Magento\Framework\Exception\FileSystemException
* @internal param ProductImage $gallery
* @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
@@ -117,7 +127,8 @@ public function __construct(
ThemeCustomizationConfig $themeCustomizationConfig,
Collection $themeCollection,
Filesystem $filesystem,
- Database $fileStorageDatabase = null
+ Database $fileStorageDatabase = null,
+ StoreManagerInterface $storeManager = null
) {
$this->appState = $appState;
$this->imageConfig = $imageConfig;
@@ -131,7 +142,8 @@ public function __construct(
$this->mediaDirectory = $filesystem->getDirectoryWrite(DirectoryList::MEDIA);
$this->filesystem = $filesystem;
$this->fileStorageDatabase = $fileStorageDatabase ?:
- \Magento\Framework\App\ObjectManager::getInstance()->get(Database::class);
+ ObjectManager::getInstance()->get(Database::class);
+ $this->storeManager = $storeManager ?? ObjectManager::getInstance()->get(StoreManagerInterface::class);
}
/**
@@ -163,10 +175,10 @@ public function resizeFromImageName(string $originalImageName)
* Create resized images of different sizes from themes.
*
* @param array|null $themes
- * @return \Generator
+ * @return Generator
* @throws NotFoundException
*/
- public function resizeFromThemes(array $themes = null): \Generator
+ public function resizeFromThemes(array $themes = null): Generator
{
$count = $this->productImage->getCountUsedProductImages();
if (!$count) {
@@ -226,7 +238,8 @@ private function getThemesInUse(): array
private function getViewImages(array $themes): array
{
$viewImages = [];
- /** @var \Magento\Theme\Model\Theme $theme */
+ $stores = $this->storeManager->getStores(true);
+ /** @var Theme $theme */
foreach ($themes as $theme) {
$config = $this->viewConfig->getViewConfig(
[
@@ -236,9 +249,12 @@ private function getViewImages(array $themes): array
);
$images = $config->getMediaEntities('Magento_Catalog', ImageHelper::MEDIA_TYPE_CONFIG_NODE);
foreach ($images as $imageId => $imageData) {
- $uniqIndex = $this->getUniqueImageIndex($imageData);
- $imageData['id'] = $imageId;
- $viewImages[$uniqIndex] = $imageData;
+ foreach ($stores as $store) {
+ $data = $this->paramsBuilder->build($imageData, (int) $store->getId());
+ $uniqIndex = $this->getUniqueImageIndex($data);
+ $data['id'] = $imageId;
+ $viewImages[$uniqIndex] = $data;
+ }
}
}
return $viewImages;
@@ -280,13 +296,13 @@ private function makeImage(string $originalImagePath, array $imageParams): Image
/**
* Resize image.
*
- * @param array $viewImage
+ * @param array $imageParams
* @param string $originalImagePath
* @param string $originalImageName
*/
- private function resize(array $viewImage, string $originalImagePath, string $originalImageName)
+ private function resize(array $imageParams, string $originalImagePath, string $originalImageName)
{
- $imageParams = $this->paramsBuilder->build($viewImage);
+ unset($imageParams['id']);
$image = $this->makeImage($originalImagePath, $imageParams);
$imageAsset = $this->assertImageFactory->create(
[
diff --git a/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php b/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php
index d612fb9e3b67b..f0e1efa7806e4 100644
--- a/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php
+++ b/app/code/Magento/MediaStorage/Test/Unit/Service/ImageResizeTest.php
@@ -17,6 +17,7 @@
use Magento\Framework\View\ConfigInterface as ViewConfig;
use Magento\Framework\Config\View;
use Magento\Catalog\Model\ResourceModel\Product\Image as ProductImage;
+use Magento\Store\Model\StoreManagerInterface;
use Magento\Theme\Model\Config\Customization as ThemeCustomizationConfig;
use Magento\Theme\Model\ResourceModel\Theme\Collection;
use Magento\MediaStorage\Helper\File\Storage\Database;
@@ -119,7 +120,15 @@ class ImageResizeTest extends \PHPUnit\Framework\TestCase
* @var string
*/
private $testfilepath;
+ /**
+ * @var \PHPUnit\Framework\MockObject\MockObject|StoreManagerInterface
+ */
+ private $storeManager;
+ /**
+ * @inheritDoc
+ * @SuppressWarnings(PHPMD.ExcessiveMethodLength)
+ */
protected function setUp()
{
$this->testfilename = "image.jpg";
@@ -139,6 +148,7 @@ protected function setUp()
$this->themeCollectionMock = $this->createMock(Collection::class);
$this->filesystemMock = $this->createMock(Filesystem::class);
$this->databaseMock = $this->createMock(Database::class);
+ $this->storeManager = $this->getMockForAbstractClass(StoreManagerInterface::class);
$this->mediaDirectoryMock = $this->getMockBuilder(Filesystem::class)
->disableOriginalConstructor()
@@ -203,6 +213,16 @@ protected function setUp()
->method('getViewConfig')
->willReturn($this->viewMock);
+ $store = $this->getMockForAbstractClass(\Magento\Store\Api\Data\StoreInterface::class);
+ $store
+ ->expects($this->any())
+ ->method('getId')
+ ->willReturn(1);
+ $this->storeManager
+ ->expects($this->any())
+ ->method('getStores')
+ ->willReturn([$store]);
+
$this->service = new \Magento\MediaStorage\Service\ImageResize(
$this->appStateMock,
$this->imageConfigMock,
@@ -214,7 +234,8 @@ protected function setUp()
$this->themeCustomizationConfigMock,
$this->themeCollectionMock,
$this->filesystemMock,
- $this->databaseMock
+ $this->databaseMock,
+ $this->storeManager
);
}
diff --git a/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php
new file mode 100644
index 0000000000000..b23dc7fbbf532
--- /dev/null
+++ b/app/code/Magento/MessageQueue/Setup/ConfigOptionsList.php
@@ -0,0 +1,108 @@
+selectOptions,
+ self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES,
+ 'Should consumers wait for a message from the queue? 1 - Yes, 0 - No',
+ self::DEFAULT_CONSUMERS_WAIT_FOR_MESSAGES
+ ),
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+ */
+ public function createConfig(array $data, DeploymentConfig $deploymentConfig)
+ {
+ $configData = new ConfigData(ConfigFilePool::APP_ENV);
+
+ if (!$this->isDataEmpty($data, self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES)) {
+ $configData->set(
+ self::CONFIG_PATH_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES,
+ (int)$data[self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES]
+ );
+ }
+
+ return [$configData];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function validate(array $options, DeploymentConfig $deploymentConfig)
+ {
+ $errors = [];
+
+ if (!$this->isDataEmpty($options, self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES)
+ && !in_array($options[self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES], $this->selectOptions)) {
+ $errors[] = 'You can use only 1 or 0 for ' . self::INPUT_KEY_QUEUE_CONSUMERS_WAIT_FOR_MESSAGES . ' option';
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Check if data ($data) with key ($key) is empty
+ *
+ * @param array $data
+ * @param string $key
+ * @return bool
+ */
+ private function isDataEmpty(array $data, $key)
+ {
+ if (isset($data[$key]) && $data[$key] !== '') {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php b/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php
new file mode 100644
index 0000000000000..547287066a30f
--- /dev/null
+++ b/app/code/Magento/Msrp/Setup/Patch/Data/ChangeMsrpAttributeLabel.php
@@ -0,0 +1,66 @@
+categorySetupFactory = $categorySetupFactory;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function apply()
+ {
+ /** @var \Magento\Catalog\Setup\CategorySetup $categorySetup */
+ $categorySetup = $this->categorySetupFactory->create();
+ $entityTypeId = $categorySetup->getEntityTypeId(Product::ENTITY);
+ $msrpAttribute = $categorySetup->getAttribute($entityTypeId, 'msrp');
+ $categorySetup->updateAttribute(
+ $entityTypeId,
+ $msrpAttribute['attribute_id'],
+ 'frontend_label',
+ 'Minimum Advertised Price'
+ );
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public static function getDependencies()
+ {
+ return [
+ InitializeMsrpAttributes::class,
+ ];
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function getAliases()
+ {
+ return [];
+ }
+}
diff --git a/app/code/Magento/Newsletter/i18n/en_US.csv b/app/code/Magento/Newsletter/i18n/en_US.csv
index 388b583f990b1..f390f6792635d 100644
--- a/app/code/Magento/Newsletter/i18n/en_US.csv
+++ b/app/code/Magento/Newsletter/i18n/en_US.csv
@@ -152,3 +152,4 @@ Store,Store
"Store View","Store View"
"Newsletter Subscriptions","Newsletter Subscriptions"
"We have updated your subscription.","We have updated your subscription."
+"Are you sure you want to delete the selected subscriber(s)?","Are you sure you want to delete the selected subscriber(s)?"
diff --git a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml
index e8600c2d49d7b..aef84c068100a 100644
--- a/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml
+++ b/app/code/Magento/Newsletter/view/adminhtml/layout/newsletter_subscriber_block.xml
@@ -29,6 +29,7 @@
-
- Delete
- */*/massDelete
+ - Are you sure you want to delete the selected subscriber(s)?
diff --git a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml
index 4db5f489aa4a2..2b29d2211b9d1 100644
--- a/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml
+++ b/app/code/Magento/OfflineShipping/etc/adminhtml/system.xml
@@ -31,6 +31,7 @@
+ validate-number validate-zero-or-greater
@@ -92,6 +93,7 @@
+ validate-number validate-zero-or-greater
@@ -130,6 +132,7 @@
+ validate-number validate-zero-or-greater
diff --git a/app/code/Magento/Paypal/Model/SmartButtonConfig.php b/app/code/Magento/Paypal/Model/SmartButtonConfig.php
index c491f2978e560..ede9cacf25d40 100644
--- a/app/code/Magento/Paypal/Model/SmartButtonConfig.php
+++ b/app/code/Magento/Paypal/Model/SmartButtonConfig.php
@@ -7,7 +7,10 @@
namespace Magento\Paypal\Model;
+use Magento\Checkout\Helper\Data;
+use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Locale\ResolverInterface;
+use Magento\Store\Model\ScopeInterface;
/**
* Smart button config
@@ -34,21 +37,29 @@ class SmartButtonConfig
*/
private $allowedFunding;
+ /**
+ * @var ScopeConfigInterface
+ */
+ private $scopeConfig;
+
/**
* @param ResolverInterface $localeResolver
* @param ConfigFactory $configFactory
+ * @param ScopeConfigInterface $scopeConfig
* @param array $defaultStyles
* @param array $allowedFunding
*/
public function __construct(
ResolverInterface $localeResolver,
ConfigFactory $configFactory,
+ ScopeConfigInterface $scopeConfig,
$defaultStyles = [],
$allowedFunding = []
) {
$this->localeResolver = $localeResolver;
$this->config = $configFactory->create();
$this->config->setMethod(Config::METHOD_EXPRESS);
+ $this->scopeConfig = $scopeConfig;
$this->defaultStyles = $defaultStyles;
$this->allowedFunding = $allowedFunding;
}
@@ -61,6 +72,10 @@ public function __construct(
*/
public function getConfig(string $page): array
{
+ $isGuestCheckoutAllowed = $this->scopeConfig->isSetFlag(
+ Data::XML_PATH_GUEST_CHECKOUT,
+ ScopeInterface::SCOPE_STORE
+ );
return [
'merchantId' => $this->config->getValue('merchant_id'),
'environment' => ((int)$this->config->getValue('sandbox_flag') ? 'sandbox' : 'production'),
@@ -68,7 +83,8 @@ public function getConfig(string $page): array
'allowedFunding' => $this->getAllowedFunding($page),
'disallowedFunding' => $this->getDisallowedFunding(),
'styles' => $this->getButtonStyles($page),
- 'isVisibleOnProductPage' => (int)$this->config->getValue('visible_on_product')
+ 'isVisibleOnProductPage' => $this->config->getValue('visible_on_product'),
+ 'isGuestCheckoutAllowed' => $isGuestCheckoutAllowed
];
}
diff --git a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml
index 01f4c0e7bae64..c3e99854ce3ac 100644
--- a/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml
+++ b/app/code/Magento/Paypal/Test/Mftf/ActionGroup/OtherPayPalConfigurationActionGroup.xml
@@ -28,7 +28,21 @@
-
+
+
+ Expands the 'OTHER PAYPAL PAYMENT SOLUTIONS' tab on the Admin Configuration page. Enables the provided PayPal Config type for the provided Country Code without saving.
+
+
+
+
+
+
+
+
+
+
+
+
Expands the 'OTHER PAYPAL PAYMENT SOLUTIONS' tab on the Admin Configuration page. Enables the provided PayPal Config type for the provided Country Code.
diff --git a/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml
new file mode 100644
index 0000000000000..c653c1f03fd74
--- /dev/null
+++ b/app/code/Magento/Paypal/Test/Mftf/Test/AdminOnePayPalSolutionsEnabledAtTheSameTimeTest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php
index ed62efe36c472..5aa3dee0874b2 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/SmartButtonConfigTest.php
@@ -7,10 +7,15 @@
namespace Magento\Paypal\Test\Unit\Model;
+use Magento\Framework\App\Config\ScopeConfigInterface;
+use Magento\Paypal\Model\Config;
use Magento\Paypal\Model\SmartButtonConfig;
use Magento\Framework\Locale\ResolverInterface;
use Magento\Paypal\Model\ConfigFactory;
+/**
+ * Class SmartButtonConfigTest
+ */
class SmartButtonConfigTest extends \PHPUnit\Framework\TestCase
{
/**
@@ -31,9 +36,15 @@ class SmartButtonConfigTest extends \PHPUnit\Framework\TestCase
protected function setUp()
{
$this->localeResolverMock = $this->getMockForAbstractClass(ResolverInterface::class);
- $this->configMock = $this->getMockBuilder(\Magento\Paypal\Model\Config::class)
+ $this->configMock = $this->getMockBuilder(Config::class)
->disableOriginalConstructor()
->getMock();
+
+ /** @var ScopeConfigInterface|\PHPUnit_Framework_MockObject_MockObject $scopeConfigMock */
+ $scopeConfigMock = $this->getMockForAbstractClass(ScopeConfigInterface::class);
+ $scopeConfigMock->method('isSetFlag')
+ ->willReturn(true);
+
/** @var \PHPUnit_Framework_MockObject_MockObject $configFactoryMock */
$configFactoryMock = $this->getMockBuilder(ConfigFactory::class)
->disableOriginalConstructor()
@@ -43,12 +54,15 @@ protected function setUp()
$this->model = new SmartButtonConfig(
$this->localeResolverMock,
$configFactoryMock,
+ $scopeConfigMock,
$this->getDefaultStyles(),
$this->getAllowedFundings()
);
}
/**
+ * Tests config.
+ *
* @param string $page
* @param string $locale
* @param string $disallowedFundings
@@ -78,22 +92,33 @@ public function testGetConfig(
array $expected = []
) {
$this->localeResolverMock->expects($this->any())->method('getLocale')->willReturn($locale);
- $this->configMock->expects($this->any())->method('getValue')->will($this->returnValueMap([
- ['merchant_id', null, 'merchant'],
- ['sandbox_flag', null, true],
- ['disable_funding_options', null, $disallowedFundings],
- ["{$page}_page_button_customize", null, $isCustomize],
- ["{$page}_page_button_layout", null, $layout],
- ["{$page}_page_button_size", null, $size],
- ["{$page}_page_button_color", null, $color],
- ["{$page}_page_button_shape", null, $shape],
- ["{$page}_page_button_label", null, $label],
- [$page . '_page_button_' . $installmentPeriodLocale . '_installment_period', null, $installmentPeriodLabel]
- ]));
+ $this->configMock->method('getValue')->will(
+ $this->returnValueMap(
+ [
+ ['merchant_id', null, 'merchant'],
+ ['sandbox_flag', null, true],
+ ['disable_funding_options', null, $disallowedFundings],
+ ["{$page}_page_button_customize", null, $isCustomize],
+ ["{$page}_page_button_layout", null, $layout],
+ ["{$page}_page_button_size", null, $size],
+ ["{$page}_page_button_color", null, $color],
+ ["{$page}_page_button_shape", null, $shape],
+ ["{$page}_page_button_label", null, $label],
+ [
+ $page . '_page_button_' . $installmentPeriodLocale . '_installment_period',
+ null,
+ $installmentPeriodLabel
+ ]
+ ]
+ )
+ );
self::assertEquals($expected, $this->model->getConfig($page));
}
+ /**
+ * @return array
+ */
public function getConfigDataProvider()
{
return include __DIR__ . '/_files/expected_config.php';
diff --git a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php
index 1442642a324b9..478607f9956e6 100644
--- a/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php
+++ b/app/code/Magento/Paypal/Test/Unit/Model/_files/expected_config.php
@@ -32,7 +32,8 @@
'label' => 'installment',
'installmentperiod' => 0
],
- 'isVisibleOnProductPage' => 0
+ 'isVisibleOnProductPage' => 0,
+ 'isGuestCheckoutAllowed' => true
]
],
'checkout' => [
@@ -61,7 +62,8 @@
'label' => 'installment',
'installmentperiod' => 0
],
- 'isVisibleOnProductPage' => 0
+ 'isVisibleOnProductPage' => 0,
+ 'isGuestCheckoutAllowed' => true
]
],
'mini_cart' => [
@@ -89,7 +91,8 @@
'shape' => 'rect',
'label' => 'paypal'
],
- 'isVisibleOnProductPage' => 0
+ 'isVisibleOnProductPage' => 0,
+ 'isGuestCheckoutAllowed' => true
]
],
'mini_cart' => [
@@ -117,7 +120,8 @@
'shape' => 'rect',
'label' => 'paypal'
],
- 'isVisibleOnProductPage' => 0
+ 'isVisibleOnProductPage' => 0,
+ 'isGuestCheckoutAllowed' => true
]
],
'product' => [
@@ -145,7 +149,8 @@
'shape' => 'rect',
'label' => 'paypal',
],
- 'isVisibleOnProductPage' => 0
+ 'isVisibleOnProductPage' => 0,
+ 'isGuestCheckoutAllowed' => true
]
]
];
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml
index 51297a96438d2..36da32c9c14b1 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_ca.xml
@@ -193,6 +193,7 @@
wps_other
payflow_link_ca
+
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml
index 28cc075e0c619..28555d3ee5599 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_es.xml
@@ -85,6 +85,7 @@
wps_other
+
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml
index 7f1fcc08334fe..fea756ba66f3a 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_fr.xml
@@ -85,6 +85,7 @@
wps_other
+
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 d8b765b9b4d22..0ce5cba8fe1dd 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_gb.xml
@@ -85,6 +85,7 @@
wps_express
+
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml
index 50ce14e66ee0c..8fdc15c15b233 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_hk.xml
@@ -85,6 +85,7 @@
wps_other
+
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml
index de059dcc59c39..75615209ec986 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_it.xml
@@ -85,6 +85,7 @@
wps_other
+
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml
index d9fc7ef3f201c..af0dab810e128 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_jp.xml
@@ -85,6 +85,7 @@
wps_other
+
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml
index c5b8b09c3a2cf..320a66b1a9b36 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_nz.xml
@@ -85,6 +85,7 @@
wps_other
+
diff --git a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml
index b7924e770aa22..6a51330e060f7 100644
--- a/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml
+++ b/app/code/Magento/Paypal/etc/adminhtml/rules/payment_us.xml
@@ -414,6 +414,7 @@
paypal_payflowpro_with_express_checkout
payflow_link_us
+
diff --git a/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js
index f4adaae06a112..b2be5fe2b3d2b 100644
--- a/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js
+++ b/app/code/Magento/Paypal/view/frontend/web/js/in-context/product-express-checkout.js
@@ -21,15 +21,22 @@ define([
/** @inheritdoc */
initialize: function (config, element) {
var cart = customerData.get('cart'),
- customer = customerData.get('customer');
+ customer = customerData.get('customer'),
+ isGuestCheckoutAllowed;
this._super();
+ isGuestCheckoutAllowed = cart().isGuestCheckoutAllowed;
+
+ if (typeof isGuestCheckoutAllowed === 'undefined') {
+ isGuestCheckoutAllowed = config.clientConfig.isGuestCheckoutAllowed;
+ }
+
if (config.clientConfig.isVisibleOnProductPage) {
this.renderPayPalButtons(element);
}
- this.declinePayment = !customer().firstname && !cart().isGuestCheckoutAllowed;
+ this.declinePayment = !customer().firstname && !isGuestCheckoutAllowed;
return this;
},
diff --git a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php
index e9a63dad6e169..3ce148ee80b8c 100644
--- a/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php
+++ b/app/code/Magento/Quote/Model/Quote/Address/Total/Shipping.php
@@ -3,6 +3,9 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+
+declare(strict_types=1);
+
namespace Magento\Quote\Model\Quote\Address\Total;
use Magento\Framework\Pricing\PriceCurrencyInterface;
@@ -112,17 +115,21 @@ public function collect(
*/
public function fetch(\Magento\Quote\Model\Quote $quote, \Magento\Quote\Model\Quote\Address\Total $total)
{
- $amount = $total->getShippingAmount();
- $shippingDescription = $total->getShippingDescription();
- $title = ($shippingDescription)
- ? __('Shipping & Handling (%1)', $shippingDescription)
- : __('Shipping & Handling');
+ if (!$quote->getIsVirtual()) {
+ $amount = $total->getShippingAmount();
+ $shippingDescription = $total->getShippingDescription();
+ $title = ($shippingDescription)
+ ? __('Shipping & Handling (%1)', $shippingDescription)
+ : __('Shipping & Handling');
- return [
- 'code' => $this->getCode(),
- 'title' => $title,
- 'value' => $amount
- ];
+ return [
+ 'code' => $this->getCode(),
+ 'title' => $title,
+ 'value' => $amount
+ ];
+ } else {
+ return [];
+ }
}
/**
diff --git a/app/code/Magento/Quote/Model/Quote/Item/Repository.php b/app/code/Magento/Quote/Model/Quote/Item/Repository.php
index 1fb0a2d7107f1..6fb512a619de4 100644
--- a/app/code/Magento/Quote/Model/Quote/Item/Repository.php
+++ b/app/code/Magento/Quote/Model/Quote/Item/Repository.php
@@ -1,34 +1,40 @@
quoteRepository = $quoteRepository;
$this->productRepository = $productRepository;
$this->itemDataFactory = $itemDataFactory;
+ $this->cartItemOptionsProcessor = $cartItemOptionsProcessor;
$this->cartItemProcessors = $cartItemProcessors;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function getList($cartId)
{
@@ -71,21 +80,26 @@ public function getList($cartId)
/** @var \Magento\Quote\Model\Quote\Item $item */
foreach ($quote->getAllVisibleItems() as $item) {
- $item = $this->getCartItemOptionsProcessor()->addProductOptions($item->getProductType(), $item);
- $output[] = $this->getCartItemOptionsProcessor()->applyCustomOptions($item);
+ $item = $this->cartItemOptionsProcessor->addProductOptions($item->getProductType(), $item);
+ $output[] = $this->cartItemOptionsProcessor->applyCustomOptions($item);
}
return $output;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem)
{
/** @var \Magento\Quote\Model\Quote $quote */
$cartId = $cartItem->getQuoteId();
- $quote = $this->quoteRepository->getActive($cartId);
+ if (!$cartId) {
+ throw new InputException(
+ __('"%fieldName" is required. Enter and try again.', ['fieldName' => 'quoteId'])
+ );
+ }
+ $quote = $this->quoteRepository->getActive($cartId);
$quoteItems = $quote->getItems();
$quoteItems[] = $cartItem;
$quote->setItems($quoteItems);
@@ -95,7 +109,7 @@ public function save(\Magento\Quote\Api\Data\CartItemInterface $cartItem)
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function deleteById($cartId, $itemId)
{
@@ -116,17 +130,4 @@ public function deleteById($cartId, $itemId)
return true;
}
-
- /**
- * @return CartItemOptionsProcessor
- * @deprecated 100.1.0
- */
- private function getCartItemOptionsProcessor()
- {
- if (!$this->cartItemOptionsProcessor instanceof CartItemOptionsProcessor) {
- $this->cartItemOptionsProcessor = ObjectManager::getInstance()->get(CartItemOptionsProcessor::class);
- }
-
- return $this->cartItemOptionsProcessor;
- }
}
diff --git a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php
index 4ecd8b021d7f0..dd4f2f6e7470c 100644
--- a/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php
+++ b/app/code/Magento/Quote/Test/Unit/Model/Quote/Item/RepositoryTest.php
@@ -7,64 +7,75 @@
namespace Magento\Quote\Test\Unit\Model\Quote\Item;
+use Magento\Catalog\Api\ProductRepositoryInterface;
+use Magento\Catalog\Model\CustomOptions\CustomOptionProcessor;
+use Magento\Catalog\Model\Product;
+use Magento\Quote\Api\CartRepositoryInterface;
+use Magento\Quote\Api\Data\CartItemInterfaceFactory;
+use Magento\Quote\Model\Quote;
+use Magento\Quote\Model\Quote\Address;
+use Magento\Quote\Model\Quote\Item;
+use Magento\Quote\Model\Quote\Item\CartItemOptionsProcessor;
+use Magento\Quote\Model\Quote\Item\Repository;
+use PHPUnit\Framework\MockObject\MockObject;
+
/**
* @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class RepositoryTest extends \PHPUnit\Framework\TestCase
{
/**
- * @var \Magento\Framework\TestFramework\Unit\Helper\ObjectManager
+ * @var Repository
*/
- private $objectManager;
+ private $repository;
/**
- * @var \Magento\Quote\Api\CartItemRepositoryInterface
+ * @var CartRepositoryInterface|MockObject
*/
- protected $repository;
+ private $quoteRepositoryMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var ProductRepositoryInterface|MockObject
*/
- protected $quoteRepositoryMock;
+ private $productRepositoryMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
- protected $productRepositoryMock;
+ private $itemMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
- protected $itemMock;
+ private $quoteMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
- protected $quoteMock;
+ private $productMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var MockObject
*/
- protected $productMock;
+ private $quoteItemMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var CartItemInterfaceFactory|MockObject
*/
- protected $quoteItemMock;
+ private $itemDataFactoryMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var CustomOptionProcessor|MockObject
*/
- protected $itemDataFactoryMock;
-
- /** @var \Magento\Catalog\Model\CustomOptions\CustomOptionProcessor|\PHPUnit_Framework_MockObject_MockObject */
- protected $customOptionProcessor;
+ private $customOptionProcessor;
- /** @var \PHPUnit_Framework_MockObject_MockObject */
- protected $shippingAddressMock;
+ /**
+ * @var Address|MockObject
+ */
+ private $shippingAddressMock;
/**
- * @var \PHPUnit_Framework_MockObject_MockObject
+ * @var CartItemOptionsProcessor|MockObject
*/
private $optionsProcessorMock;
@@ -73,40 +84,29 @@ class RepositoryTest extends \PHPUnit\Framework\TestCase
*/
protected function setUp()
{
- $this->objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
- $this->quoteRepositoryMock = $this->createMock(\Magento\Quote\Api\CartRepositoryInterface::class);
- $this->productRepositoryMock = $this->createMock(\Magento\Catalog\Api\ProductRepositoryInterface::class);
- $this->itemDataFactoryMock =
- $this->createPartialMock(\Magento\Quote\Api\Data\CartItemInterfaceFactory::class, ['create']);
- $this->itemMock = $this->createMock(\Magento\Quote\Model\Quote\Item::class);
- $this->quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
- $this->productMock = $this->createMock(\Magento\Catalog\Model\Product::class);
+ $this->quoteRepositoryMock = $this->createMock(CartRepositoryInterface::class);
+ $this->productRepositoryMock = $this->createMock(ProductRepositoryInterface::class);
+ $this->itemDataFactoryMock = $this->createPartialMock(CartItemInterfaceFactory::class, ['create']);
+ $this->itemMock = $this->createMock(Item::class);
+ $this->quoteMock = $this->createMock(Quote::class);
+ $this->productMock = $this->createMock(Product::class);
$methods = ['getId', 'getSku', 'getQty', 'setData', '__wakeUp', 'getProduct', 'addProduct'];
$this->quoteItemMock =
- $this->createPartialMock(\Magento\Quote\Model\Quote\Item::class, $methods);
- $this->customOptionProcessor = $this->createMock(
- \Magento\Catalog\Model\CustomOptions\CustomOptionProcessor::class
- );
+ $this->createPartialMock(Item::class, $methods);
+ $this->customOptionProcessor = $this->createMock(CustomOptionProcessor::class);
$this->shippingAddressMock = $this->createPartialMock(
- \Magento\Quote\Model\Quote\Address::class,
+ Address::class,
['setCollectShippingRates']
);
+ $this->optionsProcessorMock = $this->createMock(CartItemOptionsProcessor::class);
- $this->optionsProcessorMock = $this->createMock(
- \Magento\Quote\Model\Quote\Item\CartItemOptionsProcessor::class
- );
-
- $this->repository = new \Magento\Quote\Model\Quote\Item\Repository(
+ $this->repository = new Repository(
$this->quoteRepositoryMock,
$this->productRepositoryMock,
$this->itemDataFactoryMock,
+ $this->optionsProcessorMock,
['custom_options' => $this->customOptionProcessor]
);
- $this->objectManager->setBackwardCompatibleProperty(
- $this->repository,
- 'cartItemOptionsProcessor',
- $this->optionsProcessorMock
- );
}
/**
@@ -118,7 +118,7 @@ public function testSave()
$itemId = 20;
$quoteMock = $this->createPartialMock(
- \Magento\Quote\Model\Quote::class,
+ Quote::class,
['getItems', 'setItems', 'collectTotals', 'getLastAddedItem']
);
@@ -197,11 +197,11 @@ public function testDeleteWithCouldNotSaveException()
public function testGetList()
{
$productType = 'type';
- $quoteMock = $this->createMock(\Magento\Quote\Model\Quote::class);
+ $quoteMock = $this->createMock(Quote::class);
$this->quoteRepositoryMock->expects($this->once())->method('getActive')
->with(33)
->will($this->returnValue($quoteMock));
- $itemMock = $this->createMock(\Magento\Quote\Model\Quote\Item::class);
+ $itemMock = $this->createMock(Item::class);
$quoteMock->expects($this->once())->method('getAllVisibleItems')->will($this->returnValue([$itemMock]));
$itemMock->expects($this->once())->method('getProductType')->willReturn($productType);
diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
index fb2a9706792cc..11719db2d1b8f 100644
--- a/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
+++ b/app/code/Magento/QuoteGraphQl/Model/Cart/AddSimpleProductToCart.php
@@ -55,7 +55,7 @@ public function execute(Quote $cart, array $cartItemData): void
$sku = $this->extractSku($cartItemData);
try {
- $product = $this->productRepository->get($sku);
+ $product = $this->productRepository->get($sku, false, null, true);
} catch (NoSuchEntityException $e) {
throw new GraphQlNoSuchEntityException(__('Could not find a product with SKU "%sku"', ['sku' => $sku]));
}
diff --git a/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php
new file mode 100644
index 0000000000000..014b7cf90f3eb
--- /dev/null
+++ b/app/code/Magento/QuoteGraphQl/Model/Resolver/CartTotalQuantity.php
@@ -0,0 +1,34 @@
+getItemsSummaryQty();
+ }
+}
diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
index 6d7f4daf40ded..c458b5e9dc05d 100644
--- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
+++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls
@@ -197,6 +197,7 @@ type Cart {
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")
prices: CartPrices @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartPrices")
+ total_quantity: Float! @resolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartTotalQuantity")
}
interface CartAddressInterface @typeResolver(class: "\\Magento\\QuoteGraphQl\\Model\\Resolver\\CartAddressTypeResolver") {
diff --git a/app/code/Magento/Sales/Model/Order/Email/Sender.php b/app/code/Magento/Sales/Model/Order/Email/Sender.php
index 564fd1e2a4b98..ab1a158336d30 100644
--- a/app/code/Magento/Sales/Model/Order/Email/Sender.php
+++ b/app/code/Magento/Sales/Model/Order/Email/Sender.php
@@ -12,8 +12,9 @@
/**
* Class Sender
- * @api
*
+ * phpcs:disable Magento2.Classes.AbstractApi
+ * @api
* @since 100.0.2
*/
abstract class Sender
@@ -87,10 +88,12 @@ protected function checkAndSend(Order $order)
$this->logger->error($e->getMessage());
return false;
}
- try {
- $sender->sendCopyTo();
- } catch (\Exception $e) {
- $this->logger->error($e->getMessage());
+ if ($this->identityContainer->getCopyMethod() == 'copy') {
+ try {
+ $sender->sendCopyTo();
+ } catch (\Exception $e) {
+ $this->logger->error($e->getMessage());
+ }
}
return true;
}
diff --git a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php
index 1ae5d7479952b..c4523981ac729 100644
--- a/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php
+++ b/app/code/Magento/Sales/Model/Order/Email/SenderBuilder.php
@@ -84,7 +84,7 @@ public function sendCopyTo()
{
$copyTo = $this->identityContainer->getEmailCopyTo();
- if (!empty($copyTo) && $this->identityContainer->getCopyMethod() == 'copy') {
+ if (!empty($copyTo)) {
$this->configureEmailTemplate();
foreach ($copyTo as $email) {
$this->transportBuilder->addTo($email);
diff --git a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml
index 2ad40de685088..3f178ae02102a 100644
--- a/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml
+++ b/app/code/Magento/Sales/Test/Mftf/ActionGroup/AdminOrderActionGroup.xml
@@ -535,4 +535,7 @@
+
+
+
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php
index 467476c9bb406..13ed0739348b2 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Creditmemo/Sender/EmailSenderTest.php
@@ -283,6 +283,10 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending
->willReturn($emailSendingResult);
if ($emailSendingResult) {
+ $this->identityContainerMock->expects($this->once())
+ ->method('getCopyMethod')
+ ->willReturn('copy');
+
$this->senderBuilderFactoryMock->expects($this->once())
->method('create')
->willReturn($this->senderMock);
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php
index 1f074d7262f4d..287daa2fba4b9 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/CreditmemoSenderTest.php
@@ -57,7 +57,7 @@ protected function setUp()
$this->identityContainerMock = $this->createPartialMock(
\Magento\Sales\Model\Order\Email\Container\CreditmemoIdentity::class,
- ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId']
+ ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod']
);
$this->identityContainerMock->expects($this->any())
->method('getStore')
@@ -138,6 +138,10 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema
->willReturn($emailSendingResult);
if ($emailSendingResult) {
+ $this->identityContainerMock->expects($this->once())
+ ->method('getCopyMethod')
+ ->willReturn('copy');
+
$this->senderBuilderFactoryMock->expects($this->once())
->method('create')
->willReturn($this->senderMock);
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php
index d1aa5af53da4d..3315ec8eb4196 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/InvoiceSenderTest.php
@@ -57,7 +57,7 @@ protected function setUp()
$this->identityContainerMock = $this->createPartialMock(
\Magento\Sales\Model\Order\Email\Container\InvoiceIdentity::class,
- ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId']
+ ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod']
);
$this->identityContainerMock->expects($this->any())
->method('getStore')
@@ -144,6 +144,10 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema
->willReturn($emailSendingResult);
if ($emailSendingResult) {
+ $this->identityContainerMock->expects($this->once())
+ ->method('getCopyMethod')
+ ->willReturn('copy');
+
$this->senderBuilderFactoryMock->expects($this->once())
->method('create')
->willReturn($this->senderMock);
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 88053ea684ce8..bfea2d63ef1bb 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
@@ -30,7 +30,7 @@ protected function setUp()
$this->identityContainerMock = $this->createPartialMock(
\Magento\Sales\Model\Order\Email\Container\OrderIdentity::class,
- ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId']
+ ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod']
);
$this->identityContainerMock->expects($this->any())
->method('getStore')
@@ -77,6 +77,10 @@ public function testSend($configValue, $forceSyncMode, $emailSendingResult, $sen
->willReturn($emailSendingResult);
if ($emailSendingResult) {
+ $this->identityContainerMock->expects($senderSendException ? $this->never() : $this->once())
+ ->method('getCopyMethod')
+ ->willReturn('copy');
+
$addressMock = $this->createMock(\Magento\Sales\Model\Order\Address::class);
$this->addressRenderer->expects($this->any())
@@ -214,6 +218,10 @@ public function testSendVirtualOrder($isVirtualOrder, $formatCallCount, $expecte
->method('isEnabled')
->willReturn(true);
+ $this->identityContainerMock->expects($this->once())
+ ->method('getCopyMethod')
+ ->willReturn('copy');
+
$addressMock = $this->createMock(\Magento\Sales\Model\Order\Address::class);
$this->addressRenderer->expects($this->exactly($formatCallCount))
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php
index 2d7b42bccae5a..96bbb1aea7abd 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Email/Sender/ShipmentSenderTest.php
@@ -57,7 +57,7 @@ protected function setUp()
$this->identityContainerMock = $this->createPartialMock(
\Magento\Sales\Model\Order\Email\Container\ShipmentIdentity::class,
- ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId']
+ ['getStore', 'isEnabled', 'getConfigValue', 'getTemplateId', 'getGuestTemplateId', 'getCopyMethod']
);
$this->identityContainerMock->expects($this->any())
->method('getStore')
@@ -144,6 +144,10 @@ public function testSend($configValue, $forceSyncMode, $customerNoteNotify, $ema
->willReturn($emailSendingResult);
if ($emailSendingResult) {
+ $this->identityContainerMock->expects($this->once())
+ ->method('getCopyMethod')
+ ->willReturn('copy');
+
$this->senderBuilderFactoryMock->expects($this->once())
->method('create')
->willReturn($this->senderMock);
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 24cd54e3a46b3..adfb697e70331 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
@@ -48,11 +48,14 @@ protected function setUp()
['getTemplateVars', 'getTemplateOptions', 'getTemplateId']
);
- $this->storeMock = $this->createPartialMock(\Magento\Store\Model\Store::class, [
- 'getStoreId',
- '__wakeup',
- 'getId',
- ]);
+ $this->storeMock = $this->createPartialMock(
+ \Magento\Store\Model\Store::class,
+ [
+ 'getStoreId',
+ '__wakeup',
+ 'getId',
+ ]
+ );
$this->identityContainerMock = $this->createPartialMock(
\Magento\Sales\Model\Order\Email\Container\ShipmentIdentity::class,
@@ -165,9 +168,6 @@ public function testSendCopyTo()
$transportMock = $this->createMock(
\Magento\Sales\Test\Unit\Model\Order\Email\Stub\TransportInterfaceMock::class
);
- $this->identityContainerMock->expects($this->once())
- ->method('getCopyMethod')
- ->will($this->returnValue('copy'));
$this->identityContainerMock->expects($this->never())
->method('getCustomerEmail');
$this->identityContainerMock->expects($this->never())
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php
index dcf689cf7d53b..6db1ec0392e0e 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Invoice/Sender/EmailSenderTest.php
@@ -282,6 +282,10 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending
->willReturn($emailSendingResult);
if ($emailSendingResult) {
+ $this->identityContainerMock->expects($this->once())
+ ->method('getCopyMethod')
+ ->willReturn('copy');
+
$this->senderBuilderFactoryMock->expects($this->once())
->method('create')
->willReturn($this->senderMock);
diff --git a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php
index 391e99ba6f835..2262fbf03c1a1 100644
--- a/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php
+++ b/app/code/Magento/Sales/Test/Unit/Model/Order/Shipment/Sender/EmailSenderTest.php
@@ -284,6 +284,10 @@ public function testSend($configValue, $forceSyncMode, $isComment, $emailSending
->willReturn($emailSendingResult);
if ($emailSendingResult) {
+ $this->identityContainerMock->expects($this->once())
+ ->method('getCopyMethod')
+ ->willReturn('copy');
+
$this->senderBuilderFactoryMock->expects($this->once())
->method('create')
->willReturn($this->senderMock);
diff --git a/app/code/Magento/SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml b/app/code/Magento/SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml
index 2eed5ba6c548b..64918c24cdc61 100644
--- a/app/code/Magento/SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml
+++ b/app/code/Magento/SalesRule/view/adminhtml/templates/promo/salesrulejs.phtml
@@ -64,7 +64,7 @@ function generateCouponCodes(idPrefix, generateUrl, grid) {
try {
response = JSON.parse(transport.responseText);
} catch (e) {
- console.warn('An error occured while parsing response');
+ console.warn('An error occurred while parsing response');
}
}
if (couponCodesGrid) {
diff --git a/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml
index 85e34d7c0d294..aeadf69050912 100644
--- a/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml
+++ b/app/code/Magento/Search/Test/Mftf/ActionGroup/AdminSearchTermActionGroup.xml
@@ -36,4 +36,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml
index 63a861b697a86..0ec33c48f259e 100644
--- a/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml
+++ b/app/code/Magento/Search/Test/Mftf/Test/StorefrontVerifySearchSuggestionByProductDescriptionTest.xml
@@ -16,64 +16,69 @@
-
-
-
-
+
-
+
+
+
+
+
+
+
+
-
-
-
-
+
+
+
+
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
diff --git a/app/code/Magento/Store/Model/Store.php b/app/code/Magento/Store/Model/Store.php
index dab9c55c216d9..0bc371da0aab9 100644
--- a/app/code/Magento/Store/Model/Store.php
+++ b/app/code/Magento/Store/Model/Store.php
@@ -912,7 +912,7 @@ public function setCurrentCurrencyCode($code)
$defaultCode = ($this->_storeManager->getStore() !== null)
? $this->_storeManager->getStore()->getDefaultCurrency()->getCode()
: $this->_storeManager->getWebsite()->getDefaultStore()->getDefaultCurrency()->getCode();
-
+
$this->_httpContext->setValue(Context::CONTEXT_CURRENCY, $code, $defaultCode);
}
return $this;
@@ -1279,7 +1279,20 @@ public function isActive()
public function beforeDelete()
{
$this->_configDataResource->clearScopeData(ScopeInterface::SCOPE_STORES, $this->getId());
- return parent::beforeDelete();
+ parent::beforeDelete();
+ if ($this->getId() === $this->getGroup()->getDefaultStoreId()) {
+ $ids = $this->getGroup()->getStoreIds();
+ if (!empty($ids) && count($ids) > 1) {
+ unset($ids[$this->getId()]);
+ $defaultId = current($ids);
+ } else {
+ $defaultId = null;
+ }
+ $this->getGroup()->setDefaultStoreId($defaultId);
+ $this->getGroup()->save();
+ }
+
+ return $this;
}
/**
@@ -1300,18 +1313,6 @@ function () use ($store) {
parent::afterDelete();
$this->_configCacheType->clean();
- if ($this->getId() === $this->getGroup()->getDefaultStoreId()) {
- $ids = $this->getGroup()->getStoreIds();
- if (!empty($ids) && count($ids) > 1) {
- unset($ids[$this->getId()]);
- $defaultId = current($ids);
- } else {
- $defaultId = null;
- }
- $this->getGroup()->setDefaultStoreId($defaultId);
- $this->getGroup()->save();
- }
-
return $this;
}
diff --git a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml
index cd9016ebf6d7f..4c00071da6b61 100644
--- a/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml
+++ b/app/code/Magento/Store/Test/Mftf/ActionGroup/AdminCreateNewStoreGroupActionGroup.xml
@@ -71,6 +71,50 @@
+
+
+ Edit store group.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Change the default store view for provided store group.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Asserts that the provided store view is default in provided store group.
+
+
+
+
+
+
+
Clicks on the 1st Store in the 'Stores' grid. Validates that the provided Details (Website, Store Group Name, Store Group Code and Root Category) are present and correct.
diff --git a/app/code/Magento/Store/Test/Mftf/Section/AdminEditStoreGroupSection.xml b/app/code/Magento/Store/Test/Mftf/Section/AdminEditStoreGroupSection.xml
new file mode 100644
index 0000000000000..343373c61da9b
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Section/AdminEditStoreGroupSection.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml
index 25e93f8f6ff4c..ed879a82d3f59 100644
--- a/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminCreateStoreGroupTest.xml
@@ -21,6 +21,14 @@
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteDefaultStoreViewTest.xml b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteDefaultStoreViewTest.xml
new file mode 100644
index 0000000000000..85fd8561f90b5
--- /dev/null
+++ b/app/code/Magento/Store/Test/Mftf/Test/AdminDeleteDefaultStoreViewTest.xml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/code/Magento/Theme/Block/Html/Topmenu.php b/app/code/Magento/Theme/Block/Html/Topmenu.php
index fd8aaa7708cf3..77b9144069502 100644
--- a/app/code/Magento/Theme/Block/Html/Topmenu.php
+++ b/app/code/Magento/Theme/Block/Html/Topmenu.php
@@ -133,7 +133,7 @@ protected function _countItems($items)
*
* @param Menu $items
* @param int $limit
- * @return array|void
+ * @return array
*
* @todo: Add Depth Level limit, and better logic for columns
*/
@@ -141,7 +141,7 @@ protected function _columnBrake($items, $limit)
{
$total = $this->_countItems($items);
if ($total <= $limit) {
- return;
+ return [];
}
$result[] = ['total' => $total, 'max' => (int)ceil($total / ceil($total / $limit))];
diff --git a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/media/editor_plugin_src.js b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/media/editor_plugin_src.js
index a0d4ef2ae38b8..0da100efcbf50 100644
--- a/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/media/editor_plugin_src.js
+++ b/app/code/Magento/Tinymce3/view/base/web/tiny_mce/plugins/media/editor_plugin_src.js
@@ -255,7 +255,7 @@
vspace : data.vspace,
src : self.editor.theme.url + '/img/trans.gif',
'class' : 'mceItemMedia mceItem' + self.getType(data.type).name,
- 'data-mce-json' : JSON.serialize(data, "'")
+ 'data-mce-json' : JSON.serialize(data)
});
img.width = data.width || (data.type == 'audio' ? "300" : "320");
@@ -880,7 +880,7 @@
vspace : vspace,
align : align,
bgcolor : bgcolor,
- "data-mce-json" : JSON.serialize(data, "'")
+ "data-mce-json" : JSON.serialize(data)
});
}
});
diff --git a/app/code/Magento/Ui/view/base/web/js/form/client.js b/app/code/Magento/Ui/view/base/web/js/form/client.js
index 1c1274512c979..a16c211607e8a 100644
--- a/app/code/Magento/Ui/view/base/web/js/form/client.js
+++ b/app/code/Magento/Ui/view/base/web/js/form/client.js
@@ -63,6 +63,9 @@ define([
var $wrapper = $('').addClass(messagesClass).html(msg);
$('.page-main-actions', selectorPrefix).after($wrapper);
+ $('html, body').animate({
+ scrollTop: $('.page-main-actions', selectorPrefix).offset().top
+ });
}
});
});
diff --git a/app/code/Magento/Ups/etc/adminhtml/system.xml b/app/code/Magento/Ups/etc/adminhtml/system.xml
index 8b9dc30a0188b..f1b8b22820cba 100644
--- a/app/code/Magento/Ups/etc/adminhtml/system.xml
+++ b/app/code/Magento/Ups/etc/adminhtml/system.xml
@@ -97,6 +97,7 @@
+ validate-number validate-zero-or-greater
diff --git a/app/code/Magento/Usps/etc/adminhtml/system.xml b/app/code/Magento/Usps/etc/adminhtml/system.xml
index 0bdaf49297f05..0849572e7eb1c 100644
--- a/app/code/Magento/Usps/etc/adminhtml/system.xml
+++ b/app/code/Magento/Usps/etc/adminhtml/system.xml
@@ -136,6 +136,7 @@
+ validate-number validate-zero-or-greater
diff --git a/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php
new file mode 100644
index 0000000000000..1134dd219059b
--- /dev/null
+++ b/app/code/Magento/Widget/Controller/Adminhtml/Widget/Instance/MassDelete.php
@@ -0,0 +1,125 @@
+deleteWidgetById = $deleteWidgetById;
+ }
+
+ /**
+ * Execute action
+ *
+ * @return Redirect
+ * @throws \Exception
+ */
+ public function execute(): Redirect
+ {
+ $deletedInstances = 0;
+ $notDeletedInstances = [];
+ /** @var array $instanceIds */
+ $instanceIds = $this->getInstanceIds();
+
+ if (!count($instanceIds)) {
+ $this->messageManager->addErrorMessage(__('No widget instance IDs were provided to be deleted.'));
+
+ /** @var Redirect $resultRedirect */
+ $resultRedirect = $this->getResultPage();
+
+ return $resultRedirect->setPath('*/*/');
+ }
+
+ foreach ($instanceIds as $instanceId) {
+ try {
+ $this->deleteWidgetById->execute((int)$instanceId);
+ $deletedInstances++;
+ } catch (NoSuchEntityException $e) {
+ $notDeletedInstances[] = $instanceId;
+ }
+ }
+
+ if ($deletedInstances) {
+ $this->messageManager->addSuccessMessage(__('A total of %1 record(s) have been deleted.', $deletedInstances));
+ }
+
+ if (count($notDeletedInstances)) {
+ $this->messageManager->addErrorMessage(
+ __(
+ 'Widget(s) with ID(s) %1 were not found',
+ trim(implode(', ', $notDeletedInstances))
+ )
+ );
+ }
+
+ /** @var Redirect $resultRedirect */
+ $resultRedirect = $this->getResultPage();
+
+ return $resultRedirect->setPath('*/*/');
+ }
+
+ /**
+ * Get instance IDs.
+ *
+ * @return array
+ */
+ private function getInstanceIds(): array
+ {
+ $instanceIds = $this->getRequest()->getParam('delete');
+
+ if (!is_array($instanceIds)) {
+ return [];
+ }
+
+ return $instanceIds;
+ }
+
+ /**
+ * Get result page.
+ *
+ * @return ResultInterface|null
+ */
+ private function getResultPage(): ?ResultInterface
+ {
+ return $this->resultFactory->create(ResultFactory::TYPE_REDIRECT);
+ }
+}
diff --git a/app/code/Magento/Widget/Model/DeleteWidgetById.php b/app/code/Magento/Widget/Model/DeleteWidgetById.php
new file mode 100644
index 0000000000000..4c5d23d5a056b
--- /dev/null
+++ b/app/code/Magento/Widget/Model/DeleteWidgetById.php
@@ -0,0 +1,81 @@
+resourceModel = $resourceModel;
+ $this->instanceFactory = $instanceFactory;
+ }
+
+ /**
+ * Delete widget instance by given instance ID
+ *
+ * @param int $instanceId
+ * @return void
+ * @throws \Exception
+ */
+ public function execute(int $instanceId) : void
+ {
+ $model = $this->getWidgetById($instanceId);
+
+ $this->resourceModel->delete($model);
+ }
+
+ /**
+ * Get widget instance by given instance ID
+ *
+ * @param int $instanceId
+ * @return WidgetInstance
+ * @throws NoSuchEntityException
+ */
+ private function getWidgetById(int $instanceId): WidgetInstance
+ {
+ /** @var WidgetInstance $widgetInstance */
+ $widgetInstance = $this->instanceFactory->create();
+
+ $this->resourceModel->load($widgetInstance, $instanceId);
+
+ if (!$widgetInstance->getId()) {
+ throw new NoSuchEntityException(
+ __(
+ 'No such entity with instance_id = %instance_id',
+ ['instance_id' => $instanceId]
+ )
+ );
+ }
+
+ return $widgetInstance;
+ }
+}
diff --git a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml
index 8ca3fab413b25..c78f9ec225be4 100644
--- a/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml
+++ b/app/code/Magento/Widget/view/adminhtml/layout/adminhtml_widget_instance_block.xml
@@ -15,6 +15,20 @@
ASC
Magento\Widget\Model\ResourceModel\Widget\Instance\Collection
+
+
+ instance_id
+ delete
+ 1
+
+ -
+
- Delete
+ - */*/massDelete
+ - 0
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/setEmailTextLengthLimitActionGroup.xml b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/setEmailTextLengthLimitActionGroup.xml
new file mode 100755
index 0000000000000..685e1ab9f8714
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/ActionGroup/setEmailTextLengthLimitActionGroup.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml b/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml
old mode 100644
new mode 100755
index a8220ad0cfca3..4a25a8d449dd3
--- a/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml
+++ b/app/code/Magento/Wishlist/Test/Mftf/Data/WishlistData.xml
@@ -14,5 +14,8 @@
JohnDoe123456789@example.com,JohnDoe987654321@example.com,JohnDoe123456abc@example.com
Sharing message.
+ 255
+ 1
+ 10000
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Section/WishListShareOptionsSection.xml b/app/code/Magento/Wishlist/Test/Mftf/Section/WishListShareOptionsSection.xml
new file mode 100755
index 0000000000000..b69740572cea6
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/Section/WishListShareOptionsSection.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/code/Magento/Wishlist/Test/Mftf/Test/AdminCustomerWishListShareOptionsInputValidationTest.xml b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminCustomerWishListShareOptionsInputValidationTest.xml
new file mode 100755
index 0000000000000..da51bdf917e37
--- /dev/null
+++ b/app/code/Magento/Wishlist/Test/Mftf/Test/AdminCustomerWishListShareOptionsInputValidationTest.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{Wishlist.min_email_text_length_limit}}
+ minimumWishListTextLengthLimit
+
+
+
+
+
+
+
+
+ {{Wishlist.max_email_text_length_limit}}
+ maximumWishListTextLengthLimit
+
+
+
+
+
+
+
+
+ The value is not within the specified range.
+ enterWishListTextLengthLimitLowerThanMinimum
+
+
+
+
+
+
+
+
+ The value is not within the specified range.
+ enterWishListTextLengthLimitHigherThanMaximum
+
+
+
diff --git a/app/code/Magento/Wishlist/etc/adminhtml/system.xml b/app/code/Magento/Wishlist/etc/adminhtml/system.xml
index 1e26a1195a7fe..e61c07abca993 100644
--- a/app/code/Magento/Wishlist/etc/adminhtml/system.xml
+++ b/app/code/Magento/Wishlist/etc/adminhtml/system.xml
@@ -29,7 +29,7 @@
- 255 by default
+ 255 by default. Max - 10000
validate-digits validate-digits-range digits-range-1-10000
diff --git a/app/code/Magento/Wishlist/i18n/en_US.csv b/app/code/Magento/Wishlist/i18n/en_US.csv
index a9acce448c80c..7bcbd0751b7e9 100644
--- a/app/code/Magento/Wishlist/i18n/en_US.csv
+++ b/app/code/Magento/Wishlist/i18n/en_US.csv
@@ -101,7 +101,7 @@ Back,Back
"Max Emails Allowed to be Sent","Max Emails Allowed to be Sent"
"10 by default. Max - 10000","10 by default. Max - 10000"
"Email Text Length Limit","Email Text Length Limit"
-"255 by default","255 by default"
+"255 by default. Max - 10000","255 by default. Max - 10000"
"General Options","General Options"
Enabled,Enabled
"My Wish List Link","My Wish List Link"
diff --git a/app/design/adminhtml/Magento/backend/web/js/theme.js b/app/design/adminhtml/Magento/backend/web/js/theme.js
index 8e3b89dcf7e4e..39b364ea8553f 100644
--- a/app/design/adminhtml/Magento/backend/web/js/theme.js
+++ b/app/design/adminhtml/Magento/backend/web/js/theme.js
@@ -267,17 +267,18 @@ define('globalNavigation', [
if (subMenu.length) {
e.preventDefault();
}
-
- menuItem.addClass('_show')
- .siblings(menuItemSelector)
- .removeClass('_show');
-
- subMenu.attr('aria-expanded', 'true');
-
closeBtn.on('click', close);
- this.overlay.show(0).on('click', close);
- this.menuLinks.last().off('blur');
+ if ($(menuItem).hasClass('_show')) {
+ closeBtn.trigger('click');
+ } else {
+ menuItem.addClass('_show')
+ .siblings(menuItemSelector)
+ .removeClass('_show');
+ subMenu.attr('aria-expanded', 'true');
+ this.overlay.show(0).on('click', close);
+ this.menuLinks.last().off('blur');
+ }
},
/**
diff --git a/composer.json b/composer.json
index 293cb06ef403c..bcd0c7a03cbdf 100644
--- a/composer.json
+++ b/composer.json
@@ -147,6 +147,7 @@
"magento/module-currency-symbol": "*",
"magento/module-customer": "*",
"magento/module-customer-analytics": "*",
+ "magento/module-customer-downloadable-graph-ql": "*",
"magento/module-customer-import-export": "*",
"magento/module-deploy": "*",
"magento/module-developer": "*",
diff --git a/composer.lock b/composer.lock
index f67eb50675314..bc426a3729b43 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "a4299e3f4f0d4dd4915f37a5dde8e2ed",
+ "content-hash": "ab7dfb217c80189b2f101f020085f221",
"packages": [
{
"name": "braintree/braintree_php",
@@ -1552,28 +1552,28 @@
"authors": [
{
"name": "Jim Wigginton",
- "role": "Lead Developer",
- "email": "terrafrost@php.net"
+ "email": "terrafrost@php.net",
+ "role": "Lead Developer"
},
{
"name": "Patrick Monnerat",
- "role": "Developer",
- "email": "pm@datasphere.ch"
+ "email": "pm@datasphere.ch",
+ "role": "Developer"
},
{
"name": "Andreas Fischer",
- "role": "Developer",
- "email": "bantu@phpbb.com"
+ "email": "bantu@phpbb.com",
+ "role": "Developer"
},
{
"name": "Hans-Jürgen Petrich",
- "role": "Developer",
- "email": "petrich@tronic-media.com"
+ "email": "petrich@tronic-media.com",
+ "role": "Developer"
},
{
"name": "Graham Campbell",
- "role": "Developer",
- "email": "graham@alt-three.com"
+ "email": "graham@alt-three.com",
+ "role": "Developer"
}
],
"description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.",
@@ -2402,7 +2402,7 @@
},
{
"name": "Gert de Pagter",
- "email": "BackEndTea@gmail.com"
+ "email": "backendtea@gmail.com"
}
],
"description": "Symfony polyfill for ctype functions",
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/Carrier.php b/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/Carrier.php
new file mode 100644
index 0000000000000..b3c3c124cfe47
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/Carrier.php
@@ -0,0 +1,111 @@
+mockResponseLoader = $mockResponseLoader;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function _getCgiQuotes()
+ {
+ $responseBody = $this->mockResponseLoader->loadForRequest($this->_rawRequest->getDestCountry());
+ return $this->_parseCgiResponse($responseBody);
+ }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/MockResponseBodyLoader.php b/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/MockResponseBodyLoader.php
new file mode 100644
index 0000000000000..fe1750fa648f3
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/Model/MockResponseBodyLoader.php
@@ -0,0 +1,63 @@
+moduleDirectory = $moduleDirectory;
+ $this->fileIo = $fileIo;
+ }
+
+ /**
+ * Loads mock cgi response body for a given country
+ *
+ * @param string $country
+ * @return string
+ * @throws NotFoundException
+ */
+ public function loadForRequest(string $country): string
+ {
+ $country = strtolower($country);
+ $moduleDir = $this->moduleDirectory->getDir('Magento_TestModuleUps');
+
+ $responsePath = sprintf(static::RESPONSE_FILE_PATTERN, $moduleDir, $country);
+
+ if (!$this->fileIo->fileExists($responsePath)) {
+ throw new NotFoundException(__('%1 is not a valid destination country.', $country));
+ }
+
+ return $this->fileIo->read($responsePath);
+ }
+}
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_ca.txt b/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_ca.txt
new file mode 100644
index 0000000000000..eca3e47a7e138
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_ca.txt
@@ -0,0 +1,5 @@
+UPSOnLine4%XDM%90034%US%M4L 1V3%CA%081%1%138.17%0.00%138.17%-1%
+4%XPR%90034%US%M4L 1V3%CA%081%1%95.07%0.00%95.07%12:00 P.M.%
+4%WXS%90034%US%M4L 1V3%CA%481%1%93.99%0.00%93.99%-1%
+4%XPD%90034%US%M4L 1V3%CA%071%1%85.85%0.00%85.85%-1%
+4%STD%90034%US%M4L 1V3%CA%053%1%27.08%0.00%27.08%-1%
\ No newline at end of file
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_us.txt b/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_us.txt
new file mode 100644
index 0000000000000..56f73dbd93a5a
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/_files/mock_response_us.txt
@@ -0,0 +1,5 @@
+UPSOnLine4%1DM%90034%US%75477%US%106%1%112.44%0.00%112.44%12:00 P.M.%
+4%1DA%90034%US%75477%US%106%1%80.42%0.00%80.42%End of Day%
+4%2DA%90034%US%75477%US%206%1%39.05%0.00%39.05%End of Day%
+4%3DS%90034%US%75477%US%306%1%31.69%0.00%31.69%End of Day%
+4%GND%90034%US%75477%US%006%1%15.61%0.00%15.61%End of Day%
\ No newline at end of file
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/di.xml b/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/di.xml
new file mode 100644
index 0000000000000..28c2fa8e4d45f
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/di.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/module.xml b/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/module.xml
new file mode 100644
index 0000000000000..77d1d15f78d7d
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/etc/module.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
diff --git a/dev/tests/api-functional/_files/Magento/TestModuleUps/registration.php b/dev/tests/api-functional/_files/Magento/TestModuleUps/registration.php
new file mode 100644
index 0000000000000..0668a2522e874
--- /dev/null
+++ b/dev/tests/api-functional/_files/Magento/TestModuleUps/registration.php
@@ -0,0 +1,13 @@
+getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleUps') === null) {
+ ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleUps', __DIR__);
+}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php
index 9145f06542fdd..49f614e335bf1 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/ConfigurableProduct/AddConfigurableProductToCartTest.php
@@ -68,6 +68,78 @@ public function testAddConfigurableProductToCart()
self::assertArrayHasKey('value_label', $option);
}
+ /**
+ * @magentoApiDataFixture Magento/ConfigurableProduct/_files/product_configurable.php
+ * @magentoApiDataFixture Magento/Checkout/_files/active_quote.php
+ */
+ public function testAddMultipleConfigurableProductToCart()
+ {
+ $searchResponse = $this->graphQlQuery($this->getFetchProductQuery('configurable'));
+ $product = current($searchResponse['products']['items']);
+
+ $quantityOne = 1;
+ $quantityTwo = 2;
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_order_1');
+ $parentSku = $product['sku'];
+ $skuOne = 'simple_10';
+ $skuTwo = 'simple_20';
+ $valueIdOne = $product['configurable_options'][0]['values'][0]['value_index'];
+
+ $query = <<graphQlMutation($query);
+
+ $cartItems = $response['addConfigurableProductsToCart']['cart']['items'];
+ self::assertCount(2, $cartItems);
+
+ foreach ($cartItems as $cartItem) {
+ if ($cartItem['configurable_options'][0]['value_id'] === $valueIdOne) {
+ self::assertEquals($quantityOne, $cartItem['quantity']);
+ } else {
+ self::assertEquals($quantityTwo, $cartItem['quantity']);
+ }
+ }
+ }
+
/**
* TODO: Verify whether exception should be thrown in this scenario
*
@@ -141,7 +213,7 @@ public function testAddVariationFromAnotherConfigurableProductWithDifferentSuper
$quantity
);
- $this->graphQlMutation($query);
+ $this->graphQlMutation($query);
}
/**
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/CustomerDownloadableProductsTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php
similarity index 97%
rename from dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/CustomerDownloadableProductsTest.php
rename to dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php
index a7701d23884d3..6b8aad83edac7 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/DownloadableProduct/CustomerDownloadableProductsTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/CustomerDownloadableProduct/CustomerDownloadableProductTest.php
@@ -5,7 +5,7 @@
*/
declare(strict_types=1);
-namespace Magento\GraphQl\DownloadableProduct;
+namespace Magento\GraphQl\CustomerDownloadableProduct;
use Magento\Integration\Api\CustomerTokenServiceInterface;
use Magento\TestFramework\Helper\Bootstrap;
@@ -14,13 +14,12 @@
/**
* Test retrieving of customer downloadable products.
*/
-class CustomerDownloadableProductsTest extends GraphQlAbstract
+class CustomerDownloadableProductTest extends GraphQlAbstract
{
/**
* @var CustomerTokenServiceInterface
*/
private $customerTokenService;
-
/**
* @inheritdoc
*/
@@ -29,7 +28,6 @@ protected function setUp()
$objectManager = Bootstrap::getObjectManager();
$this->customerTokenService = $objectManager->get(CustomerTokenServiceInterface::class);
}
-
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
* @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
@@ -42,23 +40,17 @@ public function testCustomerDownloadableProducts()
self::assertArrayHasKey('items', $response['customerDownloadableProducts']);
self::assertCount(1, $response['customerDownloadableProducts']['items']);
-
self::assertArrayHasKey('date', $response['customerDownloadableProducts']['items'][0]);
self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['date']);
-
self::assertArrayHasKey('download_url', $response['customerDownloadableProducts']['items'][0]);
self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['download_url']);
-
self::assertArrayHasKey('order_increment_id', $response['customerDownloadableProducts']['items'][0]);
self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['order_increment_id']);
-
self::assertArrayHasKey('remaining_downloads', $response['customerDownloadableProducts']['items'][0]);
self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['remaining_downloads']);
-
self::assertArrayHasKey('status', $response['customerDownloadableProducts']['items'][0]);
self::assertNotEmpty($response['customerDownloadableProducts']['items'][0]['status']);
}
-
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
* @magentoApiDataFixture Magento/Downloadable/_files/product_downloadable.php
@@ -71,7 +63,6 @@ public function testGuestCannotAccessDownloadableProducts()
{
$this->graphQlQuery($this->getQuery());
}
-
/**
* @magentoApiDataFixture Magento/Customer/_files/customer.php
*/
@@ -79,11 +70,9 @@ public function testCustomerHasNoOrders()
{
$query = $this->getQuery();
$response = $this->graphQlQuery($query, [], '', $this->getHeaderMap());
-
self::assertArrayHasKey('items', $response['customerDownloadableProducts']);
self::assertCount(0, $response['customerDownloadableProducts']['items']);
}
-
/**
* @return string
*/
@@ -103,7 +92,6 @@ private function getQuery(): string
}
QUERY;
}
-
/**
* @param string $username
* @param string $password
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php
index 60acb3a7a4d44..69bcc73dd27a1 100644
--- a/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/IntrospectionQueryTest.php
@@ -53,7 +53,135 @@ public function testIntrospectionQuery()
defaultValue
}
QUERY;
-
$this->assertArrayHasKey('__schema', $this->graphQlQuery($query));
}
+
+ /**
+ * Tests that Introspection Query with deprecated annotations on enum values, fields are read.
+ */
+ public function testIntrospectionQueryWithDeprecatedAnnotationOnEnumAndFieldValues()
+ {
+ $query
+ = <<graphQlQuery($query);
+ $this->assertArrayHasKey('__schema', $response);
+ $schemaResponseFields = $response['__schema']['types'];
+ $enumValueReasonArray = $this->getEnumValueDeprecatedReason($schemaResponseFields);
+ $fieldsValueReasonArray = $this->getFieldsValueDeprecatedReason($schemaResponseFields);
+ $expectedOutput = require __DIR__ . '/_files/schema_response_sdl_deprecated_annotation.php';
+
+ // checking field values deprecated reason
+ $fieldDeprecatedReason = [];
+ $fieldsArray = $expectedOutput[0]['fields'];
+ foreach ($fieldsArray as $field) {
+ if ($field['isDeprecated'] === true) {
+ $fieldDeprecatedReason [] = $field['deprecationReason'];
+ }
+ }
+ $this->assertNotEmpty($fieldDeprecatedReason);
+ $this->assertContains(
+ 'Symbol was missed. Use `default_display_currency_code`.',
+ $fieldDeprecatedReason
+ );
+
+ $this->assertContains(
+ 'Symbol was missed. Use `default_display_currency_code`.',
+ $fieldsValueReasonArray
+ );
+
+ $this->assertNotEmpty(
+ array_intersect($fieldDeprecatedReason, $fieldsValueReasonArray)
+ );
+
+ // checking enum values deprecated reason
+ $enumValueDeprecatedReason = [];
+ $enumValuesArray = $expectedOutput[1]['enumValues'];
+ foreach ($enumValuesArray as $enumValue) {
+ if ($enumValue['isDeprecated'] === true) {
+ $enumValueDeprecatedReason [] = $enumValue['deprecationReason'];
+ }
+ }
+ $this->assertNotEmpty($enumValueDeprecatedReason);
+ $this->assertContains(
+ '`sample_url` serves to get the downloadable sample',
+ $enumValueDeprecatedReason
+ );
+ $this->assertContains(
+ '`sample_url` serves to get the downloadable sample',
+ $enumValueReasonArray
+ );
+ $this->assertNotEmpty(
+ array_intersect($enumValueDeprecatedReason, $enumValueReasonArray)
+ );
+ }
+
+ /**
+ * Get the enum values deprecated reasons from the schema
+ *
+ * @param array $schemaResponseFields
+ * @return array
+ */
+ private function getEnumValueDeprecatedReason($schemaResponseFields): array
+ {
+ $enumValueReasonArray = [];
+ foreach ($schemaResponseFields as $schemaResponseField) {
+ if (!empty($schemaResponseField['enumValues'])) {
+ foreach ($schemaResponseField['enumValues'] as $enumValueDeprecationReasonArray) {
+ if (!empty($enumValueDeprecationReasonArray['deprecationReason'])) {
+ $enumValueReasonArray[] = $enumValueDeprecationReasonArray['deprecationReason'];
+ }
+ }
+ }
+ }
+ return $enumValueReasonArray;
+ }
+
+ /**
+ * Get the fields values deprecated reasons from the schema
+ *
+ * @param array $schemaResponseFields
+ * @return array
+ */
+ private function getFieldsValueDeprecatedReason($schemaResponseFields): array
+ {
+ $fieldsValueReasonArray = [];
+ foreach ($schemaResponseFields as $schemaResponseField) {
+ if (!empty($schemaResponseField['fields'])) {
+ foreach ($schemaResponseField['fields'] as $fieldsValueDeprecatedReasonArray) {
+ if (!empty($fieldsValueDeprecatedReasonArray['deprecationReason'])) {
+ $fieldsValueReasonArray[] = $fieldsValueDeprecatedReasonArray['deprecationReason'];
+ }
+ }
+ }
+ }
+ return $fieldsValueReasonArray;
+ }
}
diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTotalQuantityTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTotalQuantityTest.php
new file mode 100644
index 0000000000000..aa7cb16ec1296
--- /dev/null
+++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/GetCartTotalQuantityTest.php
@@ -0,0 +1,64 @@
+getMaskedQuoteIdByReservedOrderId = $objectManager->get(GetMaskedQuoteIdByReservedOrderId::class);
+ }
+
+ /**
+ * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php
+ * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php
+ */
+ public function testGetTotalQuantity()
+ {
+ $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote');
+
+ $query = $this->getQuery($maskedQuoteId);
+
+ $response = $this->graphQlQuery($query);
+
+ self::assertArrayHasKey('cart', $response);
+ $cart = $response['cart'];
+ self::assertArrayHasKey('total_quantity', $cart);
+ self::assertEquals(2, $cart['total_quantity']);
+ }
+
+ /**
+ * Create cart query
+ *
+ * @param string $maskedQuoteId
+ * @return string
+ */
+ private function getQuery(string $maskedQuoteId): string
+ {
+ return << 'OBJECT',
+ 'name'=> 'Currency',
+ 'description'=> '',
+ 'fields'=> [
+ [
+ 'name'=> 'available_currency_codes',
+ 'description'=> null,
+ 'isDeprecated'=> false,
+ 'deprecationReason'=> null
+ ],
+ [
+ 'name'=> 'base_currency_code',
+ 'description'=> null,
+ 'isDeprecated'=> false,
+ 'deprecationReason'=> null
+ ],
+
+ [
+ 'name'=> 'base_currency_symbol',
+ 'description'=> null,
+ 'isDeprecated'=> false,
+ 'deprecationReason'=> null
+ ],
+ [
+ 'name'=> 'default_display_currecy_code',
+ 'description'=> null,
+ 'isDeprecated'=> true,
+ 'deprecationReason'=> 'Symbol was missed. Use `default_display_currency_code`.'
+ ],
+ [
+ 'name'=> 'default_display_currecy_symbol',
+ 'description'=> null,
+ 'isDeprecated'=> true,
+ 'deprecationReason'=> 'Symbol was missed. Use `default_display_currency_code`.'
+ ],
+ [
+ 'name'=> 'default_display_currency_code',
+ 'description'=> null,
+ 'isDeprecated'=> false,
+ 'deprecationReason'=> null
+ ],
+ [
+ 'name'=> 'default_display_currency_symbol',
+ 'description'=> null,
+ 'isDeprecated'=> false,
+ 'deprecationReason'=> null
+ ],
+ [
+ 'name'=> 'exchange_rates',
+ 'description'=> null,
+ 'isDeprecated'=> false,
+ 'deprecationReason'=> null
+ ],
+
+ ],
+ 'enumValues'=> null
+ ],
+ [
+ 'kind' => 'ENUM',
+ 'name' => 'DownloadableFileTypeEnum',
+ 'description' => '',
+ 'fields' => null,
+ 'enumValues' => [
+ [
+ 'name' => 'FILE',
+ 'description' => '',
+ 'isDeprecated' => true,
+ 'deprecationReason' => 'sample_url` serves to get the downloadable sample'
+ ],
+ [
+ 'name' => 'URL',
+ 'description' => '',
+ 'isDeprecated' => true,
+ 'deprecationReason' => '`sample_url` serves to get the downloadable sample'
+ ]
+ ],
+ 'possibleTypes' => null
+ ],
+];
diff --git a/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php b/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php
index d1b56bcf5c0f8..2901ff86dd5e7 100644
--- a/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php
+++ b/dev/tests/functional/tests/app/Magento/Msrp/Test/Constraint/AssertProductEditPageAdvancedPricingFields.php
@@ -11,16 +11,16 @@
use Magento\Mtf\Fixture\FixtureInterface;
/**
- * Check "Manufacturer's Suggested Retail Price" field on "Advanced pricing" page.
+ * Check "Minimum Advertised Price" field on "Advanced pricing" page.
*/
class AssertProductEditPageAdvancedPricingFields extends AbstractConstraint
{
/**
- * Title of "Manufacturer's Suggested Retail Price" field.
+ * Title of "Minimum Advertised Price" field.
*
* @var string
*/
- private $manufacturerFieldTitle = 'Manufacturer\'s Suggested Retail Price';
+ private $manufacturerFieldTitle = 'Minimum Advertised Price';
/**
* @param CatalogProductEdit $catalogProductEdit
@@ -35,7 +35,7 @@ public function processAssert(CatalogProductEdit $catalogProductEdit, FixtureInt
\PHPUnit\Framework\Assert::assertTrue(
$advancedPricing->checkField($this->manufacturerFieldTitle),
- '"Manufacturer\'s Suggested Retail Price" field is not correct.'
+ '"Minimum Advertised Price" field is not correct.'
);
}
@@ -46,6 +46,6 @@ public function processAssert(CatalogProductEdit $catalogProductEdit, FixtureInt
*/
public function toString()
{
- return '"Manufacturer\'s Suggested Retail Price" field is correct.';
+ return '"Minimum Advertised Price" field is correct.';
}
}
diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php
new file mode 100644
index 0000000000000..7eca15cc5d94a
--- /dev/null
+++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Model/Config.php
@@ -0,0 +1,28 @@
+active = true;
+ }
+
+ public function disableObserver()
+ {
+ $this->active = false;
+ }
+
+ public function isActive()
+ {
+ return $this->active;
+ }
+}
diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php
new file mode 100644
index 0000000000000..7cc6504dbfb75
--- /dev/null
+++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/Observer/AfterCollectTotals.php
@@ -0,0 +1,49 @@
+config = $config;
+ $this->session = $messageManager;
+ }
+
+ /**
+ * @param Observer $observer
+ * @return void
+ */
+ public function execute(\Magento\Framework\Event\Observer $observer)
+ {
+ $observer->getEvent();
+ if ($this->config->isActive()) {
+ $this->session->getQuote();
+ }
+ }
+}
diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/events.xml b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/events.xml
new file mode 100644
index 0000000000000..91dd450e1934f
--- /dev/null
+++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/events.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/module.xml b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/module.xml
new file mode 100644
index 0000000000000..a24489ba74173
--- /dev/null
+++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/etc/module.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/registration.php b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/registration.php
new file mode 100644
index 0000000000000..61e2553951b62
--- /dev/null
+++ b/dev/tests/integration/_files/Magento/TestModuleQuoteTotalsObserver/registration.php
@@ -0,0 +1,12 @@
+getPath(ComponentRegistrar::MODULE, 'Magento_TestModuleQuoteTotalsObserver') === null) {
+ ComponentRegistrar::register(ComponentRegistrar::MODULE, 'Magento_TestModuleQuoteTotalsObserver', __DIR__);
+}
diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php
index 4ef5a4dd14c08..0b1e8196ef007 100644
--- a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php
+++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Fixture/full_order_with_capture.php
@@ -7,13 +7,16 @@
declare(strict_types=1);
use Magento\AuthorizenetAcceptjs\Gateway\Config;
+use Magento\Sales\Api\InvoiceRepositoryInterface;
use Magento\Sales\Model\Order\Payment;
use Magento\Sales\Model\OrderRepository;
+use Magento\Sales\Model\Service\InvoiceService;
use Magento\TestFramework\Helper\Bootstrap;
use Magento\Sales\Api\TransactionRepositoryInterface;
use Magento\Sales\Model\Order\Payment\Transaction;
use Magento\Sales\Model\Order\Payment\Transaction\BuilderInterface as TransactionBuilder;
+// phpcs:ignore Magento2.Security.IncludeFile.FoundIncludeFile
$order = include __DIR__ . '/../_files/full_order.php';
$objectManager = Bootstrap::getObjectManager();
@@ -24,11 +27,32 @@
$payment->setAuthorizationTransaction(false);
$payment->setParentTransactionId(4321);
-
/** @var OrderRepository $orderRepo */
$orderRepo = $objectManager->get(OrderRepository::class);
$orderRepo->save($order);
+/** @var InvoiceService $invoiceService */
+$invoiceService = $objectManager->get(InvoiceService::class);
+$invoice = $invoiceService->prepareInvoice($order);
+$invoice->setIncrementId('100000001');
+$invoice->register();
+
+/** @var InvoiceRepositoryInterface $invoiceRepository */
+$invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class);
+$invoice = $invoiceRepository->save($invoice);
+
+
+/** @var \Magento\Sales\Model\Order\CreditmemoFactory $creditmemoFactory */
+$creditmemoFactory = $objectManager->get(\Magento\Sales\Model\Order\CreditmemoFactory::class);
+$creditmemo = $creditmemoFactory->createByInvoice($invoice, $invoice->getData());
+$creditmemo->setOrder($order);
+$creditmemo->setState(Magento\Sales\Model\Order\Creditmemo::STATE_OPEN);
+$creditmemo->setIncrementId('100000001');
+
+/** @var \Magento\Sales\Api\CreditmemoRepositoryInterface $creditmemoRepository */
+$creditmemoRepository = $objectManager->get(\Magento\Sales\Api\CreditmemoRepositoryInterface::class);
+$creditmemoRepository->save($creditmemo);
+
/** @var TransactionBuilder $transactionBuilder */
$transactionBuilder = $objectManager->create(TransactionBuilder::class);
$transactionAuthorize = $transactionBuilder->setPayment($payment)
diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php
index 6e06d749f3906..0206ecd6b876b 100644
--- a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php
+++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/Gateway/Command/RefundSettledCommandTest.php
@@ -10,7 +10,10 @@
use Magento\AuthorizenetAcceptjs\Gateway\AbstractTest;
use Magento\Payment\Gateway\Command\CommandPoolInterface;
+use Magento\Sales\Api\Data\CreditmemoInterface;
+use Magento\Sales\Model\Order;
use Magento\Sales\Model\Order\Payment;
+use Magento\Sales\Model\ResourceModel\Order\Creditmemo\CollectionFactory as CreditmemoCollectionFactory;
class RefundSettledCommandTest extends AbstractTest
{
@@ -29,6 +32,7 @@ public function testRefundSettledCommand()
$order = $this->getOrderWithIncrementId('100000001');
$payment = $order->getPayment();
+ $payment->setCreditmemo($this->getCreditmemo($order));
$paymentDO = $this->paymentFactory->create($payment);
@@ -41,13 +45,35 @@ public function testRefundSettledCommand()
$this->responseMock->method('getBody')
->willReturn(json_encode($response));
- $command->execute([
- 'payment' => $paymentDO,
- 'amount' => 100.00
- ]);
+ $command->execute(
+ [
+ 'payment' => $paymentDO,
+ 'amount' => 100.00
+ ]
+ );
/** @var Payment $payment */
$this->assertTrue($payment->getIsTransactionClosed());
$this->assertSame('5678', $payment->getTransactionId());
}
+
+ /**
+ * Retrieve creditmemo from order.
+ *
+ * @param Order $order
+ * @return CreditmemoInterface
+ */
+ private function getCreditmemo(Order $order): CreditmemoInterface
+ {
+ /** @var \Magento\Sales\Model\ResourceModel\Order\Creditmemo\Collection $creditMemoCollection */
+ $creditMemoCollection = $this->objectManager->create(CreditmemoCollectionFactory::class)->create();
+
+ /** @var CreditmemoInterface $creditMemo */
+ $creditMemo = $creditMemoCollection
+ ->setOrderFilter($order)
+ ->setPageSize(1)
+ ->getFirstItem();
+
+ return $creditMemo;
+ }
}
diff --git a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php
index cac7c38971ae5..420d0f55cf34e 100644
--- a/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php
+++ b/dev/tests/integration/testsuite/Magento/AuthorizenetAcceptjs/_files/full_order.php
@@ -41,12 +41,14 @@
->setMetaDescription('meta description')
->setVisibility(Visibility::VISIBILITY_BOTH)
->setStatus(Status::STATUS_ENABLED)
- ->setStockData([
- 'use_config_manage_stock' => 1,
- 'qty' => 100,
- 'is_qty_decimal' => 0,
- 'is_in_stock' => 1,
- ])->setCanSaveCustomOptions(true)
+ ->setStockData(
+ [
+ 'use_config_manage_stock' => 1,
+ 'qty' => 100,
+ 'is_qty_decimal' => 0,
+ 'is_in_stock' => 1,
+ ]
+ )->setCanSaveCustomOptions(true)
->setHasOptions(false);
/** @var ProductRepositoryInterface $productRepository */
@@ -65,6 +67,7 @@
->setLastname('Doe')
->setShippingMethod('flatrate_flatrate');
+/** @var Payment $payment */
$payment = $objectManager->create(Payment::class);
$payment->setAdditionalInformation('ccLast4', '1111');
$payment->setAdditionalInformation('opaqueDataDescriptor', 'mydescriptor');
@@ -116,6 +119,7 @@
->setShippingAddress($shippingAddress)
->setShippingDescription('Flat Rate - Fixed')
->setShippingAmount(10)
+ ->setBaseShippingAmount(10)
->setStoreId(1)
->addItem($orderItem1)
->addItem($orderItem2)
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
old mode 100644
new mode 100755
index 476f01eb277df..e218c508b7d3e
--- a/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/Catalog/Model/ResourceModel/ProductTest.php
@@ -9,7 +9,17 @@
use Magento\Framework\ObjectManagerInterface;
use Magento\TestFramework\Helper\Bootstrap;
use PHPUnit\Framework\TestCase;
+use Magento\Framework\Exception\NoSuchEntityException;
+use Magento\Framework\Exception\CouldNotSaveException;
+use Magento\Framework\Exception\InputException;
+use Magento\Framework\Exception\StateException;
+/**
+ * Tests product resource model
+ *
+ * @see \Magento\Catalog\Model\ResourceModel\Product
+ * @see \Magento\Catalog\Model\ResourceModel\AbstractResource
+ */
class ProductTest extends TestCase
{
/**
@@ -53,6 +63,87 @@ public function testGetAttributeRawValue()
self::assertEquals($product->getName(), $actual);
}
+ /**
+ * @magentoAppArea adminhtml
+ * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php
+ * @throws NoSuchEntityException
+ * @throws CouldNotSaveException
+ * @throws InputException
+ * @throws StateException
+ */
+ public function testGetAttributeRawValueGetDefault()
+ {
+ $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true);
+ $product->setCustomAttribute('store_scoped_attribute_code', 'default_value');
+ $this->productRepository->save($product);
+
+ $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1);
+ $this->assertEquals('default_value', $actual);
+ }
+
+ /**
+ * @magentoAppArea adminhtml
+ * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php
+ * @throws NoSuchEntityException
+ * @throws CouldNotSaveException
+ * @throws InputException
+ * @throws StateException
+ */
+ public function testGetAttributeRawValueGetStoreSpecificValueNoDefault()
+ {
+ $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true);
+ $product->setCustomAttribute('store_scoped_attribute_code', null);
+ $this->productRepository->save($product);
+
+ $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 1, true);
+ $product->setCustomAttribute('store_scoped_attribute_code', 'store_value');
+ $this->productRepository->save($product);
+
+ $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1);
+ $this->assertEquals('store_value', $actual);
+ }
+
+ /**
+ * @magentoAppArea adminhtml
+ * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php
+ * @throws NoSuchEntityException
+ * @throws CouldNotSaveException
+ * @throws InputException
+ * @throws StateException
+ */
+ public function testGetAttributeRawValueGetStoreSpecificValueWithDefault()
+ {
+ $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true);
+ $product->setCustomAttribute('store_scoped_attribute_code', 'default_value');
+ $this->productRepository->save($product);
+
+ $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 1, true);
+ $product->setCustomAttribute('store_scoped_attribute_code', 'store_value');
+ $this->productRepository->save($product);
+
+ $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1);
+ $this->assertEquals('store_value', $actual);
+ }
+
+ /**
+ * @magentoAppArea adminhtml
+ * @magentoDataFixture Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php
+ * @throws NoSuchEntityException
+ * @throws CouldNotSaveException
+ * @throws InputException
+ * @throws StateException
+ * @throws NoSuchEntityException
+ */
+ public function testGetAttributeRawValueGetStoreValueFallbackToDefault()
+ {
+ $product = $this->productRepository->get('simple_with_store_scoped_custom_attribute', true, 0, true);
+ $product->setCustomAttribute('store_scoped_attribute_code', 'default_value');
+ $this->productRepository->save($product);
+
+ $actual = $this->model->getAttributeRawValue($product->getId(), 'store_scoped_attribute_code', 1);
+ $this->assertEquals('default_value', $actual);
+ }
+
/**
* @magentoAppArea adminhtml
* @magentoDataFixture Magento/Catalog/_files/product_special_price.php
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php
new file mode 100755
index 0000000000000..183d531f947e8
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute.php
@@ -0,0 +1,72 @@
+get(ProductRepositoryInterface::class);
+/** @var ProductFactory $productFactory */
+$productFactory = $objectManager->get(ProductFactory::class);
+/** @var ProductAttributeRepositoryInterface $attributeRepository */
+$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class);
+
+
+/** @var $installer CategorySetup */
+$installer = $objectManager->create(CategorySetup::class);
+$entityModel = $objectManager->create(Entity::class);
+$attributeSetId = $installer->getAttributeSetId(Product::ENTITY, 'Default');
+$entityTypeId = $entityModel->setType(Product::ENTITY)
+ ->getTypeId();
+$groupId = $installer->getDefaultAttributeGroupId($entityTypeId, $attributeSetId);
+
+/** @var ProductAttributeInterface $attribute */
+$attribute = $objectManager->create(ProductAttributeInterface::class);
+
+$attribute->setAttributeCode('store_scoped_attribute_code')
+ ->setEntityTypeId($entityTypeId)
+ ->setIsVisible(true)
+ ->setFrontendInput('text')
+ ->setIsFilterable(1)
+ ->setIsUserDefined(1)
+ ->setUsedInProductListing(1)
+ ->setBackendType('varchar')
+ ->setIsUsedInGrid(1)
+ ->setIsVisibleInGrid(1)
+ ->setIsFilterableInGrid(1)
+ ->setFrontendLabel('nobody cares')
+ ->setAttributeGroupId($groupId)
+ ->setAttributeSetId(4);
+
+$attributeRepository->save($attribute);
+
+$product = $productFactory->create()
+ ->setTypeId(Type::TYPE_SIMPLE)
+ ->setAttributeSetId(4)
+ ->setName('Simple With Store Scoped Custom Attribute')
+ ->setSku('simple_with_store_scoped_custom_attribute')
+ ->setPrice(100)
+ ->setVisibility(1)
+ ->setStockData(
+ [
+ 'use_config_manage_stock' => 1,
+ 'qty' => 100,
+ 'is_in_stock' => 1,
+ ]
+ )
+ ->setStatus(1);
+$product->setCustomAttribute('store_scoped_attribute_code', 'default_value');
+$productRepository->save($product);
diff --git a/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php
new file mode 100755
index 0000000000000..54c832dd6a6ff
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Catalog/_files/product_simple_with_custom_store_scope_attribute_rollback.php
@@ -0,0 +1,41 @@
+get(ProductRepositoryInterface::class);
+/** @var ProductAttributeRepositoryInterface $attributeRepository */
+$attributeRepository = $objectManager->get(ProductAttributeRepositoryInterface::class);
+/** @var Registry $registry */
+$registry = $objectManager->get(Registry::class);
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', true);
+
+try {
+ /** @var \Magento\Catalog\Api\Data\ProductInterface $product */
+ $product = $productRepository->get('simple_with_store_scoped_custom_attribute');
+ $productRepository->delete($product);
+} catch (NoSuchEntityException $e) {
+}
+
+try {
+ /** @var \Magento\Catalog\Api\Data\ProductAttributeInterface $attribute */
+ $attribute = $attributeRepository->get('store_scoped_attribute_code');
+ $attributeRepository->delete($attribute);
+} catch (NoSuchEntityException $e) {
+}
+
+$registry->unregister('isSecureArea');
+$registry->register('isSecureArea', false);
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
index 75ac6d3f9935b..04ca1d017f9ec 100644
--- a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/ProductTest.php
@@ -25,6 +25,7 @@
use Magento\Framework\Registry;
use Magento\ImportExport\Model\Import;
use Magento\Store\Model\Store;
+use Magento\UrlRewrite\Model\ResourceModel\UrlRewriteCollection;
use Psr\Log\LoggerInterface;
use Magento\ImportExport\Model\Import\Source\Csv;
@@ -626,7 +627,7 @@ function ($input) {
explode(',', $optionData)
)
);
- // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge
+ // phpcs:ignore Magento2.Performance.ForeachArrayMerge
$option = array_merge(...$option);
if (!empty($option['type']) && !empty($option['name'])) {
@@ -693,14 +694,14 @@ protected function mergeWithExistingData(
}
} else {
$existingOptionId = array_search($optionKey, $expectedOptions);
- // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge
+ // phpcs:ignore Magento2.Performance.ForeachArrayMerge
$expectedData[$existingOptionId] = array_merge(
$this->getOptionData($option),
$expectedData[$existingOptionId]
);
if ($optionValues) {
foreach ($optionValues as $optionKey => $optionValue) {
- // phpcs:ignore Magento2.Performance.ForeachArrayMerge.ForeachArrayMerge
+ // phpcs:ignore Magento2.Performance.ForeachArrayMerge
$expectedValues[$existingOptionId][$optionKey] = array_merge(
$optionValue,
$expectedValues[$existingOptionId][$optionKey]
@@ -1710,6 +1711,63 @@ public function testImportWithNonExistingImage()
}
}
+ /**
+ * @magentoDataFixture Magento/Catalog/_files/product_without_options.php
+ * @magentoDbIsolation enabled
+ * @magentoAppIsolation enabled
+ */
+ public function testUpdateUrlRewritesOnImport()
+ {
+ $filesystem = $this->objectManager->create(\Magento\Framework\Filesystem::class);
+
+ $directory = $filesystem->getDirectoryWrite(DirectoryList::ROOT);
+
+ $source = $this->objectManager->create(
+ \Magento\ImportExport\Model\Import\Source\Csv::class,
+ [
+ 'file' => __DIR__ . '/_files/products_to_import_with_category.csv',
+ 'directory' => $directory
+ ]
+ );
+ $errors = $this->_model->setParameters(
+ [
+ 'behavior' => \Magento\ImportExport\Model\Import::BEHAVIOR_APPEND,
+ 'entity' => \Magento\Catalog\Model\Product::ENTITY
+ ]
+ )->setSource(
+ $source
+ )->validateData();
+
+ $this->assertTrue($errors->getErrorsCount() == 0);
+
+ $this->_model->importData();
+
+ /** @var \Magento\Catalog\Model\Product $product */
+ $product = $this->objectManager->create(\Magento\Catalog\Model\ProductRepository::class)->get('simple');
+
+ $repUrlRewriteCol = $this->objectManager->create(
+ UrlRewriteCollection::class
+ );
+
+ /** @var UrlRewriteCollection $collUrlRewrite */
+ $collUrlRewrite = $repUrlRewriteCol->addFieldToSelect(['request_path'])
+ ->addFieldToFilter('entity_id', ['eq'=> $product->getEntityId()])
+ ->addFieldToFilter('entity_type', ['eq'=> 'product'])
+ ->load();
+
+ $this->assertCount(2, $collUrlRewrite);
+
+ $this->assertEquals(
+ sprintf('%s.html', $product->getUrlKey()),
+ $collUrlRewrite->getFirstItem()->getRequestPath()
+ );
+
+ $this->assertContains(
+ sprintf('men/tops/%s.html', $product->getUrlKey()),
+ $collUrlRewrite->getLastItem()->getRequestPath()
+ );
+ }
+
/**
* @magentoDataFixture Magento/Catalog/_files/product_simple_with_url_key.php
* @magentoDbIsolation enabled
diff --git a/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_category.csv b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_category.csv
new file mode 100644
index 0000000000000..fec3f049737e9
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/CatalogImportExport/Model/Import/_files/products_to_import_with_category.csv
@@ -0,0 +1,2 @@
+sku,store_view_code,attribute_set_code,product_type,categories
+simple,default,Default,simple,Default Category/Men/Tops
\ No newline at end of file
diff --git a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php
index af8d1c7af134b..2ffce44b32cfe 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/GraphQl/Config/GraphQlReaderTest.php
@@ -200,11 +200,14 @@ enumValues(includeDeprecated: true) {
$sortFields = ['inputFields', 'fields'];
foreach ($sortFields as $sortField) {
isset($searchTerm[$sortField]) && is_array($searchTerm[$sortField])
- ? usort($searchTerm[$sortField], function ($a, $b) {
- $cmpField = 'name';
- return isset($a[$cmpField]) && isset($b[$cmpField])
+ ? usort(
+ $searchTerm[$sortField],
+ function ($a, $b) {
+ $cmpField = 'name';
+ return isset($a[$cmpField]) && isset($b[$cmpField])
? strcmp($a[$cmpField], $b[$cmpField]) : 0;
- }) : null;
+ }
+ ) : null;
}
$this->assertTrue(
diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php
index bda232c7fb9c4..e38eccc73597e 100644
--- a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php
+++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/QueueTestCaseAbstract.php
@@ -45,18 +45,24 @@ class QueueTestCaseAbstract extends \PHPUnit\Framework\TestCase
/**
* @var PublisherConsumerController
*/
- private $publisherConsumerController;
+ protected $publisherConsumerController;
+ /**
+ * @inheritdoc
+ */
protected function setUp()
{
$this->objectManager = Bootstrap::getObjectManager();
$this->logFilePath = TESTS_TEMP_DIR . "/MessageQueueTestLog.txt";
- $this->publisherConsumerController = $this->objectManager->create(PublisherConsumerController::class, [
- 'consumers' => $this->consumers,
- 'logFilePath' => $this->logFilePath,
- 'maxMessages' => $this->maxMessages,
- 'appInitParams' => \Magento\TestFramework\Helper\Bootstrap::getInstance()->getAppInitParams()
- ]);
+ $this->publisherConsumerController = $this->objectManager->create(
+ PublisherConsumerController::class,
+ [
+ 'consumers' => $this->consumers,
+ 'logFilePath' => $this->logFilePath,
+ 'maxMessages' => $this->maxMessages,
+ 'appInitParams' => \Magento\TestFramework\Helper\Bootstrap::getInstance()->getAppInitParams()
+ ]
+ );
try {
$this->publisherConsumerController->initialize();
@@ -70,6 +76,9 @@ protected function setUp()
$this->publisher = $this->publisherConsumerController->getPublisher();
}
+ /**
+ * @inheritdoc
+ */
protected function tearDown()
{
$this->publisherConsumerController->stopConsumers();
@@ -85,25 +94,35 @@ protected function waitForAsynchronousResult($expectedLinesCount, $logFilePath)
{
try {
//$expectedLinesCount, $logFilePath
- $this->publisherConsumerController->waitForAsynchronousResult([$this, 'checkLogsExists'], [
- $expectedLinesCount, $logFilePath
- ]);
+ $this->publisherConsumerController->waitForAsynchronousResult(
+ [$this, 'checkLogsExists'],
+ [$expectedLinesCount, $logFilePath]
+ );
} catch (PreconditionFailedException $e) {
$this->fail($e->getMessage());
}
}
+ /**
+ * Checks that logs exist
+ *
+ * @param int $expectedLinesCount
+ * @return bool
+ */
public function checkLogsExists($expectedLinesCount)
{
+ //phpcs:ignore Magento2.Functions.DiscouragedFunction
$actualCount = file_exists($this->logFilePath) ? count(file($this->logFilePath)) : 0;
return $expectedLinesCount === $actualCount;
}
/**
* Workaround for https://bugs.php.net/bug.php?id=72286
+ * phpcs:disable Magento2.Functions.StaticFunction
*/
public static function tearDownAfterClass()
{
+ // phpcs:enable Magento2.Functions.StaticFunction
if (version_compare(phpversion(), '7') == -1) {
$closeConnection = new \ReflectionMethod(\Magento\Amqp\Model\Config::class, 'closeConnection');
$closeConnection->setAccessible(true);
diff --git a/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php
new file mode 100644
index 0000000000000..ba5809b6634c2
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Framework/MessageQueue/UseCase/WaitAndNotWaitMessagesTest.php
@@ -0,0 +1,150 @@
+msgObject = $this->objectManager->create(AsyncTestData::class);
+ $this->reader = $this->objectManager->get(FileReader::class);
+ $this->filesystem = $this->objectManager->get(Filesystem::class);
+ $this->config = $this->loadConfig();
+ }
+
+ /**
+ * Check if consumers wait for messages from the queue
+ */
+ public function testWaitForMessages()
+ {
+ $this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 1]], $this->config);
+
+ foreach ($this->messages as $message) {
+ $this->publishMessage($message);
+ }
+
+ $this->waitForAsynchronousResult(count($this->messages), $this->logFilePath);
+
+ foreach ($this->messages as $item) {
+ $this->assertContains($item, file_get_contents($this->logFilePath));
+ }
+
+ $this->publishMessage('message4');
+ $this->waitForAsynchronousResult(count($this->messages) + 1, $this->logFilePath);
+ $this->assertContains('message4', file_get_contents($this->logFilePath));
+ }
+
+ /**
+ * Check if consumers do not wait for messages from the queue and die
+ */
+ public function testNotWaitForMessages(): void
+ {
+ $this->publisherConsumerController->stopConsumers();
+
+ $config = $this->config;
+ $config['queue']['consumers_wait_for_messages'] = 0;
+ $this->writeConfig($config);
+
+ $this->assertArraySubset(['queue' => ['consumers_wait_for_messages' => 0]], $this->loadConfig());
+ foreach ($this->messages as $message) {
+ $this->publishMessage($message);
+ }
+
+ $this->publisherConsumerController->startConsumers();
+ $this->waitForAsynchronousResult(count($this->messages), $this->logFilePath);
+
+ foreach ($this->messages as $item) {
+ $this->assertContains($item, file_get_contents($this->logFilePath));
+ }
+
+ // Checks that consumers do not wait 4th message and die
+ $this->assertArraySubset(
+ ['mixed.sync.and.async.queue.consumer' => []],
+ $this->publisherConsumerController->getConsumersProcessIds()
+ );
+ }
+
+ /**
+ * @param string $message
+ */
+ private function publishMessage(string $message): void
+ {
+ $this->msgObject->setValue($message);
+ $this->msgObject->setTextFilePath($this->logFilePath);
+ $this->publisher->publish('multi.topic.queue.topic.c', $this->msgObject);
+ }
+
+ /**
+ * @return array
+ */
+ private function loadConfig(): array
+ {
+ return $this->reader->load(ConfigFilePool::APP_ENV);
+ }
+
+ /**
+ * @param array $config
+ */
+ private function writeConfig(array $config): void
+ {
+ $writer = $this->objectManager->get(Writer::class);
+ $writer->saveConfig([ConfigFilePool::APP_ENV => $config], true);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function tearDown()
+ {
+ parent::tearDown();
+ $this->writeConfig($this->config);
+ }
+}
diff --git a/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php b/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php
index df8302f2f132f..8f3feaf5c24f4 100644
--- a/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php
+++ b/dev/tests/integration/testsuite/Magento/MediaStorage/Helper/File/Storage/DatabaseTest.php
@@ -50,8 +50,7 @@ protected function setUp()
/**
* test for \Magento\MediaStorage\Model\File\Storage\Database::deleteFolder()
*
- * @magentoDataFixture Magento/MediaStorage/_files/database_mode.php
- * @magentoDbIsolation disabled
+ * @magentoDataFixtureBeforeTransaction Magento/MediaStorage/_files/database_mode.php
*/
public function testDeleteFolder()
{
diff --git a/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php b/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php
index 1330fe8ea6af7..ef92d6668be4a 100644
--- a/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php
+++ b/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode.php
@@ -12,6 +12,7 @@
$database = $objectManager->get(\Magento\MediaStorage\Helper\File\Storage\Database::class);
$database->getStorageDatabaseModel()->init();
+/** @var Magento\Framework\App\Config\ConfigResource\ConfigInterface $config */
$config = $objectManager->get(Magento\Framework\App\Config\ConfigResource\ConfigInterface::class);
$config->saveConfig('system/media_storage_configuration/media_storage', '1');
$config->saveConfig('system/media_storage_configuration/media_database', 'default_setup');
diff --git a/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode_rollback.php b/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode_rollback.php
new file mode 100644
index 0000000000000..b226a8de54976
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/MediaStorage/_files/database_mode_rollback.php
@@ -0,0 +1,15 @@
+get(Magento\Framework\App\Config\ConfigResource\ConfigInterface::class);
+$config->deleteConfig('system/media_storage_configuration/media_storage');
+$config->deleteConfig('system/media_storage_configuration/media_database');
+$objectManager->get(Magento\Framework\App\Config\ReinitableConfigInterface::class)->reinit();
diff --git a/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php
new file mode 100644
index 0000000000000..2b0bc8f4d1d05
--- /dev/null
+++ b/dev/tests/integration/testsuite/Magento/Quote/Model/QuoteInfiniteLoopTest.php
@@ -0,0 +1,138 @@
+objectManager = Bootstrap::getObjectManager();
+ $this->config = $this->objectManager->get(\Magento\TestModuleQuoteTotalsObserver\Model\Config::class);
+ $this->config->disableObserver();
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function tearDown()
+ {
+ $this->config->disableObserver();
+ $this->objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class);
+ }
+
+ /**
+ * @dataProvider getLoadQuoteParametersProvider
+ *
+ * @param $triggerRecollect
+ * @param $observerEnabled
+ * @return void
+ */
+ public function testLoadQuoteSuccessfully($triggerRecollect, $observerEnabled): void
+ {
+ $originalQuote = $this->generateQuote($triggerRecollect);
+ $quoteId = $originalQuote->getId();
+
+ $this->assertGreaterThan(0, $quoteId, "The quote should have a database id");
+ $this->assertEquals(
+ $triggerRecollect,
+ $originalQuote->getTriggerRecollect(),
+ "trigger_recollect failed to be set"
+ );
+
+ if ($observerEnabled) {
+ $this->config->enableObserver();
+ }
+
+ /** @var $session \Magento\Checkout\Model\Session */
+ $this->objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class);
+ $session = $this->objectManager->get(\Magento\Checkout\Model\Session::class);
+ $session->setQuoteId($quoteId);
+
+ $quote = $session->getQuote();
+ $this->assertEquals($quoteId, $quote->getId(), "The loaded quote should have the same ID as the initial quote");
+ $this->assertEquals(0, $quote->getTriggerRecollect(), "trigger_recollect should be unset after a quote reload");
+ }
+
+ /**
+ * @return array
+ */
+ public function getLoadQuoteParametersProvider()
+ {
+ return [
+ [0, false],
+ [0, true],
+ [1, false],
+ //[1, true], this combination of trigger recollect and third party code causes the loop, tested separately
+ ];
+ }
+
+ /**
+ * @expectedException \LogicException
+ * @expectedExceptionMessage Infinite loop detected, review the trace for the looping path
+ *
+ * @return void
+ */
+ public function testLoadQuoteWithTriggerRecollectInfiniteLoop(): void
+ {
+ $originalQuote = $this->generateQuote();
+ $quoteId = $originalQuote->getId();
+
+ $this->assertGreaterThan(0, $quoteId, "The quote should have a database id");
+ $this->assertEquals(1, $originalQuote->getTriggerRecollect(), "The quote has trigger_recollect set");
+
+ // Enable an observer which gets the quote from the session
+ // The observer hooks into part of the collect totals process for an easy demonstration of the loop.
+ $this->config->enableObserver();
+
+ /** @var $session \Magento\Checkout\Model\Session */
+ $this->objectManager->removeSharedInstance(\Magento\Checkout\Model\Session::class);
+ $session = $this->objectManager->get(\Magento\Checkout\Model\Session::class);
+ $session->setQuoteId($quoteId);
+ $session->getQuote();
+ }
+
+ /**
+ * Generate a quote with trigger_recollect and save it in the database.
+ *
+ * @param int $triggerRecollect
+ * @return Quote
+ */
+ private function generateQuote($triggerRecollect = 1)
+ {
+ //Fully init a quote with standard quote session procedure
+ /** @var $session \Magento\Checkout\Model\Session */
+ $session = $this->objectManager->create(\Magento\Checkout\Model\Session::class);
+ $session->setQuoteId(null);
+ $quote = $session->getQuote();
+ $quote->setTriggerRecollect($triggerRecollect);
+
+ /** @var \Magento\Quote\Api\CartRepositoryInterface $quoteRepository */
+ $quoteRepository = $this->objectManager->create('\Magento\Quote\Api\CartRepositoryInterface');
+ $quoteRepository->save($quote);
+ return $quote;
+ }
+}
diff --git a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php
index 707c3442d4056..1d49d9343a282 100644
--- a/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php
+++ b/dev/tests/static/framework/Magento/CodeMessDetector/Rule/Design/CookieAndSessionMisuse.php
@@ -16,6 +16,8 @@
/**
* Session and Cookies must be used only in HTML Presentation layer.
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
*/
class CookieAndSessionMisuse extends AbstractRule implements ClassAware
{
@@ -67,6 +69,19 @@ private function isLayoutProcessor(\ReflectionClass $class): bool
);
}
+ /**
+ * Is given class a View Model?
+ *
+ * @param \ReflectionClass $class
+ * @return bool
+ */
+ private function isViewModel(\ReflectionClass $class): bool
+ {
+ return $class->isSubclassOf(
+ \Magento\Framework\View\Element\Block\ArgumentInterface::class
+ );
+ }
+
/**
* Is given class an HTML UI Document?
*
@@ -191,6 +206,7 @@ public function apply(AbstractNode $node)
&& !$this->isControllerPlugin($class)
&& !$this->isBlockPlugin($class)
&& !$this->isLayoutProcessor($class)
+ && !$this->isViewModel($class)
) {
$this->addViolation($node, [$node->getFullQualifiedName()]);
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Argument.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Argument.php
index 39d767fa74ad8..6a8ab2e61ecba 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Argument.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Argument.php
@@ -54,6 +54,11 @@ class Argument implements FieldInterface
*/
private $defaultValue;
+ /**
+ * @var array
+ */
+ private $deprecated;
+
/**
* @param string $name
* @param string $type
@@ -64,6 +69,8 @@ class Argument implements FieldInterface
* @param string $itemType
* @param bool $itemsRequired
* @param string $defaultValue
+ * @param array $deprecated
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
string $name,
@@ -74,7 +81,8 @@ public function __construct(
bool $isList,
string $itemType = '',
bool $itemsRequired = false,
- string $defaultValue = null
+ string $defaultValue = null,
+ array $deprecated = []
) {
$this->name = $name;
$this->type = $isList ? $itemType : $type;
@@ -84,6 +92,7 @@ public function __construct(
$this->isList = $isList;
$this->itemsRequired = $itemsRequired;
$this->defaultValue = $defaultValue;
+ $this->deprecated = $deprecated;
}
/**
@@ -175,4 +184,14 @@ public function hasDefaultValue() : bool
{
return $this->defaultValue ? true: false;
}
+
+ /**
+ * Return the deprecated
+ *
+ * @return array
+ */
+ public function getDeprecated() : array
+ {
+ return $this->deprecated;
+ }
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/ArgumentFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/ArgumentFactory.php
index 86eee7afd13bd..79114ae1d0e45 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/ArgumentFactory.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/ArgumentFactory.php
@@ -10,7 +10,7 @@
use Magento\Framework\ObjectManagerInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class ArgumentFactory
{
@@ -51,7 +51,8 @@ public function createFromConfigData(
'isList' => isset($argumentData['itemType']),
'itemType' => isset($argumentData['itemType']) ? $argumentData['itemType'] : '',
'itemsRequired' => isset($argumentData['itemsRequired']) ? $argumentData['itemsRequired'] : false,
- 'defaultValue' => isset($argumentData['defaultValue']) ? $argumentData['defaultValue'] : null
+ 'defaultValue' => isset($argumentData['defaultValue']) ? $argumentData['defaultValue'] : null,
+ 'deprecated' => isset($argumentData['deprecated']) ? $argumentData['deprecated'] : [],
]
);
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumFactory.php
index 3e0974aedd473..f8b42f5611f30 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumFactory.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumFactory.php
@@ -12,7 +12,7 @@
use Magento\Framework\ObjectManagerInterface;
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
class EnumFactory implements ConfigElementFactoryInterface
{
@@ -71,7 +71,8 @@ public function createFromConfigData(array $data): ConfigElementInterface
$values[$item['_value']] = $this->enumValueFactory->create(
$item['name'],
$item['_value'],
- isset($item['description']) ? $item['description'] : ''
+ isset($item['description']) ? $item['description'] : '',
+ isset($item['deprecationReason']) ? $item['deprecationReason'] : ''
);
}
return $this->create(
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php
index 151c07caf16a2..22bb1db8d2787 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValue.php
@@ -29,16 +29,23 @@ class EnumValue implements ConfigElementInterface
*/
private $description;
+ /**
+ * @var string
+ */
+ private $deprecationReason;
+
/**
* @param string $name
* @param string $value
* @param string $description
+ * @param string $deprecationReason
*/
- public function __construct(string $name, string $value, string $description = '')
+ public function __construct(string $name, string $value, string $description = '', string $deprecationReason = '')
{
$this->name = $name;
$this->value = $value;
$this->description = $description;
+ $this->deprecationReason = $deprecationReason;
}
/**
@@ -70,4 +77,14 @@ public function getDescription() : string
{
return $this->description;
}
+
+ /**
+ * Get the enum value's deprecatedReason.
+ *
+ * @return string
+ */
+ public function getDeprecatedReason() : string
+ {
+ return $this->deprecationReason;
+ }
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php
index c5fb75fb3566c..45402d25f3d8a 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/EnumValueFactory.php
@@ -34,16 +34,22 @@ public function __construct(
* @param string $name
* @param string $value
* @param string $description
+ * @param string $deprecationReason
* @return EnumValue
*/
- public function create(string $name, string $value, string $description = ''): EnumValue
- {
+ public function create(
+ string $name,
+ string $value,
+ string $description = '',
+ string $deprecationReason = ''
+ ): EnumValue {
return $this->objectManager->create(
EnumValue::class,
[
'name' => $name,
'value' => $value,
- 'description' => $description
+ 'description' => $description,
+ 'deprecationReason' => $deprecationReason
]
);
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php
index 0fc51e4ecd069..0b1b8ae3da31b 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Field.php
@@ -53,6 +53,11 @@ class Field implements OutputFieldInterface
*/
private $cache;
+ /**
+ * @var array
+ */
+ private $deprecated;
+
/**
* @param string $name
* @param string $type
@@ -63,6 +68,8 @@ class Field implements OutputFieldInterface
* @param string $description
* @param array $arguments
* @param array $cache
+ * @param array $deprecated
+ * @SuppressWarnings(PHPMD.ExcessiveParameterList)
*/
public function __construct(
string $name,
@@ -73,7 +80,8 @@ public function __construct(
string $resolver = '',
string $description = '',
array $arguments = [],
- array $cache = []
+ array $cache = [],
+ array $deprecated = []
) {
$this->name = $name;
$this->type = $isList ? $itemType : $type;
@@ -83,6 +91,7 @@ public function __construct(
$this->description = $description;
$this->arguments = $arguments;
$this->cache = $cache;
+ $this->deprecated = $deprecated;
}
/**
@@ -164,4 +173,14 @@ public function getCache() : array
{
return $this->cache;
}
+
+ /**
+ * Return the deprecated annotation for the field
+ *
+ * @return array
+ */
+ public function getDeprecated() : array
+ {
+ return $this->deprecated;
+ }
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php
index 60191b69be47f..e4144b3038d33 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/FieldFactory.php
@@ -37,6 +37,7 @@ public function __construct(
* @param array $fieldData
* @param array $arguments
* @return Field
+ * @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function createFromConfigData(
array $fieldData,
@@ -46,7 +47,7 @@ public function createFromConfigData(
$isList = false;
//check if type ends with []
- if ($fieldType{strlen($fieldType) - 2} == '[' && $fieldType{strlen($fieldType) - 1} == ']') {
+ if ($fieldType[strlen($fieldType) - 2] == '[' && $fieldType[strlen($fieldType) - 1] == ']') {
$isList = true;
$fieldData['type'] = str_replace('[]', '', $fieldData['type']);
$fieldData['itemType'] = str_replace('[]', '', $fieldData['type']);
@@ -62,8 +63,9 @@ public function createFromConfigData(
'itemType' => isset($fieldData['itemType']) ? $fieldData['itemType'] : '',
'resolver' => isset($fieldData['resolver']) ? $fieldData['resolver'] : '',
'description' => isset($fieldData['description']) ? $fieldData['description'] : '',
- 'cache' => isset($fieldData['cache']) ? $fieldData['cache'] : [],
'arguments' => $arguments,
+ 'cache' => isset($fieldData['cache']) ? $fieldData['cache'] : [],
+ 'deprecated' => isset($fieldData['deprecated']) ? $fieldData['deprecated'] : [],
]
);
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php
index 8e86f701672c6..3ebad28f7b308 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/Input.php
@@ -27,19 +27,27 @@ class Input implements TypeInterface
*/
private $description;
+ /**
+ * @var array
+ */
+ private $deprecated;
+
/**
* @param string $name
* @param Field[] $fields
* @param string $description
+ * @param array $deprecated
*/
public function __construct(
string $name,
array $fields,
- string $description
+ string $description,
+ array $deprecated = []
) {
$this->name = $name;
$this->fields = $fields;
$this->description = $description;
+ $this->deprecated = $deprecated;
}
/**
@@ -71,4 +79,14 @@ public function getDescription(): string
{
return $this->description;
}
+
+ /**
+ * Return the deprecated annotation for the input
+ *
+ * @return array
+ */
+ public function getDeprecated() : array
+ {
+ return $this->deprecated;
+ }
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php b/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php
index 0e7ccb831a5a4..a23e83684d43c 100644
--- a/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php
+++ b/lib/internal/Magento/Framework/GraphQl/Config/Element/InputFactory.php
@@ -72,7 +72,8 @@ private function create(
[
'name' => $typeData['name'],
'fields' => $fields,
- 'description' => isset($typeData['description']) ? $typeData['description'] : ''
+ 'description' => isset($typeData['description']) ? $typeData['description'] : '',
+ 'deprecated' => isset($typeData['deprecated']) ? $typeData['deprecated'] : []
]
);
}
diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/Enum.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/Enum.php
index 6080fd0dd73e2..beb4b5a311c94 100644
--- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/Enum.php
+++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Enum/Enum.php
@@ -22,12 +22,13 @@ public function __construct(EnumElement $configElement)
{
$config = [
'name' => $configElement->getName(),
- 'description' => $configElement->getDescription(),
+ 'description' => $configElement->getDescription()
];
foreach ($configElement->getValues() as $value) {
$config['values'][$value->getValue()] = [
'value' => $value->getValue(),
- 'description' => $value->getDescription()
+ 'description' => $value->getDescription(),
+ 'deprecationReason'=> $value->getDeprecatedReason()
];
}
parent::__construct($config);
diff --git a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php
index 034a5702090d9..e3f0945cb8dfd 100644
--- a/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php
+++ b/lib/internal/Magento/Framework/GraphQl/Schema/Type/Output/ElementMapper/Formatter/Fields.php
@@ -142,6 +142,12 @@ private function getFieldConfig(
$fieldConfig['description'] = $field->getDescription();
}
+ if (!empty($field->getDeprecated())) {
+ if (isset($field->getDeprecated()['reason'])) {
+ $fieldConfig['deprecationReason'] = $field->getDeprecated()['reason'];
+ }
+ }
+
if ($field->getResolver() != null) {
/** @var ResolverInterface $resolver */
$resolver = $this->objectManager->get($field->getResolver());
diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php
new file mode 100644
index 0000000000000..922ae87ecf449
--- /dev/null
+++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/DeprecatedAnnotationReader.php
@@ -0,0 +1,35 @@
+name->value == 'deprecated') {
+ foreach ($directive->arguments as $directiveArgument) {
+ if ($directiveArgument->name->value == 'reason') {
+ $argumentsMap = ["reason" => $directiveArgument->value->value];
+ }
+ }
+ }
+ }
+ return $argumentsMap;
+ }
+}
diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php
index 7438a4e3da932..217a233eae20c 100644
--- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php
+++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/MetaReader/FieldMetaReader.php
@@ -27,20 +27,29 @@ class FieldMetaReader
*/
private $cacheAnnotationReader;
+ /**
+ * @var DeprecatedAnnotationReader
+ */
+ private $deprecatedAnnotationReader;
+
/**
* @param TypeMetaWrapperReader $typeMetaReader
* @param DocReader $docReader
* @param CacheAnnotationReader|null $cacheAnnotationReader
+ * @param DeprecatedAnnotationReader|null $deprecatedAnnotationReader
*/
public function __construct(
TypeMetaWrapperReader $typeMetaReader,
DocReader $docReader,
- CacheAnnotationReader $cacheAnnotationReader = null
+ CacheAnnotationReader $cacheAnnotationReader = null,
+ DeprecatedAnnotationReader $deprecatedAnnotationReader = null
) {
$this->typeMetaReader = $typeMetaReader;
$this->docReader = $docReader;
- $this->cacheAnnotationReader = $cacheAnnotationReader ?? \Magento\Framework\App\ObjectManager::getInstance()
- ->get(CacheAnnotationReader::class);
+ $this->cacheAnnotationReader = $cacheAnnotationReader
+ ?? \Magento\Framework\App\ObjectManager::getInstance()->get(CacheAnnotationReader::class);
+ $this->deprecatedAnnotationReader = $deprecatedAnnotationReader
+ ?? \Magento\Framework\App\ObjectManager::getInstance()->get(DeprecatedAnnotationReader::class);
}
/**
@@ -72,10 +81,14 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra
$result['description'] = $this->docReader->read($fieldMeta->astNode->directives);
}
- if ($this->docReader->read($fieldMeta->astNode->directives)) {
+ if ($this->cacheAnnotationReader->read($fieldMeta->astNode->directives)) {
$result['cache'] = $this->cacheAnnotationReader->read($fieldMeta->astNode->directives);
}
+ if ($this->deprecatedAnnotationReader->read($fieldMeta->astNode->directives)) {
+ $result['deprecated'] = $this->deprecatedAnnotationReader->read($fieldMeta->astNode->directives);
+ }
+
$arguments = $fieldMeta->args;
foreach ($arguments as $argumentMeta) {
$argumentName = $argumentMeta->name;
@@ -86,19 +99,43 @@ public function read(\GraphQL\Type\Definition\FieldDefinition $fieldMeta) : arra
$result['arguments'][$argumentName]['defaultValue'] = $argumentMeta->defaultValue;
}
$typeMeta = $argumentMeta->getType();
- $result['arguments'][$argumentName] = array_merge(
- $result['arguments'][$argumentName],
- $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER)
- );
+ $result['arguments'][$argumentName] = $this->argumentMetaType($typeMeta, $argumentMeta, $result);
if ($this->docReader->read($argumentMeta->astNode->directives)) {
$result['arguments'][$argumentName]['description'] =
$this->docReader->read($argumentMeta->astNode->directives);
}
+
+ if ($this->deprecatedAnnotationReader->read($argumentMeta->astNode->directives)) {
+ $result['arguments'][$argumentName]['deprecated'] =
+ $this->deprecatedAnnotationReader->read($argumentMeta->astNode->directives);
+ }
}
return $result;
}
+ /**
+ * Get the argumentMetaType result array
+ *
+ * @param \GraphQL\Type\Definition\InputType $typeMeta
+ * @param \GraphQL\Type\Definition\FieldArgument $argumentMeta
+ * @param array $result
+ * @return array
+ */
+ private function argumentMetaType(
+ \GraphQL\Type\Definition\InputType $typeMeta,
+ \GraphQL\Type\Definition\FieldArgument $argumentMeta,
+ $result
+ ) : array {
+ $argumentName = $argumentMeta->name;
+ $result['arguments'][$argumentName] = array_merge(
+ $result['arguments'][$argumentName],
+ $this->typeMetaReader->read($typeMeta, TypeMetaWrapperReader::ARGUMENT_PARAMETER)
+ );
+
+ return $result['arguments'][$argumentName];
+ }
+
/**
* Read resolver if an annotation with the class of the resolver is defined in the meta
*
diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php
index 3e9e819078db8..e4dec7afdab0a 100644
--- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php
+++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/EnumType.php
@@ -23,13 +23,14 @@ class EnumType implements TypeMetaReaderInterface
/**
* @param DocReader $docReader
*/
- public function __construct(DocReader $docReader)
- {
+ public function __construct(
+ DocReader $docReader
+ ) {
$this->docReader = $docReader;
}
/**
- * {@inheritdoc}
+ * @inheritdoc
*/
public function read(\GraphQL\Type\Definition\Type $typeMeta) : array
{
@@ -42,7 +43,9 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array
foreach ($typeMeta->getValues() as $enumValueMeta) {
$result['items'][$enumValueMeta->value] = [
'name' => strtolower($enumValueMeta->name),
- '_value' => $enumValueMeta->value
+ '_value' => $enumValueMeta->value,
+ 'description' => $enumValueMeta->description,
+ 'deprecationReason' =>$enumValueMeta->deprecationReason
];
if ($this->docReader->read($enumValueMeta->astNode->directives)) {
@@ -56,6 +59,7 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array
}
return $result;
+
} else {
return [];
}
diff --git a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php
index 7614c4954091d..ba8e46dd60557 100644
--- a/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php
+++ b/lib/internal/Magento/Framework/GraphQlSchemaStitching/GraphQlReader/Reader/ObjectType.php
@@ -12,6 +12,7 @@
use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DocReader;
use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\ImplementsReader;
use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\CacheAnnotationReader;
+use Magento\Framework\GraphQlSchemaStitching\GraphQlReader\MetaReader\DeprecatedAnnotationReader;
/**
* Composite configuration reader to handle the object type meta
@@ -38,24 +39,33 @@ class ObjectType implements TypeMetaReaderInterface
*/
private $cacheAnnotationReader;
+ /**
+ * @var DeprecatedAnnotationReader
+ */
+ private $deprecatedAnnotationReader;
+
/**
* ObjectType constructor.
* @param FieldMetaReader $fieldMetaReader
* @param DocReader $docReader
* @param ImplementsReader $implementsAnnotation
* @param CacheAnnotationReader|null $cacheAnnotationReader
+ * @param DeprecatedAnnotationReader|null $deprecatedAnnotationReader
*/
public function __construct(
FieldMetaReader $fieldMetaReader,
DocReader $docReader,
ImplementsReader $implementsAnnotation,
- CacheAnnotationReader $cacheAnnotationReader = null
+ CacheAnnotationReader $cacheAnnotationReader = null,
+ DeprecatedAnnotationReader $deprecatedAnnotationReader = null
) {
$this->fieldMetaReader = $fieldMetaReader;
$this->docReader = $docReader;
$this->implementsAnnotation = $implementsAnnotation;
$this->cacheAnnotationReader = $cacheAnnotationReader ?? \Magento\Framework\App\ObjectManager::getInstance()
->get(CacheAnnotationReader::class);
+ $this->deprecatedAnnotationReader = $deprecatedAnnotationReader
+ ?? \Magento\Framework\App\ObjectManager::getInstance()->get(DeprecatedAnnotationReader::class);
}
/**
@@ -85,13 +95,17 @@ public function read(\GraphQL\Type\Definition\Type $typeMeta) : array
}
if ($this->docReader->read($typeMeta->astNode->directives)) {
- $result['description'] = $this->docReader->read($typeMeta->astNode->directives);
+ $result['description'] = $this->docReader->read($typeMeta->astNode->directives);
}
- if ($this->docReader->read($typeMeta->astNode->directives)) {
+ if ($this->cacheAnnotationReader->read($typeMeta->astNode->directives)) {
$result['cache'] = $this->cacheAnnotationReader->read($typeMeta->astNode->directives);
}
+ if ($this->deprecatedAnnotationReader->read($typeMeta->astNode->directives)) {
+ $result['deprecated'] = $this->deprecatedAnnotationReader->read($typeMeta->astNode->directives);
+ }
+
return $result;
} else {
return [];
diff --git a/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php b/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php
index fe0a84af3ca93..559959b55fc61 100644
--- a/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php
+++ b/lib/internal/Magento/Framework/MessageQueue/CallbackInvoker.php
@@ -8,6 +8,7 @@
use Magento\Framework\MessageQueue\PoisonPill\PoisonPillCompareInterface;
use Magento\Framework\MessageQueue\PoisonPill\PoisonPillReadInterface;
+use Magento\Framework\App\DeploymentConfig;
/**
* Class CallbackInvoker to invoke callbacks for consumer classes
@@ -29,16 +30,24 @@ class CallbackInvoker implements CallbackInvokerInterface
*/
private $poisonPillCompare;
+ /**
+ * @var DeploymentConfig
+ */
+ private $deploymentConfig;
+
/**
* @param PoisonPillReadInterface $poisonPillRead
* @param PoisonPillCompareInterface $poisonPillCompare
+ * @param DeploymentConfig $deploymentConfig
*/
public function __construct(
PoisonPillReadInterface $poisonPillRead,
- PoisonPillCompareInterface $poisonPillCompare
+ PoisonPillCompareInterface $poisonPillCompare,
+ DeploymentConfig $deploymentConfig
) {
$this->poisonPillRead = $poisonPillRead;
$this->poisonPillCompare = $poisonPillCompare;
+ $this->deploymentConfig = $deploymentConfig;
}
/**
@@ -56,13 +65,29 @@ public function invoke(QueueInterface $queue, $maxNumberOfMessages, $callback)
do {
$message = $queue->dequeue();
// phpcs:ignore Magento2.Functions.DiscouragedFunction
- } while ($message === null && (sleep(1) === 0));
+ } while ($message === null && $this->isWaitingNextMessage() && (sleep(1) === 0));
+
+ if ($message === null) {
+ break;
+ }
+
if (false === $this->poisonPillCompare->isLatestVersion($this->poisonPillVersion)) {
$queue->reject($message);
// phpcs:ignore Magento2.Security.LanguageConstruct.ExitUsage
exit(0);
}
+
$callback($message);
}
}
+
+ /**
+ * Checks if consumers should wait for message from the queue
+ *
+ * @return bool
+ */
+ private function isWaitingNextMessage(): bool
+ {
+ return $this->deploymentConfig->get('queue/consumers_wait_for_messages', 1) === 1;
+ }
}
diff --git a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php
index 7a3eb3b16baca..e7ee0e19a1d43 100644
--- a/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php
+++ b/lib/internal/Magento/Framework/MessageQueue/Test/Unit/ConsumerTest.php
@@ -77,6 +77,11 @@ class ConsumerTest extends \PHPUnit\Framework\TestCase
*/
private $poisonPillCompare;
+ /**
+ * @var \Magento\Framework\App\DeploymentConfig|\PHPUnit_Framework_MockObject_MockObject
+ */
+ private $deploymentConfig;
+
/**
* Set up.
*
@@ -95,6 +100,7 @@ protected function setUp()
->disableOriginalConstructor()->getMock();
$this->logger = $this->getMockBuilder(\Psr\Log\LoggerInterface::class)
->disableOriginalConstructor()->getMock();
+ $this->deploymentConfig = $this->createMock(\Magento\Framework\App\DeploymentConfig::class);
$objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this);
$this->poisonPillCompare = $this->getMockBuilder(PoisonPillCompareInterface::class)
@@ -104,7 +110,8 @@ protected function setUp()
//Hard dependency used because CallbackInvoker invokes closure logic defined inside of Customer class.
$this->callbackInvoker = new \Magento\Framework\MessageQueue\CallbackInvoker(
$this->poisonPillRead,
- $this->poisonPillCompare
+ $this->poisonPillCompare,
+ $this->deploymentConfig
);
$this->consumer = $objectManager->getObject(
\Magento\Framework\MessageQueue\Consumer::class,
diff --git a/lib/internal/Magento/Framework/Model/EntitySnapshot.php b/lib/internal/Magento/Framework/Model/EntitySnapshot.php
index 99f0c7f4ed42d..6ae7cb346a7df 100644
--- a/lib/internal/Magento/Framework/Model/EntitySnapshot.php
+++ b/lib/internal/Magento/Framework/Model/EntitySnapshot.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Framework\Model;
@@ -44,6 +45,8 @@ public function __construct(
}
/**
+ * Register snapshot of entity data.
+ *
* @param string $entityType
* @param object $entity
* @return void
@@ -55,7 +58,7 @@ public function registerSnapshot($entityType, $entity)
$entityData = $hydrator->extract($entity);
$attributes = $this->attributeProvider->getAttributes($entityType);
$this->snapshotData[$entityType][$entityData[$metadata->getIdentifierField()]]
- = array_intersect_key($entityData, $attributes);
+ = array_intersect(\array_keys($entityData), $attributes);
}
/**
diff --git a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php
index beb0b2784f5fd..6b7fcd131ba8b 100644
--- a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php
+++ b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProvider.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Framework\Model\EntitySnapshot;
@@ -53,31 +54,29 @@ public function __construct(
* Returns array of fields
*
* @param string $entityType
- * @return array
+ * @return string[]
* @throws \Exception
*/
public function getAttributes($entityType)
{
if (!isset($this->registry[$entityType])) {
$metadata = $this->metadataPool->getMetadata($entityType);
- $this->registry[$entityType] = $metadata->getEntityConnection()->describeTable($metadata->getEntityTable());
- if ($metadata->getLinkField() != $metadata->getIdentifierField()) {
- unset($this->registry[$entityType][$metadata->getLinkField()]);
- }
- $providers = [];
- if (isset($this->providers[$entityType])) {
- $providers = $this->providers[$entityType];
- } elseif (isset($this->providers['default'])) {
- $providers = $this->providers['default'];
+ $entityDescription = $metadata->getEntityConnection()->describeTable($metadata->getEntityTable());
+ if ($metadata->getLinkField() !== $metadata->getIdentifierField()) {
+ unset($entityDescription[$metadata->getLinkField()]);
}
+ $attributes = [];
+ $attributes[] = \array_keys($entityDescription);
+
+ $providers = $this->providers[$entityType] ?? $this->providers['default'] ?? [];
foreach ($providers as $providerClass) {
$provider = $this->objectManager->get($providerClass);
- $this->registry[$entityType] = array_merge(
- $this->registry[$entityType],
- $provider->getAttributes($entityType)
- );
+ $attributes[] = $provider->getAttributes($entityType);
}
+
+ $this->registry[$entityType] = \array_merge(...$attributes);
}
+
return $this->registry[$entityType];
}
}
diff --git a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProviderInterface.php b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProviderInterface.php
index f71f9e591630f..ad09e2ef5a479 100644
--- a/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProviderInterface.php
+++ b/lib/internal/Magento/Framework/Model/EntitySnapshot/AttributeProviderInterface.php
@@ -3,6 +3,7 @@
* Copyright © Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
+declare(strict_types=1);
namespace Magento\Framework\Model\EntitySnapshot;
@@ -12,8 +13,10 @@
interface AttributeProviderInterface
{
/**
+ * Returns array of fields
+ *
* @param string $entityType
- * @return array
+ * @return string[]
*/
public function getAttributes($entityType);
}